97 lines
3.4 KiB
TypeScript
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)
|
|
})
|
|
})
|
|
}) |