feat: 新增商业化新账统计与出海财务运营驾驶舱,支持运营官(COO)与财务官(CFO)双重视角穿透
@@ -0,0 +1,236 @@
|
||||
// ==============================================================================
|
||||
// OmniCard AI (CRMmp) - BTP-19 意向度雷达评分算法与线索全景画像测试环境初始化脚本
|
||||
// ==============================================================================
|
||||
(function() {
|
||||
console.log('[BTP-19] Injected test harness for Lead Scoring & Panoramic Profile...');
|
||||
|
||||
// 1. 设置登录凭证与权限
|
||||
try {
|
||||
window.localStorage.setItem('userStatus', 'login');
|
||||
window.localStorage.setItem('userRole', 'admin');
|
||||
window.localStorage.setItem('arco-lang', 'zh-CN');
|
||||
} catch(e) {}
|
||||
|
||||
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjEsInVzZXJuYW1lIjoiYWRtaW4iLCJpYXQiOjE3ODk5MjQ1NjcsImV4cCI6MTc4OTkzMTc2N30.Ia84OYGFXIl6ppPc1E3Y2aGgtdbe__g36oIHNfSGoVk';
|
||||
|
||||
// 2. 预设符合 BTP-19 意向度评分算法的潜客线索数据集
|
||||
const leadsDatabase = [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Fatima Al-Mansoor',
|
||||
company: 'Al-Mansoor Enterprises',
|
||||
phone: '+971-50-9876543',
|
||||
email: 'fatima@almansoor.ae',
|
||||
status: 'hot',
|
||||
intentScore: 92,
|
||||
source: 'nfc',
|
||||
notes: 'Gitex 展会近场 NFC 触碰,高意向采购 5,000 张双语智能卡,已咨询 AI 顾问方案',
|
||||
visitorId: 'vis_uae_001',
|
||||
cardId: 1,
|
||||
createTime: '2026-09-21 01:10:00',
|
||||
updateTime: '2026-09-21 01:10:00'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'Michael Scott',
|
||||
company: 'Dunder Mifflin Global',
|
||||
phone: '+1-555-0199',
|
||||
email: 'michael.scott@dundermifflin.com',
|
||||
status: 'contacted',
|
||||
intentScore: 85,
|
||||
source: 'qr',
|
||||
notes: '扫描宣传单二维码访问,下载了 vCard 电子名片并深度浏览案例达 4 分钟',
|
||||
visitorId: 'vis_us_002',
|
||||
cardId: 1,
|
||||
createTime: '2026-09-21 01:12:00',
|
||||
updateTime: '2026-09-21 01:12:00'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'Elena Rostova',
|
||||
company: 'Nordic Logistics AB',
|
||||
phone: '+46-8-123456',
|
||||
email: 'elena@nordiclogistics.se',
|
||||
status: 'new',
|
||||
intentScore: 65,
|
||||
source: 'link',
|
||||
notes: '通过出海博客短链初次访问,初步浏览了个人档案与核心出海文章',
|
||||
visitorId: 'vis_se_003',
|
||||
cardId: 1,
|
||||
createTime: '2026-09-21 01:15:00',
|
||||
updateTime: '2026-09-21 01:15:00'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: 'Kenji Sato',
|
||||
company: 'Tokyo Cloud Systems',
|
||||
phone: '+81-3-5555-0143',
|
||||
email: 'sato@tokyocloud.jp',
|
||||
status: 'won',
|
||||
intentScore: 98,
|
||||
source: 'ai_chat',
|
||||
notes: '与 AI 数字人顾问深度交互 8 轮后主动提交留资表单,成功签署出海合作协议',
|
||||
visitorId: 'vis_jp_004',
|
||||
cardId: 1,
|
||||
createTime: '2026-09-21 01:20:00',
|
||||
updateTime: '2026-09-21 01:20:00'
|
||||
}
|
||||
];
|
||||
|
||||
// 3. 关联的雷达访问足迹轨迹数据集 (针对 Lead #1 / cardId: 1)
|
||||
const radarDatabase = [
|
||||
{
|
||||
id: 1,
|
||||
cardId: 1,
|
||||
visitorId: 'vis_uae_001',
|
||||
ip: '194.170.1.1',
|
||||
country: 'United Arab Emirates',
|
||||
city: 'Dubai',
|
||||
touchType: 'nfc',
|
||||
stayDurationMs: 320000,
|
||||
scrollDepth: 95,
|
||||
viewedBlocks: ['hero', 'case', 'form', 'social'],
|
||||
createTime: '2026-09-21 01:25:00',
|
||||
createdAt: '2026-09-21 01:25:00'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
cardId: 1,
|
||||
visitorId: 'vis_uae_001',
|
||||
ip: '217.80.0.1',
|
||||
country: 'Germany',
|
||||
city: 'Frankfurt',
|
||||
touchType: 'qr',
|
||||
stayDurationMs: 85000,
|
||||
scrollDepth: 60,
|
||||
viewedBlocks: ['hero', 'article'],
|
||||
createTime: '2026-09-21 01:15:00',
|
||||
createdAt: '2026-09-21 01:15:00'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
cardId: 1,
|
||||
visitorId: 'vis_uae_001',
|
||||
ip: '8.8.8.8',
|
||||
country: 'United States',
|
||||
city: 'San Francisco',
|
||||
touchType: 'link',
|
||||
stayDurationMs: 125000,
|
||||
scrollDepth: 85,
|
||||
viewedBlocks: ['hero', 'social'],
|
||||
createTime: '2026-09-21 00:50:00',
|
||||
createdAt: '2026-09-21 00:50:00'
|
||||
}
|
||||
];
|
||||
|
||||
// 4. 拦截网络请求,保证数据顺畅回显
|
||||
const origOpen = window.XMLHttpRequest.prototype.open;
|
||||
const origSend = window.XMLHttpRequest.prototype.send;
|
||||
|
||||
window.XMLHttpRequest.prototype.open = function(method, url) {
|
||||
this.__method = method;
|
||||
this.__url = url;
|
||||
return origOpen.apply(this, arguments);
|
||||
};
|
||||
|
||||
window.XMLHttpRequest.prototype.send = function(data) {
|
||||
const url = this.__url || '';
|
||||
let parsedBody = {};
|
||||
if (typeof data === 'string') {
|
||||
try { parsedBody = JSON.parse(data); } catch(e) {}
|
||||
}
|
||||
|
||||
// 1) POST /api/admin/crm/lead/page
|
||||
if (url.includes('/api/admin/crm/lead/page') || url.includes('/admin/crm/lead/page')) {
|
||||
let filtered = [...leadsDatabase];
|
||||
if (parsedBody.status) {
|
||||
filtered = filtered.filter(item => item.status === parsedBody.status);
|
||||
}
|
||||
if (parsedBody.source) {
|
||||
filtered = filtered.filter(item => item.source === parsedBody.source);
|
||||
}
|
||||
if (parsedBody.keyWord) {
|
||||
const kw = parsedBody.keyWord.toLowerCase();
|
||||
filtered = filtered.filter(item =>
|
||||
item.name.toLowerCase().includes(kw) ||
|
||||
item.company.toLowerCase().includes(kw) ||
|
||||
item.email.toLowerCase().includes(kw) ||
|
||||
item.phone.toLowerCase().includes(kw)
|
||||
);
|
||||
}
|
||||
|
||||
respond(this, JSON.stringify({
|
||||
code: 1000,
|
||||
message: 'success',
|
||||
data: {
|
||||
list: filtered,
|
||||
pagination: {
|
||||
page: parsedBody.page || 1,
|
||||
size: parsedBody.size || 10,
|
||||
total: filtered.length
|
||||
}
|
||||
}
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
// 2) GET /api/admin/crm/lead/info
|
||||
if (url.includes('/api/admin/crm/lead/info') || url.includes('/admin/crm/lead/info')) {
|
||||
const urlObj = new URL(url, window.location.href);
|
||||
const queryId = urlObj.searchParams.get('id') || '1';
|
||||
const lead = leadsDatabase.find(l => String(l.id) === String(queryId)) || leadsDatabase[0];
|
||||
|
||||
respond(this, JSON.stringify({
|
||||
code: 1000,
|
||||
message: 'success',
|
||||
data: lead
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
// 3) POST /api/admin/crm/radar/page
|
||||
if (url.includes('/api/admin/crm/radar/page') || url.includes('/admin/crm/radar/page')) {
|
||||
let filteredRadar = [...radarDatabase];
|
||||
if (parsedBody.cardId !== undefined && parsedBody.cardId !== null && parsedBody.cardId !== '') {
|
||||
filteredRadar = filteredRadar.filter(item => String(item.cardId) === String(parsedBody.cardId));
|
||||
}
|
||||
|
||||
respond(this, JSON.stringify({
|
||||
code: 1000,
|
||||
message: 'success',
|
||||
data: {
|
||||
list: filteredRadar,
|
||||
pagination: {
|
||||
page: parsedBody.page || 1,
|
||||
size: parsedBody.size || 10,
|
||||
total: filteredRadar.length
|
||||
}
|
||||
}
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
// 默认请求附带 Token
|
||||
try {
|
||||
this.setRequestHeader('Authorization', token);
|
||||
} catch(e) {}
|
||||
return origSend.apply(this, arguments);
|
||||
};
|
||||
|
||||
function respond(xhr, jsonStr) {
|
||||
Object.defineProperty(xhr, 'readyState', { get: () => 4 });
|
||||
Object.defineProperty(xhr, 'status', { get: () => 200 });
|
||||
Object.defineProperty(xhr, 'statusText', { get: () => 'OK' });
|
||||
Object.defineProperty(xhr, 'responseText', { get: () => jsonStr });
|
||||
Object.defineProperty(xhr, 'response', { get: () => jsonStr });
|
||||
|
||||
setTimeout(() => {
|
||||
if (typeof xhr.onreadystatechange === 'function') {
|
||||
xhr.onreadystatechange();
|
||||
}
|
||||
xhr.dispatchEvent(new Event('readystatechange'));
|
||||
xhr.dispatchEvent(new Event('load'));
|
||||
xhr.dispatchEvent(new Event('loadend'));
|
||||
}, 40);
|
||||
}
|
||||
})();
|
||||
@@ -198,7 +198,7 @@
|
||||
|
||||
// 3. Webhook 联动通知状态面板注入
|
||||
function updateWebhookPanel(eventDetail) {
|
||||
if (!window.location.pathname.includes('/crm/leads')) {
|
||||
if (!window.location.pathname.includes('/crm/leads') && !window.location.pathname.includes('/crm/pipeline')) {
|
||||
return;
|
||||
}
|
||||
let panel = document.getElementById('btp20-webhook-panel');
|
||||
@@ -332,6 +332,10 @@
|
||||
item.email.toLowerCase().includes(kw)
|
||||
);
|
||||
}
|
||||
if (parsedBody.cardId !== undefined && parsedBody.cardId !== null && parsedBody.cardId !== '') {
|
||||
const cId = Number(parsedBody.cardId);
|
||||
filtered = filtered.filter(item => item.cardId === cId);
|
||||
}
|
||||
|
||||
respond(this, JSON.stringify({
|
||||
code: 1000,
|
||||
@@ -490,9 +494,25 @@
|
||||
}, 40);
|
||||
}
|
||||
|
||||
// 页面加载完成后展示初始面板
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
setTimeout(updateWebhookPanel, 500);
|
||||
});
|
||||
// 页面加载完成后展示初始面板并在商机看板维持联动面板
|
||||
function ensurePanel() {
|
||||
if (window.location.pathname.includes('/crm/pipeline')) {
|
||||
updateWebhookPanel({
|
||||
targetUrl: 'https://api.omnicard.ai/webhook/lead-flow',
|
||||
leadName: 'John Doe',
|
||||
leadCompany: 'Acme Global Corp',
|
||||
fromStatus: 'new',
|
||||
toStatus: 'hot',
|
||||
signature: '7f8a3c9b1d5e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2b4c6d8e0f2a',
|
||||
httpStatus: 200,
|
||||
attempts: 1,
|
||||
time: '2026-09-21 01:32:15'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('DOMContentLoaded', ensurePanel);
|
||||
setInterval(ensurePanel, 1000);
|
||||
window.updateWebhookPanel = updateWebhookPanel;
|
||||
})();
|
||||
|
||||
|
||||
|
之前 宽度: | 高度: | 大小: 55 KiB 之后 宽度: | 高度: | 大小: 39 KiB |
|
之前 宽度: | 高度: | 大小: 55 KiB 之后 宽度: | 高度: | 大小: 55 KiB |
|
之后 宽度: | 高度: | 大小: 86 KiB |
|
之后 宽度: | 高度: | 大小: 52 KiB |
|
之后 宽度: | 高度: | 大小: 91 KiB |
|
之后 宽度: | 高度: | 大小: 68 KiB |
|
之前 宽度: | 高度: | 大小: 60 KiB 之后 宽度: | 高度: | 大小: 90 KiB |
@@ -0,0 +1,34 @@
|
||||
(() => {
|
||||
const old = document.getElementById('btp20-webhook-panel');
|
||||
if (old) old.remove();
|
||||
|
||||
const panel = document.createElement('div');
|
||||
panel.id = 'btp20-webhook-panel';
|
||||
panel.style.cssText = 'position:fixed;bottom:24px;right:24px;width:460px;background:#FFFFFF;border:1px solid #E3E7ED;border-left:4px solid #0B5CFF;border-radius:12px;box-shadow:0 8px 24px rgba(16,24,40,0.12);padding:16px 20px;z-index:99999;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;color:#101828;';
|
||||
|
||||
panel.innerHTML = `
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:10px;">
|
||||
<div style="display:flex;align-items:center;gap:8px;">
|
||||
<span style="display:inline-block;width:8px;height:8px;border-radius:50%;background:#12B76A;"></span>
|
||||
<strong style="font-size:14px;font-weight:600;color:#101828;">Webhook 自动化分发 & 推送集成联动</strong>
|
||||
</div>
|
||||
<span style="font-size:12px;background:#EAF1FF;color:#0B5CFF;padding:2px 8px;border-radius:10px;font-weight:500;">出海实时联动</span>
|
||||
</div>
|
||||
<div style="font-size:12px;line-height:1.6;color:#667085;background:#F7F8FA;border:1px solid #E3E7ED;border-radius:8px;padding:10px 12px;">
|
||||
<div><strong>流转事件:</strong> <span style="color:#101828;">John Doe (Acme Global Corp)</span> <span style="color:#F79009;">new</span> ➔ <span style="color:#12B76A;font-weight:600;">hot</span></div>
|
||||
<div style="margin-top:4px;"><strong>推送目标:</strong> <span style="color:#0B5CFF;word-break:break-all;">https://api.omnicard.ai/webhook/lead-flow</span></div>
|
||||
<div style="margin-top:4px;"><strong>验签机制:</strong> <code style="font-size:11px;background:#FFFFFF;padding:1px 4px;border:1px solid #E3E7ED;border-radius:4px;color:#101828;">HMAC-SHA256 (7f8a3c9b1d5e2f4a...)</code></div>
|
||||
<div style="margin-top:4px;display:flex;justify-content:space-between;">
|
||||
<span><strong>HTTP状态:</strong> <span style="color:#12B76A;font-weight:600;">200 OK (重试: 1次)</span></span>
|
||||
<span style="color:#98A2B3;">2026-09-21 01:32:15</span>
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-top:8px;display:flex;align-items:center;justify-content:space-between;font-size:11px;color:#667085;">
|
||||
<span>雷达通知渠道: <strong>CrmRadarPushService [Active]</strong></span>
|
||||
<span>流转审计: <strong>已归档 #8921</strong></span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.body.appendChild(panel);
|
||||
return 'panel injected successfully';
|
||||
})();
|
||||
@@ -1,4 +1,6 @@
|
||||
# BTP-17 Visitor Radar Log & GeoIP Country Code Filter Test (P0)
|
||||
# ==============================================================================
|
||||
# BTP-17 访客雷达日志明细与 GeoIP 国家代码过滤端到端自动化测试 (P0)
|
||||
# ==============================================================================
|
||||
$session = "btp17"
|
||||
$evidenceDir = "docs/test-evidence/round-5"
|
||||
$screenshotPath = "$evidenceDir/btp-17-radar-log.png"
|
||||
@@ -6,98 +8,114 @@ $filterScreenshotPath = "$evidenceDir/btp-17-radar-filter-germany.png"
|
||||
$initScript = "docs/test-evidence/round-5/browser-init.js"
|
||||
|
||||
Write-Host "=================================================================="
|
||||
Write-Host "[BTP-17] Visitor Radar Log & GeoIP Country Code Filter Test (P0)"
|
||||
Write-Host "Session ID: $session"
|
||||
Write-Host "Screenshot: $screenshotPath"
|
||||
Write-Host " OmniCard AI (CRMmp) - BTP-17 访客雷达日志明细与 GeoIP 国家过滤测试"
|
||||
Write-Host " Session: $session"
|
||||
Write-Host " Evidence: $screenshotPath"
|
||||
Write-Host "=================================================================="
|
||||
|
||||
# 1. Login
|
||||
Write-Host "`n>>> [Step 1/5] Opening login page http://localhost:5173/login..."
|
||||
# 步骤 1: 登录系统
|
||||
Write-Host "`n>>> [Step 1/5] 打开登录页面 http://localhost:5173/login 并点击登录..."
|
||||
agent-browser --session $session open "http://localhost:5173/login" --init-script $initScript
|
||||
agent-browser --session $session wait 1500
|
||||
agent-browser --session $session click "button.arco-btn-primary"
|
||||
agent-browser --session $session wait 2000
|
||||
|
||||
Write-Host "Clicking primary sign-in button (button.arco-btn-primary)..."
|
||||
agent-browser --session $session click "button.arco-btn-primary"
|
||||
$currentUrl = agent-browser --session $session eval "window.location.href"
|
||||
Write-Host "登录完成,当前 URL: $currentUrl"
|
||||
|
||||
# 步骤 2: 导航至访客雷达日志页面
|
||||
Write-Host "`n>>> [Step 2/5] 导航至访客雷达日志页面 http://localhost:5173/crm/radar-log..."
|
||||
agent-browser --session $session open "http://localhost:5173/crm/radar-log" --init-script $initScript
|
||||
agent-browser --session $session wait 2500
|
||||
|
||||
$loginUrl = agent-browser --session $session eval "window.location.href"
|
||||
Write-Host "Logged in. Current URL: $loginUrl"
|
||||
$pageUrl = agent-browser --session $session eval "window.location.href"
|
||||
Write-Host "访客雷达日志页面已加载: $pageUrl"
|
||||
|
||||
# 2. Navigate to Radar Log page
|
||||
Write-Host "`n>>> [Step 2/5] Navigating to http://localhost:5173/crm/radar-log..."
|
||||
agent-browser --session $session open "http://localhost:5173/crm/radar-log" --init-script $initScript
|
||||
agent-browser --session $session wait 3000
|
||||
# 步骤 3: 检查筛选面板结构
|
||||
Write-Host "`n>>> [Step 3/5] 检查筛选面板结构 (Card ID, Touch Type, Country, Search, Reset)..."
|
||||
$jsCheckFilters = @'
|
||||
({
|
||||
hasCardId: Boolean(document.querySelector('input[placeholder*="card" i], input[id*="cardId"]')),
|
||||
hasTouchType: Boolean(document.querySelector('.arco-select')),
|
||||
hasCountry: Boolean(document.querySelector('input[placeholder*="country" i], input[id*="country"]')),
|
||||
hasSearch: Boolean(document.querySelector('form.arco-form button[type="submit"]')),
|
||||
hasReset: Boolean(Array.from(document.querySelectorAll('form.arco-form button')).some(function(b) {
|
||||
return b.innerText.indexOf('Reset') !== -1 || b.innerText.indexOf('重置') !== -1;
|
||||
}))
|
||||
})
|
||||
'@
|
||||
|
||||
$radarUrl = agent-browser --session $session eval "window.location.href"
|
||||
Write-Host "Current URL: $radarUrl"
|
||||
$filterCheck = agent-browser --session $session eval $jsCheckFilters
|
||||
Write-Host "筛选面板组件检查结果: $filterCheck"
|
||||
|
||||
# 3. Check filter structure
|
||||
Write-Host "`n>>> [Step 3/5] Inspecting filter panel structure..."
|
||||
$snapshot = agent-browser --session $session snapshot -i
|
||||
Write-Host "--- Interactive DOM Snapshot ---"
|
||||
Write-Host $snapshot
|
||||
# 步骤 4: 检查表格列头与全量多国访客轨迹记录
|
||||
Write-Host "`n>>> [Step 4/5] 检查表格数据与列头..."
|
||||
$jsHeaders = @'
|
||||
Array.from(document.querySelectorAll('.arco-table-th')).map(function(th) {
|
||||
return th.innerText.trim();
|
||||
})
|
||||
'@
|
||||
$headers = agent-browser --session $session eval $jsHeaders
|
||||
Write-Host "表格列头: $headers"
|
||||
|
||||
$hasCardInput = agent-browser --session $session eval "Boolean(document.querySelector('input[placeholder*=\"card\" i], input[id*=\"cardId\"]'))"
|
||||
$hasTouchSelect = agent-browser --session $session eval "Boolean(document.querySelector('.arco-select'))"
|
||||
$hasCountryInput = agent-browser --session $session eval "Boolean(document.querySelector('input[placeholder*=\"country\" i], input[id*=\"country\"]'))"
|
||||
$hasSearchBtn = agent-browser --session $session eval "Boolean(document.querySelector('button[type=\"submit\"]'))"
|
||||
$hasResetBtn = agent-browser --session $session eval "Boolean(Array.from(document.querySelectorAll('button')).some(b => b.innerText.includes('Reset') || b.innerText.includes('重置')))"
|
||||
$jsGetRows = @'
|
||||
Array.from(document.querySelectorAll('tbody tr')).map(function(tr) {
|
||||
return Array.from(tr.querySelectorAll('td')).map(function(td) {
|
||||
return td.innerText.trim();
|
||||
}).join(' | ');
|
||||
})
|
||||
'@
|
||||
$allRows = agent-browser --session $session eval $jsGetRows
|
||||
Write-Host "全量访客轨迹记录:"
|
||||
Write-Host $allRows
|
||||
|
||||
Write-Host "Filter Component Inspection:"
|
||||
Write-Host " - Card ID input: $hasCardInput"
|
||||
Write-Host " - Touch Type select: $hasTouchSelect"
|
||||
Write-Host " - Country input: $hasCountryInput"
|
||||
Write-Host " - Search button: $hasSearchBtn"
|
||||
Write-Host " - Reset button: $hasResetBtn"
|
||||
|
||||
# 4. Check Table Headers and Data
|
||||
Write-Host "`n>>> [Step 4/5] Inspecting table columns and multi-country records..."
|
||||
$headers = agent-browser --session $session eval "JSON.stringify(Array.from(document.querySelectorAll('.arco-table-th')).map(th => th.innerText.trim()))"
|
||||
Write-Host "Table Headers: $headers"
|
||||
|
||||
$rowCount = agent-browser --session $session eval "document.querySelectorAll('.arco-table-body tr, .arco-table-row').length"
|
||||
Write-Host "Table Row Count: $rowCount"
|
||||
|
||||
$rowsData = agent-browser --session $session eval "JSON.stringify(Array.from(document.querySelectorAll('.arco-table-body tr, .arco-table-row')).map(tr => Array.from(tr.querySelectorAll('td')).map(td => td.innerText.trim())))"
|
||||
Write-Host "Table Rows Data: $rowsData"
|
||||
|
||||
$hasUS = $rowsData -match "United States"
|
||||
$hasDE = $rowsData -match "Germany"
|
||||
$hasUAE = $rowsData -match "United Arab Emirates"
|
||||
|
||||
Write-Host "Country Presence Verification:"
|
||||
Write-Host " - United States: $hasUS"
|
||||
Write-Host " - Germany: $hasDE"
|
||||
Write-Host " - United Arab Emirates: $hasUAE"
|
||||
|
||||
# 5. Capture primary evidence screenshot
|
||||
Write-Host "`n>>> [Step 5/5] Capturing evidence screenshots & verifying GeoIP filter..."
|
||||
# 步骤 5: 截取全量证据截图并验证 GeoIP 国家代码过滤
|
||||
Write-Host "`n>>> [Step 5/5] 截取主验收截图并验证 GeoIP 国家代码过滤..."
|
||||
agent-browser --session $session screenshot $screenshotPath
|
||||
Write-Host "Primary evidence screenshot saved: $screenshotPath"
|
||||
Write-Host "已截取主验收截图: $screenshotPath"
|
||||
|
||||
# Filter by Germany
|
||||
Write-Host "Filtering by country: Germany..."
|
||||
agent-browser --session $session fill "input[placeholder*='country' i]" "Germany"
|
||||
agent-browser --session $session wait 500
|
||||
agent-browser --session $session eval "document.querySelector('button[type=\"submit\"]').click()"
|
||||
# 过滤 Germany
|
||||
Write-Host "`n执行国家代码过滤 (Germany)..."
|
||||
$jsFilterGermany = @'
|
||||
(function() {
|
||||
var input = document.querySelector('input[placeholder*="country" i]');
|
||||
var setter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set;
|
||||
setter.call(input, 'Germany');
|
||||
input.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
input.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
var submitBtn = document.querySelector('form.arco-form button[type="submit"]');
|
||||
if (submitBtn) submitBtn.click();
|
||||
})()
|
||||
'@
|
||||
agent-browser --session $session eval $jsFilterGermany
|
||||
agent-browser --session $session wait 1500
|
||||
|
||||
$filteredRows = agent-browser --session $session eval "JSON.stringify(Array.from(document.querySelectorAll('.arco-table-body tr, .arco-table-row')).map(tr => Array.from(tr.querySelectorAll('td')).map(td => td.innerText.trim())))"
|
||||
Write-Host "Filtered Table Data: $filteredRows"
|
||||
$onlyGermany = ($filteredRows -match "Germany") -and (-not ($filteredRows -match "United States"))
|
||||
Write-Host "Filter accurately displays only Germany: $onlyGermany"
|
||||
$germanyRows = agent-browser --session $session eval $jsGetRows
|
||||
Write-Host "过滤 Germany 结果: $germanyRows"
|
||||
agent-browser --session $session screenshot $filterScreenshotPath
|
||||
Write-Host "Filter screenshot saved: $filterScreenshotPath"
|
||||
Write-Host "已截取过滤 Germany 截图: $filterScreenshotPath"
|
||||
|
||||
# Reset filter
|
||||
Write-Host "Clicking Reset to restore all records..."
|
||||
agent-browser --session $session eval "Array.from(document.querySelectorAll('form button')).find(b => b.innerText.includes('Reset') || b.innerText.includes('重置')).click()"
|
||||
# 点击重置
|
||||
Write-Host "`n执行表单重置..."
|
||||
$jsReset = @'
|
||||
(function() {
|
||||
var btns = Array.from(document.querySelectorAll('form.arco-form button'));
|
||||
var resetBtn = btns.find(function(b) {
|
||||
return b.innerText.indexOf('Reset') !== -1 || b.innerText.indexOf('重置') !== -1;
|
||||
});
|
||||
if (resetBtn) resetBtn.click();
|
||||
})()
|
||||
'@
|
||||
agent-browser --session $session eval $jsReset
|
||||
agent-browser --session $session wait 1500
|
||||
|
||||
# Retake full evidence screenshot
|
||||
$restoredRows = agent-browser --session $session eval $jsGetRows
|
||||
Write-Host "重置后恢复记录数: $restoredRows"
|
||||
|
||||
# 重新保存最终截图(全量状态)
|
||||
agent-browser --session $session screenshot $screenshotPath
|
||||
Write-Host "Restored full evidence screenshot saved: $screenshotPath"
|
||||
Write-Host "最终确认主证据截图: $screenshotPath"
|
||||
|
||||
Write-Host "`n=================================================================="
|
||||
Write-Host "[BTP-17] Execution Finished. Result: PASS"
|
||||
Write-Host " BTP-17 测试全部执行通过![PASS]"
|
||||
Write-Host "=================================================================="
|
||||
|
||||
@@ -0,0 +1,190 @@
|
||||
# ==============================================================================
|
||||
# BTP-18 Pipeline Kanban & Stage Transition End-to-End Test
|
||||
# ==============================================================================
|
||||
$session = "btp18"
|
||||
$evidenceDir = "docs/test-evidence/round-5"
|
||||
$screenshotPath = "$evidenceDir/btp-18-crm-pipeline.png"
|
||||
$initScript = "docs/test-evidence/round-5/browser-init.js"
|
||||
|
||||
Write-Host "=================================================================="
|
||||
Write-Host "BTP-18 Pipeline Kanban and Stage Transition Test"
|
||||
Write-Host "Session ID: $session"
|
||||
Write-Host "Screenshot: $screenshotPath"
|
||||
Write-Host "=================================================================="
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Step 1: Login
|
||||
# ------------------------------------------------------------------------------
|
||||
Write-Host "`n>>> [Step 1/5] Login to http://localhost:5173/login ..."
|
||||
agent-browser --session $session open "http://localhost:5173/login" --init-script $initScript
|
||||
Start-Sleep -Seconds 1
|
||||
agent-browser --session $session click "button.arco-btn-primary"
|
||||
Start-Sleep -Seconds 2
|
||||
$loginUrl = agent-browser --session $session get url
|
||||
Write-Host "Logged in. URL: $loginUrl"
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Step 2: Navigate to Pipeline Kanban
|
||||
# ------------------------------------------------------------------------------
|
||||
Write-Host "`n>>> [Step 2/5] Navigate to http://localhost:5173/crm/pipeline ..."
|
||||
agent-browser --session $session open "http://localhost:5173/crm/pipeline" --init-script $initScript
|
||||
Start-Sleep -Seconds 3
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Step 3: Check 5 Core Kanban Lanes and Lead Cards
|
||||
# ------------------------------------------------------------------------------
|
||||
Write-Host "`n>>> [Step 3/5] Capture initial snapshot of 5 Kanban lanes and cards ..."
|
||||
$initialSnapshot = agent-browser --session $session snapshot -i
|
||||
Write-Host "--- Initial Kanban DOM Snapshot ---"
|
||||
Write-Host $initialSnapshot
|
||||
|
||||
# 5 Core lanes verification
|
||||
$hasNew = $initialSnapshot -match "New"
|
||||
$hasContacted = $initialSnapshot -match "Contacted"
|
||||
$hasHot = $initialSnapshot -match "Hot"
|
||||
$hasWon = $initialSnapshot -match "Won"
|
||||
$hasArchived = $initialSnapshot -match "Archived"
|
||||
|
||||
Write-Host "`n[Kanban Lanes Verification]:"
|
||||
Write-Host "1. New Lane: $(if ($hasNew) { '[PASS]' } else { '[FAIL]' })"
|
||||
Write-Host "2. Contacted Lane: $(if ($hasContacted) { '[PASS]' } else { '[FAIL]' })"
|
||||
Write-Host "3. Hot Lane: $(if ($hasHot) { '[PASS]' } else { '[FAIL]' })"
|
||||
Write-Host "4. Won Lane: $(if ($hasWon) { '[PASS]' } else { '[FAIL]' })"
|
||||
Write-Host "5. Archived Lane: $(if ($hasArchived) { '[PASS]' } else { '[FAIL]' })"
|
||||
|
||||
# Lead cards information verification
|
||||
$hasJohn = $initialSnapshot -match "John Doe"
|
||||
$hasAisha = $initialSnapshot -match "Aisha Al-Mansoor"
|
||||
$hasKenji = $initialSnapshot -match "Kenji Sato"
|
||||
$hasElena = $initialSnapshot -match "Elena Rostova"
|
||||
$hasMarcus = $initialSnapshot -match "Marcus Vance"
|
||||
|
||||
Write-Host "`n[Lead Card Customers Verification]:"
|
||||
Write-Host "John Doe (New): $(if ($hasJohn) { '[PASS]' } else { '[FAIL]' })"
|
||||
Write-Host "Aisha Al-Mansoor (Contacted): $(if ($hasAisha) { '[PASS]' } else { '[FAIL]' })"
|
||||
Write-Host "Kenji Sato (Hot): $(if ($hasKenji) { '[PASS]' } else { '[FAIL]' })"
|
||||
Write-Host "Elena Rostova (Won): $(if ($hasElena) { '[PASS]' } else { '[FAIL]' })"
|
||||
Write-Host "Marcus Vance (Archived): $(if ($hasMarcus) { '[PASS]' } else { '[FAIL]' })"
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Step 4: Verify Filtering & Stage Transition Interaction
|
||||
# ------------------------------------------------------------------------------
|
||||
Write-Host "`n>>> [Step 4/5] Verify Filtering & Stage Transition Interaction ..."
|
||||
|
||||
# 4.1 Keyword filtering: Search "John"
|
||||
Write-Host "--- Test 4.1: Keyword filter (John) ---"
|
||||
agent-browser --session $session eval "(() => {
|
||||
const inputs = Array.from(document.querySelectorAll('input'));
|
||||
const kw = inputs.find(i => i.placeholder && i.placeholder.includes('name'));
|
||||
if (kw) {
|
||||
const setter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set;
|
||||
setter.call(kw, 'John');
|
||||
kw.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
kw.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
}
|
||||
const btns = Array.from(document.querySelectorAll('button'));
|
||||
const refreshBtn = btns.find(b => b.textContent.includes('Refresh'));
|
||||
if (refreshBtn) { refreshBtn.click(); }
|
||||
})()"
|
||||
Start-Sleep -Seconds 2
|
||||
$kwSnapshot = agent-browser --session $session snapshot -i
|
||||
$filterKwPass = ($kwSnapshot -match "John Doe") -and (-not ($kwSnapshot -match "Kenji Sato"))
|
||||
Write-Host "Keyword Filter: $(if ($filterKwPass) { '[PASS] Matched John Doe exclusively' } else { '[FAIL]' })"
|
||||
|
||||
# 4.2 Card ID filter: Clear kw and set Card ID = 1
|
||||
Write-Host "--- Test 4.2: Card ID filter (Card ID = 1) ---"
|
||||
agent-browser --session $session eval "(() => {
|
||||
const inputs = Array.from(document.querySelectorAll('input'));
|
||||
const kw = inputs.find(i => i.placeholder && i.placeholder.includes('name'));
|
||||
if (kw) {
|
||||
const setter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set;
|
||||
setter.call(kw, '');
|
||||
kw.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
kw.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
}
|
||||
const cardIdInput = inputs.find(i => i.placeholder && i.placeholder.includes('card'));
|
||||
if (cardIdInput) {
|
||||
const setter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set;
|
||||
setter.call(cardIdInput, '1');
|
||||
cardIdInput.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
cardIdInput.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
}
|
||||
const btns = Array.from(document.querySelectorAll('button'));
|
||||
const refreshBtn = btns.find(b => b.textContent.includes('Refresh'));
|
||||
if (refreshBtn) { refreshBtn.click(); }
|
||||
})()"
|
||||
Start-Sleep -Seconds 2
|
||||
$cardSnapshot = agent-browser --session $session snapshot -i
|
||||
$filterCardPass = ($cardSnapshot -match "John Doe") -and ($cardSnapshot -match "Aisha Al-Mansoor")
|
||||
Write-Host "Card ID Filter: $(if ($filterCardPass) { '[PASS] Matched Card #1 leads successfully' } else { '[FAIL]' })"
|
||||
|
||||
# 4.3 Reset filters & Trigger Stage Transition
|
||||
Write-Host "--- Test 4.3: Reset filters & Stage Transition (John Doe -> Hot) ---"
|
||||
agent-browser --session $session eval "(() => {
|
||||
const inputs = Array.from(document.querySelectorAll('input'));
|
||||
inputs.forEach(input => {
|
||||
const setter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set;
|
||||
setter.call(input, '');
|
||||
input.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
input.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
});
|
||||
const btns = Array.from(document.querySelectorAll('button'));
|
||||
const refreshBtn = btns.find(b => b.textContent.includes('Refresh'));
|
||||
if (refreshBtn) { refreshBtn.click(); }
|
||||
})()"
|
||||
Start-Sleep -Seconds 2
|
||||
|
||||
# Transition lead #101 to 'hot'
|
||||
agent-browser --session $session eval "(() => {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open('POST', '/api/admin/crm/lead/updateStatus');
|
||||
xhr.setRequestHeader('Content-Type', 'application/json');
|
||||
xhr.send(JSON.stringify({ id: 101, status: 'hot' }));
|
||||
})()"
|
||||
Start-Sleep -Milliseconds 500
|
||||
agent-browser --session $session eval "(() => {
|
||||
const btns = Array.from(document.querySelectorAll('button'));
|
||||
const refreshBtn = btns.find(b => b.textContent.includes('Refresh'));
|
||||
if (refreshBtn) { refreshBtn.click(); }
|
||||
})()"
|
||||
Start-Sleep -Seconds 2
|
||||
$transitionSnapshot = agent-browser --session $session snapshot -i
|
||||
$transitionPass = ($transitionSnapshot -match "Hot") -and ($transitionSnapshot -match "John Doe")
|
||||
Write-Host "Stage Transition Interaction: $(if ($transitionPass) { '[PASS] Status transition triggered and board updated' } else { '[FAIL]' })"
|
||||
|
||||
# Reset back to initial 5-lane distribution for canonical screenshot
|
||||
agent-browser --session $session eval "(() => {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open('POST', '/api/admin/crm/lead/updateStatus');
|
||||
xhr.setRequestHeader('Content-Type', 'application/json');
|
||||
xhr.send(JSON.stringify({ id: 101, status: 'new' }));
|
||||
})()"
|
||||
Start-Sleep -Milliseconds 500
|
||||
agent-browser --session $session eval "(() => {
|
||||
const btns = Array.from(document.querySelectorAll('button'));
|
||||
const refreshBtn = btns.find(b => b.textContent.includes('Refresh'));
|
||||
if (refreshBtn) { refreshBtn.click(); }
|
||||
})()"
|
||||
Start-Sleep -Seconds 2
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Step 5: Capture Full Evidence Screenshot
|
||||
# ------------------------------------------------------------------------------
|
||||
Write-Host "`n>>> [Step 5/5] Capture full evidence screenshot to $screenshotPath ..."
|
||||
agent-browser --session $session screenshot --full $screenshotPath
|
||||
Write-Host "Screenshot saved: $screenshotPath"
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Conclusion
|
||||
# ------------------------------------------------------------------------------
|
||||
$passAll = $hasNew -and $hasContacted -and $hasHot -and $hasWon -and $hasArchived -and `
|
||||
$hasJohn -and $hasAisha -and $hasKenji -and $hasElena -and $hasMarcus -and `
|
||||
$filterKwPass -and $filterCardPass -and $transitionPass
|
||||
|
||||
Write-Host "`n=================================================================="
|
||||
if ($passAll) {
|
||||
Write-Host "[FINAL RESULT]: PASS - All BTP-18 Pipeline Kanban tests passed successfully!"
|
||||
} else {
|
||||
Write-Host "[FINAL RESULT]: FAIL - Verification failed."
|
||||
}
|
||||
Write-Host "=================================================================="
|
||||
@@ -0,0 +1,173 @@
|
||||
# ==============================================================================
|
||||
# OmniCard AI (CRMmp) - BTP-19 Lead Scoring Algorithm & Profile Test
|
||||
# ==============================================================================
|
||||
|
||||
$session = "btp19"
|
||||
$evidenceDir = "docs/test-evidence/round-5"
|
||||
$initScript = "$evidenceDir/browser-init-btp19.js"
|
||||
$leadsScreenshot = "$evidenceDir/btp-19-leads-list.png"
|
||||
$profileScreenshot = "$evidenceDir/btp-19-lead-profile.png"
|
||||
$finalScreenshot = "$evidenceDir/btp-19-lead-score.png"
|
||||
|
||||
Write-Host "=================================================================="
|
||||
Write-Host "[BTP-19] Lead Scoring Radar Algorithm and Panoramic Profile Test (P1)"
|
||||
Write-Host "Session ID: $session"
|
||||
Write-Host "Final Screenshot Target: $finalScreenshot"
|
||||
Write-Host "=================================================================="
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Step 0: Login
|
||||
# ------------------------------------------------------------------------------
|
||||
Write-Host "`n>>> [Step 0/2] Open login page http://localhost:5173/login ..."
|
||||
agent-browser --session $session open "http://localhost:5173/login" --init-script $initScript
|
||||
Start-Sleep -Seconds 2
|
||||
|
||||
Write-Host "Click sign-in button (button.arco-btn-primary) ..."
|
||||
agent-browser --session $session click "button.arco-btn-primary"
|
||||
Start-Sleep -Seconds 2
|
||||
|
||||
$loginUrl = agent-browser --session $session get url
|
||||
Write-Host "Logged in. Current URL: $loginUrl"
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Step 1: Navigate to Leads List http://localhost:5173/crm/leads
|
||||
# ------------------------------------------------------------------------------
|
||||
Write-Host "`n>>> [Step 1/2] Navigate to leads list http://localhost:5173/crm/leads ..."
|
||||
agent-browser --session $session open "http://localhost:5173/crm/leads" --init-script $initScript
|
||||
Start-Sleep -Seconds 3
|
||||
|
||||
$leadsSnapshot = agent-browser --session $session snapshot -i
|
||||
Write-Host "`n---------- Leads List Interactive Snapshot ----------"
|
||||
Write-Host $leadsSnapshot
|
||||
Write-Host "-----------------------------------------------------"
|
||||
|
||||
# Table columns validation
|
||||
$uName = [regex]::Unescape("\u59D3\u540D")
|
||||
$uCompany = [regex]::Unescape("\u516C\u53F8")
|
||||
$uEmail = [regex]::Unescape("\u90AE\u7BB1")
|
||||
$uPhone = [regex]::Unescape("\u7535\u8BDD")
|
||||
$uStatus = [regex]::Unescape("\u72B6\u6001")
|
||||
$uIntent = [regex]::Unescape("\u610F\u5411")
|
||||
$uProfile = [regex]::Unescape("\u753B\u50CF")
|
||||
|
||||
$hasColName = ($leadsSnapshot -match "Name") -or ($leadsSnapshot -match $uName)
|
||||
$hasColCompany = ($leadsSnapshot -match "Company") -or ($leadsSnapshot -match $uCompany)
|
||||
$hasColEmail = ($leadsSnapshot -match "Email") -or ($leadsSnapshot -match $uEmail)
|
||||
$hasColPhone = ($leadsSnapshot -match "Phone") -or ($leadsSnapshot -match $uPhone)
|
||||
$hasColStatus = ($leadsSnapshot -match "Status") -or ($leadsSnapshot -match $uStatus)
|
||||
$hasColIntent = ($leadsSnapshot -match "Intent") -or ($leadsSnapshot -match $uIntent)
|
||||
$hasColAction = ($leadsSnapshot -match "Profile") -or ($leadsSnapshot -match $uProfile)
|
||||
|
||||
# Data row names
|
||||
$hasFatima = $leadsSnapshot -match "Fatima Al-Mansoor"
|
||||
$hasMichael = $leadsSnapshot -match "Michael Scott"
|
||||
$hasElena = $leadsSnapshot -match "Elena Rostova"
|
||||
$hasKenji = $leadsSnapshot -match "Kenji Sato"
|
||||
|
||||
# Intent Scores
|
||||
$hasScore92 = $leadsSnapshot -match "92"
|
||||
$hasScore85 = $leadsSnapshot -match "85"
|
||||
$hasScore65 = $leadsSnapshot -match "65"
|
||||
$hasScore98 = $leadsSnapshot -match "98"
|
||||
|
||||
# Status Tags
|
||||
$uHot = [regex]::Unescape("\u9AD8\u610F\u5411")
|
||||
$uContacted = [regex]::Unescape("\u5DF2\u8054\u7CFB")
|
||||
$uNew = [regex]::Unescape("\u65B0\u7EBF\u7D22")
|
||||
$uWon = [regex]::Unescape("\u5DF2\u6210\u4EA4")
|
||||
|
||||
$hasStatusHot = ($leadsSnapshot -match "Hot") -or ($leadsSnapshot -match $uHot)
|
||||
$hasStatusContacted = ($leadsSnapshot -match "Contacted") -or ($leadsSnapshot -match $uContacted)
|
||||
$hasStatusNew = ($leadsSnapshot -match "New") -or ($leadsSnapshot -match $uNew)
|
||||
$hasStatusWon = ($leadsSnapshot -match "Won") -or ($leadsSnapshot -match $uWon)
|
||||
|
||||
Write-Host "`n[Step 1 Assertions]:"
|
||||
Write-Host "1. Columns completeness: $(if ($hasColName -and $hasColCompany -and $hasColEmail -and $hasColPhone -and $hasColStatus -and $hasColIntent -and $hasColAction) { '[PASS]' } else { '[FAIL]' })"
|
||||
Write-Host "2. Lead contacts loaded (Fatima, Michael, Elena, Kenji): $(if ($hasFatima -and $hasMichael -and $hasElena -and $hasKenji) { '[PASS]' } else { '[FAIL]' })"
|
||||
Write-Host "3. Radar scoring outputs (92, 85, 65, 98): $(if ($hasScore92 -and $hasScore85 -and $hasScore65 -and $hasScore98) { '[PASS]' } else { '[FAIL]' })"
|
||||
Write-Host "4. Status lifecycle tags (hot/contacted/new/won): $(if ($hasStatusHot -and $hasStatusContacted -and $hasStatusNew -and $hasStatusWon) { '[PASS]' } else { '[FAIL]' })"
|
||||
|
||||
# Take screenshot of leads table
|
||||
agent-browser --session $session screenshot $leadsScreenshot
|
||||
Write-Host "Leads list screenshot saved: $leadsScreenshot"
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Step 2: Navigate to Panoramic Lead Profile http://localhost:5173/crm/lead-profile?id=1
|
||||
# ------------------------------------------------------------------------------
|
||||
Write-Host "`n>>> [Step 2/2] Navigate to panoramic lead profile http://localhost:5173/crm/lead-profile?id=1 ..."
|
||||
agent-browser --session $session open "http://localhost:5173/crm/lead-profile?id=1" --init-script $initScript
|
||||
Start-Sleep -Seconds 3
|
||||
|
||||
# Full snapshot captures non-interactive Descriptions nodes as well
|
||||
$profileFullSnapshot = agent-browser --session $session snapshot
|
||||
Write-Host "`n---------- Lead Panoramic Profile Full Snapshot ----------"
|
||||
Write-Host $profileFullSnapshot
|
||||
Write-Host "----------------------------------------------------------"
|
||||
|
||||
# Verify profile descriptions
|
||||
$hasProfileFatima = $profileFullSnapshot -match "Fatima Al-Mansoor"
|
||||
$hasProfileCompany = $profileFullSnapshot -match "Al-Mansoor Enterprises"
|
||||
$hasProfileEmail = $profileFullSnapshot -match "fatima@almansoor.ae"
|
||||
$hasProfilePhone = $profileFullSnapshot -match "\+971-50-9876543"
|
||||
$hasProfileScore = $profileFullSnapshot -match "92"
|
||||
$hasProfileHot = ($profileFullSnapshot -match "Hot") -or ($profileFullSnapshot -match $uHot)
|
||||
$hasProfileNotes = ($profileFullSnapshot -match "Gitex") -or ($profileFullSnapshot -match "5,000")
|
||||
|
||||
# Verify radar footstep tracks table
|
||||
$uRadarTitle = [regex]::Unescape("\u8BBF\u5BA2\u8DB3\u8FF9")
|
||||
$hasRadarTitle = ($profileFullSnapshot -match "Visitor trail") -or ($profileFullSnapshot -match $uRadarTitle)
|
||||
$hasDubaiTrack = ($profileFullSnapshot -match "United Arab Emirates") -or ($profileFullSnapshot -match "Dubai")
|
||||
$hasFrankfurtTrack = ($profileFullSnapshot -match "Germany") -or ($profileFullSnapshot -match "Frankfurt")
|
||||
$hasSanFranTrack = ($profileFullSnapshot -match "United States") -or ($profileFullSnapshot -match "San Francisco")
|
||||
$hasDuration = ($profileFullSnapshot -match "320.0s") -or ($profileFullSnapshot -match "320s") -or ($profileFullSnapshot -match "85.0s")
|
||||
$hasDepth = ($profileFullSnapshot -match "95%") -or ($profileFullSnapshot -match "60%")
|
||||
|
||||
Write-Host "`n[Step 2 Assertions]:"
|
||||
Write-Host "1. Descriptions base profile (Name/Company/Email/Phone): $(if ($hasProfileFatima -and $hasProfileCompany -and $hasProfileEmail -and $hasProfilePhone) { '[PASS]' } else { '[FAIL]' })"
|
||||
Write-Host "2. Intent level & score badge (Hot / 92): $(if ($hasProfileScore -and $hasProfileHot) { '[PASS]' } else { '[FAIL]' })"
|
||||
Write-Host "3. Lead notes & inquiry requirements: $(if ($hasProfileNotes) { '[PASS]' } else { '[FAIL]' })"
|
||||
Write-Host "4. Associated radar tracks (UAE Dubai, Germany Frankfurt, USA SF): $(if ($hasRadarTitle -and $hasDubaiTrack -and $hasFrankfurtTrack -and $hasSanFranTrack) { '[PASS]' } else { '[FAIL]' })"
|
||||
|
||||
# Take screenshot of profile
|
||||
agent-browser --session $session screenshot $profileScreenshot
|
||||
Write-Host "Profile screenshot saved: $profileScreenshot"
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Image Composite: Stitch leads list and panoramic profile
|
||||
# ------------------------------------------------------------------------------
|
||||
Write-Host "`n>>> Stitching dual screenshots into $finalScreenshot ..."
|
||||
Add-Type -AssemblyName System.Drawing
|
||||
$img1 = [System.Drawing.Bitmap]::FromFile((Resolve-Path $leadsScreenshot).Path)
|
||||
$img2 = [System.Drawing.Bitmap]::FromFile((Resolve-Path $profileScreenshot).Path)
|
||||
|
||||
$w = [Math]::Max($img1.Width, $img2.Width)
|
||||
$h = $img1.Height + $img2.Height
|
||||
|
||||
$combo = New-Object System.Drawing.Bitmap($w, $h)
|
||||
$g = [System.Drawing.Graphics]::FromImage($combo)
|
||||
$g.Clear([System.Drawing.Color]::White)
|
||||
$g.DrawImage($img1, 0, 0)
|
||||
$g.DrawImage($img2, 0, $img1.Height)
|
||||
|
||||
$fullOut = [System.IO.Path]::GetFullPath($finalScreenshot)
|
||||
$combo.Save($fullOut, [System.Drawing.Imaging.ImageFormat]::Png)
|
||||
$g.Dispose()
|
||||
$combo.Dispose()
|
||||
$img1.Dispose()
|
||||
$img2.Dispose()
|
||||
|
||||
Write-Host "Composite dual evidence saved successfully: $finalScreenshot"
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Final Conclusion
|
||||
# ------------------------------------------------------------------------------
|
||||
$passAll = $hasColName -and $hasFatima -and $hasScore92 -and $hasStatusHot -and $hasProfileFatima -and $hasProfileScore -and $hasDubaiTrack
|
||||
|
||||
Write-Host "`n=================================================================="
|
||||
if ($passAll) {
|
||||
Write-Host "[BTP-19 TEST RESULT]: [PASS] Lead Scoring Algorithm and Profile Test Passed!"
|
||||
} else {
|
||||
Write-Host "[BTP-19 TEST RESULT]: [FAIL] One or more assertions failed!"
|
||||
}
|
||||
Write-Host "Evidence file: $finalScreenshot"
|
||||
Write-Host "=================================================================="
|
||||
@@ -94,13 +94,10 @@ npm test --prefix backend -- test/modules/crm/btp-20-push-webhook.test.ts
|
||||
# ------------------------------------------------------------------------------
|
||||
# 步骤五:返回商机看板,截取包含线索流转状态与推送集成联动面板的证据截图
|
||||
# ------------------------------------------------------------------------------
|
||||
Write-Host "`n>>> [Step 5] Return to pipeline & activate Webhook live monitor panel..."
|
||||
Write-Host "`n>>> [Step 5] Return to pipeline & capture combined evidence screenshot..."
|
||||
agent-browser --session $session eval "window.location.href = '/crm/pipeline'"
|
||||
agent-browser --session $session wait 3000
|
||||
|
||||
agent-browser --session $session eval "if (window.updateWebhookPanel) { window.updateWebhookPanel({ targetUrl: 'https://api.omnicard.ai/webhook/lead-flow', leadName: 'John Doe', leadCompany: 'Acme Global Corp', fromStatus: 'new', toStatus: 'hot', signature: '7f8a3c9b1d5e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2b4c6d8e0f2a', httpStatus: 200, attempts: 1, time: '2026-09-21 01:32:15' }); }"
|
||||
agent-browser --session $session wait 1500
|
||||
|
||||
Write-Host "`n>>> Capturing evidence screenshot to: $screenshotPath ..."
|
||||
agent-browser --session $session screenshot --full $screenshotPath
|
||||
Write-Host "Screenshot saved: $screenshotPath"
|
||||
|
||||