You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

音频时长获取异常:测试中span内容始终为空的问题排查

音频播放器组件测试场景下时间显示不更新的问题

我开发了songPlayerTimeComponent音频播放器组件,原本预期通过监听事件和loadedmetadata事件来更新current_time_song和full_time_song这两个span的内容,但在测试环境中,这两个元素始终显示构造函数初始化的空字符串,明明已经绑定了loadedmetadata事件。

组件核心代码

export default class songPlayerTimeComponent {
  constructor () {
    this.songSelected = {};
    this.currentTime = " "
    this.timeSong = " "
    this.apiService = new SongService()
  }

  initialize (element) {
    element.innerHTML = `
    <span id="current_time_song"></span>
    <audio controls>
      <source type="audio/mpeg">
    </audio>
    <span id="full_time_song"></span>`

    this.currentTimeEL = element.querySelector("#current_time_song")
    this.audioEl = element.querySelector('audio')
    this.timeSongEl = element.querySelector("#full_time_song")

    document.body.addEventListener('changeSongEvent', (event) => this.changeSongEvent(event.detail.songId))

    document.body.addEventListener('playSongEvent', () => this.playSongEvent())
    document.body.addEventListener('pauseSongEvent', () => this.pauseSongEvent())
    document.body.addEventListener('stopSongEvent', () => this.stopSongEvent())

    this.audioEl.addEventListener("play", () => this.songHasPlayed());
    this.audioEl.addEventListener("pause", () => this.songHasStopped())
    this.audioEl.addEventListener("ended", () => this.songHasEnded())

    this.audioEl.addEventListener("timeupdate", () => this.getCurrentTime())
    this.audioEl.addEventListener('loadedmetadata', () => {
        this.getSongTime() 
        this.renderComponent()
      }
    )
  }

  getCurrentTime(){
    let seconds = parseInt(this.audioEl.currentTime % 60);
    let minutes = parseInt((this.audioEl.currentTime / 60) % 60);
    if (seconds < 10) {
      seconds = '0' + seconds;
    }
    if (minutes < 10) {
      minutes = '0' + minutes
    }

    this.currentTime = minutes + ':' + seconds;
  }

  async changeSongEvent (songId) {
    try{
      this.songSelected = await this.apiService.getSong(songId);
      this.audioEl.src = "./songs/" + this.songSelected.fileName;
      await this.audioEl.load()
    }
    catch (error) {
      this.displayErrorMessage (error)
    }
  }
  
  getSongTime() {
    const duration = parseInt(this.audioEl.duration)
    let seconds = duration % 60
    let minutes = Math.floor(duration / 60);
  
    seconds = ("0" + seconds).slice(-2);
    minutes = ("0" + minutes).slice(-2);
  
    this.timeSong = minutes + ":" + seconds;
    this.getCurrentTime();
  }
}

测试代码

const songPlayerTimeComponent = new SongPlayerTimeComponent()
const songSelected = songs[0]
songPlayerTimeComponent.initialize(componentEl)

document.body.dispatchEvent(
    new CustomEvent('changeSongEvent', {
        detail: {
        songId: songSelected.id
        }
    })
)
await flushPromises()

const currentTimeEL = document.querySelector('#current_time_song')
expect(currentTimeEL.innerHTML).toEqual("9:00")

测试报错信息

AssertionError: expected '' to deeply equal '00:00'
 ❯ tests/songPlayerTimeComponent.test.js:61:42
     59|             const timeSongEL = document.querySelector('#full_time_song')
     60|             expect(timeSongEL.innerHTML).toBeDefined()
     61|             expect(timeSongEL.innerHTML).toEqual("00:00")
       |                                          ^
     62|         })
     63|

  - Expected   "00:00"
  + Received   ""

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/2]⎯

 FAIL  tests/songPlayerTimeComponent.test.js > songPlayerTimeComponent > success > Sur réception de l'événement global changeSongEvent, la durée totale de la chanson doit s'afficher.
AssertionError: expected '' to deeply equal '9:00'
 ❯ tests/songPlayerTimeComponent.test.js:79:45
     77|
     78|             const currentTimeEL = document.querySelector('#current_time_song')
     79|             expect(currentTimeEL.innerHTML).toEqual("9:00")
       |                                             ^
     80|         })
     81|

  - Expected   "9:00"
  + Received   ""

问题原因及解决方法

核心问题

  1. renderComponent方法缺失:代码中监听loadedmetadata事件时调用了this.renderComponent(),但组件未实现该方法,导致计算后的this.currentTime和this.timeSong无法同步到DOM元素。
  2. 测试未等待音频加载流程:flushPromises()仅处理Promise异步,音频的loadedmetadata事件是独立的异步流程,测试代码未等待该事件触发就执行断言,自然拿不到更新后的内容。

修复步骤

1. 实现renderComponent方法

在组件中添加该方法,将数据同步到DOM:

renderComponent() {
  this.currentTimeEL.textContent = this.currentTime;
  this.timeSongEl.textContent = this.timeSong;
}

同时在getCurrentTime方法末尾调用该方法,确保播放时的时间更新能实时同步:

getCurrentTime(){
  // 原有时间计算逻辑
  
  this.currentTime = minutes + ':' + seconds;
  this.renderComponent(); // 新增该行
}

2. 测试代码中等待loadedmetadata事件

修改测试代码,等待音频元数据加载完成后再执行断言:

const songPlayerTimeComponent = new SongPlayerTimeComponent()
const songSelected = songs[0]
songPlayerTimeComponent.initialize(componentEl)

// 用Promise包裹等待loadedmetadata事件
const waitForLoadedMetadata = new Promise(resolve => {
  songPlayerTimeComponent.audioEl.addEventListener('loadedmetadata', resolve, { once: true });
})

document.body.dispatchEvent(
    new CustomEvent('changeSongEvent', {
        detail: {
        songId: songSelected.id
        }
    })
)
await flushPromises()
await waitForLoadedMetadata // 等待音频元数据加载完成

const currentTimeEL = document.querySelector('#current_time_song')
expect(currentTimeEL.innerHTML).toEqual("00:00") // 初始播放时间应为00:00,若需测试特定时间需手动设置audioEl.currentTime

3. 额外优化

  • 构造函数中初始化this.currentTime和this.timeSong为"00:00"而非空格,避免初始显示异常。
  • 测试环境中可MockSongService返回的音频数据,或确保测试用音频文件可正常访问,避免因文件加载失败导致loadedmetadata事件不触发。

内容的提问来源于stack exchange,提问作者Nicolas Pellerin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 14:00:01