How to use getRenderTargetTime method in Playwright Internal

Best JavaScript code snippet using playwright-internal

Run Playwright Internal automation tests on LambdaTest cloud grid

Perform automation testing on 3000+ real desktop and mobile devices online.

ReactFiberCompleteWork.old.js

Source: ReactFiberCompleteWork.old.js Github

copy
1
2  function markUpdate(workInProgress) {
3    // Tag the fiber with an update effect. This turns a Placement into
4    // a PlacementAndUpdate.
5    workInProgress.flags |= Update;
6  }
7
8  function markRef$1(workInProgress) {
9    workInProgress.flags |= Ref;
10  }
11
12  var appendAllChildren;
13  var updateHostContainer;
14  var updateHostComponent$1;
15  var updateHostText$1;
16
17  {
18    // Mutation mode
19    appendAllChildren = function (parent, workInProgress, needsVisibilityToggle, isHidden) {
20      // We only have the top Fiber that was created but we need recurse down its
21      // children to find all the terminal nodes.
22      var node = workInProgress.child;
23
24      while (node !== null) {
25        if (node.tag === HostComponent || node.tag === HostText) {
26          appendInitialChild(parent, node.stateNode);
27        } else if (node.tag === HostPortal) ; else if (node.child !== null) {
28          node.child.return = node;
29          node = node.child;
30          continue;
31        }
32
33        if (node === workInProgress) {
34          return;
35        }
36
37        while (node.sibling === null) {
38          if (node.return === null || node.return === workInProgress) {
39            return;
40          }
41
42          node = node.return;
43        }
44
45        node.sibling.return = node.return;
46        node = node.sibling;
47      }
48    };
49
50    updateHostContainer = function (workInProgress) {// Noop
51    };
52
53    updateHostComponent$1 = function (current, workInProgress, type, newProps, rootContainerInstance) {
54      // If we have an alternate, that means this is an update and we need to
55      // schedule a side-effect to do the updates.
56      var oldProps = current.memoizedProps;
57
58      if (oldProps === newProps) {
59        // In mutation mode, this is sufficient for a bailout because
60        // we won't touch this node even if children changed.
61        return;
62      } // If we get updated because one of our children updated, we don't
63      // have newProps so we'll have to reuse them.
64      // TODO: Split the update API as separate for the props vs. children.
65      // Even better would be if children weren't special cased at all tho.
66
67
68      var instance = workInProgress.stateNode;
69      var currentHostContext = getHostContext(); // TODO: Experiencing an error where oldProps is null. Suggests a host
70      // component is hitting the resume path. Figure out why. Possibly
71      // related to `hidden`.
72
73      var updatePayload = prepareUpdate(instance, type, oldProps, newProps, rootContainerInstance, currentHostContext); // TODO: Type this specific to this type of component.
74
75      workInProgress.updateQueue = updatePayload; // If the update payload indicates that there is a change or if there
76      // is a new ref we mark this as an update. All the work is done in commitWork.
77
78      if (updatePayload) {
79        markUpdate(workInProgress);
80      }
81    };
82
83    updateHostText$1 = function (current, workInProgress, oldText, newText) {
84      // If the text differs, mark it as an update. All the work in done in commitWork.
85      if (oldText !== newText) {
86        markUpdate(workInProgress);
87      }
88    };
89  }
90
91  function cutOffTailIfNeeded(renderState, hasRenderedATailFallback) {
92    if (getIsHydrating()) {
93      // If we're hydrating, we should consume as many items as we can
94      // so we don't leave any behind.
95      return;
96    }
97
98    switch (renderState.tailMode) {
99      case 'hidden':
100        {
101          // Any insertions at the end of the tail list after this point
102          // should be invisible. If there are already mounted boundaries
103          // anything before them are not considered for collapsing.
104          // Therefore we need to go through the whole tail to find if
105          // there are any.
106          var tailNode = renderState.tail;
107          var lastTailNode = null;
108
109          while (tailNode !== null) {
110            if (tailNode.alternate !== null) {
111              lastTailNode = tailNode;
112            }
113
114            tailNode = tailNode.sibling;
115          } // Next we're simply going to delete all insertions after the
116          // last rendered item.
117
118
119          if (lastTailNode === null) {
120            // All remaining items in the tail are insertions.
121            renderState.tail = null;
122          } else {
123            // Detach the insertion after the last node that was already
124            // inserted.
125            lastTailNode.sibling = null;
126          }
127
128          break;
129        }
130
131      case 'collapsed':
132        {
133          // Any insertions at the end of the tail list after this point
134          // should be invisible. If there are already mounted boundaries
135          // anything before them are not considered for collapsing.
136          // Therefore we need to go through the whole tail to find if
137          // there are any.
138          var _tailNode = renderState.tail;
139          var _lastTailNode = null;
140
141          while (_tailNode !== null) {
142            if (_tailNode.alternate !== null) {
143              _lastTailNode = _tailNode;
144            }
145
146            _tailNode = _tailNode.sibling;
147          } // Next we're simply going to delete all insertions after the
148          // last rendered item.
149
150
151          if (_lastTailNode === null) {
152            // All remaining items in the tail are insertions.
153            if (!hasRenderedATailFallback && renderState.tail !== null) {
154              // We suspended during the head. We want to show at least one
155              // row at the tail. So we'll keep on and cut off the rest.
156              renderState.tail.sibling = null;
157            } else {
158              renderState.tail = null;
159            }
160          } else {
161            // Detach the insertion after the last node that was already
162            // inserted.
163            _lastTailNode.sibling = null;
164          }
165
166          break;
167        }
168    }
169  }
170
171  function completeWork(current, workInProgress, renderLanes) {
172    var newProps = workInProgress.pendingProps;
173
174    switch (workInProgress.tag) {
175      case IndeterminateComponent:
176      case LazyComponent:
177      case SimpleMemoComponent:
178      case FunctionComponent:
179      case ForwardRef:
180      case Fragment:
181      case Mode:
182      case Profiler:
183      case ContextConsumer:
184      case MemoComponent:
185        return null;
186
187      case ClassComponent:
188        {
189          var Component = workInProgress.type;
190
191          if (isContextProvider(Component)) {
192            popContext(workInProgress);
193          }
194
195          return null;
196        }
197
198      case HostRoot:
199        {
200          popHostContainer(workInProgress);
201          popTopLevelContextObject(workInProgress);
202          resetWorkInProgressVersions();
203          var fiberRoot = workInProgress.stateNode;
204
205          if (fiberRoot.pendingContext) {
206            fiberRoot.context = fiberRoot.pendingContext;
207            fiberRoot.pendingContext = null;
208          }
209
210          if (current === null || current.child === null) {
211            // If we hydrated, pop so that we can delete any remaining children
212            // that weren't hydrated.
213            var wasHydrated = popHydrationState(workInProgress);
214
215            if (wasHydrated) {
216              // If we hydrated, then we'll need to schedule an update for
217              // the commit side-effects on the root.
218              markUpdate(workInProgress);
219            } else if (!fiberRoot.hydrate) {
220              // Schedule an effect to clear this container at the start of the next commit.
221              // This handles the case of React rendering into a container with previous children.
222              // It's also safe to do for updates too, because current.child would only be null
223              // if the previous render was null (so the the container would already be empty).
224              workInProgress.flags |= Snapshot;
225            }
226          }
227
228          updateHostContainer(workInProgress);
229          return null;
230        }
231
232      case HostComponent:
233        {
234          popHostContext(workInProgress);
235          var rootContainerInstance = getRootHostContainer();
236          var type = workInProgress.type;
237
238          if (current !== null && workInProgress.stateNode != null) {
239            updateHostComponent$1(current, workInProgress, type, newProps, rootContainerInstance);
240
241            if (current.ref !== workInProgress.ref) {
242              markRef$1(workInProgress);
243            }
244          } else {
245            if (!newProps) {
246              if (!(workInProgress.stateNode !== null)) {
247                {
248                  throw Error( "We must have new props for new mounts. This error is likely caused by a bug in React. Please file an issue." );
249                }
250              } // This can happen when we abort work.
251
252
253              return null;
254            }
255
256            var currentHostContext = getHostContext(); // TODO: Move createInstance to beginWork and keep it on a context
257            // "stack" as the parent. Then append children as we go in beginWork
258            // or completeWork depending on whether we want to add them top->down or
259            // bottom->up. Top->down is faster in IE11.
260
261            var _wasHydrated = popHydrationState(workInProgress);
262
263            if (_wasHydrated) {
264              // TODO: Move this and createInstance step into the beginPhase
265              // to consolidate.
266              if (prepareToHydrateHostInstance(workInProgress, rootContainerInstance, currentHostContext)) {
267                // If changes to the hydrated node need to be applied at the
268                // commit-phase we mark this as such.
269                markUpdate(workInProgress);
270              }
271            } else {
272              var instance = createInstance(type, newProps, rootContainerInstance, currentHostContext, workInProgress);
273              appendAllChildren(instance, workInProgress, false, false);
274              workInProgress.stateNode = instance; // Certain renderers require commit-time effects for initial mount.
275              // (eg DOM renderer supports auto-focus for certain elements).
276              // Make sure such renderers get scheduled for later work.
277
278              if (finalizeInitialChildren(instance, type, newProps, rootContainerInstance)) {
279                markUpdate(workInProgress);
280              }
281            }
282
283            if (workInProgress.ref !== null) {
284              // If there is a ref on a host node we need to schedule a callback
285              markRef$1(workInProgress);
286            }
287          }
288
289          return null;
290        }
291
292      case HostText:
293        {
294          var newText = newProps;
295
296          if (current && workInProgress.stateNode != null) {
297            var oldText = current.memoizedProps; // If we have an alternate, that means this is an update and we need
298            // to schedule a side-effect to do the updates.
299
300            updateHostText$1(current, workInProgress, oldText, newText);
301          } else {
302            if (typeof newText !== 'string') {
303              if (!(workInProgress.stateNode !== null)) {
304                {
305                  throw Error( "We must have new props for new mounts. This error is likely caused by a bug in React. Please file an issue." );
306                }
307              } // This can happen when we abort work.
308
309            }
310
311            var _rootContainerInstance = getRootHostContainer();
312
313            var _currentHostContext = getHostContext();
314
315            var _wasHydrated2 = popHydrationState(workInProgress);
316
317            if (_wasHydrated2) {
318              if (prepareToHydrateHostTextInstance(workInProgress)) {
319                markUpdate(workInProgress);
320              }
321            } else {
322              workInProgress.stateNode = createTextInstance(newText, _rootContainerInstance, _currentHostContext, workInProgress);
323            }
324          }
325
326          return null;
327        }
328
329      case SuspenseComponent:
330        {
331          popSuspenseContext(workInProgress);
332          var nextState = workInProgress.memoizedState;
333
334          {
335            if (nextState !== null && nextState.dehydrated !== null) {
336              if (current === null) {
337                var _wasHydrated3 = popHydrationState(workInProgress);
338
339                if (!_wasHydrated3) {
340                  {
341                    throw Error( "A dehydrated suspense component was completed without a hydrated node. This is probably a bug in React." );
342                  }
343                }
344
345                prepareToHydrateHostSuspenseInstance(workInProgress);
346
347                {
348                  markSpawnedWork(OffscreenLane);
349                }
350
351                return null;
352              } else {
353                // We should never have been in a hydration state if we didn't have a current.
354                // However, in some of those paths, we might have reentered a hydration state
355                // and then we might be inside a hydration state. In that case, we'll need to exit out of it.
356                resetHydrationState();
357
358                if ((workInProgress.flags & DidCapture) === NoFlags) {
359                  // This boundary did not suspend so it's now hydrated and unsuspended.
360                  workInProgress.memoizedState = null;
361                } // If nothing suspended, we need to schedule an effect to mark this boundary
362                // as having hydrated so events know that they're free to be invoked.
363                // It's also a signal to replay events and the suspense callback.
364                // If something suspended, schedule an effect to attach retry listeners.
365                // So we might as well always mark this.
366
367
368                workInProgress.flags |= Update;
369                return null;
370              }
371            }
372          }
373
374          if ((workInProgress.flags & DidCapture) !== NoFlags) {
375            // Something suspended. Re-render with the fallback children.
376            workInProgress.lanes = renderLanes; // Do not reset the effect list.
377
378            if ( (workInProgress.mode & ProfileMode) !== NoMode) {
379              transferActualDuration(workInProgress);
380            }
381
382            return workInProgress;
383          }
384
385          var nextDidTimeout = nextState !== null;
386          var prevDidTimeout = false;
387
388          if (current === null) {
389            if (workInProgress.memoizedProps.fallback !== undefined) {
390              popHydrationState(workInProgress);
391            }
392          } else {
393            var prevState = current.memoizedState;
394            prevDidTimeout = prevState !== null;
395          }
396
397          if (nextDidTimeout && !prevDidTimeout) {
398            // If this subtreee is running in blocking mode we can suspend,
399            // otherwise we won't suspend.
400            // TODO: This will still suspend a synchronous tree if anything
401            // in the concurrent tree already suspended during this render.
402            // This is a known bug.
403            if ((workInProgress.mode & BlockingMode) !== NoMode) {
404              // TODO: Move this back to throwException because this is too late
405              // if this is a large tree which is common for initial loads. We
406              // don't know if we should restart a render or not until we get
407              // this marker, and this is too late.
408              // If this render already had a ping or lower pri updates,
409              // and this is the first time we know we're going to suspend we
410              // should be able to immediately restart from within throwException.
411              var hasInvisibleChildContext = current === null && workInProgress.memoizedProps.unstable_avoidThisFallback !== true;
412
413              if (hasInvisibleChildContext || hasSuspenseContext(suspenseStackCursor.current, InvisibleParentSuspenseContext)) {
414                // If this was in an invisible tree or a new render, then showing
415                // this boundary is ok.
416                renderDidSuspend();
417              } else {
418                // Otherwise, we're going to have to hide content so we should
419                // suspend for longer if possible.
420                renderDidSuspendDelayIfPossible();
421              }
422            }
423          }
424
425          {
426            // TODO: Only schedule updates if these values are non equal, i.e. it changed.
427            if (nextDidTimeout || prevDidTimeout) {
428              // If this boundary just timed out, schedule an effect to attach a
429              // retry listener to the promise. This flag is also used to hide the
430              // primary children. In mutation mode, we also need the flag to
431              // *unhide* children that were previously hidden, so check if this
432              // is currently timed out, too.
433              workInProgress.flags |= Update;
434            }
435          }
436
437          return null;
438        }
439
440      case HostPortal:
441        popHostContainer(workInProgress);
442        updateHostContainer(workInProgress);
443
444        if (current === null) {
445          preparePortalMount(workInProgress.stateNode.containerInfo);
446        }
447
448        return null;
449
450      case ContextProvider:
451        // Pop provider fiber
452        popProvider(workInProgress);
453        return null;
454
455      case IncompleteClassComponent:
456        {
457          // Same as class component case. I put it down here so that the tags are
458          // sequential to ensure this switch is compiled to a jump table.
459          var _Component = workInProgress.type;
460
461          if (isContextProvider(_Component)) {
462            popContext(workInProgress);
463          }
464
465          return null;
466        }
467
468      case SuspenseListComponent:
469        {
470          popSuspenseContext(workInProgress);
471          var renderState = workInProgress.memoizedState;
472
473          if (renderState === null) {
474            // We're running in the default, "independent" mode.
475            // We don't do anything in this mode.
476            return null;
477          }
478
479          var didSuspendAlready = (workInProgress.flags & DidCapture) !== NoFlags;
480          var renderedTail = renderState.rendering;
481
482          if (renderedTail === null) {
483            // We just rendered the head.
484            if (!didSuspendAlready) {
485              // This is the first pass. We need to figure out if anything is still
486              // suspended in the rendered set.
487              // If new content unsuspended, but there's still some content that
488              // didn't. Then we need to do a second pass that forces everything
489              // to keep showing their fallbacks.
490              // We might be suspended if something in this render pass suspended, or
491              // something in the previous committed pass suspended. Otherwise,
492              // there's no chance so we can skip the expensive call to
493              // findFirstSuspended.
494              var cannotBeSuspended = renderHasNotSuspendedYet() && (current === null || (current.flags & DidCapture) === NoFlags);
495
496              if (!cannotBeSuspended) {
497                var row = workInProgress.child;
498
499                while (row !== null) {
500                  var suspended = findFirstSuspended(row);
501
502                  if (suspended !== null) {
503                    didSuspendAlready = true;
504                    workInProgress.flags |= DidCapture;
505                    cutOffTailIfNeeded(renderState, false); // If this is a newly suspended tree, it might not get committed as
506                    // part of the second pass. In that case nothing will subscribe to
507                    // its thennables. Instead, we'll transfer its thennables to the
508                    // SuspenseList so that it can retry if they resolve.
509                    // There might be multiple of these in the list but since we're
510                    // going to wait for all of them anyway, it doesn't really matter
511                    // which ones gets to ping. In theory we could get clever and keep
512                    // track of how many dependencies remain but it gets tricky because
513                    // in the meantime, we can add/remove/change items and dependencies.
514                    // We might bail out of the loop before finding any but that
515                    // doesn't matter since that means that the other boundaries that
516                    // we did find already has their listeners attached.
517
518                    var newThennables = suspended.updateQueue;
519
520                    if (newThennables !== null) {
521                      workInProgress.updateQueue = newThennables;
522                      workInProgress.flags |= Update;
523                    } // Rerender the whole list, but this time, we'll force fallbacks
524                    // to stay in place.
525                    // Reset the effect list before doing the second pass since that's now invalid.
526
527
528                    if (renderState.lastEffect === null) {
529                      workInProgress.firstEffect = null;
530                    }
531
532                    workInProgress.lastEffect = renderState.lastEffect; // Reset the child fibers to their original state.
533
534                    resetChildFibers(workInProgress, renderLanes); // Set up the Suspense Context to force suspense and immediately
535                    // rerender the children.
536
537                    pushSuspenseContext(workInProgress, setShallowSuspenseContext(suspenseStackCursor.current, ForceSuspenseFallback));
538                    return workInProgress.child;
539                  }
540
541                  row = row.sibling;
542                }
543              }
544
545              if (renderState.tail !== null && now() > getRenderTargetTime()) {
546                // We have already passed our CPU deadline but we still have rows
547                // left in the tail. We'll just give up further attempts to render
548                // the main content and only render fallbacks.
549                workInProgress.flags |= DidCapture;
550                didSuspendAlready = true;
551                cutOffTailIfNeeded(renderState, false); // Since nothing actually suspended, there will nothing to ping this
552                // to get it started back up to attempt the next item. While in terms
553                // of priority this work has the same priority as this current render,
554                // it's not part of the same transition once the transition has
555                // committed. If it's sync, we still want to yield so that it can be
556                // painted. Conceptually, this is really the same as pinging.
557                // We can use any RetryLane even if it's the one currently rendering
558                // since we're leaving it behind on this node.
559
560                workInProgress.lanes = SomeRetryLane;
561
562                {
563                  markSpawnedWork(SomeRetryLane);
564                }
565              }
566            } else {
567              cutOffTailIfNeeded(renderState, false);
568            } // Next we're going to render the tail.
569
570          } else {
571            // Append the rendered row to the child list.
572            if (!didSuspendAlready) {
573              var _suspended = findFirstSuspended(renderedTail);
574
575              if (_suspended !== null) {
576                workInProgress.flags |= DidCapture;
577                didSuspendAlready = true; // Ensure we transfer the update queue to the parent so that it doesn't
578                // get lost if this row ends up dropped during a second pass.
579
580                var _newThennables = _suspended.updateQueue;
581
582                if (_newThennables !== null) {
583                  workInProgress.updateQueue = _newThennables;
584                  workInProgress.flags |= Update;
585                }
586
587                cutOffTailIfNeeded(renderState, true); // This might have been modified.
588
589                if (renderState.tail === null && renderState.tailMode === 'hidden' && !renderedTail.alternate && !getIsHydrating() // We don't cut it if we're hydrating.
590                ) {
591                    // We need to delete the row we just rendered.
592                    // Reset the effect list to what it was before we rendered this
593                    // child. The nested children have already appended themselves.
594                    var lastEffect = workInProgress.lastEffect = renderState.lastEffect; // Remove any effects that were appended after this point.
595
596                    if (lastEffect !== null) {
597                      lastEffect.nextEffect = null;
598                    } // We're done.
599
600
601                    return null;
602                  }
603              } else if ( // The time it took to render last row is greater than the remaining
604              // time we have to render. So rendering one more row would likely
605              // exceed it.
606              now() * 2 - renderState.renderingStartTime > getRenderTargetTime() && renderLanes !== OffscreenLane) {
607                // We have now passed our CPU deadline and we'll just give up further
608                // attempts to render the main content and only render fallbacks.
609                // The assumption is that this is usually faster.
610                workInProgress.flags |= DidCapture;
611                didSuspendAlready = true;
612                cutOffTailIfNeeded(renderState, false); // Since nothing actually suspended, there will nothing to ping this
613                // to get it started back up to attempt the next item. While in terms
614                // of priority this work has the same priority as this current render,
615                // it's not part of the same transition once the transition has
616                // committed. If it's sync, we still want to yield so that it can be
617                // painted. Conceptually, this is really the same as pinging.
618                // We can use any RetryLane even if it's the one currently rendering
619                // since we're leaving it behind on this node.
620
621                workInProgress.lanes = SomeRetryLane;
622
623                {
624                  markSpawnedWork(SomeRetryLane);
625                }
626              }
627            }
628
629            if (renderState.isBackwards) {
630              // The effect list of the backwards tail will have been added
631              // to the end. This breaks the guarantee that life-cycles fire in
632              // sibling order but that isn't a strong guarantee promised by React.
633              // Especially since these might also just pop in during future commits.
634              // Append to the beginning of the list.
635              renderedTail.sibling = workInProgress.child;
636              workInProgress.child = renderedTail;
637            } else {
638              var previousSibling = renderState.last;
639
640              if (previousSibling !== null) {
641                previousSibling.sibling = renderedTail;
642              } else {
643                workInProgress.child = renderedTail;
644              }
645
646              renderState.last = renderedTail;
647            }
648          }
649
650          if (renderState.tail !== null) {
651            // We still have tail rows to render.
652            // Pop a row.
653            var next = renderState.tail;
654            renderState.rendering = next;
655            renderState.tail = next.sibling;
656            renderState.lastEffect = workInProgress.lastEffect;
657            renderState.renderingStartTime = now();
658            next.sibling = null; // Restore the context.
659            // TODO: We can probably just avoid popping it instead and only
660            // setting it the first time we go from not suspended to suspended.
661
662            var suspenseContext = suspenseStackCursor.current;
663
664            if (didSuspendAlready) {
665              suspenseContext = setShallowSuspenseContext(suspenseContext, ForceSuspenseFallback);
666            } else {
667              suspenseContext = setDefaultShallowSuspenseContext(suspenseContext);
668            }
669
670            pushSuspenseContext(workInProgress, suspenseContext); // Do a pass over the next row.
671
672            return next;
673          }
674
675          return null;
676        }
677
678      case FundamentalComponent:
679        {
680
681          break;
682        }
683
684      case ScopeComponent:
685        {
686
687          break;
688        }
689
690      case Block:
691        {
692          return null;
693        }
694
695      case OffscreenComponent:
696      case LegacyHiddenComponent:
697        {
698          popRenderLanes(workInProgress);
699
700          if (current !== null) {
701            var _nextState = workInProgress.memoizedState;
702            var _prevState = current.memoizedState;
703            var prevIsHidden = _prevState !== null;
704            var nextIsHidden = _nextState !== null;
705
706            if (prevIsHidden !== nextIsHidden && newProps.mode !== 'unstable-defer-without-hiding') {
707              workInProgress.flags |= Update;
708            }
709          }
710
711          return null;
712        }
713    }
714
715    {
716      {
717        throw Error( "Unknown unit of work tag (" + workInProgress.tag + "). This error is likely caused by a bug in React. Please file an issue." );
718      }
719    }
720  }
Full Screen

