Yevhen Klymentiev
dark
light
console
darkness
y.klymentiev@gmail.com
Reusable Snippets|Practical utility code for everyday use — custom-built and ready to share

waitForElement

Waits for an element matching the selector to appear in the DOM.

TypeScript
Copied!
1/**
2 * Waits for an element matching the selector to appear in the DOM.
3 *
4 * @param selector - CSS selector to match.
5 * @param timeout - Max time to wait in ms (default: 5000).
6 * @returns A Promise that resolves with the element or rejects on timeout.
7 */
8export function waitForElement(
9  selector: string,
10  timeout: number = 5000
11): Promise<Element> {
12  return new Promise((resolve, reject) => {
13    const existing = document.querySelector(selector);
14    if (existing) return resolve(existing);
15
16    const observer = new MutationObserver(() => {
17      const el = document.querySelector(selector);
18      if (el) {
19        observer.disconnect();
20        resolve(el);
21      }
22    });
23
24    observer.observe(document.body, {
25      childList: true,
26      subtree: true,
27    });
28
29    const timer = setTimeout(() => {
30      observer.disconnect();
31      reject(new Error(`Element "${selector}" not found within ${timeout}ms`));
32    }, timeout);
33  });
34}
  • Asynchronous DOM Readiness

    Allows waiting for dynamically rendered elements without polling or blocking the main thread.

  • MutationObserver-Based Efficiency

    Utilizes MutationObserver to detect DOM changes in a performant, event-driven way.

  • Built-in Timeout Safety

    Automatically rejects the promise if the element doesn't appear within the specified time, avoiding potential hangs.

  • Immediate Resolution When Already Present

    Resolves instantly if the element already exists in the DOM, optimizing for fast-loading pages.

  • Reusable and Flexible

    Accepts any CSS selector, making it adaptable for various UI components or conditional DOM scenarios.

Tests | Examples

TypeScript
Copied!
1beforeEach(() => {
2  document.body.innerHTML = '';
3});
4
5test('resolves if element already exists', async () => {
6  const div = document.createElement('div');
7  div.className = 'test';
8  document.body.appendChild(div);
9
10  const el = await waitForElement('.test');
11  expect(el).toBe(div);
12});
13
14test('resolves when element is added later', async () => {
15  setTimeout(() => {
16    const div = document.createElement('div');
17    div.id = 'delayed';
18    document.body.appendChild(div);
19  }, 50);
20
21  const el = await waitForElement('#delayed');
22  expect(el).toBeInstanceOf(HTMLElement);
23  expect(el?.id).toBe('delayed');
24});
25
26test('rejects after timeout', async () => {
27  await expect(waitForElement('.missing', 100)).rejects.toThrow(
28    'Element ".missing" not found within 100ms'
29  );
30});

Common Use Cases

  • Waiting for Lazy-Loaded Components

    Detect when React/Vue components, modals, or third-party widgets are injected into the DOM.

  • Automation and Testing

    Use in integration tests or browser automation to ensure elements are ready before interaction.

  • User Interaction Hooks

    Trigger behavior (e.g., animations, tracking, accessibility adjustments) once a UI element appears.

  • Progressive Enhancement

    Apply enhancements or listeners only after specific content becomes available in the DOM.

  • Single-Page Application Routing

    React to route-driven DOM changes where elements are not immediately rendered.

Codebase: Utilities -> Browser & DOM -> waitForElement | Yevhen Klymentiev