新增性能基准测试、安全验证、国际化完整性验证等专项测试; 修复 E2E 测试选择器与当前 UI 不匹配问题; 补充多语言 locale 缺失的 key; 更新 Playwright 配置支持跨浏览器测试; 同步更新测试报告与 README 进度章节。 单元测试 408/408 通过,E2E 测试 39/39 通过(4 种浏览器), 专项测试 47/47 通过,整体覆盖率 77.5%。
75 lines
2.7 KiB
TypeScript
75 lines
2.7 KiB
TypeScript
import { test, expect } from '@playwright/test'
|
|
|
|
test.describe('安全验证 E2E 测试', () => {
|
|
test.describe('TC-SEC-E2E-001: XSS 注入防护', () => {
|
|
test('搜索关键词中的 XSS 脚本不应执行', async ({ page }) => {
|
|
// 导航到黄历搜索页面,携带 XSS 关键词
|
|
const xssPayload = encodeURIComponent('<script>alert("xss")</script>')
|
|
await page.goto(`/#/pages/almanac-search/index?keyword=${xssPayload}`)
|
|
await page.waitForLoadState('networkidle')
|
|
await page.waitForTimeout(1000)
|
|
|
|
// 验证页面没有因为 XSS 而崩溃
|
|
const pageContent = page.locator('.page-content')
|
|
await expect(pageContent).toBeVisible()
|
|
|
|
// 验证没有 alert 弹窗(通过页面未崩溃间接验证)
|
|
// 验证搜索输入框的值被正确转义显示
|
|
const searchInput = page.locator('input, textarea, [contenteditable]').first()
|
|
const isVisible = await searchInput.isVisible().catch(() => false)
|
|
if (isVisible) {
|
|
const value = await searchInput.inputValue().catch(() => '')
|
|
expect(value).not.toContain('<script>')
|
|
}
|
|
})
|
|
})
|
|
|
|
test.describe('TC-SEC-E2E-002: 页面内容安全', () => {
|
|
test('页面不应包含外部脚本引用', async ({ page }) => {
|
|
await page.goto('/')
|
|
await page.waitForLoadState('networkidle')
|
|
|
|
// 检查页面中是否有外部脚本
|
|
const scripts = await page.evaluate(() => {
|
|
return Array.from(document.querySelectorAll('script')).map(s => s.src)
|
|
})
|
|
|
|
// 所有脚本应为相对路径或内联
|
|
for (const src of scripts) {
|
|
if (src) {
|
|
expect(src.startsWith('http://localhost')).toBeTruthy()
|
|
}
|
|
}
|
|
})
|
|
|
|
test('页面 Content-Type 应为 text/html', async ({ page }) => {
|
|
const response = await page.goto('/')
|
|
const headers = response?.headers()
|
|
expect(headers?.['content-type']).toContain('text/html')
|
|
})
|
|
})
|
|
|
|
test.describe('TC-SEC-E2E-003: 本地存储安全', () => {
|
|
test('localStorage 不应包含敏感信息', async ({ page }) => {
|
|
await page.goto('/')
|
|
await page.waitForLoadState('networkidle')
|
|
|
|
const storage = await page.evaluate(() => {
|
|
const items: Record<string, string> = {}
|
|
for (let i = 0; i < localStorage.length; i++) {
|
|
const key = localStorage.key(i)!
|
|
items[key] = localStorage.getItem(key) || ''
|
|
}
|
|
return items
|
|
})
|
|
|
|
// 验证存储键名不含敏感词
|
|
const sensitivePatterns = ['password', 'token', 'secret', 'credential']
|
|
for (const key of Object.keys(storage)) {
|
|
for (const pattern of sensitivePatterns) {
|
|
expect(key.toLowerCase()).not.toContain(pattern)
|
|
}
|
|
}
|
|
})
|
|
})
|
|
}) |