Files
zhangxiang 7d93c13b18 chore: 完成v1.0.0最终验收,更新测试报告与文档
新增CSP安全配置与对应测试用例,补充并发负载测试,更新README进度与统计数据,新增最终验收报告与测试计划文档,修复已知测试问题
2026-08-13 09:31:11 +08:00

97 lines
3.4 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)
}
}
})
})
test.describe('TC-SEC-E2E-004: 内容安全策略 (CSP) 验证', () => {
test('页面不应加载未知外部资源', async ({ page }) => {
const requests: string[] = []
page.on('request', request => {
const url = request.url()
// 允许本地资源、data URI 和 uni-app 框架已知 CDN 资源
if (
!url.startsWith('http://localhost') &&
!url.startsWith('data:') &&
!url.startsWith('https://cdn.dcloud.net.cn')
) {
requests.push(url)
}
})
await page.goto('/')
await page.waitForLoadState('networkidle')
expect(requests).toHaveLength(0)
})
})
})