ReactFiberCompleteWork.js

Source: ReactFiberCompleteWork.js Github

copy
1import {
2  createTextInstance,
3  supportsMutation,
4} from "../react-dom/ReactDOMHostConfig";
5import { Snapshot } from "./ReactFiberFlags";
6import { getRootHostContainer } from "./ReactFiberHostContext";
7import { FunctionComponent, HostComponent, HostRoot } from "./ReactWokTags";
8
9let appendAllChildren;
10let updateHostContainer;
11let updateHostComponent;
12let updateHostText;
13
14function markUpdate(workInProgress) {
15  // Tag the fiber with an update effect. This turns a Placement into
16  // a PlacementAndUpdate.
17  workInProgress.flags |= Update;
18}
19
20// 源码中还有其他模式, 但我们现在只关注host为react-dom的情况, 此时supportsMutation = true
21if (supportsMutation) {
22  appendAllChildren = function (
23    parent,
24    workInProgress,
25    needsVisibilityToggle,
26    isHidden
27  ) {
28    // We only have the top Fiber that was created but we need recurse down its
29    // children to find all the terminal nodes.
30    let node = workInProgress.child;
31    while (node !== null) {
32      if (node.tag === HostComponent || node.tag === HostText) {
33        appendInitialChild(parent, node.stateNode);
34      } else if (enableFundamentalAPI && node.tag === FundamentalComponent) {
35        appendInitialChild(parent, node.stateNode.instance);
36      } else if (node.tag === HostPortal) {
37        // If we have a portal child, then we don't want to traverse
38        // down its children. Instead, we'll get insertions from each child in
39        // the portal directly.
40      } else if (node.child !== null) {
41        node.child.return = node;
42        node = node.child;
43        continue;
44      }
45      if (node === workInProgress) {
46        return;
47      }
48      while (node.sibling === null) {
49        if (node.return === null || node.return === workInProgress) {
50          return;
51        }
52        node = node.return;
53      }
54      node.sibling.return = node.return;
55      node = node.sibling;
56    }
57  };
58
59  updateHostContainer = function (workInProgress) {
60    // Noop
61  };
62  updateHostComponent = function (
63    current,
64    workInProgress,
65    type,
66    newProps,
67    rootContainerInstance
68  ) {
69    // If we have an alternate, that means this is an update and we need to
70    // schedule a side-effect to do the updates.
71    const oldProps = current.memoizedProps;
72    if (oldProps === newProps) {
73      // In mutation mode, this is sufficient for a bailout because
74      // we won't touch this node even if children changed.
75      return;
76    }
77
78    // If we get updated because one of our children updated, we don't
79    // have newProps so we'll have to reuse them.
80    // TODO: Split the update API as separate for the props vs. children.
81    // Even better would be if children weren't special cased at all tho.
82    const instance = workInProgress.stateNode;
83    const currentHostContext = getHostContext();
84    // TODO: Experiencing an error where oldProps is null. Suggests a host
85    // component is hitting the resume path. Figure out why. Possibly
86    // related to `hidden`.
87    const updatePayload = prepareUpdate(
88      instance,
89      type,
90      oldProps,
91      newProps,
92      rootContainerInstance,
93      currentHostContext
94    );
95    // TODO: Type this specific to this type of component.
96    workInProgress.updateQueue = updatePayload;
97    // If the update payload indicates that there is a change or if there
98    // is a new ref we mark this as an update. All the work is done in commitWork.
99    // 打上更新的标签(副作用标签?)
100    if (updatePayload) {
101      markUpdate(workInProgress);
102    }
103  };
104  updateHostText = function (current, workInProgress, oldText, newText) {
105    // If the text differs, mark it as an update. All the work in done in commitWork.
106    if (oldText !== newText) {
107      markUpdate(workInProgress);
108    }
109  };
110}
111
112// 此函数的返回值是在completeWork阶段产生的新工作, 会交予beiginWork继续执行
113export function completeWork(current, workInProgress, renderLanes) {
114  const newProps = workInProgress.pendingProps;
115
116  switch (workInProgress.tag) {
117    case IndeterminateComponent:
118    case LazyComponent:
119    case SimpleMemoComponent:
120    case FunctionComponent:
121    case ForwardRef:
122    case Fragment:
123    case Mode:
124    case Profiler:
125    case ContextConsumer:
126    case MemoComponent:
127      return null;
128    case ClassComponent: {
129      const Component = workInProgress.type;
130      // 暂时跳过context
131      if (isLegacyContextProvider(Component)) {
132        popLegacyContext(workInProgress);
133      }
134      return null;
135    }
136    case HostRoot: {
137      // completeWork来到host root fiber
138      popHostContainer(workInProgress);
139      popTopLevelLegacyContextObject(workInProgress);
140      resetMutableSourceWorkInProgressVersions();
141      const fiberRoot = workInProgress.stateNode;
142      if (fiberRoot.pendingContext) {
143        fiberRoot.context = fiberRoot.pendingContext;
144        fiberRoot.pendingContext = null;
145      }
146
147      // 初次渲染, current是没有child的
148      // wip倒是有, 此时wip比current完整
149      if (current === null || current.child === null) {
150        // Schedule an effect to clear this container at the start of the next commit.
151        // This handles the case of React rendering into a container with previous children.
152        // It's also safe to do for updates too, because current.child would only be null
153        // if the previous render was null (so the the container would already be empty).
154        // 这里HostRoot的flags其实就是Snapshot, 这个会影响后面effectList的生成和commit阶段的工作
155        workInProgress.flags |= Snapshot;
156      }
157
158      // 这个函数啥都不做。。。
159      updateHostContainer(workInProgress);
160      return null;
161    }
162    case HostComponent: {
163      popHostContext(workInProgress);
164      const rootContainerInstance = getRootHostContainer();
165      const type = workInProgress.type;
166      // current 和 wip.stateNode 都存在, 说明wip是一个复用的组件
167      if (current !== null && workInProgress.stateNode != null) {
168        updateHostComponent(
169          current,
170          workInProgress,
171          type,
172          newProps,
173          rootContainerInstance
174        );
175
176        if (current.ref !== workInProgress.ref) {
177          markRef(workInProgress);
178        }
179      } else {
180        if (!newProps) {
181          invariant(
182            workInProgress.stateNode !== null,
183            "We must have new props for new mounts. This error is likely " +
184              "caused by a bug in React. Please file an issue."
185          );
186          // This can happen when we abort work.
187          return null;
188        }
189
190        const currentHostContext = getHostContext();
191        // TODO: Move createInstance to beginWork and keep it on a context
192        // "stack" as the parent. Then append children as we go in beginWork
193        // or completeWork depending on whether we want to add them top->down or
194        // bottom->up. Top->down is faster in IE11.
195        const wasHydrated = popHydrationState(workInProgress);
196        if (wasHydrated) {
197          // TODO: Move this and createInstance step into the beginPhase
198          // to consolidate.
199          if (
200            prepareToHydrateHostInstance(
201              workInProgress,
202              rootContainerInstance,
203              currentHostContext
204            )
205          ) {
206            // If changes to the hydrated node need to be applied at the
207            // commit-phase we mark this as such.
208            markUpdate(workInProgress);
209          }
210        } else {
211          // 创建dom
212          const instance = createInstance(
213            type,
214            newProps,
215            rootContainerInstance,
216            currentHostContext,
217            workInProgress
218          );
219          // dom appendChild wip下child中tag为HostComponent / HostText的
220          appendAllChildren(instance, workInProgress, false, false);
221
222          workInProgress.stateNode = instance;
223
224          // Certain renderers require commit-time effects for initial mount.
225          // (eg DOM renderer supports auto-focus for certain elements).
226          // Make sure such renderers get scheduled for later work.
227          if (
228            finalizeInitialChildren(
229              instance,
230              type,
231              newProps,
232              rootContainerInstance,
233              currentHostContext
234            )
235          ) {
236            markUpdate(workInProgress);
237          }
238        }
239
240        if (workInProgress.ref !== null) {
241          // If there is a ref on a host node we need to schedule a callback
242          markRef(workInProgress);
243        }
244      }
245      return null;
246    }
247    case HostText: {
248      // 在我学习的例子中, 第一次应该来到这个分支
249      const newText = newProps;
250
251      if (current && workInProgress.stateNode != null) {
252        // 这是一个更新阶段, 而非初次渲染
253        const oldText = current.memoizedProps;
254        // If we have an alternate, that means this is an update and we need
255        // to schedule a side-effect to do the updates.
256        updateHostText(current, workInProgress, oldText, newText);
257      } else {
258        if (typeof newText !== "string") {
259          invariant(
260            workInProgress.stateNode !== null,
261            "We must have new props for new mounts. This error is likely " +
262              "caused by a bug in React. Please file an issue."
263          );
264          // This can happen when we abort work.
265        }
266
267        // rootContainerInstance 是beginWork阶段, 处理HostRoot节点时, 通过 beginWork -> updateHostRoot -> pushHostRootContext -> pushHostContainer 设置的
268        const rootContainerInstance = getRootHostContainer();
269        // context 相关, 暂时跳过
270        // const currentHostContext = getHostContext();
271        // 创建Dom节点, 赋值给wip的stateNode
272        workInProgress.stateNode = createTextInstance(
273          newText,
274          rootContainerInstance,
275          // currentHostContext,
276          workInProgress
277        );
278      }
279
280      // 单纯如一个TextNode是不会产生新的工作的
281      return null;
282    }
283    case SuspenseComponent: {
284      popSuspenseContext(workInProgress);
285      const nextState = workInProgress.memoizedState;
286
287      if (enableSuspenseServerRenderer) {
288        if (nextState !== null && nextState.dehydrated !== null) {
289          if (current === null) {
290            const wasHydrated = popHydrationState(workInProgress);
291            invariant(
292              wasHydrated,
293              "A dehydrated suspense component was completed without a hydrated node. " +
294                "This is probably a bug in React."
295            );
296            prepareToHydrateHostSuspenseInstance(workInProgress);
297            if (enableSchedulerTracing) {
298              markSpawnedWork(OffscreenLane);
299            }
300            return null;
301          } else {
302            // We should never have been in a hydration state if we didn't have a current.
303            // However, in some of those paths, we might have reentered a hydration state
304            // and then we might be inside a hydration state. In that case, we'll need to exit out of it.
305            resetHydrationState();
306            if ((workInProgress.flags & DidCapture) === NoFlags) {
307              // This boundary did not suspend so it's now hydrated and unsuspended.
308              workInProgress.memoizedState = null;
309            }
310            // If nothing suspended, we need to schedule an effect to mark this boundary
311            // as having hydrated so events know that they're free to be invoked.
312            // It's also a signal to replay events and the suspense callback.
313            // If something suspended, schedule an effect to attach retry listeners.
314            // So we might as well always mark this.
315            workInProgress.flags |= Update;
316            return null;
317          }
318        }
319      }
320
321      if ((workInProgress.flags & DidCapture) !== NoFlags) {
322        // Something suspended. Re-render with the fallback children.
323        workInProgress.lanes = renderLanes;
324        // Do not reset the effect list.
325        if (
326          enableProfilerTimer &&
327          (workInProgress.mode & ProfileMode) !== NoMode
328        ) {
329          transferActualDuration(workInProgress);
330        }
331        return workInProgress;
332      }
333
334      const nextDidTimeout = nextState !== null;
335      let prevDidTimeout = false;
336      if (current === null) {
337        if (workInProgress.memoizedProps.fallback !== undefined) {
338          popHydrationState(workInProgress);
339        }
340      } else {
341        const prevState = current.memoizedState;
342        prevDidTimeout = prevState !== null;
343      }
344
345      if (nextDidTimeout && !prevDidTimeout) {
346        // If this subtreee is running in blocking mode we can suspend,
347        // otherwise we won't suspend.
348        // TODO: This will still suspend a synchronous tree if anything
349        // in the concurrent tree already suspended during this render.
350        // This is a known bug.
351        if ((workInProgress.mode & BlockingMode) !== NoMode) {
352          // TODO: Move this back to throwException because this is too late
353          // if this is a large tree which is common for initial loads. We
354          // don't know if we should restart a render or not until we get
355          // this marker, and this is too late.
356          // If this render already had a ping or lower pri updates,
357          // and this is the first time we know we're going to suspend we
358          // should be able to immediately restart from within throwException.
359          const hasInvisibleChildContext =
360            current === null &&
361            workInProgress.memoizedProps.unstable_avoidThisFallback !== true;
362          if (
363            hasInvisibleChildContext ||
364            hasSuspenseContext(
365              suspenseStackCursor.current,
366              InvisibleParentSuspenseContext
367            )
368          ) {
369            // If this was in an invisible tree or a new render, then showing
370            // this boundary is ok.
371            renderDidSuspend();
372          } else {
373            // Otherwise, we're going to have to hide content so we should
374            // suspend for longer if possible.
375            renderDidSuspendDelayIfPossible();
376          }
377        }
378      }
379
380      if (supportsPersistence) {
381        // TODO: Only schedule updates if not prevDidTimeout.
382        if (nextDidTimeout) {
383          // If this boundary just timed out, schedule an effect to attach a
384          // retry listener to the promise. This flag is also used to hide the
385          // primary children.
386          workInProgress.flags |= Update;
387        }
388      }
389      if (supportsMutation) {
390        // TODO: Only schedule updates if these values are non equal, i.e. it changed.
391        if (nextDidTimeout || prevDidTimeout) {
392          // If this boundary just timed out, schedule an effect to attach a
393          // retry listener to the promise. This flag is also used to hide the
394          // primary children. In mutation mode, we also need the flag to
395          // *unhide* children that were previously hidden, so check if this
396          // is currently timed out, too.
397          workInProgress.flags |= Update;
398        }
399      }
400      if (
401        enableSuspenseCallback &&
402        workInProgress.updateQueue !== null &&
403        workInProgress.memoizedProps.suspenseCallback != null
404      ) {
405        // Always notify the callback
406        workInProgress.flags |= Update;
407      }
408      return null;
409    }
410    case HostPortal:
411      popHostContainer(workInProgress);
412      updateHostContainer(workInProgress);
413      if (current === null) {
414        preparePortalMount(workInProgress.stateNode.containerInfo);
415      }
416      return null;
417    case ContextProvider:
418      // Pop provider fiber
419      popProvider(workInProgress);
420      return null;
421    case IncompleteClassComponent: {
422      // Same as class component case. I put it down here so that the tags are
423      // sequential to ensure this switch is compiled to a jump table.
424      const Component = workInProgress.type;
425      if (isLegacyContextProvider(Component)) {
426        popLegacyContext(workInProgress);
427      }
428      return null;
429    }
430    case SuspenseListComponent: {
431      popSuspenseContext(workInProgress);
432
433      const renderState = workInProgress.memoizedState;
434
435      if (renderState === null) {
436        // We're running in the default, "independent" mode.
437        // We don't do anything in this mode.
438        return null;
439      }
440
441      let didSuspendAlready = (workInProgress.flags & DidCapture) !== NoFlags;
442
443      const renderedTail = renderState.rendering;
444      if (renderedTail === null) {
445        // We just rendered the head.
446        if (!didSuspendAlready) {
447          // This is the first pass. We need to figure out if anything is still
448          // suspended in the rendered set.
449
450          // If new content unsuspended, but there's still some content that
451          // didn't. Then we need to do a second pass that forces everything
452          // to keep showing their fallbacks.
453
454          // We might be suspended if something in this render pass suspended, or
455          // something in the previous committed pass suspended. Otherwise,
456          // there's no chance so we can skip the expensive call to
457          // findFirstSuspended.
458          const cannotBeSuspended =
459            renderHasNotSuspendedYet() &&
460            (current === null || (current.flags & DidCapture) === NoFlags);
461          if (!cannotBeSuspended) {
462            let row = workInProgress.child;
463            while (row !== null) {
464              const suspended = findFirstSuspended(row);
465              if (suspended !== null) {
466                didSuspendAlready = true;
467                workInProgress.flags |= DidCapture;
468                cutOffTailIfNeeded(renderState, false);
469
470                // If this is a newly suspended tree, it might not get committed as
471                // part of the second pass. In that case nothing will subscribe to
472                // its thennables. Instead, we'll transfer its thennables to the
473                // SuspenseList so that it can retry if they resolve.
474                // There might be multiple of these in the list but since we're
475                // going to wait for all of them anyway, it doesn't really matter
476                // which ones gets to ping. In theory we could get clever and keep
477                // track of how many dependencies remain but it gets tricky because
478                // in the meantime, we can add/remove/change items and dependencies.
479                // We might bail out of the loop before finding any but that
480                // doesn't matter since that means that the other boundaries that
481                // we did find already has their listeners attached.
482                const newThennables = suspended.updateQueue;
483                if (newThennables !== null) {
484                  workInProgress.updateQueue = newThennables;
485                  workInProgress.flags |= Update;
486                }
487
488                // Rerender the whole list, but this time, we'll force fallbacks
489                // to stay in place.
490                // Reset the effect list before doing the second pass since that's now invalid.
491                if (renderState.lastEffect === null) {
492                  workInProgress.firstEffect = null;
493                }
494                workInProgress.lastEffect = renderState.lastEffect;
495                // Reset the child fibers to their original state.
496                resetChildFibers(workInProgress, renderLanes);
497
498                // Set up the Suspense Context to force suspense and immediately
499                // rerender the children.
500                pushSuspenseContext(
501                  workInProgress,
502                  setShallowSuspenseContext(
503                    suspenseStackCursor.current,
504                    ForceSuspenseFallback
505                  )
506                );
507                return workInProgress.child;
508              }
509              row = row.sibling;
510            }
511          }
512
513          if (renderState.tail !== null && now() > getRenderTargetTime()) {
514            // We have already passed our CPU deadline but we still have rows
515            // left in the tail. We'll just give up further attempts to render
516            // the main content and only render fallbacks.
517            workInProgress.flags |= DidCapture;
518            didSuspendAlready = true;
519
520            cutOffTailIfNeeded(renderState, false);
521
522            // Since nothing actually suspended, there will nothing to ping this
523            // to get it started back up to attempt the next item. While in terms
524            // of priority this work has the same priority as this current render,
525            // it's not part of the same transition once the transition has
526            // committed. If it's sync, we still want to yield so that it can be
527            // painted. Conceptually, this is really the same as pinging.
528            // We can use any RetryLane even if it's the one currently rendering
529            // since we're leaving it behind on this node.
530            workInProgress.lanes = SomeRetryLane;
531            if (enableSchedulerTracing) {
532              markSpawnedWork(SomeRetryLane);
533            }
534          }
535        } else {
536          cutOffTailIfNeeded(renderState, false);
537        }
538        // Next we're going to render the tail.
539      } else {
540        // Append the rendered row to the child list.
541        if (!didSuspendAlready) {
542          const suspended = findFirstSuspended(renderedTail);
543          if (suspended !== null) {
544            workInProgress.flags |= DidCapture;
545            didSuspendAlready = true;
546
547            // Ensure we transfer the update queue to the parent so that it doesn't
548            // get lost if this row ends up dropped during a second pass.
549            const newThennables = suspended.updateQueue;
550            if (newThennables !== null) {
551              workInProgress.updateQueue = newThennables;
552              workInProgress.flags |= Update;
553            }
554
555            cutOffTailIfNeeded(renderState, true);
556            // This might have been modified.
557            if (
558              renderState.tail === null &&
559              renderState.tailMode === "hidden" &&
560              !renderedTail.alternate &&
561              !getIsHydrating() // We don't cut it if we're hydrating.
562            ) {
563              // We need to delete the row we just rendered.
564              // Reset the effect list to what it was before we rendered this
565              // child. The nested children have already appended themselves.
566              const lastEffect = (workInProgress.lastEffect =
567                renderState.lastEffect);
568              // Remove any effects that were appended after this point.
569              if (lastEffect !== null) {
570                lastEffect.nextEffect = null;
571              }
572              // We're done.
573              return null;
574            }
575          } else if (
576            // The time it took to render last row is greater than the remaining
577            // time we have to render. So rendering one more row would likely
578            // exceed it.
579            now() * 2 - renderState.renderingStartTime >
580              getRenderTargetTime() &&
581            renderLanes !== OffscreenLane
582          ) {
583            // We have now passed our CPU deadline and we'll just give up further
584            // attempts to render the main content and only render fallbacks.
585            // The assumption is that this is usually faster.
586            workInProgress.flags |= DidCapture;
587            didSuspendAlready = true;
588
589            cutOffTailIfNeeded(renderState, false);
590
591            // Since nothing actually suspended, there will nothing to ping this
592            // to get it started back up to attempt the next item. While in terms
593            // of priority this work has the same priority as this current render,
594            // it's not part of the same transition once the transition has
595            // committed. If it's sync, we still want to yield so that it can be
596            // painted. Conceptually, this is really the same as pinging.
597            // We can use any RetryLane even if it's the one currently rendering
598            // since we're leaving it behind on this node.
599            workInProgress.lanes = SomeRetryLane;
600            if (enableSchedulerTracing) {
601              markSpawnedWork(SomeRetryLane);
602            }
603          }
604        }
605        if (renderState.isBackwards) {
606          // The effect list of the backwards tail will have been added
607          // to the end. This breaks the guarantee that life-cycles fire in
608          // sibling order but that isn't a strong guarantee promised by React.
609          // Especially since these might also just pop in during future commits.
610          // Append to the beginning of the list.
611          renderedTail.sibling = workInProgress.child;
612          workInProgress.child = renderedTail;
613        } else {
614          const previousSibling = renderState.last;
615          if (previousSibling !== null) {
616            previousSibling.sibling = renderedTail;
617          } else {
618            workInProgress.child = renderedTail;
619          }
620          renderState.last = renderedTail;
621        }
622      }
623
624      if (renderState.tail !== null) {
625        // We still have tail rows to render.
626        // Pop a row.
627        const next = renderState.tail;
628        renderState.rendering = next;
629        renderState.tail = next.sibling;
630        renderState.lastEffect = workInProgress.lastEffect;
631        renderState.renderingStartTime = now();
632        next.sibling = null;
633
634        // Restore the context.
635        // TODO: We can probably just avoid popping it instead and only
636        // setting it the first time we go from not suspended to suspended.
637        let suspenseContext = suspenseStackCursor.current;
638        if (didSuspendAlready) {
639          suspenseContext = setShallowSuspenseContext(
640            suspenseContext,
641            ForceSuspenseFallback
642          );
643        } else {
644          suspenseContext = setDefaultShallowSuspenseContext(suspenseContext);
645        }
646        pushSuspenseContext(workInProgress, suspenseContext);
647        // Do a pass over the next row.
648        return next;
649      }
650      return null;
651    }
652    case FundamentalComponent: {
653      if (enableFundamentalAPI) {
654        const fundamentalImpl = workInProgress.type.impl;
655        let fundamentalInstance = workInProgress.stateNode;
656
657        if (fundamentalInstance === null) {
658          const getInitialState = fundamentalImpl.getInitialState;
659          let fundamentalState;
660          if (getInitialState !== undefined) {
661            fundamentalState = getInitialState(newProps);
662          }
663          fundamentalInstance = workInProgress.stateNode =
664            createFundamentalStateInstance(
665              workInProgress,
666              newProps,
667              fundamentalImpl,
668              fundamentalState || {}
669            );
670          const instance = getFundamentalComponentInstance(fundamentalInstance);
671          fundamentalInstance.instance = instance;
672          if (fundamentalImpl.reconcileChildren === false) {
673            return null;
674          }
675          appendAllChildren(instance, workInProgress, false, false);
676          mountFundamentalComponent(fundamentalInstance);
677        } else {
678          // We fire update in commit phase
679          const prevProps = fundamentalInstance.props;
680          fundamentalInstance.prevProps = prevProps;
681          fundamentalInstance.props = newProps;
682          fundamentalInstance.currentFiber = workInProgress;
683          if (supportsPersistence) {
684            const instance = cloneFundamentalInstance(fundamentalInstance);
685            fundamentalInstance.instance = instance;
686            appendAllChildren(instance, workInProgress, false, false);
687          }
688          const shouldUpdate =
689            shouldUpdateFundamentalComponent(fundamentalInstance);
690          if (shouldUpdate) {
691            markUpdate(workInProgress);
692          }
693        }
694        return null;
695      }
696      break;
697    }
698    case ScopeComponent: {
699      if (enableScopeAPI) {
700        if (current === null) {
701          const scopeInstance = createScopeInstance();
702          workInProgress.stateNode = scopeInstance;
703          prepareScopeUpdate(scopeInstance, workInProgress);
704          if (workInProgress.ref !== null) {
705            markRef(workInProgress);
706            markUpdate(workInProgress);
707          }
708        } else {
709          if (workInProgress.ref !== null) {
710            markUpdate(workInProgress);
711          }
712          if (current.ref !== workInProgress.ref) {
713            markRef(workInProgress);
714          }
715        }
716        return null;
717      }
718      break;
719    }
720    case Block:
721      if (enableBlocksAPI) {
722        return null;
723      }
724      break;
725    case OffscreenComponent:
726    case LegacyHiddenComponent: {
727      popRenderLanes(workInProgress);
728      if (current !== null) {
729        const nextState = workInProgress.memoizedState;
730        const prevState = current.memoizedState;
731
732        const prevIsHidden = prevState !== null;
733        const nextIsHidden = nextState !== null;
734        if (
735          prevIsHidden !== nextIsHidden &&
736          newProps.mode !== "unstable-defer-without-hiding"
737        ) {
738          workInProgress.flags |= Update;
739        }
740      }
741      return null;
742    }
743  }
744  invariant(
745    false,
746    "Unknown unit of work tag (%s). This error is likely caused by a bug in " +
747      "React. Please file an issue.",
748    workInProgress.tag
749  );
750}
751
Full Screen

Accelerate Your Automation Test Cycles With LambdaTest

Leverage LambdaTest’s cloud-based platform to execute your automation tests in parallel and trim down your test execution time significantly. Your first 100 automation testing minutes are on us.

Try LambdaTest

Run JavaScript Tests on LambdaTest Cloud Grid

Execute automation tests with Playwright Internal on a cloud-based Grid of 3000+ real browsers and operating systems for both web and mobile applications.

Test now for Free
LambdaTestX

We use cookies to give you the best experience. Cookies help to provide a more personalized experience and relevant advertising for you, and web analytics for us. Learn More in our Cookies policy, Privacy & Terms of service

Allow Cookie
Sarah

I hope you find the best code examples for your project.

If you want to accelerate automated browser testing, try LambdaTest. Your first 100 automation testing minutes are FREE.

Sarah Elson (Product & Growth Lead)