@@ -16,6 +16,7 @@ import {
1616 traceResultIsSuccess ,
1717} from '../src/trace-processing/parse.js' ;
1818
19+ import { serverHooks } from './server.js' ;
1920import { loadTraceAsBuffer } from './trace-processing/fixtures/load.js' ;
2021import {
2122 getImageContent ,
@@ -63,8 +64,8 @@ describe('McpResponse', () => {
6364 await withMcpContext ( async ( response , context ) => {
6465 const page = context . getSelectedPage ( ) ;
6566 await page . setContent (
66- html `< button > Click me</ button
67- > < input
67+ html `< button > Click me</ button >
68+ < input
6869 type ="text "
6970 value ="Input "
7071 /> ` ,
@@ -145,6 +146,111 @@ describe('McpResponse', () => {
145146 }
146147 } ) ;
147148
149+ it ( 'preserves mapping ids across multiple snapshots' , async ( ) => {
150+ await withMcpContext ( async ( response , context ) => {
151+ const page = context . getSelectedPage ( ) ;
152+ await page . setContent ( html `
153+ < div >
154+ < button id ="btn1 "> Button 1</ button >
155+ < span id ="span1 "> Span 1</ span >
156+ </ div >
157+ ` ) ;
158+ response . includeSnapshot ( ) ;
159+ // First snapshot
160+ const res1 = await response . handle ( 'test' , context ) ;
161+ const text1 = getTextContent ( res1 . content [ 0 ] ) ;
162+ const btn1IdMatch = text1 . match ( / u i d = ( \S + ) .* B u t t o n 1 / ) ;
163+ const span1IdMatch = text1 . match ( / u i d = ( \S + ) .* S p a n 1 / ) ;
164+
165+ assert . ok ( btn1IdMatch , 'Button 1 ID not found in first snapshot' ) ;
166+ assert . ok ( span1IdMatch , 'Span 1 ID not found in first snapshot' ) ;
167+
168+ const btn1Id = btn1IdMatch [ 1 ] ;
169+ const span1Id = span1IdMatch [ 1 ] ;
170+
171+ // Modify page: add a new element before the others to potentially shift indices if not stable
172+ await page . evaluate ( ( ) => {
173+ const newBtn = document . createElement ( 'button' ) ;
174+ newBtn . textContent = 'Button 2' ;
175+ document . body . prepend ( newBtn ) ;
176+ } ) ;
177+
178+ // Second snapshot
179+ const res2 = await response . handle ( 'test' , context ) ;
180+ const text2 = getTextContent ( res2 . content [ 0 ] ) ;
181+
182+ const btn1IdMatch2 = text2 . match ( / u i d = ( \S + ) .* B u t t o n 1 / ) ;
183+ const span1IdMatch2 = text2 . match ( / u i d = ( \S + ) .* S p a n 1 / ) ;
184+ const btn2IdMatch = text2 . match ( / u i d = ( \S + ) .* B u t t o n 2 / ) ;
185+
186+ assert . ok ( btn1IdMatch2 , 'Button 1 ID not found in second snapshot' ) ;
187+ assert . ok ( span1IdMatch2 , 'Span 1 ID not found in second snapshot' ) ;
188+ assert . ok ( btn2IdMatch , 'Button 2 ID not found in second snapshot' ) ;
189+
190+ assert . strictEqual (
191+ btn1IdMatch2 [ 1 ] ,
192+ btn1Id ,
193+ 'Button 1 ID changed between snapshots' ,
194+ ) ;
195+ assert . strictEqual (
196+ span1IdMatch2 [ 1 ] ,
197+ span1Id ,
198+ 'Span 1 ID changed between snapshots' ,
199+ ) ;
200+ assert . notStrictEqual (
201+ btn2IdMatch [ 1 ] ,
202+ btn1Id ,
203+ 'Button 2 ID collides with Button 1' ,
204+ ) ;
205+ assert . notStrictEqual (
206+ btn2IdMatch [ 1 ] ,
207+ btn1Id ,
208+ 'Button 2 ID collides with Button 1' ,
209+ ) ;
210+ } ) ;
211+ } ) ;
212+
213+ describe ( 'navigation' , ( ) => {
214+ const server = serverHooks ( ) ;
215+
216+ it ( 'resets ids after navigation' , async ( ) => {
217+ await withMcpContext ( async ( response , context ) => {
218+ server . addHtmlRoute (
219+ '/page.html' ,
220+ html `
221+ < div >
222+ < button id ="btn1 "> Button 1</ button >
223+ </ div >
224+ ` ,
225+ ) ;
226+ const page = context . getSelectedPage ( ) ;
227+ await page . goto ( server . getRoute ( '/page.html' ) ) ;
228+
229+ response . includeSnapshot ( ) ;
230+ const res1 = await response . handle ( 'test' , context ) ;
231+ const text1 = getTextContent ( res1 . content [ 0 ] ) ;
232+ const btn1IdMatch = text1 . match ( / u i d = ( \S + ) .* B u t t o n 1 / ) ;
233+ assert . ok ( btn1IdMatch , 'Button 1 ID not found in first snapshot' ) ;
234+ const btn1Id = btn1IdMatch [ 1 ] ;
235+
236+ // Navigate to the same page again (or meaningful navigation)
237+ await page . goto ( server . getRoute ( '/page.html' ) ) ;
238+
239+ const res2 = await response . handle ( 'test' , context ) ;
240+ const text2 = getTextContent ( res2 . content [ 0 ] ) ;
241+ const btn1IdMatch2 = text2 . match ( / u i d = ( \S + ) .* B u t t o n 1 / ) ;
242+ assert . ok ( btn1IdMatch2 , 'Button 1 ID not found in second snapshot' ) ;
243+ const btn1Id2 = btn1IdMatch2 [ 1 ] ;
244+
245+ assert . notStrictEqual (
246+ btn1Id2 ,
247+ btn1Id ,
248+ 'ID should reset after navigation' ,
249+ ) ;
250+ } ) ;
251+ } ) ;
252+ } ) ;
253+
148254 it ( 'adds throttling setting when it is not null' , async t => {
149255 await withMcpContext ( async ( response , context ) => {
150256 context . setNetworkConditions ( 'Slow 3G' ) ;
0 commit comments