Jsdom: Apakah jsdom mendukung elemen video dan audio?

Dibuat pada 19 Feb 2018  ·  14Komentar  ·  Sumber: jsdom/jsdom

Info dasar:

  • Versi Node.js: 8.94
  • versi jsdom: 11.6.2

Informasi

Saya ingin menjalankan unit-tests (menggunakan ava dan browser-env ) untuk pemuat aset yang mendukung pra-pemuatan gambar, audio, dan video. Saya ingin tahu apakah jsdom mendukung elemen audio dan video. Ketika saya mencoba membuat dan memanggil video.load() pada elemen video ( HTMLVideoElement yang merupakan HTMLMediaElement ), jsdom mengembalikan kesalahan ini:

Error: Not implemented: HTMLMediaElement.prototype.load

Saya berasumsi tidak ada dukungan untuk elemen video dan audio. Saya tidak menemukan apa pun tentang dukungan video dan audio di jsdom, mungkin ada yang hilang?

Komentar yang paling membantu

jsdom tidak mendukung operasi pemuatan atau pemutaran media apa pun. Sebagai solusinya, Anda dapat menambahkan beberapa rintisan dalam pengaturan pengujian Anda:

window.HTMLMediaElement.prototype.load = () => { /* do nothing */ };
window.HTMLMediaElement.prototype.play = () => { /* do nothing */ };
window.HTMLMediaElement.prototype.pause = () => { /* do nothing */ };
window.HTMLMediaElement.prototype.addTextTrack = () => { /* do nothing */ };

Semua 14 komentar

jsdom tidak mendukung operasi pemuatan atau pemutaran media apa pun. Sebagai solusinya, Anda dapat menambahkan beberapa rintisan dalam pengaturan pengujian Anda:

window.HTMLMediaElement.prototype.load = () => { /* do nothing */ };
window.HTMLMediaElement.prototype.play = () => { /* do nothing */ };
window.HTMLMediaElement.prototype.pause = () => { /* do nothing */ };
window.HTMLMediaElement.prototype.addTextTrack = () => { /* do nothing */ };

Itulah yang saya butuhkan di sini: cara untuk mensimulasikan pemuatan/pengambilan HTMLMediaElements . Dengan melakukan ini, itu tidak akan memuat audio dan video seperti di browser nyata, bukan?

Biasanya tidak diperlukan pengambilan untuk pengujian semacam itu. Rintisan ini menekan pengecualian jsdom, dan kemudian Anda akan dapat menguji logika Anda dengan peristiwa yang dikirim secara manual dari elemen video (misalnya videoElement.dispatchEvent(new window.Event("loading")); ).

Baiklah, terima kasih atas bantuannya, saya akhirnya memperbaiki tes saya. 👍

Ini telah membantu saya memulai, tetapi dalam kasus saya, saya ingin menguji apakah kondisi tertentu memengaruhi pemutaran dengan benar. Saya telah membuat fungsi pengganti play dan pause dan saya mencoba mengatur variabel paused dari elemen media tetapi mendapatkan kesalahan bahwa hanya ada pengambil untuk variabel itu. Ini membuat mengejeknya sedikit menantang.

Saya agak baru di JS. Apakah ada cara untuk mengejek variabel read-only seperti ini?

@BenBergman Anda bisa melakukan:

Object.defineProperty(HTMLMediaElement.prototype, "paused", {
  get() {
    // Your own getter, where `this` refers to the HTMLMediaElement.
  }
});

Luar biasa, terima kasih! Untuk anak cucu, pengambil saya terlihat seperti ini untuk memperhitungkan nilai default:

get() {
    if (this.mockPaused === undefined) {
        return true;
    }
    return this.mockPaused;
}

Anda dapat membuatnya lebih sederhana dengan cara ini:

Object.defineProperty(mediaTag, "paused", {
  writable: true,
  value: true,
});

dan kemudian ubah saja mediaTag.paused = true atau mediaTag.paused = false dalam pengujian Anda.

Manfaat dari pendekatan ini adalah tipe aman jika Anda menggunakan TypeScript. Anda tidak mengatur properti tiruan Anda entah bagaimana seperti (mediaTag as any).mockPaused = true .

Bahkan lebih baik, terima kasih!

