Jsdom: Unterstützt jsdom Video- und Audioelemente?

Erstellt am 19. Feb. 2018  ·  14Kommentare  ·  Quelle: jsdom/jsdom

Basisinformation:

  • Node.js-Version: 8.94
  • jsdom-Version: 11.6.2

Information

Ich möchte Unit-Tests (mit ava und browser-env ) für einen Asset-Loader ausführen, der das Vorladen von Bildern, Audio und Video unterstützt. Ich wollte wissen, ob jsdom Audio- und Videoelemente unterstützt. Wenn ich versuche, video.load() für ein Videoelement zu erstellen und aufzurufen ( HTMLVideoElement das selbst ein HTMLMediaElement ), gibt jsdom diesen Fehler zurück:

Error: Not implemented: HTMLMediaElement.prototype.load

Ich gehe davon aus, dass es keine Unterstützung für Video- und Audioelemente gibt. Ich habe in jsdom nichts über Video- und Audio-Unterstützung gefunden, vielleicht fehlt es?

Hilfreichster Kommentar

jsdom unterstützt keine Vorgänge zum Laden oder Abspielen von Medien. Als Workaround können Sie Ihrem Test-Setup einige Stubs hinzufügen:

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

Alle 14 Kommentare

jsdom unterstützt keine Vorgänge zum Laden oder Abspielen von Medien. Als Workaround können Sie Ihrem Test-Setup einige Stubs hinzufügen:

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

Das ist, was ich hier brauche: eine Möglichkeit, das Laden/Abholen von HTMLMediaElements zu simulieren. Dadurch werden Audio und Video nicht wie in einem echten Browser vorgeladen, oder?

Normalerweise ist für solche Tests kein Abrufen erforderlich. Diese Stubs unterdrücken jsdom-Ausnahmen, und dann können Sie Ihre Logik mit manuell ausgelösten Ereignissen vom Videoelement testen (zB videoElement.dispatchEvent(new window.Event("loading")); ).

Okay, danke für die Hilfe, ich habe endlich meine Tests behoben. 👍

Dies hat mir beim Einstieg geholfen, aber in meinem Fall möchte ich testen, ob bestimmte Bedingungen die Wiedergabe korrekt beeinflussen. Ich habe Ersatzfunktionen für play und pause und versuche, die Variable paused des Medienelements zu setzen, bekomme aber eine Fehlermeldung, dass es nur einen Getter für diese Variable gibt. Das macht das Verspotten etwas schwierig.

Ich bin etwas neu bei JS. Gibt es eine Möglichkeit, schreibgeschützte Variablen wie diese zu verspotten?

@BenBergman Du

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

Großartig, vielen Dank! Für die Nachwelt sieht mein Getter wie folgt aus, um einen Standardwert zu berücksichtigen:

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

So kannst du es dir noch einfacher machen:

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

und dann ändern Sie einfach mediaTag.paused = true oder mediaTag.paused = false in Ihrem Test.

Der Vorteil dieses Ansatzes besteht darin, dass er typsicher ist, falls Sie TypeScript verwenden. Sie müssen Ihre Scheineigenschaft nicht wie (mediaTag as any).mockPaused = true festlegen.

Noch besser, danke!

Wie kann ich die Videowiedergabe simulieren?
Ich habe das Abspielen und Laden abgebrochen, aber ich habe keine Ahnung, wie ich das Video zum Abspielen bringen soll (oder denke, es läuft, alles was ich brauche, ist, was öfter passiert).
Object.defineProperty(HTMLMediaElement.prototype, "play", { get() { document.getElementsByTagName('video')[0].dispatchEvent(new Event('play')); } });

jsdom unterstützt keine Vorgänge zum Laden oder Abspielen von Medien. Als Workaround können Sie Ihrem Test-Setup einige Stubs hinzufügen:

Danke für die Problemumgehung. Darf ich fragen, warum dies standardmäßig nicht unterstützt wird?

Bisher hat noch niemand einen Video- oder Audioplayer in jsdom implementiert.

Hier ist eine schnelle und schmutzige Implementierung (für Scherz und Vue) der Methoden play und pause , die auch einige der Ereignisse sendet, die ich für einen Test benötigte ( 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'))
}

Und das Beispiel des Tests (beachten Sie, dass wir audio.duration manuell festlegen müssen:

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

Ich überlegte, diese Problemumgehungen zu verwenden, aber anstatt einen Browser wie Spielfunktionen neu zu implementieren, entschied ich mich, puppeteer , dh einen echten Browser für die Tests zu verwenden. Das ist mein Setup:

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"
  }
}

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen