import assert from 'node:assert/strict'; import { readFile } from 'node:fs/promises'; import { createRequire } from 'node:module'; import test from 'node:test'; import { collectedProductPayloadSchema } from '@atlas/contracts'; import { collectPlatformDomAssets, normaliseImageUrl } from '../lib/product-media.ts'; import { collectCurrentProduct } from '../lib/source-parsers.ts'; // 使用 WXT 已有的 HTML 解析依赖构建离线 DOM,不加载样本里的远程媒体。 const require = createRequire(import.meta.url); const wxtRequire = createRequire(require.resolve('wxt')); const { parseHTML } = wxtRequire('linkedom'); const pageUrl = 'https://detail.1688.com/offer/1012823100668.html'; const videoUrl = 'https://cloud.video.taobao.com/qa-product.mp4'; const poster = 'https://cbu01.alicdn.com/img/ibank/O1CN01lCwIgP1UjKGylKwAN_!!2563422553-0-cib.jpg'; async function fixture(extra = ''): Promise { const html = await readFile(new URL('./fixtures/1688-gallery-background.html', import.meta.url), 'utf8'); const { document, window } = parseHTML(`${html}${extra}`); window.getComputedStyle = (element: HTMLElement) => ({ backgroundImage: element.style.backgroundImage }); Object.defineProperty(document, 'scripts', { get: () => document.querySelectorAll('script') }); for (const element of document.querySelectorAll('.v-image-cover')) { Object.defineProperty(element, 'offsetWidth', { value: 60 }); Object.defineProperty(element, 'offsetHeight', { value: 60 }); } return document; } test('用户画廊样本的 15 张背景缩略图完整采集、还原原图,排除参数与视频控件', async () => { const assets = collectPlatformDomAssets('1688', await fixture(), pageUrl); assert.equal(assets.length, 15); assert.ok(assets.every((asset) => asset.mediaType === 'image' && asset.role === 'source_main')); assert.equal(assets[0].url, poster); assert.ok(assets.every((asset) => asset.url.endsWith('.jpg') && !asset.url.includes('_b.jpg'))); assert.ok(assets.every((asset) => !/undefined|\.svg/.test(asset.url))); assert.equal(normaliseImageUrl('undefined', pageUrl, '1688'), undefined); }); test('画廊播放器可采集真实视频及封面,忽略推荐视频、blob 和 HLS', async () => { const root = await fixture(``); const assets = collectPlatformDomAssets('1688', root, pageUrl); const videos = assets.filter((asset) => asset.mediaType === 'video'); assert.equal(videos.length, 1); assert.equal(videos[0].url, videoUrl); assert.equal(videos[0].posterUrl, poster); assert.equal(assets.filter((asset) => asset.mediaType === 'image').length, 15); }); test('脚本画廊数据与 DOM 视频去重,完整采集载荷符合 API 契约', async () => { const gallery = { fields: { video: { url: videoUrl, videoThumbnailURL: poster } } }; const root = await fixture(``); const payload = await collectCurrentProduct(root, pageUrl); assert.equal(payload.assets.filter((asset) => asset.mediaType === 'video').length, 1); assert.equal(payload.assets.filter((asset) => asset.mediaType === 'image').length, 15); assert.equal(collectedProductPayloadSchema.safeParse(payload).success, true); }); test('尚未挂载播放器时仍可从明确的 gallery 字面值读取视频直链', async () => { const gallery = { fields: { video: { url: '', videoAddress: videoUrl, videoThumbnailURL: poster } } }; const payload = await collectCurrentProduct(await fixture(``), pageUrl); const videos = payload.assets.filter((asset) => asset.mediaType === 'video'); assert.equal(videos.length, 1); assert.equal(videos[0].url, videoUrl); assert.equal(videos[0].posterUrl, poster); });