Bagaimana saya bisa mensimulasikan pemutaran video?
Saya mematikan pemutaran dan memuat tetapi saya tidak tahu bagaimana membuat video mulai diputar (atau berpikir itu diputar, yang saya butuhkan adalah apa yang sering terjadi).
Object.defineProperty(HTMLMediaElement.prototype, "play", { get() { document.getElementsByTagName('video')[0].dispatchEvent(new Event('play')); } });

jsdom tidak mendukung operasi media pemuatan atau pemutaran apa pun. Sebagai solusinya, Anda dapat menambahkan beberapa rintisan dalam pengaturan pengujian Anda:

Terima kasih atas solusinya. Bolehkah saya bertanya mengapa ini tidak didukung secara default?

Belum ada yang mengimplementasikan pemutar video atau audio di jsdom.

Berikut ini adalah implementasi cepat dan kotor (untuk lelucon dan vue) dari metode play dan pause yang juga mengirimkan beberapa peristiwa yang saya butuhkan untuk pengujian ( loadedmetadata , play , pause ):

// Jest's setup file, setup.js

// Mock data and helper methods
global.window.HTMLMediaElement.prototype._mock = {
  paused: true,
  duration: NaN,
  _loaded: false,
   // Emulates the audio file loading
  _load: function audioInit(audio) {
    // Note: we could actually load the file from this.src and get real duration
    // and other metadata.
    // See for example: https://github.com/59naga/mock-audio-element/blob/master/src/index.js
    // For now, the 'duration' and other metadata has to be set manually in test code.
    audio.dispatchEvent(new Event('loadedmetadata'))
    audio.dispatchEvent(new Event('canplaythrough'))
  },
  // Reset audio object mock data to the initial state
  _resetMock: function resetMock(audio) {
    audio._mock = Object.assign(
      {},
      global.window.HTMLMediaElement.prototype._mock,
    )
  },
}

// Get "paused" value, it is automatically set to true / false when we play / pause the audio.
Object.defineProperty(global.window.HTMLMediaElement.prototype, 'paused', {
  get() {
    return this._mock.paused
  },
})

// Get and set audio duration
Object.defineProperty(global.window.HTMLMediaElement.prototype, 'duration', {
  get() {
    return this._mock.duration
  },
  set(value) {
    // Reset the mock state to initial (paused) when we set the duration.
    this._mock._resetMock(this)
    this._mock.duration = value
  },
})

// Start the playback.
global.window.HTMLMediaElement.prototype.play = function playMock() {
  if (!this._mock._loaded) {
    // emulate the audio file load and metadata initialization
    this._mock._load(this)
  }
  this._mock.paused = false
  this.dispatchEvent(new Event('play'))
  // Note: we could
}

// Pause the playback
global.window.HTMLMediaElement.prototype.pause = function pauseMock() {
  this._mock.paused = true
  this.dispatchEvent(new Event('pause'))
}

