Jsdom: ¿Jsdom admite elementos de video y audio?

Creado en 19 feb. 2018  ·  14Comentarios  ·  Fuente: jsdom/jsdom

Información básica:

  • Versión de Node.js: 8.94
  • versión jsdom

Información

Quiero ejecutar pruebas unitarias (usando ava y browser-env ) para un cargador de activos que admita la precarga de imágenes, audio y video. Quería saber si jsdom admite elementos de audio y video. Cuando intento crear y llamar a video.load() en un elemento de video ( HTMLVideoElement que en sí mismo es un HTMLMediaElement ), jsdom devuelve este error:

Error: Not implemented: HTMLMediaElement.prototype.load

Supongo que no hay soporte para elementos de video y audio. No he encontrado nada sobre soporte de video y audio en jsdom, ¿tal vez falta?

Comentario más útil

jsdom no admite ninguna operación de carga o reproducción de medios. Como solución alternativa, puede agregar algunos códigos auxiliares en su configuración de prueba:

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

Todos 14 comentarios

jsdom no admite ninguna operación de carga o reproducción de medios. Como solución alternativa, puede agregar algunos códigos auxiliares en su configuración de prueba:

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

Eso es lo que necesito aquí: una forma de simular la carga / recuperación de HTMLMediaElements . Al hacer esto, no precargará audio y video como en un navegador real, ¿verdad?

Por lo general, no se requiere buscar para tales pruebas. Estos stubs suprimen las excepciones de jsdom, y luego podrá probar su lógica con eventos enviados manualmente desde el elemento de video (por ejemplo, videoElement.dispatchEvent(new window.Event("loading")); ).

Muy bien, gracias por la ayuda, finalmente arreglé mis pruebas. 👍

Esto me ha ayudado a comenzar, pero en mi caso quiero probar si ciertas condiciones están afectando correctamente la reproducción. He reemplazado las funciones play y pause e intento establecer la variable paused del elemento multimedia, pero obtengo un error de que solo hay un captador para esa variable. Esto hace que burlarse de él sea un poco desafiante.

Soy algo nuevo en JS. ¿Hay alguna forma de simular variables de solo lectura como esta?

@BenBergman Podrías hacer:

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

¡Excelente gracias! Para la posteridad, mi captador se ve así para dar cuenta de un valor predeterminado:

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

Puede hacerlo aún más simple de esta manera:

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

y luego simplemente cambie mediaTag.paused = true o mediaTag.paused = false en su prueba.

El beneficio de este enfoque es que es seguro para los tipos en caso de que esté utilizando TypeScript. No debe configurar su propiedad simulada de alguna manera como (mediaTag as any).mockPaused = true .

¡Aún mejor, gracias!

¿Cómo puedo simular la reproducción de video?
He desactivado la reproducción y la carga, pero no tengo idea de cómo hacer que el video comience a reproducirse (o creo que se está reproduciendo, todo lo que necesito es lo que sucede a menudo).
Object.defineProperty(HTMLMediaElement.prototype, "play", { get() { document.getElementsByTagName('video')[0].dispatchEvent(new Event('play')); } });

jsdom no admite ninguna operación de carga o reproducción de medios. Como solución alternativa, puede agregar algunos códigos auxiliares en su configuración de prueba:

Gracias por la solución. Sin embargo, ¿puedo preguntar por qué esto no es compatible de forma predeterminada?

Nadie ha implementado un reproductor de video o audio en jsdom todavía.

Aquí hay una implementación rápida y sucia (para broma y vue) de los métodos play y pause que también envía algunos de los eventos que necesitaba para una prueba ( 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'))
}

Y el ejemplo de la prueba (tenga en cuenta que tenemos que configurar manualmente audio.duration :

  // 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)
  })

Consideré usar estas soluciones alternativas, pero en lugar de volver a implementar un navegador como las funciones de reproducción, decidí usar puppeteer , es decir, para obtener un navegador real para hacer las pruebas. Esta es mi configuración:

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"
  ]
}
** paquete.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"
  }
}

¿Fue útil esta página
0 / 5 - 0 calificaciones

Temas relacionados

khalyomede picture khalyomede  ·  3Comentarios

Progyan1997 picture Progyan1997  ·  3Comentarios

mitar picture mitar  ·  4Comentarios

vsemozhetbyt picture vsemozhetbyt  ·  4Comentarios

domenic picture domenic  ·  3Comentarios