Files
everything-is-suitable/everything-is-suitable-uniapp/e2e/security.spec.ts
T
zhangxiang 3a428d8977 test: 补充 Phase 3 专项测试并更新测试报告
新增性能基准测试、安全验证、国际化完整性验证等专项测试;
修复 E2E 测试选择器与当前 UI 不匹配问题;
补充多语言 locale 缺失的 key;
更新 Playwright 配置支持跨浏览器测试;
同步更新测试报告与 README 进度章节。

单元测试 408/408 通过,E2E 测试 39/39 通过(4 种浏览器),
专项测试 47/47 通过,整体覆盖率 77.5%。
2026-08-12 20:10:39 +08:00

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)
}
}
})
})
})