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