Jsdom: рдХреНрдпрд╛ jsdom рд╡реАрдбрд┐рдпреЛ рдФрд░ рдСрдбрд┐рдпреЛ рддрддреНрд╡реЛрдВ рдХрд╛ рд╕рдорд░реНрдерди рдХрд░рддрд╛ рд╣реИ?

рдХреЛ рдирд┐рд░реНрдорд┐рдд 19 рдлрд╝рд░ре░ 2018  ┬╖  14рдЯрд┐рдкреНрдкрдгрд┐рдпрд╛рдБ  ┬╖  рд╕реНрд░реЛрдд: jsdom/jsdom

рдмреБрдирд┐рдпрд╛рджреА рдЬрд╛рдирдХрд╛рд░реА:

  • Node.js рд╕рдВрд╕реНрдХрд░рдг: 8.94
  • jsdom рд╕рдВрд╕реНрдХрд░рдг: 11.6.2

рдЬрд╛рдирдХрд╛рд░реА

рдореИрдВ рдПрдХ рдПрд╕реЗрдЯ-рд▓реЛрдбрд░ рдХреЗ рд▓рд┐рдП рдпреВрдирд┐рдЯ-рдЯреЗрд╕реНрдЯ ( ava рдФрд░ browser-env рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ) рдЪрд▓рд╛рдирд╛ рдЪрд╛рд╣рддрд╛ рд╣реВрдВ рдЬреЛ рдЫрд╡рд┐, рдСрдбрд┐рдпреЛ рдФрд░ рд╡реАрдбрд┐рдпреЛ рдкреНрд░реА-рд▓реЛрдбрд┐рдВрдЧ рдХрд╛ рд╕рдорд░реНрдерди рдХрд░рддрд╛ рд╣реИред рдореИрдВ рдЬрд╛рдирдирд╛ рдЪрд╛рд╣рддрд╛ рдерд╛ рдХрд┐ рдХреНрдпрд╛ jsdom рдСрдбрд┐рдпреЛ рдФрд░ рд╡реАрдбрд┐рдпреЛ рддрддреНрд╡реЛрдВ рдХрд╛ рд╕рдорд░реНрдерди рдХрд░рддрд╛ рд╣реИред рдЬрдм рдореИрдВ рдПрдХ рд╡реАрдбрд┐рдпреЛ рддрддреНрд╡ ( HTMLVideoElement рдЬреЛ рд╕реНрд╡рдпрдВ рдПрдХ HTMLMediaElement ) рдкрд░ video.load() рдмрдирд╛рдиреЗ рдФрд░ рдХреЙрд▓ рдХрд░рдиреЗ рдХрд╛ рдкреНрд░рдпрд╛рд╕ рдХрд░рддрд╛ рд╣реВрдВ, рддреЛ 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 рдЪрд░ рдХреЛ рд╕реЗрдЯ рдХрд░рдиреЗ рдХрд╛ рдкреНрд░рдпрд╛рд╕ рдХрд░рддрд╛ рд╣реВрдВ рд▓реЗрдХрд┐рди рдПрдХ рддреНрд░реБрдЯрд┐ рдорд┐рд▓рддреА рд╣реИ рдХрд┐ рдЙрд╕ рдЪрд░ рдХреЗ рд▓рд┐рдП рдХреЗрд╡рд▓ рдПрдХ рдЧреЗрдЯрд░ рд╣реИред рдпрд╣ рдЗрд╕реЗ рдереЛрдбрд╝рд╛ рдЪреБрдиреМрддреАрдкреВрд░реНрдг рдмрдирд╛рддрд╛ рд╣реИред

рдореИрдВ рдХреБрдЫ рд╣рдж рддрдХ рдЬреЗ рдПрд╕ рдХреЗ рд▓рд┐рдП рдирдпрд╛ рд╣реВрдБред рдХреНрдпрд╛ рдЗрд╕ рддрд░рд╣ рдХреЗрд╡рд▓-рдкрдврд╝рдиреЗ рд╡рд╛рд▓реЗ рдЪрд░ рдХрд╛ рдирдХрд▓ рдХрд░рдиреЗ рдХрд╛ рдХреЛрдИ рддрд░реАрдХрд╛ рд╣реИ?

@ рдмреЗрдирдмрд░реНрдЧрдореИрди рдЖрдк рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ:

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 рдмрджрд▓реЗрдВред

рдЗрд╕ рджреГрд╖реНрдЯрд┐рдХреЛрдг рдХрд╛ рд▓рд╛рдн рдпрд╣ рд╣реИ рдХрд┐ рдпрджрд┐ рдЖрдк рдЯрд╛рдЗрдкрд╕реНрдХреНрд░рд┐рдкреНрдЯ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░ рд░рд╣реЗ рд╣реИрдВ рддреЛ рдпрд╣ рдЯрд╛рдЗрдк рд╕реБрд░рдХреНрд╖рд┐рдд рд╣реИред рдЖрдк рдЕрдкрдиреА рдирдХрд▓реА рд╕рдВрдкрддреНрддрд┐ рдХреЛ рдХрд┐рд╕реА рднреА рддрд░рд╣ (mediaTag as any).mockPaused = true рддрд░рд╣ рд╕реЗрдЯ рдирд╣реАрдВ рдХрд░рдирд╛ рдЪрд╛рд╣рддреЗ рд╣реИрдВред

рдФрд░ рднреА рдмреЗрд╣рддрд░, рдзрдиреНрдпрд╡рд╛рдж!

