拆分兑换凭证渲染与截图逻辑
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
// @ts-nocheck
|
||||
|
||||
import {
|
||||
BAIDU_BEIJING_TIME_URL,
|
||||
BEIJING_TIME_PROOF_CLIP,
|
||||
BEIJING_TIME_PROOF_VIEWPORT,
|
||||
} from './session-proof-constants.js'
|
||||
|
||||
export async function captureBeijingTimeProof(browserContext, { screenshotPath }) {
|
||||
const proofPage = await browserContext.newPage()
|
||||
|
||||
try {
|
||||
await proofPage.setViewportSize(BEIJING_TIME_PROOF_VIEWPORT)
|
||||
await proofPage.goto(BAIDU_BEIJING_TIME_URL, { waitUntil: 'domcontentloaded' })
|
||||
await proofPage.waitForTimeout(3_000)
|
||||
await proofPage.evaluate(() => {
|
||||
window.scrollTo(0, 0)
|
||||
})
|
||||
await proofPage.screenshot({
|
||||
path: screenshotPath,
|
||||
clip: BEIJING_TIME_PROOF_CLIP,
|
||||
})
|
||||
|
||||
return {
|
||||
screenshotPath,
|
||||
pageUrl: BAIDU_BEIJING_TIME_URL,
|
||||
}
|
||||
} finally {
|
||||
await proofPage.close().catch(() => null)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
export function buildProofHtml({ redeemImageBase64, timeImageBase64, redeemMessage, timePageUrl }) {
|
||||
const capturedAt = new Date().toLocaleString('zh-CN', {
|
||||
hour12: false,
|
||||
timeZone: 'Asia/Shanghai',
|
||||
})
|
||||
|
||||
return `<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>兑换与北京时间凭证</title>
|
||||
<style>
|
||||
:root {
|
||||
color-scheme: light;
|
||||
}
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: "PingFang SC", "Microsoft YaHei", sans-serif;
|
||||
background:
|
||||
radial-gradient(circle at top left, rgba(33, 150, 243, 0.16), transparent 28%),
|
||||
linear-gradient(180deg, #edf4ff 0%, #f6f8fc 52%, #eef1f7 100%);
|
||||
color: #142033;
|
||||
}
|
||||
.sheet {
|
||||
width: 1480px;
|
||||
margin: 0 auto;
|
||||
padding: 30px 24px 28px;
|
||||
}
|
||||
.hero {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 20px;
|
||||
align-items: flex-end;
|
||||
padding: 0 6px 18px;
|
||||
}
|
||||
.eyebrow {
|
||||
margin: 0 0 12px;
|
||||
color: #1c78d0;
|
||||
font-size: 13px;
|
||||
letter-spacing: 0.2em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
h1 {
|
||||
margin: 0;
|
||||
font-size: 44px;
|
||||
line-height: 1.08;
|
||||
}
|
||||
.summary {
|
||||
margin: 14px 0 0;
|
||||
font-size: 18px;
|
||||
line-height: 1.7;
|
||||
color: #52627a;
|
||||
}
|
||||
.meta {
|
||||
min-width: 320px;
|
||||
padding: 18px 20px;
|
||||
border-radius: 24px;
|
||||
background: rgba(255, 255, 255, 0.76);
|
||||
border: 1px solid rgba(20, 32, 51, 0.08);
|
||||
box-shadow: 0 18px 48px rgba(29, 55, 90, 0.08);
|
||||
}
|
||||
.meta strong,
|
||||
.meta span {
|
||||
display: block;
|
||||
}
|
||||
.meta strong {
|
||||
font-size: 13px;
|
||||
letter-spacing: 0.1em;
|
||||
text-transform: uppercase;
|
||||
color: #60748d;
|
||||
}
|
||||
.meta span {
|
||||
margin-top: 8px;
|
||||
font-size: 20px;
|
||||
color: #142033;
|
||||
line-height: 1.5;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.card {
|
||||
margin-top: 16px;
|
||||
padding: 20px;
|
||||
border-radius: 28px;
|
||||
background: rgba(255, 255, 255, 0.82);
|
||||
border: 1px solid rgba(20, 32, 51, 0.08);
|
||||
box-shadow: 0 24px 80px rgba(24, 41, 72, 0.1);
|
||||
}
|
||||
.card h2 {
|
||||
margin: 0;
|
||||
font-size: 30px;
|
||||
}
|
||||
.card p {
|
||||
margin: 8px 0 0;
|
||||
color: #5a6b83;
|
||||
line-height: 1.65;
|
||||
font-size: 16px;
|
||||
}
|
||||
.preview {
|
||||
margin-top: 14px;
|
||||
overflow: hidden;
|
||||
border-radius: 20px;
|
||||
border: 1px solid rgba(20, 32, 51, 0.08);
|
||||
background: #dfe7f3;
|
||||
}
|
||||
.preview img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
.preview--time {
|
||||
max-height: 500px;
|
||||
}
|
||||
.preview--time img {
|
||||
object-fit: cover;
|
||||
object-position: top center;
|
||||
}
|
||||
.url {
|
||||
margin-top: 10px;
|
||||
font-family: ui-monospace, "SFMono-Regular", Menlo, monospace;
|
||||
font-size: 14px;
|
||||
color: #58708f;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main class="sheet">
|
||||
<section class="hero">
|
||||
<div>
|
||||
<p class="eyebrow">Tencent Redeem Proof</p>
|
||||
<h1>兑换截图与北京时间截图</h1>
|
||||
<p class="summary">用于留存兑换结果与北京时间检索页的组合凭证。</p>
|
||||
</div>
|
||||
<div class="meta">
|
||||
<strong>Captured At</strong>
|
||||
<span>${escapeHtml(capturedAt)}</span>
|
||||
</div>
|
||||
</section>
|
||||
<section class="card">
|
||||
<h2>兑换结果截图</h2>
|
||||
<p>${escapeHtml(redeemMessage || '兑换完成')}</p>
|
||||
<div class="preview">
|
||||
<img src="data:image/png;base64,${redeemImageBase64}" alt="兑换结果截图" />
|
||||
</div>
|
||||
</section>
|
||||
<section class="card">
|
||||
<h2>百度检索“北京时间”截图</h2>
|
||||
<p>单独标签页打开百度搜索结果并截图。</p>
|
||||
<div class="url">${escapeHtml(timePageUrl)}</div>
|
||||
<div class="preview preview--time">
|
||||
<img src="data:image/png;base64,${timeImageBase64}" alt="北京时间截图" />
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
</html>`
|
||||
}
|
||||
|
||||
export function escapeHtml(value) {
|
||||
return String(value || '')
|
||||
.replaceAll('&', '&')
|
||||
.replaceAll('<', '<')
|
||||
.replaceAll('>', '>')
|
||||
.replaceAll('"', '"')
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
// @ts-nocheck
|
||||
|
||||
import fs from 'node:fs/promises'
|
||||
|
||||
import { buildProofHtml } from './session-proof-html.js'
|
||||
|
||||
export async function showResultDialog(page, message) {
|
||||
await page.evaluate((text) => {
|
||||
document.querySelectorAll('iframe').forEach((element) => element.remove())
|
||||
document.querySelectorAll('.pop').forEach((element) => {
|
||||
element.style.display = 'none'
|
||||
})
|
||||
|
||||
let card = document.getElementById('codex-redeem-result')
|
||||
|
||||
if (!card) {
|
||||
card = document.createElement('div')
|
||||
card.id = 'codex-redeem-result'
|
||||
card.innerHTML = `
|
||||
<div class="codex-redeem-result__eyebrow">Tencent Browser Session</div>
|
||||
<div class="codex-redeem-result__title">兑换结果</div>
|
||||
<div class="codex-redeem-result__message"></div>
|
||||
`
|
||||
document.body.appendChild(card)
|
||||
}
|
||||
|
||||
const messageNode = card.querySelector('.codex-redeem-result__message')
|
||||
|
||||
if (messageNode) {
|
||||
messageNode.textContent = text
|
||||
}
|
||||
|
||||
Object.assign(card.style, {
|
||||
position: 'fixed',
|
||||
left: '50%',
|
||||
top: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
zIndex: '99999',
|
||||
width: 'min(560px, calc(100vw - 80px))',
|
||||
padding: '32px 36px',
|
||||
borderRadius: '24px',
|
||||
background: 'rgba(10, 18, 28, 0.92)',
|
||||
boxShadow: '0 24px 80px rgba(0, 0, 0, 0.35)',
|
||||
border: '1px solid rgba(109, 241, 202, 0.25)',
|
||||
color: '#f4fbff',
|
||||
fontFamily: '"PingFang SC", "Microsoft YaHei", sans-serif',
|
||||
})
|
||||
|
||||
const styleId = 'codex-redeem-result-style'
|
||||
|
||||
if (!document.getElementById(styleId)) {
|
||||
const style = document.createElement('style')
|
||||
style.id = styleId
|
||||
style.textContent = `
|
||||
#codex-redeem-result .codex-redeem-result__eyebrow {
|
||||
font-size: 12px;
|
||||
letter-spacing: 0.18em;
|
||||
text-transform: uppercase;
|
||||
color: #6df1ca;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
#codex-redeem-result .codex-redeem-result__title {
|
||||
font-size: 34px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
#codex-redeem-result .codex-redeem-result__message {
|
||||
font-size: 24px;
|
||||
line-height: 1.5;
|
||||
color: #ffffff;
|
||||
}
|
||||
`
|
||||
document.head.appendChild(style)
|
||||
}
|
||||
|
||||
window.scrollTo(0, 0)
|
||||
}, message)
|
||||
|
||||
await page.waitForTimeout(600)
|
||||
}
|
||||
|
||||
export async function composeProofScreenshot(
|
||||
browserContext,
|
||||
{ outputPath, redeemImagePath, timeImagePath, redeemMessage, timePageUrl },
|
||||
) {
|
||||
const [redeemImageBase64, timeImageBase64] = await Promise.all([
|
||||
fs.readFile(redeemImagePath, 'base64'),
|
||||
fs.readFile(timeImagePath, 'base64'),
|
||||
])
|
||||
|
||||
const composePage = await browserContext.newPage()
|
||||
|
||||
try {
|
||||
await composePage.setViewportSize({ width: 1520, height: 1900 })
|
||||
await composePage.setContent(
|
||||
buildProofHtml({
|
||||
redeemImageBase64,
|
||||
timeImageBase64,
|
||||
redeemMessage,
|
||||
timePageUrl,
|
||||
}),
|
||||
{ waitUntil: 'domcontentloaded' },
|
||||
)
|
||||
await composePage.screenshot({ path: outputPath, fullPage: true })
|
||||
} finally {
|
||||
await composePage.close().catch(() => null)
|
||||
}
|
||||
}
|
||||
@@ -4,10 +4,10 @@ import fs from 'node:fs/promises'
|
||||
|
||||
import {
|
||||
BAIDU_BEIJING_TIME_URL,
|
||||
BEIJING_TIME_PROOF_CLIP,
|
||||
BEIJING_TIME_PROOF_VIEWPORT,
|
||||
DEFAULT_REDEEM_PROOF_MODE,
|
||||
} from './session-proof-constants.js'
|
||||
import { captureBeijingTimeProof } from './session-proof-beijing-time.js'
|
||||
import { showResultDialog, composeProofScreenshot } from './session-proof-renderer.js'
|
||||
import { resolveRedeemProofMode, shouldCaptureBeijingTimeProof } from './session-proof-mode.js'
|
||||
import { buildArtifactPaths } from './session-proof-paths.js'
|
||||
import { writeRedeemResultFile } from './session-proof-result-writer.js'
|
||||
@@ -143,299 +143,3 @@ export async function writeRedeemArtifactFiles({
|
||||
}
|
||||
|
||||
export { resolveRedeemProofMode, shouldCaptureBeijingTimeProof }
|
||||
|
||||
async function showResultDialog(page, message) {
|
||||
await page.evaluate((text) => {
|
||||
document.querySelectorAll('iframe').forEach((element) => element.remove())
|
||||
document.querySelectorAll('.pop').forEach((element) => {
|
||||
element.style.display = 'none'
|
||||
})
|
||||
|
||||
let card = document.getElementById('codex-redeem-result')
|
||||
|
||||
if (!card) {
|
||||
card = document.createElement('div')
|
||||
card.id = 'codex-redeem-result'
|
||||
card.innerHTML = `
|
||||
<div class="codex-redeem-result__eyebrow">Tencent Browser Session</div>
|
||||
<div class="codex-redeem-result__title">兑换结果</div>
|
||||
<div class="codex-redeem-result__message"></div>
|
||||
`
|
||||
document.body.appendChild(card)
|
||||
}
|
||||
|
||||
const messageNode = card.querySelector('.codex-redeem-result__message')
|
||||
|
||||
if (messageNode) {
|
||||
messageNode.textContent = text
|
||||
}
|
||||
|
||||
Object.assign(card.style, {
|
||||
position: 'fixed',
|
||||
left: '50%',
|
||||
top: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
zIndex: '99999',
|
||||
width: 'min(560px, calc(100vw - 80px))',
|
||||
padding: '32px 36px',
|
||||
borderRadius: '24px',
|
||||
background: 'rgba(10, 18, 28, 0.92)',
|
||||
boxShadow: '0 24px 80px rgba(0, 0, 0, 0.35)',
|
||||
border: '1px solid rgba(109, 241, 202, 0.25)',
|
||||
color: '#f4fbff',
|
||||
fontFamily: '"PingFang SC", "Microsoft YaHei", sans-serif',
|
||||
})
|
||||
|
||||
const styleId = 'codex-redeem-result-style'
|
||||
|
||||
if (!document.getElementById(styleId)) {
|
||||
const style = document.createElement('style')
|
||||
style.id = styleId
|
||||
style.textContent = `
|
||||
#codex-redeem-result .codex-redeem-result__eyebrow {
|
||||
font-size: 12px;
|
||||
letter-spacing: 0.18em;
|
||||
text-transform: uppercase;
|
||||
color: #6df1ca;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
#codex-redeem-result .codex-redeem-result__title {
|
||||
font-size: 34px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
#codex-redeem-result .codex-redeem-result__message {
|
||||
font-size: 24px;
|
||||
line-height: 1.5;
|
||||
color: #ffffff;
|
||||
}
|
||||
`
|
||||
document.head.appendChild(style)
|
||||
}
|
||||
|
||||
window.scrollTo(0, 0)
|
||||
}, message)
|
||||
|
||||
await page.waitForTimeout(600)
|
||||
}
|
||||
|
||||
async function captureBeijingTimeProof(browserContext, { screenshotPath }) {
|
||||
const proofPage = await browserContext.newPage()
|
||||
|
||||
try {
|
||||
await proofPage.setViewportSize(BEIJING_TIME_PROOF_VIEWPORT)
|
||||
await proofPage.goto(BAIDU_BEIJING_TIME_URL, { waitUntil: 'domcontentloaded' })
|
||||
await proofPage.waitForTimeout(3_000)
|
||||
await proofPage.evaluate(() => {
|
||||
window.scrollTo(0, 0)
|
||||
})
|
||||
await proofPage.screenshot({
|
||||
path: screenshotPath,
|
||||
clip: BEIJING_TIME_PROOF_CLIP,
|
||||
})
|
||||
|
||||
return {
|
||||
screenshotPath,
|
||||
pageUrl: BAIDU_BEIJING_TIME_URL,
|
||||
}
|
||||
} finally {
|
||||
await proofPage.close().catch(() => null)
|
||||
}
|
||||
}
|
||||
|
||||
async function composeProofScreenshot(
|
||||
browserContext,
|
||||
{ outputPath, redeemImagePath, timeImagePath, redeemMessage, timePageUrl },
|
||||
) {
|
||||
const [redeemImageBase64, timeImageBase64] = await Promise.all([
|
||||
fs.readFile(redeemImagePath, 'base64'),
|
||||
fs.readFile(timeImagePath, 'base64'),
|
||||
])
|
||||
|
||||
const composePage = await browserContext.newPage()
|
||||
|
||||
try {
|
||||
await composePage.setViewportSize({ width: 1520, height: 1900 })
|
||||
await composePage.setContent(
|
||||
buildProofHtml({
|
||||
redeemImageBase64,
|
||||
timeImageBase64,
|
||||
redeemMessage,
|
||||
timePageUrl,
|
||||
}),
|
||||
{ waitUntil: 'domcontentloaded' },
|
||||
)
|
||||
await composePage.screenshot({ path: outputPath, fullPage: true })
|
||||
} finally {
|
||||
await composePage.close().catch(() => null)
|
||||
}
|
||||
}
|
||||
|
||||
function buildProofHtml({ redeemImageBase64, timeImageBase64, redeemMessage, timePageUrl }) {
|
||||
const capturedAt = new Date().toLocaleString('zh-CN', {
|
||||
hour12: false,
|
||||
timeZone: 'Asia/Shanghai',
|
||||
})
|
||||
|
||||
return `<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>兑换与北京时间凭证</title>
|
||||
<style>
|
||||
:root {
|
||||
color-scheme: light;
|
||||
}
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: "PingFang SC", "Microsoft YaHei", sans-serif;
|
||||
background:
|
||||
radial-gradient(circle at top left, rgba(33, 150, 243, 0.16), transparent 28%),
|
||||
linear-gradient(180deg, #edf4ff 0%, #f6f8fc 52%, #eef1f7 100%);
|
||||
color: #142033;
|
||||
}
|
||||
.sheet {
|
||||
width: 1480px;
|
||||
margin: 0 auto;
|
||||
padding: 30px 24px 28px;
|
||||
}
|
||||
.hero {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 20px;
|
||||
align-items: flex-end;
|
||||
padding: 0 6px 18px;
|
||||
}
|
||||
.eyebrow {
|
||||
margin: 0 0 12px;
|
||||
color: #1c78d0;
|
||||
font-size: 13px;
|
||||
letter-spacing: 0.2em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
h1 {
|
||||
margin: 0;
|
||||
font-size: 44px;
|
||||
line-height: 1.08;
|
||||
}
|
||||
.summary {
|
||||
margin: 14px 0 0;
|
||||
font-size: 18px;
|
||||
line-height: 1.7;
|
||||
color: #52627a;
|
||||
}
|
||||
.meta {
|
||||
min-width: 320px;
|
||||
padding: 18px 20px;
|
||||
border-radius: 24px;
|
||||
background: rgba(255, 255, 255, 0.76);
|
||||
border: 1px solid rgba(20, 32, 51, 0.08);
|
||||
box-shadow: 0 18px 48px rgba(29, 55, 90, 0.08);
|
||||
}
|
||||
.meta strong,
|
||||
.meta span {
|
||||
display: block;
|
||||
}
|
||||
.meta strong {
|
||||
font-size: 13px;
|
||||
letter-spacing: 0.1em;
|
||||
text-transform: uppercase;
|
||||
color: #60748d;
|
||||
}
|
||||
.meta span {
|
||||
margin-top: 8px;
|
||||
font-size: 20px;
|
||||
color: #142033;
|
||||
line-height: 1.5;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.card {
|
||||
margin-top: 16px;
|
||||
padding: 20px;
|
||||
border-radius: 28px;
|
||||
background: rgba(255, 255, 255, 0.82);
|
||||
border: 1px solid rgba(20, 32, 51, 0.08);
|
||||
box-shadow: 0 24px 80px rgba(24, 41, 72, 0.1);
|
||||
}
|
||||
.card h2 {
|
||||
margin: 0;
|
||||
font-size: 30px;
|
||||
}
|
||||
.card p {
|
||||
margin: 8px 0 0;
|
||||
color: #5a6b83;
|
||||
line-height: 1.65;
|
||||
font-size: 16px;
|
||||
}
|
||||
.preview {
|
||||
margin-top: 14px;
|
||||
overflow: hidden;
|
||||
border-radius: 20px;
|
||||
border: 1px solid rgba(20, 32, 51, 0.08);
|
||||
background: #dfe7f3;
|
||||
}
|
||||
.preview img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
.preview--time {
|
||||
max-height: 500px;
|
||||
}
|
||||
.preview--time img {
|
||||
object-fit: cover;
|
||||
object-position: top center;
|
||||
}
|
||||
.url {
|
||||
margin-top: 10px;
|
||||
font-family: ui-monospace, "SFMono-Regular", Menlo, monospace;
|
||||
font-size: 14px;
|
||||
color: #58708f;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main class="sheet">
|
||||
<section class="hero">
|
||||
<div>
|
||||
<p class="eyebrow">Tencent Redeem Proof</p>
|
||||
<h1>兑换截图与北京时间截图</h1>
|
||||
<p class="summary">用于留存兑换结果与北京时间检索页的组合凭证。</p>
|
||||
</div>
|
||||
<div class="meta">
|
||||
<strong>Captured At</strong>
|
||||
<span>${escapeHtml(capturedAt)}</span>
|
||||
</div>
|
||||
</section>
|
||||
<section class="card">
|
||||
<h2>兑换结果截图</h2>
|
||||
<p>${escapeHtml(redeemMessage || '兑换完成')}</p>
|
||||
<div class="preview">
|
||||
<img src="data:image/png;base64,${redeemImageBase64}" alt="兑换结果截图" />
|
||||
</div>
|
||||
</section>
|
||||
<section class="card">
|
||||
<h2>百度检索“北京时间”截图</h2>
|
||||
<p>单独标签页打开百度搜索结果并截图。</p>
|
||||
<div class="url">${escapeHtml(timePageUrl)}</div>
|
||||
<div class="preview preview--time">
|
||||
<img src="data:image/png;base64,${timeImageBase64}" alt="北京时间截图" />
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
</html>`
|
||||
}
|
||||
|
||||
function escapeHtml(value) {
|
||||
return String(value || '')
|
||||
.replaceAll('&', '&')
|
||||
.replaceAll('<', '<')
|
||||
.replaceAll('>', '>')
|
||||
.replaceAll('"', '"')
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import assert from 'node:assert/strict'
|
||||
import path from 'node:path'
|
||||
|
||||
import { resolveRedeemProofMode, shouldCaptureBeijingTimeProof } from './session-proof.js'
|
||||
import { buildProofHtml } from './session-proof-html.js'
|
||||
import { buildArtifactPaths } from './session-proof-paths.js'
|
||||
|
||||
test('resolveRedeemProofMode normalizes configured modes and falls back to full', () => {
|
||||
@@ -62,3 +63,19 @@ test('buildArtifactPaths keeps redeem proof artifact filenames stable', () => {
|
||||
assert.equal(artifactPaths.htmlPath, path.join('/tmp/redeem-proof', 'page.html'))
|
||||
assert.equal(artifactPaths.resultPath, path.join('/tmp/redeem-proof', 'result.json'))
|
||||
})
|
||||
|
||||
test('buildProofHtml escapes injected text while keeping proof sections stable', () => {
|
||||
const html = buildProofHtml({
|
||||
redeemImageBase64: 'redeem-base64',
|
||||
timeImageBase64: 'time-base64',
|
||||
redeemMessage: '<b>兑换成功 & 已到账</b>',
|
||||
timePageUrl: 'https://example.com/?q=<北京时间>',
|
||||
})
|
||||
|
||||
assert.match(html, /Tencent Redeem Proof/)
|
||||
assert.match(html, /兑换截图与北京时间截图/)
|
||||
assert.match(html, /<b>兑换成功 & 已到账<\/b>/)
|
||||
assert.match(html, /https:\/\/example\.com\/\?q=<北京时间>/)
|
||||
assert.match(html, /data:image\/png;base64,redeem-base64/)
|
||||
assert.match(html, /data:image\/png;base64,time-base64/)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user