@@ -126,16 +126,25 @@ export abstract class HarnessEnvironment<E> implements HarnessLoader, LocatorFac
126
126
}
127
127
128
128
async batchCD < T > ( fn : ( ) => Promise < T > ) {
129
+ // Flipping on the `isCDBatching` flag will turn all calls to `forceStabilize` into no-ops to
130
+ // prevent triggering redundant change detection. However, we want to make sure we trigger
131
+ // change detection exactly once before flipping this flag to ensure that any pending changes
132
+ // are flushed before the harness tries to read state from DOM elements.
129
133
const alreadyBatching = isCDBatching ;
130
134
if ( ! alreadyBatching ) {
131
135
await this . forceStabilize ( ) ;
132
136
isCDBatching = true ;
133
137
}
138
+
134
139
const result = await fn ( ) ;
140
+
141
+ // We also want to run change detection exactly once after the batched operation is complete.
142
+ // This will ensure that any changes from interacting with DOM elements are properly flushed.
135
143
if ( ! alreadyBatching ) {
136
144
isCDBatching = false ;
137
145
await this . forceStabilize ( ) ;
138
146
}
147
+
139
148
return result ;
140
149
}
141
150
@@ -185,6 +194,10 @@ export abstract class HarnessEnvironment<E> implements HarnessLoader, LocatorFac
185
194
const skipSelectorCheck = ( elementQueries . length === 0 && harnessTypes . size === 1 ) ||
186
195
harnessQueries . length === 0 ;
187
196
197
+ // We want to batch change detection while we're filtering harnesses, because harness predicates
198
+ // may trigger change detection by reading state from DOM elements. If not batched these change
199
+ // detections would be triggered once per potential match element which could cause significant
200
+ // slowdown.
188
201
const perElementMatches = await this . batchCD ( ( ) =>
189
202
Promise . all ( rawElements . map ( async rawElement => {
190
203
const testElement = this . createTestElement ( rawElement ) ;
0 commit comments