@@ -202,28 +202,79 @@ await page.EvaluateAsync(
202202
203203 const samples = [];
204204 const longTasks = [];
205+ const errors = [];
206+ const pendingSamples = [];
207+ let eventCount = 0;
208+ let lastExpectedLength = -1;
209+ let pendingSampleCount = 0;
205210 const observer = new PerformanceObserver(list => {
206211 for (const entry of list.getEntries()) {
207212 longTasks.push(entry.duration);
208213 }
209214 });
210215 observer.observe({ type: "longtask" });
211216
212- input.addEventListener(args.proxyChangedEventName, () => {
213- const started = performance.now();
214- requestAnimationFrame(() => {
217+ const readRenderedLength = () =>
218+ Number.parseInt(overlay.dataset.renderedLength ?? '-1', 10);
219+
220+ const completeVisibleSamples = renderedLength => {
221+ while (pendingSamples.length > 0 && renderedLength >= pendingSamples[0].expectedLength) {
222+ const pendingSample = pendingSamples.shift();
215223 const state = harness?.getState(args.stageTestId);
216224 samples.push({
217- latency: performance.now() - started,
225+ latency: performance.now() - pendingSample. started,
218226 decorationClassCount: state?.decorationClasses?.length ?? 0,
219227 inputVisible: getComputedStyle(input).color !== args.transparentInputColor,
220- ready: state?.ready === true
228+ ready: state?.ready === true,
229+ renderedLength
221230 });
231+ }
232+
233+ pendingSampleCount = pendingSamples.length;
234+ };
235+
236+ const overlayObserver = new MutationObserver(() => {
237+ try {
238+ completeVisibleSamples(readRenderedLength());
239+ }
240+ catch (error) {
241+ errors.push(String(error?.stack ?? error));
242+ }
243+ });
244+ overlayObserver.observe(overlay, {
245+ attributes: true,
246+ childList: true,
247+ characterData: true,
248+ subtree: true
249+ });
250+
251+ input.addEventListener(args.proxyChangedEventName, event => {
252+ eventCount += 1;
253+ const expectedLength = Number.isFinite(event?.detail?.textLength)
254+ ? event.detail.textLength
255+ : input.value.length;
256+ lastExpectedLength = expectedLength;
257+ pendingSamples.push({
258+ expectedLength,
259+ started: performance.now()
222260 });
261+ pendingSampleCount = pendingSamples.length;
262+
263+ try {
264+ completeVisibleSamples(readRenderedLength());
265+ }
266+ catch (error) {
267+ errors.push(String(error?.stack ?? error));
268+ }
223269 }, { passive: true });
224270
225271 window.__editorTypingProbe = {
272+ errors,
273+ eventCount: () => eventCount,
274+ lastExpectedLength: () => lastExpectedLength,
226275 observer,
276+ overlayObserver,
277+ pendingSampleCount: () => pendingSampleCount,
227278 samples,
228279 longTasks,
229280 input,
@@ -265,6 +316,7 @@ await Expect(page.GetByTestId(UiTestIds.Editor.SourceHighlight))
265316 : -1;
266317
267318 probe.observer.disconnect();
319+ probe.overlayObserver?.disconnect?.();
268320 return {
269321 sampleCount: probe.samples.length,
270322 maxLatency: latencies.length ? latencies[latencies.length - 1] : -1,
@@ -274,23 +326,35 @@ await Expect(page.GetByTestId(UiTestIds.Editor.SourceHighlight))
274326 maxDecorationClassCount: probe.samples.length
275327 ? Math.max(...probe.samples.map(sample => sample.decorationClassCount))
276328 : 0,
329+ eventCount: typeof probe.eventCount === 'function'
330+ ? probe.eventCount()
331+ : -1,
332+ lastExpectedLength: typeof probe.lastExpectedLength === 'function'
333+ ? probe.lastExpectedLength()
334+ : -1,
277335 readyDuringTyping: probe.samples.every(sample => sample.ready === true),
278336 sawVisibleInput: probe.samples.some(sample => sample.inputVisible),
279337 finalInputColor: getComputedStyle(probe.input).color,
338+ errors: probe.errors ?? [],
339+ pendingSampleCount: typeof probe.pendingSampleCount === 'function'
340+ ? probe.pendingSampleCount()
341+ : -1,
280342 finalRenderedLength: Number.parseInt(
281343 probe.overlay.dataset.renderedLength ?? '-1',
282344 10)
283345 };
284346 }
285347 """ ) ;
286348
287- await Assert . That ( probeResult . SampleCount > 0 ) . IsTrue ( ) ;
349+ await Assert . That ( probeResult . EventCount > 0 ) . IsTrue ( ) . Because ( $ "Expected Monaco proxy change events during typing, but observed none. Pending samples: { probeResult . PendingSampleCount } , final rendered length: { probeResult . FinalRenderedLength } , last expected length: { probeResult . LastExpectedLength } .") ;
350+ await Assert . That ( probeResult . SampleCount > 0 ) . IsTrue ( ) . Because ( $ "Expected the typing probe to capture visible overlay latency samples, but observed none after { probeResult . EventCount } proxy change event(s). Pending samples: { probeResult . PendingSampleCount } , final rendered length: { probeResult . FinalRenderedLength } , last expected length: { probeResult . LastExpectedLength } , errors: { string . Join ( " || " , probeResult . Errors ) } .") ;
288351 await Assert . That ( probeResult . SawVisibleInput ) . IsFalse ( ) ;
289352 await Assert . That ( probeResult . P95Latency ) . IsBetween ( 0 , BrowserTestConstants . Editor . MaxVisibleRenderP95LatencyMs ) ;
290353 await Assert . That ( probeResult . MaxLatency ) . IsBetween ( 0 , BrowserTestConstants . Editor . MaxVisibleRenderSpikeLatencyMs ) ;
291354 await Assert . That ( probeResult . LongTaskCount <= BrowserTestConstants . Editor . AllowedTypingLongTaskCount ) . IsTrue ( ) . Because ( $ "Expected Monaco typing to avoid browser long tasks, but observed { probeResult . LongTaskCount } long task(s) with max duration { probeResult . MaxLongTaskDuration : 0.##} ms against the current budget of { BrowserTestConstants . Editor . AllowedTypingLongTaskCount } .") ;
292355 await Assert . That ( probeResult . FinalInputColor ) . IsEqualTo ( BrowserTestConstants . Editor . TransparentInputColor ) ;
293356 await Assert . That ( probeResult . ReadyDuringTyping ) . IsTrue ( ) ;
357+ await Assert . That ( probeResult . PendingSampleCount ) . IsEqualTo ( 0 ) ;
294358 await Assert . That ( probeResult . FinalRenderedLength >= BrowserTestConstants . Editor . TypingResponsivenessProbeText . Length ) . IsTrue ( ) . Because ( $ "Expected the Monaco overlay to render the full probe text during typing, but the rendered length was { probeResult . FinalRenderedLength } .") ;
295359 }
296360 finally
@@ -308,8 +372,14 @@ private sealed class TypingProbeResult
308372 {
309373 public string FinalInputColor { get ; set ; } = string . Empty ;
310374
375+ public int EventCount { get ; set ; }
376+
311377 public int FinalRenderedLength { get ; set ; }
312378
379+ public string [ ] Errors { get ; set ; } = [ ] ;
380+
381+ public int LastExpectedLength { get ; set ; }
382+
313383 public int LongTaskCount { get ; set ; }
314384
315385 public int MaxDecorationClassCount { get ; set ; }
@@ -320,6 +390,8 @@ private sealed class TypingProbeResult
320390
321391 public double P95Latency { get ; set ; }
322392
393+ public int PendingSampleCount { get ; set ; }
394+
323395 public bool ReadyDuringTyping { get ; set ; }
324396
325397 public bool SawVisibleInput { get ; set ; }
0 commit comments