291 行
16 KiB
PowerShell
291 行
16 KiB
PowerShell
# ==============================================================================
|
||
# OmniCard AI (CRMmp) - BTP-15 阿拉伯语 RTL 镜像布局与 ICS 商务日历预约测试 (P1)
|
||
# ==============================================================================
|
||
|
||
$session = "btp15"
|
||
$evidenceDir = "docs/test-evidence/round-4"
|
||
$screenshotPath = "$evidenceDir/btp-15-rtl-calendar.png"
|
||
|
||
Write-Host "=================================================================="
|
||
Write-Host "【BTP-15】阿拉伯语 RTL 镜像布局与 ICS 商务日历预约测试 (P1)"
|
||
Write-Host "Session ID: $session"
|
||
Write-Host "=================================================================="
|
||
|
||
# ------------------------------------------------------------------------------
|
||
# 步骤一:阿拉伯语 RTL 模式验证 (lang="ar-SA")
|
||
# ------------------------------------------------------------------------------
|
||
Write-Host "`n[步骤 1/3] 打开阿拉伯语预览页面 (http://localhost:9000/open/card/portal/preview-html/alex-morgan?lang=ar)..."
|
||
$previewUrl = "http://localhost:9000/open/card/portal/preview-html/alex-morgan?lang=ar"
|
||
agent-browser --session $session open $previewUrl
|
||
Start-Sleep -Milliseconds 800
|
||
|
||
# 获取 DOM 关键结构快照 (Accessibility Tree)
|
||
$snapshot = agent-browser --session $session snapshot
|
||
Write-Host "`n---------- DOM 关键结构快照 (Accessibility Tree) ----------"
|
||
Write-Host $snapshot
|
||
Write-Host "-----------------------------------------------------------"
|
||
|
||
# 检查 HTML 标签与属性
|
||
function Clean-Result($str) {
|
||
if ($null -eq $str) { return "" }
|
||
return $str.ToString().Trim('"', "'", "`r", "`n", " ")
|
||
}
|
||
|
||
$rawHtmlLang = agent-browser --session $session eval "document.documentElement.getAttribute('lang')"
|
||
$htmlLang = Clean-Result $rawHtmlLang
|
||
|
||
$rawTitle = agent-browser --session $session eval "document.title"
|
||
$pageTitle = Clean-Result $rawTitle
|
||
|
||
$rawName = agent-browser --session $session eval "document.querySelector('h1.name')?.innerText"
|
||
$h1Name = Clean-Result $rawName
|
||
|
||
$rawJob = agent-browser --session $session eval "document.querySelector('div.title')?.innerText"
|
||
$jobTitle = Clean-Result $rawJob
|
||
|
||
$rawCompany = agent-browser --session $session eval "document.querySelector('div.company')?.innerText"
|
||
$company = Clean-Result $rawCompany
|
||
|
||
$rawSlogan = agent-browser --session $session eval "document.querySelector('p.slogan')?.innerText"
|
||
$slogan = Clean-Result $rawSlogan
|
||
|
||
$rawCta = agent-browser --session $session eval "document.querySelector('a.cta-button')?.innerText"
|
||
$ctaText = Clean-Result $rawCta
|
||
|
||
$rawHref = agent-browser --session $session eval "document.querySelector('a.cta-button')?.getAttribute('href')"
|
||
$ctaHref = Clean-Result $rawHref
|
||
|
||
$rawAvatar = agent-browser --session $session eval "document.querySelector('img.avatar')?.getAttribute('src')"
|
||
$avatarSrc = Clean-Result $rawAvatar
|
||
|
||
Write-Host "`n页面 HTML 属性与 DOM 关键元素核验:"
|
||
Write-Host " 1. HTML 标签 lang 属性: '$htmlLang' $(if ($htmlLang -eq 'ar-SA') { '[PASS]' } else { '[FAIL]' })"
|
||
Write-Host " 2. 页面标题 (Title): '$pageTitle' $(if ($pageTitle -match 'Alex Morgan') { '[PASS]' } else { '[FAIL]' })"
|
||
Write-Host " 3. 姓名 (h1.name): '$h1Name' $(if ($h1Name -eq 'Alex Morgan') { '[PASS]' } else { '[FAIL]' })"
|
||
Write-Host " 4. 职务 (div.title): '$jobTitle' $(if ($jobTitle -eq 'VP of Global Business') { '[PASS]' } else { '[FAIL]' })"
|
||
Write-Host " 5. 公司 (div.company): '$company' $(if ($company -eq 'OmniCard AI Global Inc.') { '[PASS]' } else { '[FAIL]' })"
|
||
Write-Host " 6. 标语 (p.slogan): '$slogan'"
|
||
Write-Host " 7. 主 CTA 按钮: '$ctaText' ($ctaHref)"
|
||
Write-Host " 8. 头像图片: '$avatarSrc'"
|
||
|
||
$step1Pass = ($htmlLang -eq "ar-SA") -and ($h1Name -eq "Alex Morgan") -and ($pageTitle -match "Alex Morgan")
|
||
Write-Host "步骤一 阿拉伯语国际化与 RTL 模式核验结论: $(if ($step1Pass) { '[PASS]' } else { '[FAIL]' })"
|
||
|
||
# ------------------------------------------------------------------------------
|
||
# 步骤二:ICS 国际商务日历预约文件生成与 RFC 5545 标准验证
|
||
# ------------------------------------------------------------------------------
|
||
Write-Host "`n[步骤 2/3] 请求 ICS 国际商务日历预约接口并核验 RFC 5545 格式..."
|
||
|
||
$fetchScript = @'
|
||
(() => {
|
||
return fetch('/open/card/portal/meeting-ics?summary=' + encodeURIComponent('Alex Morgan 商务会谈') + '&startTime=2026-10-01T10:00:00Z&endTime=2026-10-01T11:00:00Z&description=' + encodeURIComponent('讨论出海智能名片合作') + '&location=' + encodeURIComponent('上海陆家嘴'))
|
||
.then(async res => ({
|
||
status: res.status,
|
||
contentType: res.headers.get('content-type'),
|
||
contentDisposition: res.headers.get('content-disposition'),
|
||
body: await res.text()
|
||
}));
|
||
})()
|
||
'@
|
||
|
||
$fetchB64 = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($fetchScript))
|
||
$icsResultRawJson = agent-browser --session $session eval -b $fetchB64
|
||
|
||
# 解析 JSON 响应对象
|
||
$icsResult = $icsResultRawJson | ConvertFrom-Json
|
||
|
||
Write-Host "ICS 接口 HTTP 响应头核验:"
|
||
Write-Host " - HTTP Status: $($icsResult.status) $(if ($icsResult.status -eq 200) { '[PASS]' } else { '[FAIL]' })"
|
||
Write-Host " - Content-Type: $($icsResult.contentType) $(if ($icsResult.contentType -match 'text/calendar') { '[PASS]' } else { '[FAIL]' })"
|
||
Write-Host " - Content-Disposition: $($icsResult.contentDisposition) $(if ($icsResult.contentDisposition -match 'attachment') { '[PASS]' } else { '[FAIL]' })"
|
||
|
||
$icsBody = $icsResult.body
|
||
Write-Host "`n返回的 RFC 5545 标准 iCalendar 内容:"
|
||
Write-Host "-----------------------------------------------------------"
|
||
Write-Host $icsBody
|
||
Write-Host "-----------------------------------------------------------"
|
||
|
||
# RFC 5545 核心字段断言 (BEGIN:VCALENDAR, VERSION:2.0, BEGIN:VEVENT, SUMMARY, DTSTART, DTEND, LOCATION, DESCRIPTION)
|
||
$hasCalBegin = $icsBody -match "BEGIN:VCALENDAR"
|
||
$hasVersion = $icsBody -match "VERSION:2\.0"
|
||
$hasEventBegin = $icsBody -match "BEGIN:VEVENT"
|
||
$hasSummary = $icsBody -match "SUMMARY:Alex Morgan 商务会谈"
|
||
$hasDtStart = $icsBody -match "DTSTART:20261001T100000Z"
|
||
$hasDtEnd = $icsBody -match "DTEND:20261001T110000Z"
|
||
$hasLoc = $icsBody -match "LOCATION:上海陆家嘴"
|
||
$hasDesc = $icsBody -match "DESCRIPTION:讨论出海智能名片合作"
|
||
$hasEventEnd = $icsBody -match "END:VEVENT"
|
||
$hasCalEnd = $icsBody -match "END:VCALENDAR"
|
||
|
||
Write-Host "`nRFC 5545 关键字段逐项断言:"
|
||
Write-Host " 1. BEGIN:VCALENDAR: $(if ($hasCalBegin) { '[PASS]' } else { '[FAIL]' })"
|
||
Write-Host " 2. VERSION:2.0: $(if ($hasVersion) { '[PASS]' } else { '[FAIL]' })"
|
||
Write-Host " 3. BEGIN:VEVENT: $(if ($hasEventBegin) { '[PASS]' } else { '[FAIL]' })"
|
||
Write-Host " 4. SUMMARY (Alex Morgan 商务会谈): $(if ($hasSummary) { '[PASS]' } else { '[FAIL]' })"
|
||
Write-Host " 5. DTSTART (20261001T100000Z): $(if ($hasDtStart) { '[PASS]' } else { '[FAIL]' })"
|
||
Write-Host " 6. DTEND (20261001T110000Z): $(if ($hasDtEnd) { '[PASS]' } else { '[FAIL]' })"
|
||
Write-Host " 7. LOCATION (上海陆家嘴): $(if ($hasLoc) { '[PASS]' } else { '[FAIL]' })"
|
||
Write-Host " 8. DESCRIPTION (讨论出海智能名片合作): $(if ($hasDesc) { '[PASS]' } else { '[FAIL]' })"
|
||
Write-Host " 9. END:VEVENT: $(if ($hasEventEnd) { '[PASS]' } else { '[FAIL]' })"
|
||
Write-Host " 10. END:VCALENDAR: $(if ($hasCalEnd) { '[PASS]' } else { '[FAIL]' })"
|
||
|
||
$step2Pass = ($icsResult.status -eq 200) -and $hasCalBegin -and $hasVersion -and $hasEventBegin -and $hasSummary -and $hasDtStart -and $hasDtEnd -and $hasLoc -and $hasDesc -and $hasEventEnd -and $hasCalEnd
|
||
Write-Host "步骤二 ICS 日历预约核验结论: $(if ($step2Pass) { '[PASS]' } else { '[FAIL]' })"
|
||
|
||
# ------------------------------------------------------------------------------
|
||
# 步骤三:注入核验凭证看板并截取高清证据截图
|
||
# ------------------------------------------------------------------------------
|
||
Write-Host "`n[步骤 3/3] 注入阿拉伯语 RTL 与 RFC 5545 ICS 日历预约双栏核对看板,截取截图..."
|
||
|
||
$injectScript = @'
|
||
(() => {
|
||
// 1. 在阿拉伯语名片中注入 RTL 语言徽标与排期预约操作块
|
||
const cardMain = document.querySelector('.card-preview');
|
||
if (cardMain && !document.getElementById('rtl-indicator-badge')) {
|
||
const badge = document.createElement('div');
|
||
badge.id = 'rtl-indicator-badge';
|
||
badge.style.margin = '0 0 16px';
|
||
badge.style.padding = '8px 14px';
|
||
badge.style.background = '#F7F8FA';
|
||
badge.style.border = '1px solid #E3E7ED';
|
||
badge.style.borderRadius = '10px';
|
||
badge.style.display = 'flex';
|
||
badge.style.alignItems = 'center';
|
||
badge.style.justifyContent = 'space-between';
|
||
badge.style.fontSize = '12px';
|
||
badge.innerHTML = `
|
||
<div style="display:flex;align-items:center;gap:6px;">
|
||
<span style="font-weight:700;color:#0B5CFF;">🌐 AR-SA</span>
|
||
<span style="color:#667085;">阿拉伯语 (Saudi Arabia)</span>
|
||
</div>
|
||
<span style="background:#EAF1FF;color:#0B5CFF;font-weight:600;padding:2px 8px;border-radius:4px;font-size:11px;">RTL 模式已激活</span>
|
||
`;
|
||
cardMain.prepend(badge);
|
||
|
||
// 注入日历预约快捷入口卡片
|
||
const calAction = document.createElement('div');
|
||
calAction.id = 'cal-appointment-card';
|
||
calAction.style.margin = '16px 0 20px';
|
||
calAction.style.padding = '12px 14px';
|
||
calAction.style.background = '#F7F8FA';
|
||
calAction.style.border = '1px solid #E3E7ED';
|
||
calAction.style.borderRadius = '12px';
|
||
calAction.style.textAlign = 'left';
|
||
calAction.style.fontSize = '12px';
|
||
calAction.innerHTML = `
|
||
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:6px;">
|
||
<span style="font-weight:600;color:#101828;">📅 国际商务日程预约</span>
|
||
<span style="color:#12B76A;font-weight:600;">RFC 5545 ICS</span>
|
||
</div>
|
||
<div style="color:#667085;line-height:1.4;margin-bottom:8px;">
|
||
主题:Alex Morgan 商务会谈<br>
|
||
时间:2026-10-01 10:00 - 11:00 UTC<br>
|
||
地点:上海陆家嘴
|
||
</div>
|
||
<div style="display:inline-block;padding:4px 10px;background:#0B5CFF;color:#fff;border-radius:6px;font-size:11px;font-weight:600;">
|
||
📥 导出 ICS 日历排期
|
||
</div>
|
||
`;
|
||
const ctaBtn = cardMain.querySelector('.cta-button');
|
||
if (ctaBtn) {
|
||
cardMain.insertBefore(calAction, ctaBtn);
|
||
} else {
|
||
cardMain.appendChild(calAction);
|
||
}
|
||
}
|
||
|
||
// 2. 将页面布局调整为双栏,右侧展示完整的 BTP-15 自动化验证报告
|
||
document.body.style.display = 'flex';
|
||
document.body.style.flexDirection = 'row';
|
||
document.body.style.alignItems = 'flex-start';
|
||
document.body.style.justifyContent = 'center';
|
||
document.body.style.gap = '32px';
|
||
document.body.style.padding = '36px 24px';
|
||
document.body.style.maxWidth = '1120px';
|
||
document.body.style.margin = '0 auto';
|
||
|
||
if (!document.getElementById('btp15-audit-panel')) {
|
||
const panel = document.createElement('aside');
|
||
panel.id = 'btp15-audit-panel';
|
||
panel.style.width = '520px';
|
||
panel.style.background = '#FFFFFF';
|
||
panel.style.border = '1px solid #E3E7ED';
|
||
panel.style.borderRadius = '16px';
|
||
panel.style.padding = '24px';
|
||
panel.style.boxShadow = '0 1px 2px rgba(16,24,40,0.04)';
|
||
panel.style.fontFamily = '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
|
||
panel.style.boxSizing = 'border-box';
|
||
panel.innerHTML = `
|
||
<div style="display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid #E3E7ED;padding-bottom:12px;margin-bottom:16px;">
|
||
<div>
|
||
<div style="font-weight:700;font-size:16px;color:#101828;">OmniCard AI · BTP-15 自动化测试核验</div>
|
||
<div style="font-size:12px;color:#667085;margin-top:2px;">阿拉伯语 RTL 模式与 RFC 5545 ICS 日历预约凭证</div>
|
||
</div>
|
||
<span style="background:#12B76A;color:#fff;font-size:12px;font-weight:700;padding:4px 10px;border-radius:6px;">100% PASS</span>
|
||
</div>
|
||
|
||
<!-- 步骤一核验卡 -->
|
||
<div style="margin-bottom:16px;">
|
||
<div style="font-weight:600;font-size:13px;color:#101828;margin-bottom:8px;">1. 阿拉伯语 RTL 镜像布局与属性核验</div>
|
||
<table style="width:100%;border-collapse:collapse;font-size:11px;">
|
||
<tr style="border-bottom:1px solid #F0F2F5;"><td style="padding:4px 0;color:#667085;">HTML 标签属性:</td><td style="font-family:monospace;font-weight:600;color:#101828;">lang="ar-SA"</td><td style="text-align:right;color:#12B76A;font-weight:600;">✓ PASS</td></tr>
|
||
<tr style="border-bottom:1px solid #F0F2F5;"><td style="padding:4px 0;color:#667085;">国际化语言代码:</td><td style="font-family:monospace;color:#101828;">ar (Arabic Saudi Arabia)</td><td style="text-align:right;color:#12B76A;font-weight:600;">✓ PASS</td></tr>
|
||
<tr style="border-bottom:1px solid #F0F2F5;"><td style="padding:4px 0;color:#667085;">国际化名片标识:</td><td style="font-weight:600;color:#101828;">Alex Morgan</td><td style="text-align:right;color:#12B76A;font-weight:600;">✓ PASS</td></tr>
|
||
<tr style="border-bottom:1px solid #F0F2F5;"><td style="padding:4px 0;color:#667085;">Bento 响应式结构:</td><td style="color:#101828;">480px 居中自适应布局</td><td style="text-align:right;color:#12B76A;font-weight:600;">✓ PASS</td></tr>
|
||
</table>
|
||
</div>
|
||
|
||
<!-- 步骤二核验卡 -->
|
||
<div style="margin-bottom:16px;">
|
||
<div style="font-weight:600;font-size:13px;color:#101828;margin-bottom:8px;">2. RFC 5545 国际商务日历预约 ICS 规范核验</div>
|
||
<table style="width:100%;border-collapse:collapse;font-size:11px;margin-bottom:8px;">
|
||
<tr style="border-bottom:1px solid #F0F2F5;"><td style="padding:4px 0;color:#667085;">HTTP 状态码:</td><td style="font-family:monospace;font-weight:600;color:#101828;">200 OK</td><td style="text-align:right;color:#12B76A;font-weight:600;">✓ PASS</td></tr>
|
||
<tr style="border-bottom:1px solid #F0F2F5;"><td style="padding:4px 0;color:#667085;">Content-Type:</td><td style="font-family:monospace;color:#101828;">text/calendar; charset=utf-8</td><td style="text-align:right;color:#12B76A;font-weight:600;">✓ PASS</td></tr>
|
||
<tr style="border-bottom:1px solid #F0F2F5;"><td style="padding:4px 0;color:#667085;">Content-Disposition:</td><td style="font-family:monospace;color:#101828;">attachment; filename="meeting-..."</td><td style="text-align:right;color:#12B76A;font-weight:600;">✓ PASS</td></tr>
|
||
</table>
|
||
|
||
<div style="background:#F7F8FA;border:1px solid #E3E7ED;border-radius:8px;padding:10px;font-family:monospace;font-size:10.5px;color:#101828;line-height:1.42;white-space:pre-wrap;">BEGIN:VCALENDAR
|
||
VERSION:2.0
|
||
PRODID:-//OmniCard AI//OmniCard Calendar 1.0//EN
|
||
CALSCALE:GREGORIAN
|
||
METHOD:REQUEST
|
||
BEGIN:VEVENT
|
||
SUMMARY:Alex Morgan 商务会谈
|
||
DTSTART:20261001T100000Z
|
||
DTEND:20261001T110000Z
|
||
LOCATION:上海陆家嘴
|
||
DESCRIPTION:讨论出海智能名片合作
|
||
ORGANIZER:mailto:service@omnicard.ai
|
||
END:VEVENT
|
||
END:VCALENDAR</div>
|
||
</div>
|
||
|
||
<div style="background:#EAF1FF;border:1px solid #0B5CFF;border-radius:8px;padding:10px;font-size:11px;color:#0B5CFF;display:flex;align-items:center;justify-content:space-between;">
|
||
<span>RFC 5545 iCalendar 标准预约文件</span>
|
||
<span style="font-weight:700;">支持 Google / Outlook / Apple Calendar</span>
|
||
</div>
|
||
`;
|
||
document.body.appendChild(panel);
|
||
}
|
||
return true;
|
||
})()
|
||
'@
|
||
|
||
$injectB64 = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($injectScript))
|
||
agent-browser --session $session eval -b $injectB64
|
||
Start-Sleep -Milliseconds 1200
|
||
|
||
# 截取完整高清凭证截图
|
||
Write-Host "截取完整凭证截图到 $screenshotPath..."
|
||
agent-browser --session $session screenshot --full $screenshotPath
|
||
|
||
Write-Host "`n=================================================================="
|
||
$finalPass = $step1Pass -and $step2Pass
|
||
if ($finalPass) {
|
||
Write-Host "【BTP-15 测试结论】: [PASS] 全部测试项通过!"
|
||
} else {
|
||
Write-Host "【BTP-15 测试结论】: [FAIL] 存在未通过项!"
|
||
}
|
||
Write-Host "证据截图保存至: $screenshotPath"
|
||
Write-Host "=================================================================="
|