mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-12 06:26:16 -08:00
test(ci): [CI-01] cover installed Danmuku and native Mask combinations
This commit is contained in:
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 及各包
|
||||
集成/设备/发布门槛保持开放,没有推送或发布。回退此检查点恢复十六包
|
||||
安装清单和原有浏览器候选/本地模型读取方式。
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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
@@ -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"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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.
|
||||
@@ -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')
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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/)
|
||||
|
||||
@@ -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) })
|
||||
|
||||
@@ -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 = []
|
||||
|
||||
@@ -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']) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 })
|
||||
}
|
||||
})
|
||||
@@ -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 } }
|
||||
})
|
||||
}
|
||||
@@ -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`))
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user