Skip to content

Navigation and Input

  1. Navigate to a URL.
  2. Wait for the document to be ready.
  3. Find or wait for the element.
  4. Click, type, press keys, or scroll.
  • navigate, goBack, goForward, reload
  • waitForDocumentReady
  • waitForElement
  • click, clickAt, type, press, shortcut
  • fill, selectOption, setChecked
  • dragFromToSelectors, dragFromToPoints, dragStartAt, dragMoveTo, dragReleaseAt
  • scroll, scrollTo, resize

For full locator patterns, see Locators.

All navigation commands support the same NavigateOptions shape:

  • navigate(url, options?)
  • goBack(options?)
  • goForward(options?)
  • reload(options?)
  • timeoutMs: Maximum time to wait before the navigation promise rejects.
  • waitUntil: Which navigation milestone should resolve the promise.
  • "load": Best default for normal HTML pages when you want the browser load event.
  • "domcontentloaded": Good when you only need the DOM parsed and want to continue sooner.
  • "frameNavigated": Good for early main-frame commit, especially for history navigations or unusual documents.
  • "responseReceived": Good for streaming or network-oriented flows where the response matters more than page lifecycle completion.
  • "eventSourceMessageReceived": Use only when the page creates an EventSource request and you want to wait for the first SSE message.
await view.navigate("https://example.com/dashboard", {
waitUntil: "domcontentloaded",
timeoutMs: 10000,
});
await view.goBack({
waitUntil: "frameNavigated",
timeoutMs: 10000,
});
await view.goForward({
waitUntil: "frameNavigated",
timeoutMs: 10000,
});
await view.reload({
waitUntil: "responseReceived",
timeoutMs: 10000,
});
  • Use "load" when the page behaves like a normal website and you want the safest default.
  • Use "domcontentloaded" when you plan to wait for a specific element next and do not need all subresources first.
  • Use "frameNavigated" for back/forward style flows or when you only need to know the main document changed.
  • Use "responseReceived" for redirects, streaming responses, or pages where lifecycle events may be delayed or never complete.

For top-level streaming responses such as text/event-stream, load and domcontentloaded may time out while frameNavigated or responseReceived still succeed.

Use semantic locators first, then click/type by handle.

await view.navigate("https://example.com/login");
await view.waitForDocumentReady({ state: "complete", timeoutMs: 15000 });
const username = await view.getByLabel("Username", { exact: true });
const password = await view.getByPlaceholder("Password", { exact: true });
const submit = await view.getByRole("button", { name: "Sign in", exact: true });
if (!username || !password || !submit) throw new Error("login controls not found");
await view.click(username);
await view.type("demo-user", { perCharacterDelayMs: 0 });
await view.click(password);
await view.type("demo-password", { perCharacterDelayMs: 0 });
await view.click(submit);

You can mix in getByText, getByTitle, getByDataAttribute, and getByXPath when needed.

getFrame() gives you chainable locator methods inside the matched frame.

const frame = await view.getFrame("iframe#payment-frame");
if (!frame) throw new Error("payment frame not found");
const card = await frame.getByLabel("Card number", { exact: true });
const pay = await frame.getByRole("button", { name: "Pay now", exact: true });
if (!card || !pay) throw new Error("payment controls not found");
await view.click(card);
await view.type("4242 4242 4242 4242", { perCharacterDelayMs: 0 });
await view.click(pay);

For repeated actions on the same region of the page, resolve an element once and click by reference.

const card = await view.getElementById("checkout-card");
if (!card) throw new Error("card not found");
const submit = await view.querySelectorWithin(card, "button[type='submit']");
if (!submit) throw new Error("submit button not found");
await view.click(submit);
await view.click(submit, { delayMs: 250 });
await view.disposeElement(submit);
await view.disposeElement(card);

You can still use selector and coordinate clicks when that is simpler.

Use form helpers to set values directly while still dispatching form events.

await view.fill("#name", "Alice");
await view.fill("#email", "alice@example.com");
await view.selectOption("#role", "admin");
await view.setChecked("#agree", true);

Use explicit press for full control, or shortcut for compact combo syntax.

await view.press("Enter");
await view.shortcut("Ctrl+C");
await view.shortcut(["Ctrl+A", "Escape", "Tab"]);

Element-to-element drag:

await view.dragFromToSelectors("#todo-card", "#done-column", {
steps: 16,
durationMs: 200,
});

Coordinate drag:

await view.dragFromToPoints(120, 180, 360, 180, {
steps: 10,
durationMs: 120,
});

Controlled drag lifecycle when you need custom release timing:

await view.dragStartAt(120, 180);
await view.dragMoveTo(360, 180, { steps: 8, durationMs: 100 });
await view.dragReleaseAt();
const rows = await view.querySelectorAll("table#orders tbody tr");
for (const row of rows) {
const status = await view.getAttribute(row, "data-status");
if (status === "pending") {
await view.click(row);
}
await view.disposeElement(row);
}