@@ -88,24 +88,90 @@ await page.EvaluateAsync(
8888
8989 const samples = [];
9090 const longTasks = [];
91+ const errors = [];
92+ const pendingSamples = [];
9193 const observer = new PerformanceObserver(list => {
9294 for (const entry of list.getEntries()) {
93- longTasks.push(entry.duration);
95+ longTasks.push({
96+ duration: entry.duration,
97+ endTime: entry.startTime + entry.duration,
98+ startTime: entry.startTime
99+ });
94100 }
95101 });
96102
97103 observer.observe({ type: "longtask" });
98- input.addEventListener(args.proxyChangedEventName, () => {
99- const started = performance.now();
100- requestAnimationFrame(() => {
104+
105+ const probe = {
106+ captureStart: performance.now(),
107+ errors,
108+ eventCount: 0,
109+ lastExpectedLength: -1,
110+ longTasks,
111+ observer,
112+ overlayObserver: null,
113+ pendingSampleCount: 0,
114+ pendingSamples,
115+ samples
116+ };
117+
118+ const readRenderedLength = () =>
119+ Number.parseInt(overlay.dataset.renderedLength ?? "-1", 10);
120+
121+ const completeVisibleSamples = renderedLength => {
122+ while (pendingSamples.length > 0 && renderedLength >= pendingSamples[0].expectedLength) {
123+ const pendingSample = pendingSamples.shift();
124+ const completedAt = performance.now();
101125 samples.push({
102- latency: performance.now() - started,
103- renderedLength: Number.parseInt(overlay.dataset.renderedLength ?? "-1", 10)
126+ completedAt,
127+ latency: completedAt - pendingSample.started,
128+ renderedLength,
129+ started: pendingSample.started
104130 });
131+ }
132+
133+ probe.pendingSampleCount = pendingSamples.length;
134+ };
135+
136+ const overlayObserver = new MutationObserver(() => {
137+ try {
138+ completeVisibleSamples(readRenderedLength());
139+ }
140+ catch (error) {
141+ errors.push(String(error?.stack ?? error));
142+ }
143+ });
144+
145+ overlayObserver.observe(overlay, {
146+ attributes: true,
147+ childList: true,
148+ characterData: true,
149+ subtree: true
150+ });
151+
152+ probe.overlayObserver = overlayObserver;
153+
154+ input.addEventListener(args.proxyChangedEventName, event => {
155+ probe.eventCount += 1;
156+ const expectedLength = Number.isFinite(event?.detail?.textLength)
157+ ? event.detail.textLength
158+ : input.value.length;
159+ probe.lastExpectedLength = expectedLength;
160+ pendingSamples.push({
161+ expectedLength,
162+ started: performance.now()
105163 });
164+ probe.pendingSampleCount = pendingSamples.length;
165+
166+ try {
167+ completeVisibleSamples(readRenderedLength());
168+ }
169+ catch (error) {
170+ errors.push(String(error?.stack ?? error));
171+ }
106172 }, { passive: true });
107173
108- window.__editorHugeDraftProbe = { longTasks, observer, samples } ;
174+ window.__editorHugeDraftProbe = probe ;
109175 }
110176 """ ,
111177 new
@@ -125,12 +191,39 @@ await page.EvaluateAsync(
125191 throw new Error("Huge draft performance probe was not initialized before reset.");
126192 }
127193
194+ probe.captureStart = performance.now();
195+ probe.errors.length = 0;
196+ probe.eventCount = 0;
197+ probe.lastExpectedLength = -1;
128198 probe.longTasks.length = 0;
199+ probe.pendingSampleCount = 0;
200+ probe.pendingSamples.length = 0;
129201 probe.samples.length = 0;
130202 }
131203 """ ) ;
132204 await page . Keyboard . TypeAsync ( EditorLargeDraftPerformanceTestData . FollowupTypingText , new ( ) { Delay = 0 } ) ;
133- await page . WaitForTimeoutAsync ( EditorLargeDraftPerformanceTestData . ObservationDelayMs ) ;
205+ await page . WaitForFunctionAsync (
206+ """
207+ (args) => {
208+ const overlay = document.querySelector(`[data-test="${args.overlayTestId}"]`);
209+ const probe = window.__editorHugeDraftProbe;
210+ if (!overlay || !probe) {
211+ return false;
212+ }
213+
214+ const renderedLength = Number.parseInt(overlay.dataset.renderedLength ?? "-1", 10);
215+ return renderedLength >= args.expectedLength &&
216+ probe.pendingSampleCount === 0 &&
217+ probe.samples.length >= args.expectedSampleCount;
218+ }
219+ """ ,
220+ new
221+ {
222+ expectedLength ,
223+ expectedSampleCount = EditorLargeDraftPerformanceTestData . FollowupTypingText . Length ,
224+ overlayTestId = UiTestIds . Editor . SourceHighlight
225+ } ,
226+ new ( ) { Timeout = EditorLargeDraftPerformanceTestData . ObservationDelayMs } ) ;
134227
135228 var result = await page . EvaluateAsync < HugeDraftProbeResult > (
136229 """
@@ -143,12 +236,47 @@ await page.EvaluateAsync(
143236 }
144237
145238 probe.observer.disconnect();
239+ probe.overlayObserver?.disconnect?.();
240+
241+ const samples = probe.samples ?? [];
242+ const captureStart = Number.isFinite(probe.captureStart)
243+ ? probe.captureStart
244+ : 0;
245+ const responseWindowStart = samples.length
246+ ? Math.min(...samples.map(sample => sample.started))
247+ : -1;
248+ const responseWindowEnd = samples.length
249+ ? Math.max(...samples.map(sample => sample.completedAt))
250+ : -1;
251+ const relevantWindowStart = responseWindowStart >= 0
252+ ? Math.max(captureStart, responseWindowStart)
253+ : captureStart;
254+ const observedLongTasks = (probe.longTasks ?? [])
255+ .filter(entry => entry.endTime >= captureStart);
256+ const responseWindowLongTasks = responseWindowEnd >= 0
257+ ? observedLongTasks.filter(entry =>
258+ entry.endTime >= relevantWindowStart &&
259+ entry.startTime <= responseWindowEnd)
260+ : [];
261+
146262 return {
147263 finalInputLength: input.value.length,
148264 finalRenderedLength: Number.parseInt(overlay.dataset.renderedLength ?? "-1", 10),
149- followupMaxLongTaskMs: probe.longTasks.length ? Math.max(...probe.longTasks) : 0,
150- typingLatencyMs: probe.samples[probe.samples.length - 1]?.latency ?? -1,
151- typingSampleCount: probe.samples.length
265+ eventCount: probe.eventCount ?? -1,
266+ errors: probe.errors ?? [],
267+ followupMaxLongTaskMs: responseWindowLongTasks.length
268+ ? Math.max(...responseWindowLongTasks.map(entry => entry.duration))
269+ : 0,
270+ lastExpectedLength: probe.lastExpectedLength ?? -1,
271+ maxObservedLongTaskMs: observedLongTasks.length
272+ ? Math.max(...observedLongTasks.map(entry => entry.duration))
273+ : 0,
274+ pendingSampleCount: probe.pendingSampleCount ?? -1,
275+ responseWindowDurationMs: responseWindowEnd >= 0
276+ ? Math.max(0, responseWindowEnd - relevantWindowStart)
277+ : -1,
278+ typingLatencyMs: samples[samples.length - 1]?.latency ?? -1,
279+ typingSampleCount: samples.length
152280 };
153281 }
154282 """ ,
@@ -160,11 +288,13 @@ await page.EvaluateAsync(
160288
161289 await Assert . That ( result . FinalInputLength ) . IsEqualTo ( expectedLength ) ;
162290 await Assert . That ( result . FinalRenderedLength ) . IsEqualTo ( expectedLength ) ;
163- await Assert . That ( result . TypingSampleCount >= 2 ) . IsTrue ( ) ;
291+ await Assert . That ( result . EventCount >= EditorLargeDraftPerformanceTestData . FollowupTypingText . Length ) . IsTrue ( ) . Because ( $ "Expected Monaco proxy change events for the huge draft follow-up typing, but observed { result . EventCount } . Pending samples: { result . PendingSampleCount } ; LastExpectedLength: { result . LastExpectedLength } ; FinalInputLength: { result . FinalInputLength } ; FinalRenderedLength: { result . FinalRenderedLength } .") ;
292+ await Assert . That ( result . TypingSampleCount >= EditorLargeDraftPerformanceTestData . FollowupTypingText . Length ) . IsTrue ( ) . Because ( $ "Expected the huge draft follow-up typing probe to capture visible overlay samples for each character, but observed { result . TypingSampleCount } sample(s) after { result . EventCount } proxy change event(s). Pending samples: { result . PendingSampleCount } ; LastExpectedLength: { result . LastExpectedLength } ; Errors: { string . Join ( " || " , result . Errors ) } .") ;
293+ await Assert . That ( result . PendingSampleCount ) . IsEqualTo ( 0 ) ;
164294 await Assert . That ( result . FollowupMaxLongTaskMs >= 0 &&
165- result . FollowupMaxLongTaskMs <= EditorLargeDraftPerformanceTestData . MaxHugeFollowupLongTaskMs ) . IsTrue ( ) . Because ( $ "Huge draft follow-up long task exceeded the acceptance budget. FollowupMaxLongTaskMs: { result . FollowupMaxLongTaskMs } ; MaxHugeFollowupLongTaskMs: { EditorLargeDraftPerformanceTestData . MaxHugeFollowupLongTaskMs } ; TypingLatencyMs: { result . TypingLatencyMs } ; TypingSampleCount: { result . TypingSampleCount } ; FinalInputLength: { result . FinalInputLength } ; FinalRenderedLength: { result . FinalRenderedLength } .") ;
295+ result . FollowupMaxLongTaskMs <= EditorLargeDraftPerformanceTestData . MaxHugeFollowupLongTaskMs ) . IsTrue ( ) . Because ( $ "Huge draft follow-up long task exceeded the acceptance budget. FollowupMaxLongTaskMs: { result . FollowupMaxLongTaskMs } ; MaxHugeFollowupLongTaskMs: { EditorLargeDraftPerformanceTestData . MaxHugeFollowupLongTaskMs } ; MaxObservedLongTaskMs: { result . MaxObservedLongTaskMs } ; ResponseWindowDurationMs: { result . ResponseWindowDurationMs } ; TypingLatencyMs: { result . TypingLatencyMs } ; TypingSampleCount: { result . TypingSampleCount } ; EventCount: { result . EventCount } ; PendingSampleCount: { result . PendingSampleCount } ; FinalInputLength: { result . FinalInputLength } ; FinalRenderedLength: { result . FinalRenderedLength } ; Errors: { string . Join ( " || " , result . Errors ) } .") ;
166296 await Assert . That ( result . TypingLatencyMs >= 0 &&
167- result . TypingLatencyMs <= EditorLargeDraftPerformanceTestData . MaxHugeTypingLatencyMs ) . IsTrue ( ) . Because ( $ "Huge draft typing latency exceeded the acceptance budget. TypingLatencyMs: { result . TypingLatencyMs } ; MaxHugeTypingLatencyMs: { EditorLargeDraftPerformanceTestData . MaxHugeTypingLatencyMs } ; FollowupMaxLongTaskMs: { result . FollowupMaxLongTaskMs } ; TypingSampleCount: { result . TypingSampleCount } ; FinalInputLength: { result . FinalInputLength } ; FinalRenderedLength: { result . FinalRenderedLength } .") ;
297+ result . TypingLatencyMs <= EditorLargeDraftPerformanceTestData . MaxHugeTypingLatencyMs ) . IsTrue ( ) . Because ( $ "Huge draft typing latency exceeded the acceptance budget. TypingLatencyMs: { result . TypingLatencyMs } ; MaxHugeTypingLatencyMs: { EditorLargeDraftPerformanceTestData . MaxHugeTypingLatencyMs } ; FollowupMaxLongTaskMs: { result . FollowupMaxLongTaskMs } ; MaxObservedLongTaskMs: { result . MaxObservedLongTaskMs } ; ResponseWindowDurationMs: { result . ResponseWindowDurationMs } ; TypingSampleCount: { result . TypingSampleCount } ; EventCount: { result . EventCount } ; PendingSampleCount: { result . PendingSampleCount } ; FinalInputLength: { result . FinalInputLength } ; FinalRenderedLength: { result . FinalRenderedLength } ; Errors: { string . Join ( " || " , result . Errors ) } .") ;
168298 }
169299 finally
170300 {
@@ -174,12 +304,24 @@ await Assert.That(result.TypingLatencyMs >= 0 &&
174304
175305 private sealed class HugeDraftProbeResult
176306 {
307+ public int EventCount { get ; set ; }
308+
309+ public string [ ] Errors { get ; set ; } = [ ] ;
310+
177311 public int FinalInputLength { get ; set ; }
178312
179313 public int FinalRenderedLength { get ; set ; }
180314
181315 public double FollowupMaxLongTaskMs { get ; set ; }
182316
317+ public int LastExpectedLength { get ; set ; }
318+
319+ public double MaxObservedLongTaskMs { get ; set ; }
320+
321+ public int PendingSampleCount { get ; set ; }
322+
323+ public double ResponseWindowDurationMs { get ; set ; }
324+
183325 public double TypingLatencyMs { get ; set ; }
184326
185327 public int TypingSampleCount { get ; set ; }
0 commit comments