@technical-1/interaction-helpers
v1.1.1
Published
Hardened safeClick/safeType/waitAndGet/scroll page helpers throwing typed @technical-1/core errors
Readme
@technical-1/interaction-helpers
Hardened page interaction helpers. Same ergonomics as raw Puppeteer calls but
they throw the typed @technical-1/core error hierarchy (e.g.
SelectorNotFoundError carrying the selector) instead of opaque timeouts. You
inject the Page (this package imports only puppeteer-core types).
Convenience wrapper. This package wraps puppeteer-core's
waitForSelector,click,type, and related methods with the suite's typed error hierarchy (e.g.SelectorNotFoundError) instead of opaque timeouts. It doesn't add new interaction capability beyond those puppeteer-core primitives — it exists so the rest of@technical-1/*surfaces actionable, typed errors through one consistent DI seam. If you only need click/type/wait, you can use the puppeteer-core methods directly.
import { safeClick, waitAndGet } from "@technical-1/interaction-helpers";
await safeClick(page, "button#go");
const heading = await waitAndGet(page, "h1");Surface
safeClick/safeType/waitAndGet— wait for a visible selector, then act; throwSelectorNotFoundErroron a miss. Accept aPageor aFrame.resolveFrame(page, { name | url | selector })— locate an iframe'sFramefor frame-scoped interaction.uploadFile(target, selector, files)— set files on a plain<input type=file>.targetis the injectedPageorFrame(same role aspagein the other helpers; namedtargetbecause aFrameis equally valid).uploadViaFileChooser(page, triggerSelector, files)— drive a styled upload button through the native file chooser.scroll(page, { by? })— single scroll (to bottom, or by N px).autoScroll(page, { maxScrolls?, step?, settleMs?, itemSelector? })— loop until lazy content stops growing.pressKey(page, key)/pressShortcut(page, modifiers, key)— press Enter/Escape/Tab or a Ctrl/Cmd/Shift/Alt + key combo.waitForFunction(page, fn, { timeout?, polling?, args? })— poll an in-page predicate until truthy, resolving itsJSHandle; throwsTimeoutErroron a poll timeout. Accepts aPageor aFrame.readClipboard(page)/writeClipboard(page, text)— read/write the clipboard vianavigator.clipboard, granting the clipboard permission on the page's own origin. Requires a securehttp(s)origin (throwsConfigErroronabout:blank/file:).
All helpers take an optional injected logger and emit a "step" log line; all thrown failures are @technical-1/core typed errors.
Iframe-scoped interaction
safeClick / safeType / waitAndGet / scroll / autoScroll / uploadFile
accept a Page or a Frame, so resolve the frame once and interact within it:
import { resolveFrame, safeClick } from "@technical-1/interaction-helpers";
const frame = await resolveFrame(page, { name: "checkout" });
// or: { url: /stripe\.com/ } or { selector: "iframe#payment" }
await safeClick(frame, "button#pay");File uploads
import {
uploadFile,
uploadViaFileChooser,
} from "@technical-1/interaction-helpers";
// Plain hidden <input type=file> (Page or Frame):
await uploadFile(page, "input[type=file]", "./avatar.png");
// Styled button backed by the native file chooser:
await uploadViaFileChooser(page, "button#upload", [
"./a.pdf",
"./b.pdf",
]);Infinite scroll
import { autoScroll } from "@technical-1/interaction-helpers";
// Loop until the item count stops growing (or maxScrolls is hit):
const iterations = await autoScroll(page, {
itemSelector: ".feed-item",
maxScrolls: 20,
settleMs: 800,
});Keyboard
import { pressKey, pressShortcut } from "@technical-1/interaction-helpers";
await pressKey(page, "Enter");
await pressShortcut(page, "Control", "KeyA"); // Ctrl+A
await pressShortcut(page, ["Meta", "Shift"], "KeyZ"); // Cmd+Shift+ZWait for a predicate / clipboard
import { waitForFunction, readClipboard, writeClipboard } from "@technical-1/interaction-helpers";
await waitForFunction(page, () => document.querySelectorAll(".row").length >= 10, {
timeout: 10_000,
polling: "mutation",
});
await writeClipboard(page, "prefilled");
const copied = await readClipboard(page); // e.g. verify a "copy link" button