后端新增 OssUtils/OssTokenVo/OssCorsInitRunner,通过 STS 临时凭证实现客户端直传 OSS; 前端 upload API 适配直传流程,赛事创建/作品提交/作业/富文本编辑器均已切换; 多环境(dev/test/prod) OSS 配置补全;新增 oss-direct-upload-demo 示例项目及 E2E 测试。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
131 lines
4.7 KiB
TypeScript
131 lines
4.7 KiB
TypeScript
import { test, expect } from '@playwright/test'
|
||
import path from 'path'
|
||
import { fileURLToPath } from 'url'
|
||
import fs from 'fs'
|
||
|
||
const __filename = fileURLToPath(import.meta.url)
|
||
const __dirname = path.dirname(__filename)
|
||
|
||
// 测试配置
|
||
const TENANT_CODE = 'super'
|
||
const USERNAME = 'admin'
|
||
const PASSWORD = 'admin123'
|
||
|
||
// 确保测试图片存在
|
||
const FIXTURES_DIR = path.join(__dirname, 'fixtures')
|
||
const TEST_IMAGE_PATH = path.join(FIXTURES_DIR, 'test-upload.png')
|
||
if (!fs.existsSync(FIXTURES_DIR)) {
|
||
fs.mkdirSync(FIXTURES_DIR, { recursive: true })
|
||
}
|
||
if (!fs.existsSync(TEST_IMAGE_PATH)) {
|
||
const pngData = Buffer.from(
|
||
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==',
|
||
'base64'
|
||
)
|
||
fs.writeFileSync(TEST_IMAGE_PATH, pngData)
|
||
}
|
||
|
||
test.describe('OSS 直传上传', () => {
|
||
|
||
// 单独给这个测试更长的超时
|
||
test.setTimeout(60000)
|
||
|
||
test('登录 -> 赛事创建页 -> 上传封面图片到 OSS', async ({ page }) => {
|
||
// 监听网络请求,捕获 OSS 相关请求
|
||
const ossTokenRequests: string[] = []
|
||
const ossUploadRequests: string[] = []
|
||
|
||
page.on('request', (req) => {
|
||
const url = req.url()
|
||
if (url.includes('/upload/oss/token')) {
|
||
ossTokenRequests.push(url)
|
||
}
|
||
if (url.includes('aliyuncs.com')) {
|
||
ossUploadRequests.push(url)
|
||
}
|
||
})
|
||
|
||
// ========== 1. 登录 ==========
|
||
await page.goto(`/${TENANT_CODE}/login`)
|
||
await page.waitForLoadState('domcontentloaded')
|
||
|
||
// 等待登录表单可见
|
||
await page.locator('input[placeholder="请输入用户名"]').waitFor({ state: 'visible', timeout: 10000 })
|
||
|
||
// 填写 Ant Design 表单
|
||
await page.locator('input[placeholder="请输入用户名"]').click()
|
||
await page.locator('input[placeholder="请输入用户名"]').fill(USERNAME)
|
||
await page.locator('input[placeholder="请输入密码"]').click()
|
||
await page.locator('input[placeholder="请输入密码"]').fill(PASSWORD)
|
||
|
||
// 点击登录按钮
|
||
const loginBtn = page.locator('button[type="submit"]').first()
|
||
await loginBtn.click()
|
||
|
||
// 等待登录成功(URL 不再包含 /login)
|
||
await page.waitForFunction(() => !window.location.pathname.includes('/login'), { timeout: 15000 })
|
||
console.log('[1] 登录成功, 当前页面:', page.url())
|
||
|
||
// ========== 2. 进入赛事创建页 ==========
|
||
await page.goto(`/${TENANT_CODE}/contests/create`)
|
||
await page.waitForLoadState('domcontentloaded')
|
||
|
||
// 等待表单页面加载
|
||
await page.locator('input[placeholder*="活动名称"], input[placeholder*="名称"]').first().waitFor({ timeout: 10000 })
|
||
console.log('[2] 赛事创建页加载成功')
|
||
|
||
// ========== 3. 上传封面图片 ==========
|
||
// 直接用全局的 file input(Ant Design Upload 的隐藏 input)
|
||
const fileInputs = page.locator('input[type="file"]')
|
||
const fileCount = await fileInputs.count()
|
||
console.log('[3] 发现 file input 数量:', fileCount)
|
||
|
||
// 第一个 file input 对应封面上传
|
||
await fileInputs.first().setInputFiles(TEST_IMAGE_PATH)
|
||
|
||
console.log('[3] 已选择封面文件,等待 OSS 上传...')
|
||
|
||
// 等待网络请求完成
|
||
await page.waitForTimeout(5000)
|
||
|
||
// ========== 4. 验证 ==========
|
||
console.log('[4] OSS Token 请求数:', ossTokenRequests.length)
|
||
console.log('[4] OSS 上传请求数:', ossUploadRequests.length)
|
||
|
||
// 验证:发出了 OSS Token 请求
|
||
if (ossTokenRequests.length > 0) {
|
||
console.log('[4] Token 请求 URL:', ossTokenRequests[0])
|
||
}
|
||
|
||
// 验证:发出了 OSS 上传请求
|
||
if (ossUploadRequests.length > 0) {
|
||
console.log('[4] 上传目标 URL:', ossUploadRequests[0])
|
||
expect(ossUploadRequests[0]).toContain('aliyuncs.com')
|
||
}
|
||
|
||
// 验证:检查页面上是否有上传成功的 UI 指示
|
||
const successItems = page.locator('.ant-upload-list-item-done')
|
||
const errorItems = page.locator('.ant-upload-list-item-error')
|
||
const successCount = await successItems.count()
|
||
const errorCount = await errorItems.count()
|
||
|
||
console.log('[4] 上传成功项:', successCount, '上传失败项:', errorCount)
|
||
|
||
// 检查是否有错误提示消息
|
||
const errorMsg = await page.locator('.ant-message-error').textContent().catch(() => '')
|
||
if (errorMsg) {
|
||
console.log('[4] 错误消息:', errorMsg)
|
||
}
|
||
|
||
// 核心断言
|
||
expect(ossTokenRequests.length).toBeGreaterThanOrEqual(1)
|
||
expect(ossUploadRequests.length).toBeGreaterThanOrEqual(1)
|
||
expect(errorCount).toBe(0)
|
||
|
||
console.log('\n===== OSS 直传上传测试通过 =====')
|
||
console.log('OSS Token 请求:', ossTokenRequests.length)
|
||
console.log('OSS 上传请求:', ossUploadRequests.length)
|
||
console.log('上传目标:', ossUploadRequests[0] || 'N/A')
|
||
})
|
||
})
|