@@ -356,6 +356,109 @@ async function runTikTokSourceLikeCaptureCheck(context, captureLikeEvent, expect
356356 await page . close ( ) ;
357357}
358358
359+ async function runTikTokIncrementalChatCaptureCheck ( context ) {
360+ const page = await context . newPage ( ) ;
361+
362+ await addTikTokSourceInitScript ( page , true ) ;
363+ await page . route ( 'https://www.tiktok.com/@playwright-chat/live' , async ( route ) => {
364+ await route . fulfill ( {
365+ status : 200 ,
366+ contentType : 'text/html; charset=utf-8' ,
367+ body : '<!DOCTYPE html><html><head><title>TikTok chat fixture</title></head><body><div data-e2e="chat-room"><div data-e2e="chat-message"><span data-e2e="message-owner-name">History</span><div class="Fixture-DivComment">Existing history</div></div></div></body></html>'
368+ } ) ;
369+ } ) ;
370+
371+ await page . goto ( 'https://www.tiktok.com/@playwright-chat/live' , { waitUntil : 'domcontentloaded' } ) ;
372+ await page . addScriptTag ( { path : path . join ( ROOT , 'sources' , 'tiktok.js' ) } ) ;
373+ await page . waitForTimeout ( 4500 ) ;
374+ await page . evaluate ( async ( ) => {
375+ const chatRoom = document . querySelector ( '[data-e2e="chat-room"]' ) ;
376+ const row = document . createElement ( 'div' ) ;
377+ const owner = document . createElement ( 'span' ) ;
378+ const comment = document . createElement ( 'div' ) ;
379+
380+ row . dataset . e2e = 'chat-message' ;
381+ owner . dataset . e2e = 'message-owner-name' ;
382+ owner . textContent = 'Alex' ;
383+ comment . className = 'Fixture-DivComment' ;
384+ comment . textContent = 'Hello from TikTok' ;
385+
386+ chatRoom . appendChild ( row ) ;
387+ await new Promise ( ( resolve ) => setTimeout ( resolve , 50 ) ) ;
388+ row . appendChild ( owner ) ;
389+ row . appendChild ( comment ) ;
390+ } ) ;
391+ await page . waitForTimeout ( 500 ) ;
392+
393+ const chatMessages = await page . evaluate ( ( ) => {
394+ return window . __chromeMessages
395+ . filter ( ( entry ) => entry . message && entry . message . type === 'tiktok' && entry . message . chatmessage )
396+ . map ( ( entry ) => entry . message ) ;
397+ } ) ;
398+
399+ assert ( chatMessages . length === 1 , `TikTok incremental chat emitted ${ chatMessages . length } rows instead of one.` ) ;
400+ assert ( chatMessages [ 0 ] . chatname === 'Alex' , 'TikTok incremental chat lost the author name.' ) ;
401+ assert ( chatMessages [ 0 ] . chatmessage === 'Hello from TikTok' , 'TikTok incremental chat changed the message text.' ) ;
402+ assert ( chatMessages [ 0 ] . event === false , 'TikTok normal chat was incorrectly emitted as an event.' ) ;
403+
404+ await page . evaluate ( ( ) => {
405+ const oldRoom = document . querySelector ( '[data-e2e="chat-room"]' ) ;
406+ const replacementRoom = document . createElement ( 'div' ) ;
407+ replacementRoom . dataset . e2e = 'chat-room' ;
408+ oldRoom . replaceWith ( replacementRoom ) ;
409+ } ) ;
410+ await page . waitForTimeout ( 4500 ) ;
411+ await page . evaluate ( async ( ) => {
412+ const chatRoom = document . querySelector ( '[data-e2e="chat-room"]' ) ;
413+ for ( let index = 0 ; index < 75 ; index += 1 ) {
414+ const row = document . createElement ( 'div' ) ;
415+ const owner = document . createElement ( 'span' ) ;
416+ const comment = document . createElement ( 'div' ) ;
417+ const nestedMutation = document . createElement ( 'span' ) ;
418+
419+ row . dataset . e2e = 'chat-message' ;
420+ owner . dataset . e2e = 'message-owner-name' ;
421+ owner . textContent = `Viewer ${ index } ` ;
422+ comment . className = 'Fixture-DivComment' ;
423+ comment . textContent = `Soak message ${ index } ` ;
424+
425+ chatRoom . appendChild ( row ) ;
426+ await new Promise ( ( resolve ) => setTimeout ( resolve , 15 ) ) ;
427+ row . appendChild ( owner ) ;
428+ row . appendChild ( comment ) ;
429+ comment . appendChild ( nestedMutation ) ;
430+ await new Promise ( ( resolve ) => setTimeout ( resolve , 10 ) ) ;
431+
432+ if ( chatRoom . children . length > 30 ) {
433+ chatRoom . children [ 0 ] . remove ( ) ;
434+ }
435+ }
436+ } ) ;
437+ await page . waitForFunction ( ( ) => {
438+ return window . __chromeMessages
439+ . filter ( ( entry ) => entry . message && entry . message . type === 'tiktok' && entry . message . chatmessage )
440+ . length >= 76 ;
441+ } , null , { timeout : 10000 } ) . catch ( ( ) => { } ) ;
442+
443+ const soakedChatMessages = await page . evaluate ( ( ) => {
444+ return window . __chromeMessages
445+ . filter ( ( entry ) => entry . message && entry . message . type === 'tiktok' && entry . message . chatmessage )
446+ . map ( ( entry ) => entry . message ) ;
447+ } ) ;
448+ const soakedKeys = new Set ( soakedChatMessages . map ( ( message ) => `${ message . chatname } \n${ message . chatmessage } ` ) ) ;
449+ const expectedSoakKeys = new Set ( [ 'Alex\nHello from TikTok' ] ) ;
450+ for ( let index = 0 ; index < 75 ; index += 1 ) {
451+ expectedSoakKeys . add ( `Viewer ${ index } \nSoak message ${ index } ` ) ;
452+ }
453+ const missingSoakKeys = Array . from ( expectedSoakKeys ) . filter ( ( key ) => ! soakedKeys . has ( key ) ) ;
454+
455+ assert ( soakedChatMessages . length === 76 , `TikTok soak emitted ${ soakedChatMessages . length } rows instead of 76; missing: ${ missingSoakKeys . join ( ', ' ) || 'none' } .` ) ;
456+ assert ( soakedKeys . size === 76 , 'TikTok soak emitted duplicate chat rows.' ) ;
457+ assert ( soakedChatMessages . every ( ( message ) => message . event === false ) , 'TikTok soak misclassified normal chat as events.' ) ;
458+
459+ await page . close ( ) ;
460+ }
461+
359462( async ( ) => {
360463 const server = await startStaticServer ( { root : ROOT , host : HOST , port : PORT } ) ;
361464 const blockedExternalRequests = [ ] ;
@@ -387,6 +490,15 @@ async function runTikTokSourceLikeCaptureCheck(context, captureLikeEvent, expect
387490 await route . continue ( ) ;
388491 } ) ;
389492
493+ if ( process . argv . includes ( '--tiktok-only' ) ) {
494+ await runTikTokSourceLikeCaptureCheck ( context , false , 'reactions' ) ;
495+ await runTikTokSourceLikeCaptureCheck ( context , true , '' ) ;
496+ await runTikTokIncrementalChatCaptureCheck ( context ) ;
497+ await browser . close ( ) ;
498+ console . log ( 'TikTok DOM soak passed.' ) ;
499+ return ;
500+ }
501+
390502 const popupPage = await context . newPage ( ) ;
391503 await addPopupInitScript ( popupPage , `http://${ HOST } :${ PORT } ` ) ;
392504 await popupPage . goto ( `http://${ HOST } :${ PORT } /popup.html` , { waitUntil : 'domcontentloaded' } ) ;
@@ -686,6 +798,7 @@ async function runTikTokSourceLikeCaptureCheck(context, captureLikeEvent, expect
686798
687799 await runTikTokSourceLikeCaptureCheck ( context , false , 'reactions' ) ;
688800 await runTikTokSourceLikeCaptureCheck ( context , true , '' ) ;
801+ await runTikTokIncrementalChatCaptureCheck ( context ) ;
689802
690803 await browser . close ( ) ;
691804
0 commit comments