mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-23 22:01:20 -08:00
test(refactor): [BASE-02] freeze real-browser public API contracts
This commit is contained in:
1 parent
833cc11674
commit
b382ec08f7
12 files changed
+4034
-22
No files matched your search
@@ -2,13 +2,13 @@
|
||||
|
||||
`node refactor/scripts/browser-server.mjs` 在 127.0.0.1:8083 启动独立测试服务;ARTPLAYER_TEST_PORT 可更换端口。它不启动或修改用户的 Chrome,也不占用 docs 的 8082 开发服务。
|
||||
|
||||
- `/fixtures/api.html` 加载固定核心/chapter tarball 的真实 JS,准备捕获描述符、默认配置、utils/子系统/插件形状及基础调用。
|
||||
- `/fixtures/api.html` 加载固定核心/chapter tarball 的真实 JS,捕获描述符、默认配置、utils/子系统/插件形状及基础调用。
|
||||
- `/releases/<name>/<member>` 只读取 releases.json 明确登记的成员;启动时核对 archive 完整性,不使用未编译源码或 CDN latest。
|
||||
- `/assets/sample/` 读取本仓库样本,支持普通 bytes 范围请求。当前服务用于最小基线;不是完整 ENG-05 故障注入或 HTTP Range 兼容实现,不支持后缀/多段 Range。
|
||||
- 页面将 `public-api` 报告发送给同源 `/reports/api`,服务器保存 refactor/.cache/reports/api.json。最多 1 MiB;其他 Origin 和未知写入路径失败。临时报告不能自动覆盖冻结快照。
|
||||
|
||||
运行 `node --test refactor/scripts/browser-server.test.mjs` 验证真实 HTTP 服务返回原始 JS 字节、样本范围和路由隔离;`node --check refactor/fixtures/api.js` 只检查脚本语法。两者不能证明页面能在浏览器运行。
|
||||
|
||||
2026-09-10:Chrome 连接服务失败后,用户明确授权使用 Codex 内置浏览器。内置浏览器已打开 api.html,9 项基础 API 检查全部通过,页面 errors 为空,报告已保存至缓存。BASE-02 连接阻塞解除,接续核对报告和来源、生成持久基线;此时尚未完成整个 API 基线任务,也未验证真实播放。
|
||||
2026-09-10:按用户授权使用内置浏览器后,BASE-02 最终两次采集的 14 项检查均通过,快照相同,errors 为空。持久报告、比较命令、契约覆盖及剩余范围见 [API 基线](../baselines/api-coverage.md)。本页面没有验证真实播放。
|
||||
|
||||
夹具与报告路径由基线任务维护;ENG-05 可复用并扩充受控等待、媒体错误和正式自动化。api.js 依据已核对源码设计断言,真实发布行为如有不同应调查并记录,不能修改断言来盲目通过。
|
||||
+45
-15
@@ -1,9 +1,14 @@
|
||||
/* global Artplayer, artplayerPluginChapter */
|
||||
(async () => {
|
||||
const report = { kind: 'public-api', environment: { userAgent: navigator.userAgent }, checks: [], errors: [] }
|
||||
const check = (name, result) => {
|
||||
report.checks.push({ name, passed: Boolean(result) })
|
||||
if (!result) throw new Error(name)
|
||||
const report = {
|
||||
kind: 'public-api',
|
||||
environment: { userAgent: navigator.userAgent, language: navigator.language },
|
||||
scripts: [...document.scripts].map(script => new URL(script.src).pathname),
|
||||
checks: [], errors: [],
|
||||
}
|
||||
const check = (id, result) => {
|
||||
report.checks.push({ id, passed: Boolean(result) })
|
||||
if (!result) throw new Error(id)
|
||||
}
|
||||
function descriptors(object) {
|
||||
return Object.fromEntries(Object.getOwnPropertyNames(object).sort().map((name) => {
|
||||
@@ -23,37 +28,57 @@
|
||||
if (input && typeof input === 'object') return Object.fromEntries(Object.keys(input).sort().map(key => [key, valueOf(input[key])]))
|
||||
return input
|
||||
}
|
||||
function resolvedDescriptors(object) {
|
||||
const result = new Map()
|
||||
for (let current = object; current && current !== Object.prototype; current = Object.getPrototypeOf(current)) {
|
||||
for (const [name, descriptor] of Object.entries(descriptors(current))) {
|
||||
if (!result.has(name)) result.set(name, descriptor)
|
||||
}
|
||||
}
|
||||
return Object.fromEntries([...result].sort(([a], [b]) => a.localeCompare(b)))
|
||||
}
|
||||
let art
|
||||
try {
|
||||
check('published core version', Artplayer.version === '5.4.0')
|
||||
check('API-01.version', Artplayer.version === '5.4.0')
|
||||
const defaults = valueOf(Artplayer.option)
|
||||
check('API-01.defaults-independent', Artplayer.option !== Artplayer.option && Artplayer.option.subtitle !== Artplayer.option.subtitle)
|
||||
check('API-01.default-language', defaults.lang === navigator.language.toLowerCase())
|
||||
defaults.lang = { $environment: 'navigator.language.toLowerCase()' }
|
||||
const constants = Object.fromEntries(Object.getOwnPropertyNames(Artplayer).sort()
|
||||
.filter(name => /^[A-Z_]+$/.test(name) && name !== 'STYLE')
|
||||
.map(name => [name, valueOf(Artplayer[name])]))
|
||||
art = new Artplayer({ container: '.player', url: '/assets/sample/video.mp4', muted: true, plugins: [artplayerPluginChapter()] })
|
||||
check('constructor registers exact instance', Artplayer.instances.includes(art))
|
||||
check('native media element exposed', art.video instanceof HTMLVideoElement)
|
||||
check('chapter registered synchronously', art.plugins.artplayerPluginChapter?.name === 'artplayerPluginChapter')
|
||||
check('API-01.instance-registration', Artplayer.instances.includes(art))
|
||||
check('API-01.container-selector', art.template.$container === document.querySelector('.player'))
|
||||
check('API-02.video', art.video instanceof HTMLVideoElement && art.video === art.template.$video)
|
||||
const query = art.query
|
||||
check('API-02.bound-query', query('.art-video') === art.video)
|
||||
check('API-06.sync-chapter', art.plugins.artplayerPluginChapter?.name === 'artplayerPluginChapter')
|
||||
const plugin = { name: 'baselineProbe', ping: () => 'pong' }
|
||||
const added = art.plugins.add(() => plugin)
|
||||
check('plugins.add returns synchronous registry', added === art.plugins)
|
||||
check('plugin result methods callable', art.plugins.baselineProbe.ping() === 'pong')
|
||||
check('API-06.add-return', added === art.plugins)
|
||||
check('API-06.result-call', art.plugins.baselineProbe.ping() === 'pong')
|
||||
let received = 0
|
||||
const handler = amount => { received += amount }
|
||||
check('on is chainable', art.on('baseline:event', handler) === art)
|
||||
check('API-04.on-return', art.on('baseline:event', handler) === art)
|
||||
art.emit('baseline:event', 3)
|
||||
art.off('baseline:event', handler)
|
||||
art.emit('baseline:event', 7)
|
||||
check('custom event on/emit/off', received === 3)
|
||||
check('API-04.off', received === 3)
|
||||
const integrations = {}
|
||||
for (const name of ['template', 'events', 'controls', 'setting', 'contextmenu', 'layers', 'plugins', 'storage', 'subtitle', 'i18n']) {
|
||||
integrations[name] = { own: descriptors(art[name]), prototype: descriptors(Object.getPrototypeOf(art[name])) }
|
||||
integrations[name] = { own: descriptors(art[name]), prototype: descriptors(Object.getPrototypeOf(art[name])), resolved: resolvedDescriptors(art[name]) }
|
||||
}
|
||||
report.snapshot = {
|
||||
static: descriptors(Artplayer),
|
||||
prototype: descriptors(Artplayer.prototype),
|
||||
emitterPrototype: descriptors(Object.getPrototypeOf(Artplayer.prototype)),
|
||||
defaults,
|
||||
constants,
|
||||
config: valueOf(Artplayer.config),
|
||||
utilityDescriptors: descriptors(Artplayer.utils),
|
||||
instance: descriptors(art),
|
||||
instanceResolved: resolvedDescriptors(art),
|
||||
chapterFactory: descriptors(artplayerPluginChapter),
|
||||
chapterResult: descriptors(art.plugins.artplayerPluginChapter),
|
||||
integrations,
|
||||
@@ -64,8 +89,13 @@
|
||||
}
|
||||
finally {
|
||||
if (art) {
|
||||
art.destroy(true)
|
||||
report.checks.push({ name: 'instance cleanup', passed: !Artplayer.instances.includes(art) })
|
||||
try {
|
||||
check('API-05.destroy-return', art.destroy(true) === undefined)
|
||||
check('API-05.instance-cleanup', !Artplayer.instances.includes(art))
|
||||
}
|
||||
catch (error) {
|
||||
report.errors.push(error.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
const response = await fetch('/reports/api', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(report) })
|
||||
|
||||
Reference in new issue
Block a user