Dan contoh pengujiannya (perhatikan bahwa kita harus mengatur audio.duration secara manual :

  // Test
  it('creates audio player', async () => {
    // `page` is a wrapper for a page being tested, created in beforeEach
    let player = page.player()

    // Useful to see which properties are defined where.
    // console.log(Object.getOwnPropertyDescriptors(HTMLMediaElement.prototype))
    // console.log(Object.getOwnPropertyDescriptors(HTMLMediaElement))
    // console.log(Object.getOwnPropertyDescriptors(audio))

    let audio = player.find('audio').element as HTMLAudioElement

    let audioEventReceived = false
    audio.addEventListener('play', () => {
      audioEventReceived = true
    })

    // @ts-ignore: error TS2540: Cannot assign to 'duration' because it is a read-only property.
    audio.duration = 300

    expect(audio.paused).toBe(true)
    expect(audio.duration).toBe(300)
    expect(audio.currentTime).toBe(0)

    audio.play()
    audio.currentTime += 30

    expect(audioEventReceived).toBe(true)

    expect(audio.paused).toBe(false)
    expect(audio.duration).toBe(300)
    expect(audio.currentTime).toBe(30.02)
  })

Saya mempertimbangkan untuk menggunakan solusi ini, tetapi alih-alih mengimplementasikan kembali browser seperti memainkan fitur, saya memutuskan untuk menggunakan puppeteer , yaitu, untuk mendapatkan browser nyata untuk melakukan pengujian. Ini adalah pengaturan saya:

src/reviewer.tests.ts

jest.disableAutomock()
// Use this in a test to pause its execution, allowing you to open the chrome console
// and while keeping the express server running: chrome://inspect/#devices
// jest.setTimeout(2000000000);
// debugger; await new Promise(function(resolve) {});

test('renders test site', async function() {
    let self: any = global;
    let page = self.page;
    let address = process.env.SERVER_ADDRESS;
    console.log(`The server address is '${address}'.`);
    await page.goto(`${address}/single_audio_file.html`);
    await page.waitForSelector('[data-attibute]');

    let is_paused = await page.evaluate(() => {
        let audio = document.getElementById('silence1.mp3') as HTMLAudioElement;
        return audio.paused;
    });
    expect(is_paused).toEqual(true);
});

testfiles/single_audio_file.html

<html>
    <head>
        <title>main webview</title>
        <script src="importsomething.js"></script>
    </head>
    <body>
    <div id="qa">
        <audio id="silence1.mp3" src="silence1.mp3" data-attibute="some" controls></audio>
        <script type="text/javascript">
            // doSomething();
        </script>
    </div>
    </body>
</html>

**globalTeardown.js**

module.exports = async () => {
    global.server.close();
};
**globalSetup.js**
const express = require('express');

module.exports = async () => {
    let server;
    const app = express();

    await new Promise(function(resolve) {
        server = app.listen(0, "127.0.0.1", function() {
            let address = server.address();
            process.env.SERVER_ADDRESS = `http://${address.address}:${address.port}`;
            console.log(`Running static file server on '${process.env.SERVER_ADDRESS}'...`);
            resolve();
        });
    });

    global.server = server;
    app.get('/favicon.ico', (req, res) => res.sendStatus(200));
    app.use(express.static('./testfiles'));
};
**testEnvironment.js**
const puppeteer = require('puppeteer');

// const TestEnvironment = require('jest-environment-node'); // for server node apps
const TestEnvironment = require('jest-environment-jsdom'); // for browser js apps

class ExpressEnvironment extends TestEnvironment {
    constructor(config, context) {
        let cloneconfig = Object.assign({}, config);
        cloneconfig.testURL = process.env.SERVER_ADDRESS;
        super(cloneconfig, context);
    }

    async setup() {
        await super.setup();
        let browser = await puppeteer.launch({
            // headless: false, // show the Chrome window
            // slowMo: 250, // slow things down by 250 ms
            ignoreDefaultArgs: [
                "--mute-audio",
            ],
            args: [
                "--autoplay-policy=no-user-gesture-required",
            ],
        });
        let [page] = await browser.pages(); // reuses/takes the default blank page
        // let page = await this.global.browser.newPage();

        page.on('console', async msg => console[msg._type](
            ...await Promise.all(msg.args().map(arg => arg.jsonValue()))
        ));
        this.global.page = page;
        this.global.browser = browser;
        this.global.jsdom = this.dom;
    }

    async teardown() {
        await this.global.browser.close();
        await super.teardown();
    }

    runScript(script) {
        return super.runScript(script);
    }
}

module.exports = ExpressEnvironment;
**tsconfig.json**
{
  "compilerOptions": {
    "target": "es2017"
  },
  "include": [
    "src/**/*"
  ],
  "exclude": [
    "src/**/*.test.ts"
  ]
}
**paket.json**
{
  "scripts": {
    "test": "jest",
  },
  "jest": {
    "testEnvironment": "./testEnvironment.js",
    "globalSetup": "./globalSetup.js",
    "globalTeardown": "./globalTeardown.js",
    "transform": {
      "^.+\\.(ts|tsx)$": "ts-jest"
    }
  },
  "jshintConfig": {
    "esversion": 8
  },
  "dependencies": {
    "typescript": "^3.7.3"
  },
  "devDependencies": {
    "@types/express": "^4.17.6",
    "@types/jest": "^25.2.1",
    "@types/node": "^13.11.1",
    "@types/puppeteer": "^2.0.1",
    "express": "^4.17.1",
    "jest": "^25.3.0",
    "puppeteer": "^3.0.0",
    "ts-jest": "^25.3.1"
  }
}

Apakah halaman ini membantu?
0 / 5 - 0 peringkat

Masalah terkait

machineghost picture machineghost  ·  4Komentar

kentmw picture kentmw  ·  3Komentar

josephrexme picture josephrexme  ·  4Komentar

domenic picture domenic  ·  3Komentar

mitar picture mitar  ·  4Komentar