test(ci): [CI-01] cover installed Danmuku and native Mask combinations

This commit is contained in:
Harvey Zhao committed 2026-09-15 03:56:05 +08:00
1 parent 54169fdca1
commit 92b04d75c0
27 files changed
+8768 -72

No files matched your search

@@ -185,3 +185,20 @@ Native acceptance still needs source changes, multiple players, failure recovery
devices and internal GPU/WASM resources. The default unversioned solutionPath can
load assets independently of Yarn's SDK resolution. PKG-MASK-05/06 retain the
real combination and distribution/release gates after source/type migration.
## Installed package and local model validation
`yarn test:package --browser` prepares Mask and Danmuku. With its map in
ARTPLAYER_BROWSER_ARTIFACTS, `yarn test:browser:installed danmuku-mask-native`
uses verified installed wrappers and the same fixed local model. The SDK included
in the bundle and model assets loaded through solutionPath have distinct origins;
the model is not inside the Mask tarball. The server verifies model bytes before
serving and records their file/baseline hashes in its manifest. Only metadata
newline conversion is tolerated; executed scripts and binaries are exact.
Each combination records both wrapper identities; published Danmuku controls
remain present. Missing/tampered installed bundles cannot rebuild from source.
Installed mode rejects ARTPLAYER_MASK_PROFILE=1; explicit source profiling remains
available. Native model output, CSS masking, bounded post-ready load, GPU closure,
physical devices and redistribution evidence retain their separate gates. See
../../refactor/changes/2026-09-15-CI-01-danmuku-installed.md for actual results.
@@ -398,3 +398,20 @@ require native Worker completion and visible DOM before testing replacement or
destruction. The separate pause/seek/rate case observes the original ready getter
without changing its result and records bounded native media-time/queue samples
to distinguish eligibility misses from placement failures.
## Installed browser matrix
`yarn test:package --browser` prepares the shared package roster. Set its
browser-artifacts.json as ARTPLAYER_BROWSER_ARTIFACTS and run
`yarn test:browser:installed danmuku --workers=2`. Candidate browser loaders
verify installed UMD bytes and reject conflicting explicit artifacts; the Worker
remains the one embedded in that bundle. Historical baseline/load suites retain
their frozen inputs. Source mode still compiles the normal UMD, and controlled
unit-test bundlers remain separate from native browser execution.
Selected-input attachments record candidate or published identity; PiP and Mask
combinations identify each selected package separately. Native video, DOM, RAF,
Worker and fullscreen checks retain existing assertions and timing. Injected
delays, CPU gaps, bounded pressure and capability fallbacks remain explicitly
labeled. Collection does not imply a green matrix or sustained device acceptance.
See ../../refactor/changes/2026-09-15-CI-01-danmuku-installed.md for actual results.
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,57 @@
# CI-01 Danmuku / Mask 安装产物与原生组合
## 范围和兼容性
十八包共同清单包含 Danmuku/Mask,增加十三个浏览器文件;纯历史基线/负载
文件保留固定归档,其他候选使用正常构建、pack 和隔离安装后的 UMD。
冲突 artifact 会被拒绝,不能回退源码。弹幕 Worker 随正常 UMD 内嵌执行,
没有换成单元测试中的受控 Worker;PiP/Mask 组合分别记录两个包的来源。
Mask SDK 与 solutionPath 资源分别核对。十二个固定本地模型/元数据文件
在安装服务启动时核验并加入 manifest,明确为 local-model-resource,不
把它们描述为 npm 主包内容。代码与二进制必须逐字节一致,只有元数据
允许 Git 换行归一。实际 HTTP 响应另行记录,资源清单不等同全部已请求。
生产源码、API、依赖、类型、资源 URL、既有断言和超时不变。标准安装入口
拒绝 Mask CPU profiling,明确的源码诊断保留。原生 RAF/视频/Worker、
压力、受控调度延迟、CSS/OS 全屏和 PiP 能力继续各自区分;不会将有限
负载通过称为长期稳定性或 GPU/设备验收。
## 验证
Windows x64 / Node 24.21.0 / Yarn 1.22.22。十八包正常构建、pack、仓库外
安装、离线冻结复装和完整成员核验通过(run-ifGhEv,128.40 秒,退出 0)。
通用消费者仍仅 core/chapter 的 36 运行时、5 旧类型和 8 精确类型模式。
完整 installed collection 为 67 文件/1980 项,本轮仅执行新增十三文件。
Chromium 153.0.8010.12、Firefox 155.0、WebKit 26.6 各 133 项,合计 399/399
断言通过,0 skip/失败/重试,实际退出 0,耗时 1350.066 秒(22.5 分钟)。
包括 #958 的十八个 16000-row 密度/resize 组合、二十七个实际模型及弹幕
组合、二十四个三轮播放/两档密度压力案例,以及原有输入、调度、资源和
新旧版对照。二十四个全屏用例验证三轮生命周期。
PiP 共四十八项:Chromium/Firefox 各十六项实际运行,分别保留旧版 DOM
留存和候选清理断言;WebKit 十六项是原生 API 不可用对照,不算窗口播放。
候选/旧版选择与组合的包身份分别统计,不能把身份数当唯一测试数。固定
SVG mask 边界用例不运行模型;压力和 CPU/异步间隙用例的受控部分仍显式
记录。十二个模型资源在所有安装 manifest 中一致,逐例请求另行记录。
故意继承安装 map 后,source 入口清除它。Chromium 的 #958 uniform、
替换加载/销毁、候选核心与候选弹幕的真实 Mask 组合三项 3/3,通过四个
source-build 选择身份核验(组合含两个包),64.914 秒,实际退出 0。
单元 323/323、工程 49/49、scoped lint、library 类型、严格工具链和 CI
契约通过。没有新增依赖或生产 API 改动。
进程终止后归档 ci01-danmuku-installed-report、ci01-danmuku-source-report
和 ci01-danmuku-collection-report。逐例结果、来源/源码/资源/归档摘要与
日志指纹见[机器证据](../baselines/ci-danmuku-installed-validation.json)。
此前 Chapter/Audio/DASH/JASSUB 的失败未被本轮重跑或关闭。
## CI 运行时间与剩余工作
本地仅这批安装用例已耗时 22.5 分钟;现有 browser-smoke 的 60 分钟任务
还串行包含全量三引擎 source、installed、消费者与性能等步骤。这不是远端
超时的实测结论,但足以要求后续 CI-01 进行分片和实测时间核定,不能靠
删压力用例或盲目扩大超时收尾。远端工作流尚未运行。CI-01 及各包
集成/设备/发布门槛保持开放,没有推送或发布。回退此检查点恢复十六包
安装清单和原有浏览器候选/本地模型读取方式。
+6
View File
@@ -170,3 +170,9 @@ build:ts 生成声明 URL 清单后重建站点浏览器产物,check:editor-ty
安装准备使用 `yarn test:package --browser`,从 browser-validation/scope.ts 的同一
包清单生成,不再在工作流复制列表。默认 core/chapter 消费者及显式 --include 保留;
此选项不表示全生态类型、SDK 或发布准入。
十八包安装清单的弹幕/Mask 子集实跑 399 项耗时 22.5 分钟,见
[CI-01 记录](changes/2026-09-15-CI-01-danmuku-installed.md)。当前单个
browser-smoke 仍串行运行全部 source/installed 和其他消费者/性能检查,
60 分钟预算尚未经过完整远端验证;后续必须分片并核定时长,保留完整用例
和失败报告。不能从本地子集通过推断整个工作流可在预算内完成。
+1 -1
View File
@@ -509,7 +509,7 @@
- ENG-09: [记录](changes/2026-09-12-ENG-09-integration.md) [记录](baselines/engineering-integration.json)
- ENG-10: [记录](changes/2026-09-10-ENG-10-test-reliability.md) [记录](test-reliability.md)
- ENG-11: [记录](build-analysis.md) [记录](baselines/bundle-attribution.json) [记录](changes/2026-09-11-ENG-11-build-analysis.md)
- CI-01: [记录](ci-setup.md) [记录](changes/2026-09-13-CI-01-matrix-summary.md) [记录](baselines/ci-matrix-validation.json) [记录](changes/2026-09-13-CI-01-node-consumers.md) [记录](baselines/node-consumer-validation.json) [记录](changes/2026-09-14-CI-01-installed-full.md) [记录](baselines/ci-installed-full-validation.json) [记录](changes/2026-09-14-CI-01-source-chromium.md) [记录](baselines/ci-source-chromium-validation.json) [记录](changes/2026-09-15-CI-01-ads-installed.md) [记录](baselines/ci-ads-installed-validation.json) [记录](changes/2026-09-15-CI-01-subtitles-installed.md) [记录](baselines/ci-subtitles-installed-validation.json) [记录](changes/2026-09-15-CI-01-adaptive-installed.md) [记录](baselines/ci-adaptive-installed-validation.json) [记录](changes/2026-09-15-CI-01-asr-cast-installed.md) [记录](baselines/ci-asr-cast-installed-validation.json) [记录](changes/2026-09-15-CI-01-mediabunny-installed.md) [记录](baselines/ci-mediabunny-installed-validation.json) [记录](changes/2026-09-15-CI-01-jassub-installed.md) [记录](baselines/ci-jassub-installed-validation.json)
- CI-01: [记录](ci-setup.md) [记录](changes/2026-09-13-CI-01-matrix-summary.md) [记录](baselines/ci-matrix-validation.json) [记录](changes/2026-09-13-CI-01-node-consumers.md) [记录](baselines/node-consumer-validation.json) [记录](changes/2026-09-14-CI-01-installed-full.md) [记录](baselines/ci-installed-full-validation.json) [记录](changes/2026-09-14-CI-01-source-chromium.md) [记录](baselines/ci-source-chromium-validation.json) [记录](changes/2026-09-15-CI-01-ads-installed.md) [记录](baselines/ci-ads-installed-validation.json) [记录](changes/2026-09-15-CI-01-subtitles-installed.md) [记录](baselines/ci-subtitles-installed-validation.json) [记录](changes/2026-09-15-CI-01-adaptive-installed.md) [记录](baselines/ci-adaptive-installed-validation.json) [记录](changes/2026-09-15-CI-01-asr-cast-installed.md) [记录](baselines/ci-asr-cast-installed-validation.json) [记录](changes/2026-09-15-CI-01-mediabunny-installed.md) [记录](baselines/ci-mediabunny-installed-validation.json) [记录](changes/2026-09-15-CI-01-jassub-installed.md) [记录](baselines/ci-jassub-installed-validation.json) [记录](changes/2026-09-15-CI-01-danmuku-installed.md) [记录](baselines/ci-danmuku-installed-validation.json)
- CI-02: [记录](changes/2026-09-14-CI-02-pages-artifact.md) [记录](baselines/pages-artifact-validation.json) [记录](pages-deployment.md)
- PILOT-01: [记录](changes/2026-09-10-PILOT-01-chapter.md) [记录](baselines/pilot-validation.json)
- CORE-01: [记录](changes/2026-09-10-CORE-01-typed-utils.md) [记录](baselines/core-utils-validation.json)
+9
View File
@@ -1,5 +1,14 @@
# 进度与证据
## CI-01 弹幕 / Mask 安装与原生负载矩阵
十八包打包/隔离安装通过;十三文件、三引擎 399/399 断言通过,22.5 分钟,
保留历史对照、真实模型、#958、两档三轮压力、全屏和 PiP 能力差异。
源码定向 3/3、单元 323/323、工程 49/49 通过,见
[记录](changes/2026-09-15-CI-01-danmuku-installed.md)。完整清单为
67 文件/1980 项,本轮未全跑;197/263 完成。后续补 VAST/工具/站点相应
分发覆盖,并按实测耗时拆分 CI 浏览器任务;设备/长期运行和既有失败不豁免。
## CI-01 JASSUB 主包及安装 Worker/WASM 验证
十六包打包/安装通过,JASSUB 五文件实跑 45 项:41 通过、4 失败,实际退出 1。
+3 -1
View File
@@ -774,7 +774,9 @@
"changes/2026-09-15-CI-01-mediabunny-installed.md",
"baselines/ci-mediabunny-installed-validation.json",
"changes/2026-09-15-CI-01-jassub-installed.md",
"baselines/ci-jassub-installed-validation.json"
"baselines/ci-jassub-installed-validation.json",
"changes/2026-09-15-CI-01-danmuku-installed.md",
"baselines/ci-danmuku-installed-validation.json"
]
},
{
+12
View File
@@ -118,3 +118,15 @@ Package worker inventories now participate in input freshness checks. The
platform control loads no JASSUB; historical failures, capability controls and
actual rendering must be reported separately. See
../../refactor/changes/2026-09-15-CI-01-jassub-installed.md.
Danmuku and Mask add thirteen files, including immutable historical/load controls,
Worker lifecycle/scheduling, heatmap density, timing/pressure, fullscreen, native
PiP and actual model composition. Candidate wrappers use installed UMDs; Danmuku
executes the Worker embedded in that UMD. Each composition records its selected
Danmuku and PiP/Mask identities. The model is a separately configured local asset
set, verified against frozen bytes and exposed as local-model-resource in the
server manifest. Metadata may normalize checkout newlines; executable/model bytes
may not. Installed runs reject Mask CPU profiling; source diagnostics retain it.
Actual resource requests, available assets, capability controls and successful
playback remain separate. See
../../refactor/changes/2026-09-15-CI-01-danmuku-installed.md for executed results.
+15 -1
View File
@@ -2,7 +2,7 @@ import type { PlaywrightTestConfig } from '@playwright/test'
import assert from 'node:assert/strict'
export type BrowserScope = 'source' | 'installed'
export const installedPackages = ['artplayer', 'artplayer-plugin-chapter', 'artplayer-plugin-ambilight', 'artplayer-proxy-canvas', 'artplayer-plugin-document-pip', 'artplayer-plugin-ads', 'artplayer-plugin-audio-track', 'artplayer-plugin-vtt-thumbnail', 'artplayer-plugin-multiple-subtitles', 'artplayer-plugin-hls-control', 'artplayer-plugin-dash-control', 'artplayer-plugin-auto-thumbnail', 'artplayer-plugin-asr', 'artplayer-plugin-chromecast', 'artplayer-proxy-mediabunny', 'artplayer-plugin-jassub']
export const installedPackages = ['artplayer', 'artplayer-plugin-chapter', 'artplayer-plugin-ambilight', 'artplayer-proxy-canvas', 'artplayer-plugin-document-pip', 'artplayer-plugin-ads', 'artplayer-plugin-audio-track', 'artplayer-plugin-vtt-thumbnail', 'artplayer-plugin-multiple-subtitles', 'artplayer-plugin-hls-control', 'artplayer-plugin-dash-control', 'artplayer-plugin-auto-thumbnail', 'artplayer-plugin-asr', 'artplayer-plugin-chromecast', 'artplayer-proxy-mediabunny', 'artplayer-plugin-jassub', 'artplayer-plugin-danmuku', 'artplayer-plugin-danmuku-mask']
export const installedTests = [
'playback.spec.js',
'lifecycle.spec.js',
@@ -58,6 +58,19 @@ export const installedTests = [
'jassub-hybrid.spec.js',
'jassub-render-failure.spec.js',
'jassub-platform.spec.js',
'danmuku-baseline.spec.js',
'danmuku-dpip.spec.js',
'danmuku-fullscreen.spec.js',
'danmuku-heatmap-density.spec.js',
'danmuku-input.spec.js',
'danmuku-lifetime.spec.js',
'danmuku-load-baseline.spec.js',
'danmuku-mask-boundary.spec.js',
'danmuku-mask-native.spec.js',
'danmuku-resources.spec.js',
'danmuku-scheduler.spec.js',
'danmuku-stability.spec.js',
'danmuku-timing-diagnostic.spec.js',
]
export function browserInvocation(scope: string, args: string[], environment: NodeJS.ProcessEnv) {
@@ -70,6 +83,7 @@ export function browserInvocation(scope: string, args: string[], environment: No
else {
assert(env.ARTPLAYER_BROWSER_ARTIFACTS, 'Installed browser checks require ARTPLAYER_BROWSER_ARTIFACTS')
assert(env.ARTPLAYER_MB_BROWSER_CANDIDATE !== '1', 'Installed browser checks must retain MediaBunny historical controls')
assert(env.ARTPLAYER_MASK_PROFILE !== '1', 'Installed browser checks cannot enable Mask CPU profiling')
assert(!env.ARTPLAYER_DASH_DIAGNOSTIC_SDK || env.ARTPLAYER_DASH_DIAGNOSTIC_SDK === 'none', 'Installed browser checks require the unchanged DASH SDK')
assert(!['ARTPLAYER_DASH_DIAGNOSE_STALL', 'ARTPLAYER_DASH_DIAGNOSE_GETTER', 'ARTPLAYER_DASH_DIAGNOSE_METRICS'].some(name => env[name] === '1'), 'Installed browser checks cannot use DASH recovery diagnostics')
}
+2
View File
@@ -97,6 +97,8 @@ export async function checkPackages({ release = false, include = [] } = {}) {
'artplayer-plugin-chromecast': async () => (await import('../refactor/scripts/chromecast-contract.mjs')).verifyChromecastContract(),
'artplayer-proxy-mediabunny': async () => (await import('../refactor/scripts/mb-contract.mjs')).verifyMbContract(),
'artplayer-plugin-jassub': async () => (await import('../refactor/scripts/jassub-contract.mjs')).verifyJassubContract(),
'artplayer-plugin-danmuku': async () => (await import('../refactor/scripts/danmuku-contract.mjs')).verifyDanmukuContract(),
'artplayer-plugin-danmuku-mask': async () => (await import('../refactor/scripts/danmuku-mask-contract.mjs')).verifyDanmukuMaskContract(),
'artplayer-plugin-audio-track': async () => (await import('../refactor/scripts/audio-contract.mjs')).verifyAudioContract(),
'artplayer-plugin-vtt-thumbnail': async () => (await import('../refactor/scripts/vtt-thumbnail-contract.mjs')).verifyVttThumbnailContract(),
'artplayer-plugin-multiple-subtitles': async () => (await import('../refactor/scripts/multiple-subtitles-contract.mjs')).verifyMultipleSubtitlesContract(),
+3
View File
@@ -36,6 +36,9 @@ test('Installed validation rejects diagnostic SDK substitutions and recovery pro
const candidateOnly = { ...base, ARTPLAYER_MB_BROWSER_CANDIDATE: '1' }
assert.throws(() => browserInvocation('installed', [], candidateOnly), /retain MediaBunny historical controls/)
assert.equal(browserInvocation('source', [], candidateOnly).env.ARTPLAYER_MB_BROWSER_CANDIDATE, '1')
const profile = { ...base, ARTPLAYER_MASK_PROFILE: '1' }
assert.throws(() => browserInvocation('installed', [], profile), /Mask CPU profiling/)
assert.equal(browserInvocation('source', [], profile).env.ARTPLAYER_MASK_PROFILE, '1')
for (const mode of ['upstream4', 'bufferlevel4']) {
const env = { ...base, ARTPLAYER_DASH_DIAGNOSTIC_SDK: mode }
assert.throws(() => browserInvocation('installed', [], env), /unchanged DASH SDK/)
+5 -6
View File
@@ -2,9 +2,9 @@ import assert from 'node:assert/strict'
import fs from 'node:fs'
import process from 'node:process'
import { ensureArchive, hash, readMember } from '../../refactor/scripts/releases.mjs'
import { browserCandidate } from '../helpers/browser-candidate.js'
import { observeDanmukuLoad } from '../helpers/danmuku-combination-load.js'
import { dpipCandidate } from '../helpers/dpip.js'
import { compilePackage } from '../helpers/load.js'
import { expect, test } from './fixtures.js'
const dpip = await dpipCandidate()
@@ -15,10 +15,8 @@ test.beforeAll(async () => {
const published = readMember(await ensureArchive(release), member)
assert.equal(hash(published), release.files[member])
implementations.set('published', { input: `npm ${release.version}`, code: published.toString() })
const candidate = process.env.ARTPLAYER_DANMUKU_ARTIFACT
? fs.readFileSync(process.env.ARTPLAYER_DANMUKU_ARTIFACT, 'utf8')
: await compilePackage('artplayer-plugin-danmuku', 'umd')
implementations.set('candidate', { input: process.env.ARTPLAYER_DANMUKU_ARTIFACT || 'source UMD', code: candidate })
const candidate = await browserCandidate('artplayer-plugin-danmuku', process.env.ARTPLAYER_DANMUKU_ARTIFACT)
implementations.set('candidate', { input: candidate.provenance.file || candidate.provenance.kind, ...candidate })
})
for (const core of ['published', 'candidate']) {
@@ -29,9 +27,10 @@ for (const core of ['published', 'candidate']) {
test.setTimeout(45000) // Two native PiP loads add 16 seconds of actual media progression.
const input = implementations.get(implementation)
const danmuku = input.code
await testInfo.attach('danmuku-selected-input', { contentType: 'application/json', body: JSON.stringify({ implementation, provenance: input.provenance || { kind: 'published', sha256: hash(danmuku) }, dpip: dpip.provenance || { kind: dpip.name, sha256: hash(dpip.code) } }) })
await page.goto(`/test/player.html?core=${core}`)
const supported = await page.evaluate(() => typeof window.documentPictureInPicture?.requestWindow === 'function')
const evidence = { core, implementation, ending, supported, danmuku: { input: input.input, sha256: hash(danmuku) }, dpip: { input: dpip.name, sha256: hash(dpip.code) }, outcome: 'incomplete', rounds: [], loads: [] }
const evidence = { core, implementation, ending, supported, danmuku: { input: input.input, provenance: input.provenance, sha256: hash(danmuku) }, dpip: { input: dpip.name, provenance: dpip.provenance, sha256: hash(dpip.code) }, outcome: 'incomplete', rounds: [], loads: [] }
if (!supported) {
evidence.outcome = 'native-document-pip-unavailable'
await testInfo.attach('danmuku-dpip', { contentType: 'application/json', body: JSON.stringify(evidence) })
+4 -2
View File
@@ -2,7 +2,7 @@ import assert from 'node:assert/strict'
import fs from 'node:fs'
import process from 'node:process'
import { ensureArchive, hash, readMember } from '../../refactor/scripts/releases.mjs'
import { compilePackage } from '../helpers/load.js'
import { browserCandidate } from '../helpers/browser-candidate.js'
import { expect, test } from './fixtures.js'
const implementations = new Map()
@@ -12,7 +12,8 @@ test.beforeAll(async () => {
const published = readMember(await ensureArchive(release), member)
assert.equal(hash(published), release.files[member])
implementations.set('published', { input: `npm ${release.version}`, code: published.toString() })
implementations.set('candidate', { input: process.env.ARTPLAYER_DANMUKU_ARTIFACT || 'source UMD', code: process.env.ARTPLAYER_DANMUKU_ARTIFACT ? fs.readFileSync(process.env.ARTPLAYER_DANMUKU_ARTIFACT, 'utf8') : await compilePackage('artplayer-plugin-danmuku', 'umd') })
const candidate = await browserCandidate('artplayer-plugin-danmuku', process.env.ARTPLAYER_DANMUKU_ARTIFACT)
implementations.set('candidate', { input: candidate.provenance.file || candidate.provenance.kind, ...candidate })
})
for (const core of ['published', 'candidate']) {
@@ -22,6 +23,7 @@ for (const core of ['published', 'candidate']) {
test.setTimeout(45000) // Three independent players, native entry/exit and timestamp-delivered rows.
await page.goto(`/test/player.html?core=${core}`)
const input = implementations.get(plugin)
await testInfo.attach('danmuku-selected-input', { contentType: 'application/json', body: JSON.stringify({ plugin, provenance: input.provenance || { kind: 'published', sha256: hash(input.code) } }) })
const evidence = { core, plugin, mode, input: input.input, sha256: hash(input.code), cycles: [], outcome: 'incomplete' }
await page.evaluate(() => {
window.fullscreenWorkers = []
+7 -5
View File
@@ -1,14 +1,16 @@
import fs from 'node:fs'
import process from 'node:process'
import { hash } from '../../refactor/scripts/releases.mjs'
import { compilePackage } from '../helpers/load.js'
import { browserCandidate } from '../helpers/browser-candidate.js'
import { expect, test } from './fixtures.js'
let candidate
let provenance
test.beforeAll(async () => {
candidate = process.env.ARTPLAYER_DANMUKU_ARTIFACT
? fs.readFileSync(process.env.ARTPLAYER_DANMUKU_ARTIFACT, 'utf8')
: await compilePackage('artplayer-plugin-danmuku', 'umd')
;({ code: candidate, provenance } = await browserCandidate('artplayer-plugin-danmuku', process.env.ARTPLAYER_DANMUKU_ARTIFACT))
})
test.beforeEach(async ({ browserName }, testInfo) => {
await testInfo.attach('danmuku-selected-input', { contentType: 'application/json', body: JSON.stringify({ browserName, provenance }) })
})
for (const core of ['published', 'candidate']) {
+8 -6
View File
@@ -1,14 +1,16 @@
import fs from 'node:fs'
import process from 'node:process'
import { hash } from '../../refactor/scripts/releases.mjs'
import { compilePackage } from '../helpers/load.js'
import { browserCandidate } from '../helpers/browser-candidate.js'
import { expect, test } from './fixtures.js'
let candidate
let provenance
test.beforeAll(async () => {
candidate = process.env.ARTPLAYER_DANMUKU_ARTIFACT
? fs.readFileSync(process.env.ARTPLAYER_DANMUKU_ARTIFACT, 'utf8')
: await compilePackage('artplayer-plugin-danmuku', 'umd')
;({ code: candidate, provenance } = await browserCandidate('artplayer-plugin-danmuku', process.env.ARTPLAYER_DANMUKU_ARTIFACT))
})
test.beforeEach(async ({ browserName }, testInfo) => {
await testInfo.attach('danmuku-selected-input', { contentType: 'application/json', body: JSON.stringify({ browserName, provenance }) })
})
async function setup(page, core, testInfo) {
@@ -60,7 +62,7 @@ async function setup(page, core, testInfo) {
window.inputPlugin = window.art.plugins.artplayerPluginDanmuku
})
await expect.poll(() => page.evaluate(() => window.art.isReady)).toBe(true)
await testInfo.attach('danmuku-input-build', { contentType: 'application/json', body: JSON.stringify({ core, artifact: process.env.ARTPLAYER_DANMUKU_ARTIFACT || 'in-memory source UMD', sha256: hash(candidate), worker: 'Native Blob Worker with observation only', media: '/test/pattern.mp4' }) })
await testInfo.attach('danmuku-input-build', { contentType: 'application/json', body: JSON.stringify({ core, artifact: provenance.file || provenance.kind, sha256: hash(candidate), worker: 'Native Blob Worker with observation only', media: '/test/pattern.mp4' }) })
return external
}
+6 -4
View File
@@ -2,9 +2,10 @@ import assert from 'node:assert/strict'
import fs from 'node:fs'
import process from 'node:process'
import { ensureArchive, hash, readMember } from '../../refactor/scripts/releases.mjs'
import { compilePackage } from '../helpers/load.js'
import { browserCandidate } from '../helpers/browser-candidate.js'
import { expect, test } from './fixtures.js'
let provenance
const implementations = new Map()
test.beforeAll(async () => {
const release = JSON.parse(fs.readFileSync(new URL('../../refactor/baselines/danmuku-release.json', import.meta.url), 'utf8')).release
@@ -12,9 +13,9 @@ test.beforeAll(async () => {
const bytes = readMember(await ensureArchive(release), member)
assert.equal(hash(bytes), release.files[member])
implementations.set('published', bytes.toString())
implementations.set('candidate', process.env.ARTPLAYER_DANMUKU_ARTIFACT
? fs.readFileSync(process.env.ARTPLAYER_DANMUKU_ARTIFACT, 'utf8')
: await compilePackage('artplayer-plugin-danmuku', 'umd'))
const candidate = await browserCandidate('artplayer-plugin-danmuku', process.env.ARTPLAYER_DANMUKU_ARTIFACT)
provenance = candidate.provenance
implementations.set('candidate', candidate.code)
})
for (const core of ['published', 'candidate']) {
@@ -44,6 +45,7 @@ for (const core of ['published', 'candidate']) {
}
})
const code = implementations.get(implementation)
await testInfo.attach('danmuku-selected-input', { contentType: 'application/json', body: JSON.stringify({ implementation, provenance: implementation === 'candidate' ? provenance : { kind: 'published', sha256: hash(code) } }) })
await page.addScriptTag({ content: code })
await page.evaluate(() => window.createPlayer('/assets/sample/video.mp4'))
await page.click('#play')
+8 -6
View File
@@ -1,14 +1,16 @@
import fs from 'node:fs'
import process from 'node:process'
import { hash } from '../../refactor/scripts/releases.mjs'
import { compilePackage } from '../helpers/load.js'
import { browserCandidate } from '../helpers/browser-candidate.js'
import { expect, test } from './fixtures.js'
let candidate
let provenance
test.beforeAll(async () => {
candidate = process.env.ARTPLAYER_DANMUKU_ARTIFACT
? fs.readFileSync(process.env.ARTPLAYER_DANMUKU_ARTIFACT, 'utf8')
: await compilePackage('artplayer-plugin-danmuku', 'umd')
;({ code: candidate, provenance } = await browserCandidate('artplayer-plugin-danmuku', process.env.ARTPLAYER_DANMUKU_ARTIFACT))
})
test.beforeEach(async ({ browserName }, testInfo) => {
await testInfo.attach('danmuku-selected-input', { contentType: 'application/json', body: JSON.stringify({ browserName, provenance }) })
})
async function setup(page, core, testInfo) {
@@ -79,7 +81,7 @@ async function setup(page, core, testInfo) {
await page.evaluate(() => window.art.playbackRate = 0.25)
await testInfo.attach('danmuku-mask-boundary-inputs', {
contentType: 'application/json',
body: JSON.stringify({ core, artifact: process.env.ARTPLAYER_DANMUKU_ARTIFACT || 'source UMD', sha256: hash(candidate), media: '/test/pattern.mp4', scope: 'Shared root-layer identity and CSS preservation with fixed inline SVG mask; native video, DOM, RAF and placement Worker. No Mask SDK/model/end-to-end acceptance. Eligible rows are explicit resource-test preconditions, not timestamp-delivery evidence.' }),
body: JSON.stringify({ core, artifact: provenance.file || provenance.kind, sha256: hash(candidate), media: '/test/pattern.mp4', scope: 'Shared root-layer identity and CSS preservation with fixed inline SVG mask; native video, DOM, RAF and placement Worker. No Mask SDK/model/end-to-end acceptance. Eligible rows are explicit resource-test preconditions, not timestamp-delivery evidence.' }),
})
return external
}
+21 -19
View File
@@ -2,42 +2,43 @@ import assert from 'node:assert/strict'
import fs from 'node:fs'
import process from 'node:process'
import { ensureArchive, hash, readMember } from '../../refactor/scripts/releases.mjs'
import { browserCandidate } from '../helpers/browser-candidate.js'
import { observeDanmukuLoad } from '../helpers/danmuku-combination-load.js'
import { compilePackage } from '../helpers/load.js'
import { danmukuMaskAssets } from '../helpers/danmuku-mask-assets.js'
import { expect, test } from './fixtures.js'
const artifact = process.env.ARTPLAYER_MASK_ARTIFACT || 'source UMD'
let artifact
let provenance
let danmukuProvenance
let code
const baseline = JSON.parse(fs.readFileSync('refactor/baselines/danmuku-mask-release.json', 'utf8'))
const assets = baseline.sdk.assets.map(({ file, sha256 }) => {
const bytes = fs.readFileSync(file)
const actual = hash(bytes)
// Git checkout may convert documentation/declaration newlines. Executed model
// scripts and binaries must retain their exact fixed bytes.
const normalizedLF = /\.(?:md|d\.ts|json)$/.test(file) && actual !== sha256
assert.equal(normalizedLF ? hash(bytes.toString().replaceAll('\r\n', '\n')) : actual, sha256, `Model asset differs from fixed source: ${file}`)
return { file, sha256: actual, baselineSha256: sha256, normalizedLF }
})
const assets = danmukuMaskAssets(process.cwd()).map(resource => resource.source)
const media = 'docs/assets/sample/steve-jobs.mp4'
const danmukuFile = process.env.ARTPLAYER_DANMUKU_ARTIFACT || 'source UMD'
let danmukuFile
let danmukuCode
const danmukuImplementations = new Map()
test.beforeAll(async () => {
;[code, danmukuCode] = await Promise.all([
process.env.ARTPLAYER_MASK_ARTIFACT ? fs.readFileSync(artifact, 'utf8') : compilePackage('artplayer-plugin-danmuku-mask', 'umd'),
process.env.ARTPLAYER_DANMUKU_ARTIFACT ? fs.readFileSync(danmukuFile, 'utf8') : compilePackage('artplayer-plugin-danmuku', 'umd'),
const [mask, danmuku] = await Promise.all([
browserCandidate('artplayer-plugin-danmuku-mask', process.env.ARTPLAYER_MASK_ARTIFACT),
browserCandidate('artplayer-plugin-danmuku', process.env.ARTPLAYER_DANMUKU_ARTIFACT),
])
code = mask.code
provenance = mask.provenance
artifact = provenance.file || provenance.kind
danmukuCode = danmuku.code
danmukuProvenance = danmuku.provenance
danmukuFile = danmukuProvenance.file || danmukuProvenance.kind
const release = JSON.parse(fs.readFileSync('refactor/baselines/danmuku-release.json', 'utf8')).release
const member = 'package/dist/artplayer-plugin-danmuku.js'
const published = readMember(await ensureArchive(release), member)
assert.equal(hash(published), release.files[member])
danmukuImplementations.set('published', { file: `npm ${release.version}`, code: published.toString() })
danmukuImplementations.set('candidate', { file: danmukuFile, code: danmukuCode })
danmukuImplementations.set('candidate', { file: danmukuFile, code: danmukuCode, provenance: danmukuProvenance })
})
for (const core of ['candidate', 'published', 'published-5.3.1-beta.1']) {
test(`Mask actual local MediaPipe model renders, stops and restarts with ${core} core`, async ({ page }, testInfo) => {
test.setTimeout(90000) // Includes two actual model initializations and native video decoding.
await testInfo.attach('mask-selected-input', { contentType: 'application/json', body: JSON.stringify({ core, provenance }) })
const external = []
const resources = []
const origin = new URL(testInfo.project.use.baseURL).origin
@@ -132,7 +133,7 @@ for (const core of ['candidate', 'published', 'published-5.3.1-beta.1']) {
}
finally {
const state = await page.evaluate(() => ({ snapshots: window.maskNative?.snapshots, outputs: window.maskNative?.outputs.map(({ canvas, time }) => ({ width: canvas.width, height: canvas.height, time })), video: window.art ? { time: window.art.currentTime, duration: window.art.duration, paused: window.art.video.paused, size: [window.art.video.videoWidth, window.art.video.videoHeight] } : null, mask: window.maskLayer?.style.maskImage?.slice(0, 80) }))
await testInfo.attach('actual-model-evidence', { body: JSON.stringify({ artifact, sha256: hash(code), core, assets, media: { file: media, sha256: hash(fs.readFileSync(media)) }, external, resources, state, limitation: 'Actual SDK/model, native video and output bitmap lifecycle. No assertion of private MediaPipe/GPU closure or Danmuku layout composition.' }, null, 2), contentType: 'application/json' })
await testInfo.attach('actual-model-evidence', { body: JSON.stringify({ artifact, provenance, sha256: hash(code), core, assets, media: { file: media, sha256: hash(fs.readFileSync(media)) }, external, resources, state, limitation: 'Actual SDK/model, native video and output bitmap lifecycle. No assertion of private MediaPipe/GPU closure or Danmuku layout composition.' }, null, 2), contentType: 'application/json' })
await page.evaluate(() => {
if (window.art && !window.art.isDestroy)
window.art.destroy()
@@ -143,6 +144,7 @@ for (const core of ['candidate', 'published', 'published-5.3.1-beta.1']) {
for (const implementation of ['published', 'candidate']) {
test(`Mask actual model and Danmuku share the layer through pause, seek and web fullscreen with ${core} core / ${implementation} plugin`, async ({ page }, testInfo) => {
const danmuku = danmukuImplementations.get(implementation)
await testInfo.attach('mask-selected-input', { contentType: 'application/json', body: JSON.stringify({ core, implementation, provenance, danmuku: danmuku.provenance || { kind: 'published', sha256: hash(danmuku.code) } }) })
test.setTimeout(90000)
await page.goto(`/test/player.html?core=${core}`)
await page.addScriptTag({ content: danmuku.code })
@@ -290,7 +292,7 @@ for (const core of ['candidate', 'published', 'published-5.3.1-beta.1']) {
}
finally {
const state = await page.evaluate(() => ({ times: window.maskCombination.times, visible: window.maskCombination.visible, snapshots: window.maskCombination.snapshots, scheduled: window.maskCombination.scheduled, samples: window.maskCombination.samples, seeked: window.maskCombination.seeked, incompleteLoad: window.danmukuCombinationLoadResult }))
await testInfo.attach('actual-combination-evidence', { body: JSON.stringify({ core, implementation, mask: { file: artifact, sha256: hash(code) }, danmuku: { file: danmuku.file, sha256: hash(danmuku.code) }, unexpected, state, load, limitation: 'Actual model, native media, timestamp-delivered Danmuku and CSS web fullscreen. Bounded 20 rows/s after model readiness. No OS fullscreen, GPU completion or device coverage claim.' }, null, 2), contentType: 'application/json' })
await testInfo.attach('actual-combination-evidence', { body: JSON.stringify({ core, implementation, mask: { file: artifact, provenance, sha256: hash(code) }, danmuku: { file: danmuku.file, provenance: danmuku.provenance, sha256: hash(danmuku.code) }, unexpected, state, load, limitation: 'Actual model, native media, timestamp-delivered Danmuku and CSS web fullscreen. Bounded 20 rows/s after model readiness. No OS fullscreen, GPU completion or device coverage claim.' }, null, 2), contentType: 'application/json' })
await page.evaluate(() => {
if (window.art && !window.art.isDestroy)
window.art.destroy()
+8 -6
View File
@@ -1,14 +1,16 @@
import fs from 'node:fs'
import process from 'node:process'
import { hash } from '../../refactor/scripts/releases.mjs'
import { compilePackage } from '../helpers/load.js'
import { browserCandidate } from '../helpers/browser-candidate.js'
import { expect, test } from './fixtures.js'
let candidate
let provenance
test.beforeAll(async () => {
candidate = process.env.ARTPLAYER_DANMUKU_ARTIFACT
? fs.readFileSync(process.env.ARTPLAYER_DANMUKU_ARTIFACT, 'utf8')
: await compilePackage('artplayer-plugin-danmuku', 'umd')
;({ code: candidate, provenance } = await browserCandidate('artplayer-plugin-danmuku', process.env.ARTPLAYER_DANMUKU_ARTIFACT))
})
test.beforeEach(async ({ browserName }, testInfo) => {
await testInfo.attach('danmuku-selected-input', { contentType: 'application/json', body: JSON.stringify({ browserName, provenance }) })
})
async function setup(page, core, scenario, testInfo) {
@@ -102,7 +104,7 @@ async function setup(page, core, scenario, testInfo) {
document.querySelector('#play').onclick = () => window.art.play()
}, { scenario })
await expect.poll(() => page.evaluate(() => window.art.isReady)).toBe(true)
await testInfo.attach('danmuku-resources-inputs', { contentType: 'application/json', body: JSON.stringify({ core, scenario, artifact: process.env.ARTPLAYER_DANMUKU_ARTIFACT || 'source UMD', sha256: hash(candidate), native: 'video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock' }) })
await testInfo.attach('danmuku-resources-inputs', { contentType: 'application/json', body: JSON.stringify({ core, scenario, artifact: provenance.file || provenance.kind, sha256: hash(candidate), native: 'video, RAF, DOM, Worker; observe the first one-second timer after beforeEmit in the synchronous send continuation, no fake timer clock' }) })
}
test.afterEach(async ({ page }, testInfo) => {
+8 -6
View File
@@ -1,14 +1,16 @@
import fs from 'node:fs'
import process from 'node:process'
import { hash } from '../../refactor/scripts/releases.mjs'
import { compilePackage } from '../helpers/load.js'
import { browserCandidate } from '../helpers/browser-candidate.js'
import { expect, test } from './fixtures.js'
let candidate
let provenance
test.beforeAll(async () => {
candidate = process.env.ARTPLAYER_DANMUKU_ARTIFACT
? fs.readFileSync(process.env.ARTPLAYER_DANMUKU_ARTIFACT, 'utf8')
: await compilePackage('artplayer-plugin-danmuku', 'umd')
;({ code: candidate, provenance } = await browserCandidate('artplayer-plugin-danmuku', process.env.ARTPLAYER_DANMUKU_ARTIFACT))
})
test.beforeEach(async ({ browserName }, testInfo) => {
await testInfo.attach('danmuku-selected-input', { contentType: 'application/json', body: JSON.stringify({ browserName, provenance }) })
})
async function setup(page, core, format, scenario, testInfo) {
const external = []
@@ -109,7 +111,7 @@ async function setup(page, core, format, scenario, testInfo) {
document.querySelector('#pause').onclick = () => window.danmukuPlayers[0].pause()
}, { scenario })
await expect.poll(() => page.evaluate(() => window.art.isReady && window.art.template.$video.videoWidth > 0)).toBe(true)
await testInfo.attach('danmuku-browser-inputs', { contentType: 'application/json', body: JSON.stringify({ core, plugin: 'artplayer-plugin-danmuku', format, artifact: process.env.ARTPLAYER_DANMUKU_ARTIFACT || 'in-memory source UMD', sha256: implementation.sha256, media: '/test/pattern.mp4', scenario, worker: 'Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked', heatmap: false }) })
await testInfo.attach('danmuku-browser-inputs', { contentType: 'application/json', body: JSON.stringify({ core, plugin: 'artplayer-plugin-danmuku', format, artifact: provenance.file || provenance.kind, sha256: implementation.sha256, media: '/test/pattern.mp4', scenario, worker: 'Native Blob Worker, only constructor/message/termination observation; no Worker responses, media clocks, layout or RAF mocked', heatmap: false }) })
return external
}
+6 -4
View File
@@ -2,9 +2,10 @@ import assert from 'node:assert/strict'
import fs from 'node:fs'
import process from 'node:process'
import { ensureArchive, hash, readMember } from '../../refactor/scripts/releases.mjs'
import { compilePackage } from '../helpers/load.js'
import { browserCandidate } from '../helpers/browser-candidate.js'
import { expect, test } from './fixtures.js'
let provenance
const implementations = new Map()
test.beforeAll(async () => {
const release = JSON.parse(fs.readFileSync(new URL('../../refactor/baselines/danmuku-release.json', import.meta.url), 'utf8')).release
@@ -12,9 +13,9 @@ test.beforeAll(async () => {
const published = readMember(await ensureArchive(release), member)
assert.equal(hash(published), release.files[member])
implementations.set('published', published.toString())
implementations.set('candidate', process.env.ARTPLAYER_DANMUKU_ARTIFACT
? fs.readFileSync(process.env.ARTPLAYER_DANMUKU_ARTIFACT, 'utf8')
: await compilePackage('artplayer-plugin-danmuku', 'umd'))
const candidate = await browserCandidate('artplayer-plugin-danmuku', process.env.ARTPLAYER_DANMUKU_ARTIFACT)
provenance = candidate.provenance
implementations.set('candidate', candidate.code)
})
for (const core of ['published', 'candidate']) {
@@ -26,6 +27,7 @@ for (const core of ['published', 'candidate']) {
test.setTimeout(90000)
await page.goto(`/test/player.html?core=${core}`)
const code = implementations.get(implementation)
await testInfo.attach('danmuku-selected-input', { contentType: 'application/json', body: JSON.stringify({ implementation, provenance: implementation === 'candidate' ? provenance : { kind: 'published', sha256: hash(code) } }) })
await page.addScriptTag({ content: code })
await page.evaluate(() => window.createPlayer('/assets/sample/video.mp4'))
await page.click('#play')
@@ -2,19 +2,20 @@ import assert from 'node:assert/strict'
import fs from 'node:fs'
import process from 'node:process'
import { ensureArchive, hash, readMember } from '../../refactor/scripts/releases.mjs'
import { compilePackage } from '../helpers/load.js'
import { browserCandidate } from '../helpers/browser-candidate.js'
import { expect, test } from './fixtures.js'
const release = JSON.parse(fs.readFileSync(new URL('../../refactor/baselines/danmuku-release.json', import.meta.url), 'utf8')).release
let provenance
const implementations = new Map()
test.beforeAll(async () => {
const member = `package/${release.manifest.main.replace(/^\.\//u, '')}`
const published = readMember(await ensureArchive(release), member)
assert.equal(hash(published), release.files[member])
implementations.set('published', published.toString())
implementations.set('candidate', process.env.ARTPLAYER_DANMUKU_ARTIFACT
? fs.readFileSync(process.env.ARTPLAYER_DANMUKU_ARTIFACT, 'utf8')
: await compilePackage('artplayer-plugin-danmuku', 'umd'))
const candidate = await browserCandidate('artplayer-plugin-danmuku', process.env.ARTPLAYER_DANMUKU_ARTIFACT)
provenance = candidate.provenance
implementations.set('candidate', candidate.code)
})
for (const implementation of ['published', 'candidate']) {
@@ -22,6 +23,7 @@ for (const implementation of ['published', 'candidate']) {
test(`${implementation}: native ${scenario} records actual frame and eligibility gaps`, async ({ page }, testInfo) => {
await page.goto('/test/player.html?core=candidate')
const code = implementations.get(implementation)
await testInfo.attach('danmuku-selected-input', { contentType: 'application/json', body: JSON.stringify({ implementation, provenance: implementation === 'candidate' ? provenance : { kind: 'published', sha256: hash(code) } }) })
await page.addScriptTag({ content: code })
await page.evaluate(() => window.createPlayer('/assets/sample/video.mp4'))
await page.click('#play')
+5
View File
@@ -11,6 +11,7 @@ import { ensureArchive, hash, readMember } from '../../refactor/scripts/releases
import { performanceHtml, performanceScript } from '../../scripts/performance-fixture.mjs'
import { getEntryFile } from '../../scripts/projects.js'
import { getGlobalName, getViteBuildConfig } from '../../scripts/utils.js'
import { danmukuMaskAssets } from '../helpers/danmuku-mask-assets.js'
import { jassubAssets } from '../helpers/jassub-assets.js'
import { skippableVast } from './vast-fixture.mjs'
@@ -29,6 +30,10 @@ function add(route, bytes, source) {
// An explicit artifact map takes precedence over source builds and never falls back.
const mapFile = process.env.ARTPLAYER_BROWSER_ARTIFACTS
const overrides = mapFile ? JSON.parse(fs.readFileSync(mapFile, 'utf8')) : {}
if (Object.hasOwn(overrides, 'artplayer-plugin-danmuku-mask')) {
for (const resource of danmukuMaskAssets(workspace))
add(resource.route, resource.bytes, resource.source)
}
if (Object.hasOwn(overrides, 'artplayer-plugin-jassub')) {
for (const resource of jassubAssets(workspace, mapFile))
add(resource.route, resource.bytes, resource.source)
+39
View File
@@ -0,0 +1,39 @@
import assert from 'node:assert/strict'
import fs from 'node:fs'
import os from 'node:os'
import path from 'node:path'
// eslint-disable-next-line test/no-import-node-test -- Verify fixed browser resource inputs without starting the model.
import test from 'node:test'
import { hash } from '../refactor/scripts/releases.mjs'
import { danmukuMaskAssets } from './helpers/danmuku-mask-assets.js'
test('Model assets preserve executed bytes and allow only metadata newline conversion', () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'artplayer-mask-assets-'))
const prefix = 'docs/assets/@mediapipe/selfie_segmentation/'
const files = ['worker.js', 'model.wasm', 'package.json']
const write = (file, bytes) => {
fs.mkdirSync(path.dirname(path.join(root, file)), { recursive: true })
fs.writeFileSync(path.join(root, file), bytes)
}
try {
for (const file of files) write(prefix + file, 'fixture\n')
write('refactor/baselines/danmuku-mask-release.json', JSON.stringify({ sdk: { assets: files.map(file => ({ file: prefix + file, sha256: hash('fixture\n') })) } }))
const original = danmukuMaskAssets(root)
assert(original.every(item => item.source.kind === 'local-model-resource' && !item.source.normalizedLF))
assert.equal(original[0].route, '/assets/@mediapipe/selfie_segmentation/worker.js')
for (const file of files.slice(0, 2)) {
write(prefix + file, 'fixture\r\n')
assert.throws(() => danmukuMaskAssets(root), /Model asset differs/)
write(prefix + file, 'fixture\n')
}
write(`${prefix}package.json`, 'fixture\r\n')
assert.equal(danmukuMaskAssets(root)[2].source.normalizedLF, true)
fs.unlinkSync(path.join(root, `${prefix}model.wasm`))
assert.throws(() => danmukuMaskAssets(root), { code: 'ENOENT' })
}
finally {
assert.equal(path.dirname(root), path.resolve(os.tmpdir()))
assert(path.basename(root).startsWith('artplayer-mask-assets-'))
fs.rmSync(root, { recursive: true, force: true })
}
})
+18
View File
@@ -0,0 +1,18 @@
import assert from 'node:assert/strict'
import fs from 'node:fs'
import path from 'node:path'
import { hash } from '../../refactor/scripts/releases.mjs'
export function danmukuMaskAssets(root) {
const baseline = JSON.parse(fs.readFileSync(path.join(root, 'refactor/baselines/danmuku-mask-release.json'), 'utf8'))
return baseline.sdk.assets.map(({ file, sha256 }) => {
assert(file.startsWith('docs/assets/@mediapipe/selfie_segmentation/') && !file.split('/').includes('..'), 'Unexpected model asset path')
const absolute = path.join(root, file)
const bytes = fs.readFileSync(absolute)
const actual = hash(bytes)
// Checkout newline conversion is allowed only for metadata, never executed assets.
const normalizedLF = /\.(?:md|d\.ts|json)$/.test(file) && actual !== sha256
assert.equal(normalizedLF ? hash(bytes.toString().replaceAll('\r\n', '\n')) : actual, sha256, `Model asset differs from fixed source: ${file}`)
return { route: file.slice(4), bytes, source: { kind: 'local-model-resource', file: absolute, relativeFile: file, sha256: actual, baselineSha256: sha256, normalizedLF } }
})
}
+1 -1
View File
@@ -61,7 +61,7 @@ test('Explicit installed plugin maps never fall back to source or frozen workspa
delete process.env.ARTPLAYER_AUTO_THUMBNAIL_BASELINE
process.env.ARTPLAYER_AUTO_THUMBNAIL_ARTIFACT = 'override.js'
await assert.rejects(autoThumbnailCandidate(), /cannot override artplayer-plugin-auto-thumbnail artifact/)
for (const name of ['artplayer-plugin-hls-control', 'artplayer-plugin-dash-control', 'artplayer-plugin-asr', 'artplayer-plugin-chromecast', 'artplayer-plugin-jassub']) {
for (const name of ['artplayer-plugin-hls-control', 'artplayer-plugin-dash-control', 'artplayer-plugin-asr', 'artplayer-plugin-chromecast', 'artplayer-plugin-jassub', 'artplayer-plugin-danmuku', 'artplayer-plugin-danmuku-mask']) {
await assert.rejects(browserCandidate(name), { code: 'ENOENT' })
await assert.rejects(browserCandidate(name, 'override.js'), new RegExp(`cannot override ${name} artifact`))
}