рдореИрдВ рд╡реАрдбрд┐рдпреЛ рдкреНрд▓реЗ рдХрд╛ рдЕрдиреБрдХрд░рдг рдХреИрд╕реЗ рдХрд░ рд╕рдХрддрд╛ рд╣реВрдВ?
рдореИрдВрдиреЗ рдкреНрд▓реЗ рдФрд░ рд▓реЛрдб рдХреЛ рд╕реНрдЯрдм рдХрд┐рдпрд╛, рд▓реЗрдХрд┐рди рдореБрдЭреЗ рдирд╣реАрдВ рдкрддрд╛ рдХрд┐ рд╡реАрдбрд┐рдпреЛ рдХреЛ рдХреИрд╕реЗ рдЪрд▓рд╛рдирд╛ рд╢реБрд░реВ рдХрд┐рдпрд╛ рдЬрд╛рдП (рдпрд╛ рдЗрд╕реЗ рдкреНрд▓реЗ рдХрд░рдиреЗ рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рд╕реЛрдЪреЗрдВ, рдореБрдЭреЗ рдмрд╕ рдЗрддрдирд╛ рд╣реА рдЪрд╛рд╣рд┐рдП рдХрд┐ рдмрд╛рдж рдореЗрдВ рдХреНрдпрд╛ рд╣реЛрддрд╛ рд╣реИ)ред
Object.defineProperty(HTMLMediaElement.prototype, "play", { get() { document.getElementsByTagName('video')[0].dispatchEvent(new Event('play')); } });

jsdom рдХрд┐рд╕реА рднреА рд▓реЛрдбрд┐рдВрдЧ рдпрд╛ рдкреНрд▓реЗрдмреИрдХ рдореАрдбрд┐рдпрд╛ рд╕рдВрдЪрд╛рд▓рди рдХрд╛ рд╕рдорд░реНрдерди рдирд╣реАрдВ рдХрд░рддрд╛ рд╣реИред рд╡реИрдХрд▓реНрдкрд┐рдХ рд╣рд▓ рдХреЗ рд░реВрдк рдореЗрдВ рдЖрдк рдЕрдкрдиреЗ рдкрд░реАрдХреНрд╖рдг рд╕реЗрдЯрдЕрдк рдореЗрдВ рдХреБрдЫ рд╕реНрдЯрдмреНрд╕ рдЬреЛрдбрд╝ рд╕рдХрддреЗ рд╣реИрдВ:

рдЙрдкрд╛рдп рдХреЗ рд▓рд┐рдП рдзрдиреНрдпрд╡рд╛рджред рдХреНрдпрд╛ рдореИрдВ рдкреВрдЫ рд╕рдХрддрд╛ рд╣реВрдБ рдХрд┐ рдпрд╣ рдбрд┐рдлрд╝реЙрд▓реНрдЯ рд░реВрдк рд╕реЗ рд╕рдорд░реНрдерд┐рдд рдХреНрдпреЛрдВ рдирд╣реАрдВ рд╣реИ, рд╣рд╛рд▓рд╛рдБрдХрд┐?

рдХрд┐рд╕реА рдиреЗ рдЕрднреА рддрдХ jsdom рдореЗрдВ рд╡реАрдбрд┐рдпреЛ рдпрд╛ рдСрдбрд┐рдпреЛ рдкреНрд▓реЗрдпрд░ рд▓рд╛рдЧреВ рдирд╣реАрдВ рдХрд┐рдпрд╛ рд╣реИред

рдпрд╣рд╛рдВ play рдФрд░ pause рд╡рд┐рдзрд┐рдпреЛрдВ рдХрд╛ рдПрдХ рддреНрд╡рд░рд┐рдд рдФрд░ рдЧрдВрджрд╛ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди (рдордЬрд╛рдХ рдФрд░ vue рдХреЗ рд▓рд┐рдП) рд╣реИ рдЬреЛ рдореБрдЭреЗ рдкрд░реАрдХреНрд╖рдг рдХреЗ рд▓рд┐рдП рдЖрд╡рд╢реНрдпрдХ рдХреБрдЫ рдШрдЯрдирд╛рдУрдВ рдХреЛ рднреА рднреЗрдЬрддрд╛ рд╣реИ ( 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>

**рдЧреНрд▓реЛрдмрд▓ рдЯрд┐рдпрд░рдбрд╛рдЙрди.рдЬреЗрдПрд╕**

module.exports = async () => {
    global.server.close();
};
**рд╡реИрд╢реНрд╡рд┐рдХ рд╕реЗрдЯрдЕрдк.рдЬреЗрдПрд╕**
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"
  ]
}
**рдкреИрдХреЗрдЬ.рдЬреЗрд╕рди**
{
  "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 рд░реЗрдЯрд┐рдВрдЧреНрд╕

рд╕рдВрдмрдВрдзрд┐рдд рдореБрджреНрджреЛрдВ

domenic picture domenic  ┬╖  3рдЯрд┐рдкреНрдкрдгрд┐рдпрд╛рдБ

eszthoff picture eszthoff  ┬╖  3рдЯрд┐рдкреНрдкрдгрд┐рдпрд╛рдБ

mitar picture mitar  ┬╖  4рдЯрд┐рдкреНрдкрдгрд┐рдпрд╛рдБ

camelaissani picture camelaissani  ┬╖  4рдЯрд┐рдкреНрдкрдгрд┐рдпрд╛рдБ

cg433n picture cg433n  ┬╖  3рдЯрд┐рдкреНрдкрдгрд┐рдпрд╛рдБ