]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server/tests/api/live/live.ts
Refactor requests
[github/Chocobozzz/PeerTube.git] / server / tests / api / live / live.ts
CommitLineData
af4ae64f
C
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import 'mocha'
4import * as chai from 'chai'
ca5c612b
C
5import { join } from 'path'
6import { ffprobePromise, getVideoStreamFromFile } from '@server/helpers/ffprobe-utils'
c0e8b12e 7import { HttpStatusCode } from '@shared/models'
af4ae64f 8import {
bd54ad19 9 checkLiveCleanup,
5c0904fc 10 checkLiveSegmentHash,
bd54ad19 11 checkResolutionsInMasterPlaylist,
af4ae64f 12 cleanupTests,
af4ae64f 13 doubleFollow,
254d3579 14 createMultipleServers,
5c0904fc 15 killallServers,
4f219914 16 LiveCommand,
af4ae64f 17 makeRawRequest,
68e70a74 18 sendRTMPStream,
254d3579 19 PeerTubeServer,
af4ae64f
C
20 setAccessTokensToServers,
21 setDefaultVideoChannel,
bd54ad19 22 stopFfmpeg,
97969c4e 23 testFfmpegStreamError,
af4ae64f 24 testImage,
e4bf7856 25 wait,
bd54ad19 26 waitJobs,
4f219914 27 waitUntilLivePublishedOnAllServers
d23dd9fb
C
28} from '@shared/extra-utils'
29import { LiveVideo, LiveVideoCreate, VideoDetails, VideoPrivacy, VideoState, VideoStreamingPlaylistType } from '@shared/models'
af4ae64f
C
30
31const expect = chai.expect
32
33describe('Test live', function () {
254d3579 34 let servers: PeerTubeServer[] = []
4f219914 35 let commands: LiveCommand[]
af4ae64f
C
36
37 before(async function () {
38 this.timeout(120000)
39
254d3579 40 servers = await createMultipleServers(2)
af4ae64f
C
41
42 // Get the access tokens
43 await setAccessTokensToServers(servers)
44 await setDefaultVideoChannel(servers)
45
89d241a7 46 await servers[0].config.updateCustomSubConfig({
65e6e260
C
47 newConfig: {
48 live: {
49 enabled: true,
50 allowReplay: true,
51 transcoding: {
52 enabled: false
53 }
68e70a74 54 }
af4ae64f
C
55 }
56 })
57
58 // Server 1 and server 2 follow each other
59 await doubleFollow(servers[0], servers[1])
4f219914 60
89d241a7 61 commands = servers.map(s => s.live)
af4ae64f
C
62 })
63
64 describe('Live creation, update and delete', function () {
68e70a74 65 let liveVideoUUID: string
af4ae64f
C
66
67 it('Should create a live with the appropriate parameters', async function () {
68 this.timeout(20000)
69
70 const attributes: LiveVideoCreate = {
71 category: 1,
72 licence: 2,
73 language: 'fr',
74 description: 'super live description',
75 support: 'support field',
89d241a7 76 channelId: servers[0].store.channel.id,
af4ae64f
C
77 nsfw: false,
78 waitTranscoding: false,
79 name: 'my super live',
80 tags: [ 'tag1', 'tag2' ],
81 commentsEnabled: false,
82 downloadEnabled: false,
83 saveReplay: true,
84 privacy: VideoPrivacy.PUBLIC,
85 previewfile: 'video_short1-preview.webm.jpg',
86 thumbnailfile: 'video_short1.webm.jpg'
87 }
88
04aed767 89 const live = await commands[0].create({ fields: attributes })
4f219914 90 liveVideoUUID = live.uuid
af4ae64f
C
91
92 await waitJobs(servers)
93
94 for (const server of servers) {
89d241a7 95 const video = await server.videos.get({ id: liveVideoUUID })
af4ae64f
C
96
97 expect(video.category.id).to.equal(1)
98 expect(video.licence.id).to.equal(2)
99 expect(video.language.id).to.equal('fr')
100 expect(video.description).to.equal('super live description')
101 expect(video.support).to.equal('support field')
102
89d241a7
C
103 expect(video.channel.name).to.equal(servers[0].store.channel.name)
104 expect(video.channel.host).to.equal(servers[0].store.channel.host)
af4ae64f 105
1ab60243
C
106 expect(video.isLive).to.be.true
107
af4ae64f
C
108 expect(video.nsfw).to.be.false
109 expect(video.waitTranscoding).to.be.false
110 expect(video.name).to.equal('my super live')
111 expect(video.tags).to.deep.equal([ 'tag1', 'tag2' ])
112 expect(video.commentsEnabled).to.be.false
113 expect(video.downloadEnabled).to.be.false
114 expect(video.privacy.id).to.equal(VideoPrivacy.PUBLIC)
115
116 await testImage(server.url, 'video_short1-preview.webm', video.previewPath)
117 await testImage(server.url, 'video_short1.webm', video.thumbnailPath)
118
89d241a7 119 const live = await server.live.get({ videoId: liveVideoUUID })
af4ae64f
C
120
121 if (server.url === servers[0].url) {
c655c9ef 122 expect(live.rtmpUrl).to.equal('rtmp://' + server.hostname + ':' + servers[0].rtmpPort + '/live')
af4ae64f
C
123 expect(live.streamKey).to.not.be.empty
124 } else {
125 expect(live.rtmpUrl).to.be.null
126 expect(live.streamKey).to.be.null
127 }
128
129 expect(live.saveReplay).to.be.true
130 }
131 })
132
133 it('Should have a default preview and thumbnail', async function () {
134 this.timeout(20000)
135
136 const attributes: LiveVideoCreate = {
137 name: 'default live thumbnail',
89d241a7 138 channelId: servers[0].store.channel.id,
af4ae64f
C
139 privacy: VideoPrivacy.UNLISTED,
140 nsfw: true
141 }
142
04aed767 143 const live = await commands[0].create({ fields: attributes })
4f219914 144 const videoId = live.uuid
af4ae64f
C
145
146 await waitJobs(servers)
147
148 for (const server of servers) {
89d241a7 149 const video = await server.videos.get({ id: videoId })
af4ae64f
C
150 expect(video.privacy.id).to.equal(VideoPrivacy.UNLISTED)
151 expect(video.nsfw).to.be.true
152
f2eb23cd
RK
153 await makeRawRequest(server.url + video.thumbnailPath, HttpStatusCode.OK_200)
154 await makeRawRequest(server.url + video.previewPath, HttpStatusCode.OK_200)
af4ae64f
C
155 }
156 })
157
158 it('Should not have the live listed since nobody streams into', async function () {
159 for (const server of servers) {
89d241a7 160 const { total, data } = await server.videos.list()
af4ae64f 161
d23dd9fb
C
162 expect(total).to.equal(0)
163 expect(data).to.have.lengthOf(0)
af4ae64f
C
164 }
165 })
166
167 it('Should not be able to update a live of another server', async function () {
04aed767 168 await commands[1].update({ videoId: liveVideoUUID, fields: { saveReplay: false }, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
af4ae64f
C
169 })
170
171 it('Should update the live', async function () {
172 this.timeout(10000)
173
04aed767 174 await commands[0].update({ videoId: liveVideoUUID, fields: { saveReplay: false } })
af4ae64f
C
175 await waitJobs(servers)
176 })
177
178 it('Have the live updated', async function () {
179 for (const server of servers) {
89d241a7 180 const live = await server.live.get({ videoId: liveVideoUUID })
af4ae64f
C
181
182 if (server.url === servers[0].url) {
c655c9ef 183 expect(live.rtmpUrl).to.equal('rtmp://' + server.hostname + ':' + servers[0].rtmpPort + '/live')
af4ae64f
C
184 expect(live.streamKey).to.not.be.empty
185 } else {
186 expect(live.rtmpUrl).to.be.null
187 expect(live.streamKey).to.be.null
188 }
189
190 expect(live.saveReplay).to.be.false
191 }
192 })
193
194 it('Delete the live', async function () {
195 this.timeout(10000)
196
89d241a7 197 await servers[0].videos.remove({ id: liveVideoUUID })
af4ae64f
C
198 await waitJobs(servers)
199 })
200
201 it('Should have the live deleted', async function () {
202 for (const server of servers) {
89d241a7
C
203 await server.videos.get({ id: liveVideoUUID, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
204 await server.live.get({ videoId: liveVideoUUID, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
af4ae64f
C
205 }
206 })
207 })
208
1fd61899 209 describe('Live filters', function () {
4f219914 210 let ffmpegCommand: any
1fd61899
C
211 let liveVideoId: string
212 let vodVideoId: string
213
214 before(async function () {
215 this.timeout(120000)
216
89d241a7 217 vodVideoId = (await servers[0].videos.quickUpload({ name: 'vod video' })).uuid
1fd61899 218
89d241a7 219 const liveOptions = { name: 'live', privacy: VideoPrivacy.PUBLIC, channelId: servers[0].store.channel.id }
04aed767 220 const live = await commands[0].create({ fields: liveOptions })
4f219914 221 liveVideoId = live.uuid
1fd61899 222
89d241a7 223 ffmpegCommand = await servers[0].live.sendRTMPStreamInVideo({ videoId: liveVideoId })
8ebf2a5d 224 await waitUntilLivePublishedOnAllServers(servers, liveVideoId)
1fd61899
C
225 await waitJobs(servers)
226 })
227
228 it('Should only display lives', async function () {
89d241a7 229 const { data, total } = await servers[0].videos.list({ isLive: true })
1fd61899 230
d23dd9fb
C
231 expect(total).to.equal(1)
232 expect(data).to.have.lengthOf(1)
233 expect(data[0].name).to.equal('live')
1fd61899
C
234 })
235
236 it('Should not display lives', async function () {
89d241a7 237 const { data, total } = await servers[0].videos.list({ isLive: false })
1fd61899 238
d23dd9fb
C
239 expect(total).to.equal(1)
240 expect(data).to.have.lengthOf(1)
241 expect(data[0].name).to.equal('vod video')
1fd61899
C
242 })
243
244 it('Should display my lives', async function () {
245 this.timeout(60000)
246
4f219914 247 await stopFfmpeg(ffmpegCommand)
1fd61899
C
248 await waitJobs(servers)
249
89d241a7 250 const { data } = await servers[0].videos.listMyVideos({ isLive: true })
1fd61899 251
d23dd9fb 252 const result = data.every(v => v.isLive)
1fd61899
C
253 expect(result).to.be.true
254 })
255
256 it('Should not display my lives', async function () {
89d241a7 257 const { data } = await servers[0].videos.listMyVideos({ isLive: false })
1fd61899 258
d23dd9fb 259 const result = data.every(v => !v.isLive)
1fd61899
C
260 expect(result).to.be.true
261 })
262
263 after(async function () {
89d241a7
C
264 await servers[0].videos.remove({ id: vodVideoId })
265 await servers[0].videos.remove({ id: liveVideoId })
1fd61899
C
266 })
267 })
268
68e70a74
C
269 describe('Stream checks', function () {
270 let liveVideo: LiveVideo & VideoDetails
271 let rtmpUrl: string
272
273 before(function () {
c655c9ef 274 rtmpUrl = 'rtmp://' + servers[0].hostname + ':' + servers[0].rtmpPort + ''
68e70a74 275 })
af4ae64f 276
68e70a74 277 async function createLiveWrapper () {
97969c4e
C
278 const liveAttributes = {
279 name: 'user live',
89d241a7 280 channelId: servers[0].store.channel.id,
97969c4e 281 privacy: VideoPrivacy.PUBLIC,
68e70a74 282 saveReplay: false
97969c4e
C
283 }
284
04aed767 285 const { uuid } = await commands[0].create({ fields: liveAttributes })
97969c4e 286
04aed767 287 const live = await commands[0].get({ videoId: uuid })
89d241a7 288 const video = await servers[0].videos.get({ id: uuid })
97969c4e 289
d23dd9fb 290 return Object.assign(video, live)
68e70a74 291 }
97969c4e 292
68e70a74 293 it('Should not allow a stream without the appropriate path', async function () {
2df6f943 294 this.timeout(60000)
97969c4e 295
68e70a74 296 liveVideo = await createLiveWrapper()
97969c4e 297
68e70a74
C
298 const command = sendRTMPStream(rtmpUrl + '/bad-live', liveVideo.streamKey)
299 await testFfmpegStreamError(command, true)
af4ae64f
C
300 })
301
68e70a74 302 it('Should not allow a stream without the appropriate stream key', async function () {
2df6f943 303 this.timeout(60000)
97969c4e 304
68e70a74
C
305 const command = sendRTMPStream(rtmpUrl + '/live', 'bad-stream-key')
306 await testFfmpegStreamError(command, true)
97969c4e
C
307 })
308
68e70a74 309 it('Should succeed with the correct params', async function () {
2df6f943 310 this.timeout(60000)
97969c4e 311
68e70a74
C
312 const command = sendRTMPStream(rtmpUrl + '/live', liveVideo.streamKey)
313 await testFfmpegStreamError(command, false)
af4ae64f
C
314 })
315
1ab60243
C
316 it('Should list this live now someone stream into it', async function () {
317 for (const server of servers) {
89d241a7 318 const { total, data } = await server.videos.list()
1ab60243 319
d23dd9fb
C
320 expect(total).to.equal(1)
321 expect(data).to.have.lengthOf(1)
1ab60243 322
d23dd9fb 323 const video = data[0]
1ab60243
C
324 expect(video.name).to.equal('user live')
325 expect(video.isLive).to.be.true
326 }
327 })
328
68e70a74 329 it('Should not allow a stream on a live that was blacklisted', async function () {
2df6f943 330 this.timeout(60000)
97969c4e 331
68e70a74 332 liveVideo = await createLiveWrapper()
af4ae64f 333
89d241a7 334 await servers[0].blacklist.add({ videoId: liveVideo.uuid })
af4ae64f 335
68e70a74
C
336 const command = sendRTMPStream(rtmpUrl + '/live', liveVideo.streamKey)
337 await testFfmpegStreamError(command, true)
af4ae64f
C
338 })
339
68e70a74 340 it('Should not allow a stream on a live that was deleted', async function () {
2df6f943 341 this.timeout(60000)
af4ae64f 342
68e70a74 343 liveVideo = await createLiveWrapper()
af4ae64f 344
89d241a7 345 await servers[0].videos.remove({ id: liveVideo.uuid })
af4ae64f 346
68e70a74
C
347 const command = sendRTMPStream(rtmpUrl + '/live', liveVideo.streamKey)
348 await testFfmpegStreamError(command, true)
af4ae64f
C
349 })
350 })
351
352 describe('Live transcoding', function () {
bd54ad19
C
353 let liveVideoId: string
354
355 async function createLiveWrapper (saveReplay: boolean) {
356 const liveAttributes = {
357 name: 'live video',
89d241a7 358 channelId: servers[0].store.channel.id,
bd54ad19
C
359 privacy: VideoPrivacy.PUBLIC,
360 saveReplay
361 }
362
04aed767 363 const { uuid } = await commands[0].create({ fields: liveAttributes })
4f219914 364 return uuid
bd54ad19
C
365 }
366
367 async function testVideoResolutions (liveVideoId: string, resolutions: number[]) {
368 for (const server of servers) {
89d241a7 369 const { data } = await server.videos.list()
d23dd9fb 370 expect(data.find(v => v.uuid === liveVideoId)).to.exist
bd54ad19 371
89d241a7 372 const video = await server.videos.get({ id: liveVideoId })
bd54ad19
C
373
374 expect(video.streamingPlaylists).to.have.lengthOf(1)
375
376 const hlsPlaylist = video.streamingPlaylists.find(s => s.type === VideoStreamingPlaylistType.HLS)
377 expect(hlsPlaylist).to.exist
378
379 // Only finite files are displayed
380 expect(hlsPlaylist.files).to.have.lengthOf(0)
381
57f879a5 382 await checkResolutionsInMasterPlaylist({ server, playlistUrl: hlsPlaylist.playlistUrl, resolutions })
5c0904fc
C
383
384 for (let i = 0; i < resolutions.length; i++) {
a800dbf3 385 const segmentNum = 3
0d8de275 386 const segmentName = `${i}-00000${segmentNum}.ts`
04aed767 387 await commands[0].waitUntilSegmentGeneration({ videoUUID: video.uuid, resolution: i, segment: segmentNum })
5c0904fc 388
89d241a7 389 const subPlaylist = await servers[0].streamingPlaylists.get({
57f879a5
C
390 url: `${servers[0].url}/static/streaming-playlists/hls/${video.uuid}/${i}.m3u8`
391 })
5c0904fc
C
392
393 expect(subPlaylist).to.contain(segmentName)
394
395 const baseUrlAndPath = servers[0].url + '/static/streaming-playlists/hls'
57f879a5
C
396 await checkLiveSegmentHash({
397 server,
398 baseUrlSegment: baseUrlAndPath,
399 videoUUID: video.uuid,
400 segmentName,
401 hlsPlaylist
402 })
5c0904fc 403 }
bd54ad19
C
404 }
405 }
406
407 function updateConf (resolutions: number[]) {
89d241a7 408 return servers[0].config.updateCustomSubConfig({
65e6e260
C
409 newConfig: {
410 live: {
bd54ad19 411 enabled: true,
65e6e260
C
412 allowReplay: true,
413 maxDuration: -1,
414 transcoding: {
415 enabled: true,
416 resolutions: {
417 '240p': resolutions.includes(240),
418 '360p': resolutions.includes(360),
419 '480p': resolutions.includes(480),
420 '720p': resolutions.includes(720),
421 '1080p': resolutions.includes(1080),
422 '2160p': resolutions.includes(2160)
423 }
bd54ad19
C
424 }
425 }
426 }
427 })
428 }
429
430 before(async function () {
431 await updateConf([])
432 })
af4ae64f
C
433
434 it('Should enable transcoding without additional resolutions', async function () {
dbb15e37 435 this.timeout(60000)
bd54ad19
C
436
437 liveVideoId = await createLiveWrapper(false)
438
4f219914 439 const ffmpegCommand = await commands[0].sendRTMPStreamInVideo({ videoId: liveVideoId })
8ebf2a5d 440 await waitUntilLivePublishedOnAllServers(servers, liveVideoId)
bd54ad19 441 await waitJobs(servers)
af4ae64f 442
bd54ad19
C
443 await testVideoResolutions(liveVideoId, [ 720 ])
444
4f219914 445 await stopFfmpeg(ffmpegCommand)
af4ae64f
C
446 })
447
448 it('Should enable transcoding with some resolutions', async function () {
dbb15e37 449 this.timeout(60000)
bd54ad19
C
450
451 const resolutions = [ 240, 480 ]
452 await updateConf(resolutions)
453 liveVideoId = await createLiveWrapper(false)
454
4f219914 455 const ffmpegCommand = await commands[0].sendRTMPStreamInVideo({ videoId: liveVideoId })
8ebf2a5d 456 await waitUntilLivePublishedOnAllServers(servers, liveVideoId)
bd54ad19
C
457 await waitJobs(servers)
458
459 await testVideoResolutions(liveVideoId, resolutions)
460
4f219914 461 await stopFfmpeg(ffmpegCommand)
af4ae64f
C
462 })
463
464 it('Should enable transcoding with some resolutions and correctly save them', async function () {
f42c2152 465 this.timeout(200000)
bd54ad19
C
466
467 const resolutions = [ 240, 360, 720 ]
ca5c612b 468
bd54ad19
C
469 await updateConf(resolutions)
470 liveVideoId = await createLiveWrapper(true)
471
4f219914 472 const ffmpegCommand = await commands[0].sendRTMPStreamInVideo({ videoId: liveVideoId, fixtureName: 'video_short2.webm' })
8ebf2a5d 473 await waitUntilLivePublishedOnAllServers(servers, liveVideoId)
bd54ad19
C
474 await waitJobs(servers)
475
476 await testVideoResolutions(liveVideoId, resolutions)
477
4f219914 478 await stopFfmpeg(ffmpegCommand)
04aed767 479 await commands[0].waitUntilEnded({ videoId: liveVideoId })
34caef7f 480
bd54ad19
C
481 await waitJobs(servers)
482
8ebf2a5d 483 await waitUntilLivePublishedOnAllServers(servers, liveVideoId)
e0783718 484
ca5c612b 485 const bitrateLimits = {
4ef9ea48 486 720: 5000 * 1000, // 60FPS
6b67897e
C
487 360: 1100 * 1000,
488 240: 600 * 1000
ca5c612b
C
489 }
490
bd54ad19 491 for (const server of servers) {
89d241a7 492 const video = await server.videos.get({ id: liveVideoId })
bd54ad19 493
e0783718 494 expect(video.state.id).to.equal(VideoState.PUBLISHED)
bd54ad19
C
495 expect(video.duration).to.be.greaterThan(1)
496 expect(video.files).to.have.lengthOf(0)
497
498 const hlsPlaylist = video.streamingPlaylists.find(s => s.type === VideoStreamingPlaylistType.HLS)
f2eb23cd
RK
499 await makeRawRequest(hlsPlaylist.playlistUrl, HttpStatusCode.OK_200)
500 await makeRawRequest(hlsPlaylist.segmentsSha256Url, HttpStatusCode.OK_200)
bd54ad19
C
501
502 expect(hlsPlaylist.files).to.have.lengthOf(resolutions.length)
503
504 for (const resolution of resolutions) {
505 const file = hlsPlaylist.files.find(f => f.resolution.id === resolution)
506
507 expect(file).to.exist
bd54ad19
C
508 expect(file.size).to.be.greaterThan(1)
509
884d2c39
C
510 if (resolution >= 720) {
511 expect(file.fps).to.be.approximately(60, 2)
512 } else {
513 expect(file.fps).to.be.approximately(30, 2)
514 }
515
ca5c612b 516 const filename = `${video.uuid}-${resolution}-fragmented.mp4`
89d241a7 517 const segmentPath = servers[0].servers.buildDirectory(join('streaming-playlists', 'hls', video.uuid, filename))
ca5c612b
C
518
519 const probe = await ffprobePromise(segmentPath)
520 const videoStream = await getVideoStreamFromFile(segmentPath, probe)
6b67897e 521
ca5c612b
C
522 expect(probe.format.bit_rate).to.be.below(bitrateLimits[videoStream.height])
523
f2eb23cd
RK
524 await makeRawRequest(file.torrentUrl, HttpStatusCode.OK_200)
525 await makeRawRequest(file.fileUrl, HttpStatusCode.OK_200)
bd54ad19
C
526 }
527 }
af4ae64f
C
528 })
529
530 it('Should correctly have cleaned up the live files', async function () {
bd54ad19
C
531 this.timeout(30000)
532
533 await checkLiveCleanup(servers[0], liveVideoId, [ 240, 360, 720 ])
af4ae64f
C
534 })
535 })
536
5c0904fc
C
537 describe('After a server restart', function () {
538 let liveVideoId: string
539 let liveVideoReplayId: string
540
541 async function createLiveWrapper (saveReplay: boolean) {
542 const liveAttributes = {
543 name: 'live video',
89d241a7 544 channelId: servers[0].store.channel.id,
5c0904fc
C
545 privacy: VideoPrivacy.PUBLIC,
546 saveReplay
547 }
548
04aed767 549 const { uuid } = await commands[0].create({ fields: liveAttributes })
4f219914 550 return uuid
5c0904fc
C
551 }
552
553 before(async function () {
0d8de275 554 this.timeout(120000)
5c0904fc
C
555
556 liveVideoId = await createLiveWrapper(false)
557 liveVideoReplayId = await createLiveWrapper(true)
558
559 await Promise.all([
4f219914
C
560 commands[0].sendRTMPStreamInVideo({ videoId: liveVideoId }),
561 commands[0].sendRTMPStreamInVideo({ videoId: liveVideoReplayId })
5c0904fc
C
562 ])
563
564 await Promise.all([
04aed767
C
565 commands[0].waitUntilPublished({ videoId: liveVideoId }),
566 commands[0].waitUntilPublished({ videoId: liveVideoReplayId })
5c0904fc
C
567 ])
568
04aed767
C
569 await commands[0].waitUntilSegmentGeneration({ videoUUID: liveVideoId, resolution: 0, segment: 2 })
570 await commands[0].waitUntilSegmentGeneration({ videoUUID: liveVideoReplayId, resolution: 0, segment: 2 })
0d8de275 571
9293139f 572 await killallServers([ servers[0] ])
254d3579 573 await servers[0].run()
5c0904fc
C
574
575 await wait(5000)
576 })
577
578 it('Should cleanup lives', async function () {
579 this.timeout(60000)
580
04aed767 581 await commands[0].waitUntilEnded({ videoId: liveVideoId })
5c0904fc
C
582 })
583
584 it('Should save a live replay', async function () {
0d8de275 585 this.timeout(120000)
5c0904fc 586
04aed767 587 await commands[0].waitUntilPublished({ videoId: liveVideoReplayId })
5c0904fc
C
588 })
589 })
590
af4ae64f
C
591 after(async function () {
592 await cleanupTests(servers)
593 })
594})