npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@mach25/find-elements

v2.1.0

Published

Utility to find elements on a web page using selector functions and a timeout

Downloads

42

Readme

@mach25/find-elements

npm license

Find elements on a web page that may not exist yet.

Elements injected by a third-party script, a slow-loading widget or a framework that renders after your code runs are not in the DOM when you go looking for them. findElements returns a promise that polls for the element on every animation frame and resolves as soon as it appears, or rejects once a timeout elapses.

Install

npm install @mach25/find-elements

Usage

import { findElements } from '@mach25/find-elements';

const banner = await findElements('cookie-banner');
banner.classList.add('ready');

The common case is getting a handle on a mount point that something else renders — a CMS, a template, another app — so you can attach to it:

import { createRoot } from 'react-dom/client';
import { findElements } from '@mach25/find-elements';
import App from './App';

const container = await findElements('app-root');
createRoot(container).render(<App />);

No DOMContentLoaded listener, no setTimeout guess, no MutationObserver to tear down — the mount waits exactly as long as it needs to, and throws if the container never shows up.

By default it looks the element up by id. Pass a different selector function as the second argument:

import { findElements, bySelector, getFirst } from '@mach25/find-elements';

// All matches for a CSS selector, once at least one exists
const rows = await findElements('.data-row', bySelector);

// Just the first match
const firstRow = await findElements('.data-row', getFirst(bySelector));

Failures reject, so handle them:

import { byId, findElements } from '@mach25/find-elements';

try {
  const el = await findElements('late-widget', byId, 3000);
} catch (err) {
  // Error: late-widget not found in 3012 milliseconds
}

API

findElements(selector, findFnc?, timeout?)

| Parameter | Type | Default | Description | | ---------- | -------------------------------------------- | ------- | ------------------------------------------------------------------ | | selector | string | — | Passed through to findFnc. | | findFnc | (selector: string) => HTMLElements \| null | byId | How to look the element up. | | timeout | number | 10000 | Milliseconds before giving up. A floor, not a ceiling — see below. |

Returns a Promise that resolves with whatever findFnc returned. It calls findFnc once immediately; if that comes back falsy it re-checks on each requestAnimationFrame until something is found or timeout is exceeded, then rejects with an Error.

In TypeScript the resolved type follows the lookup, so no cast is needed: the default gives you an HTMLElement, bySelector gives you a NodeListOf<Element>, and a custom document.querySelector<HTMLInputElement> lookup gives you an HTMLInputElement.

The timeout is a backstop so the promise cannot hang forever, not a precise deadline. It measures time the page was actually being rendered, not wall-clock time. Polling runs on requestAnimationFrame, which browsers stop firing in a backgrounded or hidden tab, so a tab left in the background suspends the wait rather than consuming it — the budget is spent only on frames that ran. Switch back after ten minutes away and an element with a ten second timeout still gets its full ten seconds to appear.

In practice this means a wall clock can read far more than timeout by the time the promise settles, which is the intended behaviour: an element is unlikely to render in a hidden tab, so time spent there should not count against it. The elapsed figure in the rejection message reports the time waited, not the wall clock.

setLogger(log)

Nothing is logged by default. Pass a function to hear how long each successful lookup took, which is useful when you are trying to work out whether an element is slow to render or never renders at all:

import { setLogger } from '@mach25/find-elements';

setLogger(console.info); // "app-root found in 812 milliseconds"
setLogger(null); // quiet again

Only successful lookups are reported; a timeout comes through as the rejected promise instead.

Selector functions

The lookup is a parameter rather than a fixed strategy, so a call states what it is waiting for and how to find it — findElements('.data-row', bySelector, 2000) reads as a sentence. The two built-ins cover the common cases; supplying your own is a one-liner. Each returns null on a miss, which is what lets findElements keep polling.

| Function | Looks up with | Returns | | ---------------------- | --------------------------- | ------------------------------------------- | | byId(id) | document.getElementById | The element, or null. The default. | | bySelector(selector) | document.querySelectorAll | The NodeList, or null when it is empty. |

Picking elements out of a list

bySelector matches everything, which is rarely what you want. These wrappers take a lookup that returns a NodeList and give back one that narrows it — so they slot straight into findElements:

findElements('.data-row', getFirst(bySelector)); // the first row, once one exists
findElements('.data-row', take(bySelector, 3)); // the first three, once three exist

| Wrapper | Yields | | ---------------------------- | -------------------------------------------------------- | | getFirst(findFnc) | The first match. | | getLast(findFnc) | The last match. | | getAt(findFnc, index) | The match at index. Negative counts back from the end. | | take(findFnc, count) | The first count matches, as an array. | | takeLast(findFnc, count) | The last count matches, as an array. | | filter(findFnc, predicate) | Every match satisfying predicate, as an array. |

Each one returns null until it can satisfy the request in full, which is what makes the waiting work. take(bySelector, 3) does not yield two elements and call it done — it keeps polling until the third has rendered. Likewise getAt(bySelector, 4) waits for a fifth element rather than giving up when there are four.

They wrap any NodeList lookup, not only bySelector:

findElements(
  'email',
  getFirst((name) => document.getElementsByName(name))
);

And they compose with a typed lookup, carrying the element type through:

const inputs = (s: string) => document.querySelectorAll<HTMLInputElement>(s);

const checked = await findElements(
  '.option',
  filter(inputs, (el) => el.checked)
); // HTMLInputElement[]

Writing your own

Any (selector: string) => HTMLElements | null works. The one rule is that a miss must be falsy — findElements treats any truthy return as a hit, and an empty NodeList is still an object, so returning one would resolve the promise instead of polling.

const byDataRole = (role) => document.querySelector(`[data-role="${role}"]`);
const el = await findElements('submit', byDataRole);

Migrating from 2.0

Successful lookups no longer write to console.info unless you ask for it — call setLogger(console.info) to get the old behaviour back.

The package now declares exports, so imports must go through the package root or the ./select subpath. Reaching into @mach25/find-elements/lib/… no longer resolves.

Migrating from 1.x

getFirst takes a lookup function rather than a NodeList, so it composes instead of having to be called by hand:

-findElements('.data-row', (s) => getFirst(document.querySelectorAll(s)));
+findElements('.data-row', getFirst(bySelector));

That is the only breaking change. getLast, getAt, take, takeLast and filter are new alongside it, and bySelector returns null instead of an empty NodeList when nothing matches — so passing it to findElements now waits for a match rather than resolving immediately with zero elements.

The wrappers live in their own module, but everything is re-exported from the package root, so imports are unchanged.

Requirements

Browser only — it uses document and requestAnimationFrame, so there is no Node or SSR support.

The package is ESM only ("type": "module"); require() will not work. TypeScript declarations are bundled.

Everything is available from the package root. The list wrappers are also reachable on their own subpath if you would rather be explicit about where they come from:

import { take } from '@mach25/find-elements/select';

Development

npm run build       # compile src/ to lib/
npm test            # run the suite once
npm run test:watch  # re-run on change
npm run typecheck   # type-check src/ and test/, including the type assertions

License

MIT