76 lines
7.5 KiB
JavaScript
76 lines
7.5 KiB
JavaScript
// 真实 Chrome 对照渠道画像;接口数据受控,验证累计、分层及关联预警参数。
|
|
const fs = require('node:fs/promises')
|
|
const path = require('node:path')
|
|
const assert = require('node:assert/strict')
|
|
const { chromium } = require(process.env.SW_PLAYWRIGHT_MODULE || 'playwright')
|
|
|
|
;(async () => {
|
|
const token = (await fs.readFile(path.join(process.env.TEMP, 'sw-kra-http-test-token'), 'utf8')).trim()
|
|
const browser = await chromium.launch({ channel: 'chrome', headless: true })
|
|
try {
|
|
const context = await browser.newContext({ viewport: { width: 1500, height: 1050 } })
|
|
await context.addInitScript(value => localStorage.setItem('token', value), token)
|
|
const page = await context.newPage()
|
|
const errors = [], requests = [], alerts = [], games = []
|
|
let unavailable = false
|
|
page.on('pageerror', error => errors.push(error.message))
|
|
await page.route('http://127.0.0.1:8080/api/**', async route => {
|
|
const request = route.request(), url = new URL(request.url()), api = url.pathname.slice(4)
|
|
const ok = data => route.fulfill({ json: { code: 0, msg: '成功', data } })
|
|
if (api === '/sw/promote/list') return ok({ available: true, total: 2, rows: ['lx', 'hw'].map(sourceDb => ({ id: 100, sourceDb, platformName: sourceDb === 'lx' ? '龙翔' : '汇玩', account: `${sourceDb}-channel`, nickname: `${sourceDb}测试渠道`, promoteLevel: 1, gameCount: 2, playerCount: 4, totalPayAmount: '15.00', lockStatus: 0 })) })
|
|
if (api === '/sw/promote/portrait') {
|
|
requests.push(Object.fromEntries(url.searchParams))
|
|
const period = url.searchParams.get('period'), source = url.searchParams.get('sourceDb')
|
|
const length = period === 'total' ? 21 : period === 'last7' ? 7 : 24
|
|
const trend = Array.from({ length }, (_, i) => ({ label: length === 24 ? `${String(i).padStart(2, '0')}:00` : new Date(Date.UTC(2026, 10, 3 - length + 1 + i)).toISOString().slice(5, 10), value: i === 1 ? '4' : '0' }))
|
|
const metric = value => ({ value, compareValue: period === 'total' ? null : '1', changeRate: period === 'total' ? null : '100.0', changeDiff: null, compareLabel: period === 'total' ? null : '较上一周期' })
|
|
return ok({ available: !unavailable, unavailableReason: unavailable ? '付费分层所需数据尚未接入' : '', period,
|
|
basic: { promoteId: 100, nickname: `${source}测试渠道`, account: `${source}-channel`, busierDisplayName: 'SDK(商务)', promoteLevelLabel: '一级渠道', registerTypeLabel: '公会', status: -3, statusLabel: '商务新增', gameCount: 2, createTime: '2026-11-01 08:00:00', lastLoginTime: '2026-11-03 08:00:00' },
|
|
metrics: unavailable ? null : { register: metric('4'), payAmount: metric('15.00'), payRate: metric('25.0'), active: metric('4') },
|
|
registerTrend: unavailable ? [] : trend, payTrend: unavailable ? [] : trend.map(point => ({ ...point, value: point.value === '4' ? '15.00' : '0.00' })),
|
|
payTiers: unavailable ? [] : ['whale', 'mid', 'small', 'free'].map((tier, index) => ({ tier, tierLabel: ['大R', '中R', '小R', '免费'][index], count: 1, ratio: '25.0' })), gameDistribution: [] })
|
|
}
|
|
if (api === '/sw/alertRecord/list') { alerts.push(Object.fromEntries(url.searchParams)); return ok({ available: true, pageData: { rows: [{ id: '1', createdAt: '2026-11-03 10:00:00', ruleType: 'register_anomaly', alertContent: '对应渠道预警', status: 1 }], total: 1 }, statusCount: { 1: 1 } }) }
|
|
if (api === '/sw/promote/game-distribution') { games.push(Object.fromEntries(url.searchParams)); return ok([{ gameId: 1, gameName: '累计推广游戏', registerCount: 4, payAmount: '31.00' }]) }
|
|
const response = await route.fetch({ url: 'http://127.0.0.1:18000' + api + url.search, headers: { ...request.headers(), 'x-token': token } })
|
|
return route.fulfill({ response })
|
|
})
|
|
await page.goto('http://127.0.0.1:8080/#/layout/sw-BusMgmt/ChannelList')
|
|
const row = source => page.locator('.el-table__body-wrapper tr').filter({ hasText: `${source}-channel` })
|
|
await row('lx').getByRole('button', { name: '画像', exact: true }).click()
|
|
const dialog = page.getByRole('dialog', { name: '渠道画像 — lx测试渠道', exact: true })
|
|
await dialog.getByText('活跃玩家', { exact: true }).waitFor()
|
|
await dialog.locator('.el-loading-mask').first().waitFor({ state: 'hidden' })
|
|
assert.equal(await dialog.getByText('人均充值', { exact: true }).count(), 0)
|
|
assert.equal(await dialog.locator('.pay-tier-item').count(), 4)
|
|
await dialog.getByText('对应渠道预警', { exact: true }).waitFor()
|
|
assert.equal(alerts.at(-1).targetPromoteId, '100'); assert.equal(alerts.at(-1).platformCodes, 'lx'); assert.equal(alerts.at(-1).businessId, undefined)
|
|
await Promise.all([page.waitForResponse(response => response.url().includes('/sw/promote/portrait') && response.url().includes('period=last7')), dialog.getByText('近7天', { exact: true }).click()])
|
|
assert.equal(requests.at(-1).period, 'last7')
|
|
await dialog.locator('.el-loading-mask').first().waitFor({ state: 'hidden' })
|
|
await Promise.all([page.waitForResponse(response => response.url().includes('/sw/promote/portrait') && response.url().includes('period=total')), dialog.getByText('累计', { exact: true }).click()])
|
|
await dialog.locator('.el-loading-mask').first().waitFor({ state: 'hidden' })
|
|
assert.equal(await dialog.locator('.metric-compare').count(), 0)
|
|
await page.screenshot({ path: path.join(process.env.TEMP, 'sw-channel-portrait.png'), animations: 'disabled' })
|
|
await dialog.locator('.profile-info').getByRole('button', { name: '2 款', exact: true }).click()
|
|
const gameDialog = page.getByRole('dialog', { name: '推广游戏数据 — lx测试渠道', exact: true })
|
|
await gameDialog.getByText('累计推广游戏', { exact: true }).waitFor()
|
|
assert.equal(games.at(-1).sourceDb, 'lx'); assert.equal(games.at(-1).promoteId, '100')
|
|
await gameDialog.locator('.el-drawer__close-btn').click(); await gameDialog.waitFor({ state: 'hidden' })
|
|
await dialog.getByRole('button', { name: '关闭', exact: true }).click(); await dialog.waitFor({ state: 'hidden' })
|
|
await row('hw').getByRole('button', { name: '画像', exact: true }).click()
|
|
const other = page.getByRole('dialog', { name: '渠道画像 — hw测试渠道', exact: true })
|
|
await other.getByText('对应渠道预警', { exact: true }).waitFor()
|
|
assert.equal(requests.at(-1).sourceDb, 'hw'); assert.equal(alerts.at(-1).platformCodes, 'hw')
|
|
await other.getByRole('button', { name: '关闭', exact: true }).click(); await other.waitFor({ state: 'hidden' })
|
|
unavailable = true
|
|
await row('lx').getByRole('button', { name: '画像', exact: true }).click()
|
|
await dialog.getByText('付费分层所需数据尚未接入', { exact: true }).waitFor()
|
|
assert.equal(await dialog.locator('.pay-tier-item').count(), 0)
|
|
assert.deepEqual(errors, [])
|
|
const report = { method: '真实 Chrome,画像、游戏与预警数据为受控响应,不改业务数据', passed: true, activeMetric: true, totalNoComparison: true, canonicalLast7: true, fourPayTiers: true, gameDrawer: true, platformAndChannelAlertFilter: true, crossPlatformSameID: true, unavailableIsNotZero: true, pageErrors: errors }
|
|
await fs.writeFile('docs/sw6-to-kra/channel-portrait-page-2026-11-03.json', JSON.stringify(report, null, 2) + '\n')
|
|
console.log(JSON.stringify(report)); await context.close()
|
|
} finally { await browser.close() }
|
|
})().catch(error => { console.error(error); process.exitCode = 1 })
|