Jsdom: هل يدعم jsdom عناصر الفيديو والصوت؟

تم إنشاؤها على ١٩ فبراير ٢٠١٨  ·  14تعليقات  ·  مصدر: jsdom/jsdom

معلومات أساسية:

  • إصدار Node.js: 8.94.1
  • إصدار jsdom

معلومة

أرغب في إجراء اختبارات الوحدة (باستخدام ava و browser-env ) لمحمل الأصول الذي يدعم التحميل المسبق للصور والصوت والفيديو. أردت أن أعرف ما إذا كان jsdom يدعم عناصر الصوت والفيديو. عندما أحاول إنشاء واستدعاء video.load() على عنصر فيديو ( HTMLVideoElement الذي هو في حد ذاته HTMLMediaElement ) ، يُرجع jsdom هذا الخطأ:

Error: Not implemented: HTMLMediaElement.prototype.load

أفترض أنه لا يوجد دعم لعنصر الفيديو والصوت. لم أجد شيئًا عن دعم الفيديو والصوت في jsdom ، فربما يكون مفقودًا؟

التعليق الأكثر فائدة

لا يدعم jsdom أي عمليات تحميل أو تشغيل للوسائط. كحل بديل ، يمكنك إضافة بعض التنويهات في إعداد الاختبار الخاص بك:

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

ال 14 كومينتر

لا يدعم jsdom أي عمليات تحميل أو تشغيل للوسائط. كحل بديل ، يمكنك إضافة بعض التنويهات في إعداد الاختبار الخاص بك:

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

هذا ما أحتاجه هنا: طريقة لمحاكاة تحميل / جلب HTMLMediaElements . من خلال القيام بذلك ، لن يتم تحميل الصوت والفيديو مسبقًا كما هو الحال في متصفح حقيقي ، أليس كذلك؟

عادة لا يكون الجلب مطلوبًا لمثل هذه الاختبارات. تعمل هذه التنويهات على منع استثناءات jsdom ، ومن ثم ستتمكن من اختبار منطقك باستخدام الأحداث المرسلة يدويًا من عنصر الفيديو (على سبيل المثال videoElement.dispatchEvent(new window.Event("loading")); ).

حسنًا ، شكرًا على المساعدة ، لقد أصلحت أخيرًا اختباراتي. 👍

لقد ساعدني هذا في البدء ، ولكن في حالتي أريد اختبار ما إذا كانت ظروف معينة تؤثر على التشغيل بشكل صحيح. لقد قمت باستبدال الدالتين play و pause وحاولت تعيين المتغير paused لعنصر الوسائط ، لكنني تلقيت خطأ مفاده أنه لا يوجد سوى أداة الحصول على هذا المتغير. هذا يجعل الاستهزاء بها صعبًا بعض الشيء.

أنا جديد إلى حد ما على JS. هل هناك طريقة للسخرية من متغيرات القراءة فقط مثل هذه؟

BenBergman يمكنك فعل:

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

ممتاز شكرا لك! للأجيال القادمة ، يبدو أن جالبتي بهذا الشكل لحساب قيمة افتراضية:

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

يمكنك تبسيط الأمر بهذه الطريقة:

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

ثم قم بتغيير mediaTag.paused = true أو mediaTag.paused = false في اختبارك.

فائدة هذا الأسلوب هو أنه من النوع الآمن في حال كنت تستخدم TypeScript. لا يمكنك تعيين الخاصية الوهمية الخاصة بك بطريقة ما مثل (mediaTag as any).mockPaused = true .

بل أفضل ، شكرا!

كيف يمكنني محاكاة تشغيل الفيديو؟
لقد توقفت عن التشغيل والتحميل ولكن ليس لدي أي فكرة عن كيفية بدء تشغيل الفيديو (أو أعتقد أنه يتم تشغيله ، كل ما أحتاجه هو ما يحدث بعد ذلك).
Object.defineProperty(HTMLMediaElement.prototype, "play", { get() { document.getElementsByTagName('video')[0].dispatchEvent(new Event('play')); } });

لا يدعم jsdom أي عمليات تحميل أو تشغيل وسائط. كحل بديل ، يمكنك إضافة بعض التنويهات في إعداد الاختبار الخاص بك:

شكرا على الحل. هل لي أن أسأل لماذا هذا غير مدعوم افتراضيًا ، مع ذلك؟

لم يقم أحد بتطبيق مشغل فيديو أو صوت في jsdom حتى الآن.

في ما يلي تنفيذ سريع وقذر (من أجل الدعابة والنظرة) لطريقتين play و pause والتي ترسل أيضًا بعض الأحداث التي أحتاجها للاختبار ( 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'))
}

ومثال الاختبار (لاحظ أنه يتعين علينا تعيين 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)
  })

فكرت في استخدام هذه الحلول ، ولكن بدلاً من إعادة تنفيذ متصفح مثل ميزات التشغيل ، قررت استخدام puppeteer ، أي للحصول على متصفح حقيقي لإجراء الاختبار. هذا هو الإعداد الخاص بي:

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

هل كانت هذه الصفحة مفيدة؟
0 / 5 - 0 التقييمات