DriversRecommendedOutdated drivers can make a good PC feel brokenScan driver issues before chasing fixes manually.Scan NowOctober DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsWindows FixRecommendedWindows errors stealing your time? Find the fix fastScan stability, cleanup and performance issues.Fix Now×
Skip to content
browser automation

How to Click an Array of Links Sequentially with Puppeteer

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use a for...of loop and await to handle one link at a time. If your array contains destination URLs, await page.goto(url) for each one. If it contains page elements whose clicks navigate, await each click together with a navigation waiter using Promise.all; register the waiter before the click to avoid a race.

Choose the right approach for your array

First decide what the array contains: destination URL strings or selectors for links already on the page. A URL array usually calls for navigation with page.goto(), not clicking DOM elements. A selector array calls for sequential clicks. In both cases, use a sequential loop so you can finish processing one destination before starting the next.

What the array contains Pattern What to wait for
Destination URLs for...of with await page.goto(url) Navigation completion, then any page-specific condition needed for your work
DOM links that navigate the current page Sequential locator clicks Promise.all([page.waitForNavigation(), locator.click()])
Links that update content without document navigation Sequential clicks The resulting selector, response, or page state—not a navigation event
Elements that appear or move dynamically Puppeteer locator Locator readiness checks before the action

Visit an array of destination URLs

When the array already contains destinations, navigate to each URL directly. This is the clearest pattern for visiting known pages and ensures that the next navigation does not start until processing for the current page has finished.

const urls = [
  'https://example.com/first',
  'https://example.com/second',
  'https://example.com/third',
];

for (const url of urls) {
  await page.goto(url);

  // Read or process this page before moving to the next URL.
  const title = await page.title();
  console.log(url, title);
}

The loop is sequential because each await pauses iteration until that operation settles. If a page requires additional time for client-side content to appear after its document loads, wait for a meaningful condition—such as a selector your processing depends on—before continuing. A completed navigation alone does not prove that every site-specific widget or asynchronous update is finished.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Set up the browser and page

Puppeteer controls Chrome or Firefox through its supported browser protocols. The project’s getting-started guide describes the basic workflow as launching or connecting to a browser, creating pages, and manipulating them through the API: Puppeteer getting started.

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  try {
    const page = await browser.newPage();
    const urls = ['https://example.com/first', 'https://example.com/second'];

    for (const url of urls) {
      await page.goto(url);
      console.log(await page.title());
    }
  } finally {
    await browser.close();
  }
})();

This example uses CommonJS syntax. If your project is configured for ECMAScript modules, use import puppeteer from 'puppeteer'; instead of require. Match the setup and API behavior to the Puppeteer version installed in your project; the examples here are not pinned to a package version.

Click DOM links one at a time when each click navigates

When you need to activate actual links on the current page, wait for navigation and click concurrently. The waiter must be created before the click begins; otherwise a fast navigation could occur before Puppeteer starts listening for it.

const linkSelectors = ['a#first', 'a#second', 'a#third'];

for (const selector of linkSelectors) {
  const [response] = await Promise.all([
    page.waitForNavigation(),
    page.locator(selector).click(),
  ]);

  // The navigation completed; process this destination before the next click.
  console.log('Navigated to:', page.url());
}

This pattern assumes each click navigates the current page. If the next selector only exists on the original page, it will not necessarily be available after the first navigation. In that case, collect the hrefs first and loop over those URLs, or return to the source page between clicks. Do not carry an element handle from an old document through a full navigation; query the appropriate element again in the current page.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use a locator for readiness

Puppeteer’s locator guide says locators automatically wait for an element to be present and in a suitable state for an action. For clicks, the documented checks include viewport position, visibility, enabled state, and a stable bounding box across consecutive animation frames. This helps avoid acting on an element before it is ready: Puppeteer page interactions.

For navigation-triggering clicks, the locator does not replace the navigation wait. Keep both parts of the pattern:

await Promise.all([
  page.waitForNavigation(),
  page.locator('a.next-page').click(),
]);

Lower-level selector click alternative

page.click(selector) remains a documented option. The Page API says it finds a matching element, scrolls it into view if needed, and clicks its center; it throws if no matching element exists. Pair it with the same pre-registered navigation waiter:

await Promise.all([
  page.waitForNavigation(),
  page.click('a.next-page'),
]);

The official Page API warns that if click() triggers navigation and the separate page.waitForNavigation() promise is registered afterward, a race can cause unexpected results: Puppeteer Page API. The Frame API describes the same synchronization concern: Puppeteer Frame API.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Extract hrefs before navigating when the source page has all links

If the page contains a repeated set of links and you intend to visit each destination, extract their href values while the source document is still loaded. Then navigate through the resulting strings. This avoids depending on old element handles after the document changes.

const hrefs = await page.$$eval('a.product-link', links =>
  links.map(link => link.href)
);

for (const url of hrefs) {
  await page.goto(url);
  // Process the destination here.
}

$$eval evaluates a function against the matched elements in the page and returns the result to your script. The Page API documents this method as a way to work with all matched elements: Puppeteer Page API. Extract only the values your later steps need; once navigation replaces the document, its DOM elements are no longer the current page’s elements.

Handle links that do not cause a document navigation

Some links or controls use client-side routing, update part of the page, open a new tab, or trigger a request without navigating the current page. In these cases, page.waitForNavigation() may not describe completion and can wait until its timeout. Wait for the outcome that matters to your task instead: a destination selector appearing, a known response completing, a URL changing, or a new page being created.

Client-side content update

For an in-place update, click and then wait for the resulting state. Use a selector that indicates the new content is ready, rather than an arbitrary delay.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
await page.locator('button.load-next').click();
await page.waitForSelector('.results-loaded');
// Process the updated results.

If the next iteration depends on a request rather than a visible element, use an appropriate response or request condition for the site. The correct condition is page-specific: the Puppeteer API behavior does not establish what event a particular website uses to signal completion.

New tab or popup

If activation opens a separate page, waiting for navigation on the original page is the wrong target. Listen for the browser’s new-page event before the click, then act on the new page. Check the API for the event and method signatures matching your installed Puppeteer version; the API documentation does not establish universal behavior for a particular site or link.

Avoid common sequencing mistakes

  • Do not use forEach with an async callback when you need strict order. forEach does not wait for the promises from its callback. Use for...of and await.
  • Do not start navigation waiting after clicking. Register the waiter and initiate the click in the same Promise.all.
  • Do not assume every click navigates. Choose a state, selector, response, or page-creation wait when that is what the interaction produces.
  • Do not retain element handles across full navigations. Re-query elements in the current document, or extract stable href strings before leaving the source page.
  • Do not replace an observable condition with a guessed delay. Fixed sleeps can be too short on a slow run and unnecessarily long on a fast one.

Troubleshooting

The loop clicks several links too quickly

Check whether the loop uses forEach or fails to await the click. Change it to for...of, and await the click and its completion condition inside each iteration.

The navigation wait times out

The click may update the page in place, route through client-side JavaScript, or open another tab rather than navigating the current page. Wait for the resulting page state or the newly created page instead. Also confirm the intended selector matched the actual link.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The click fails because no element matches

The selector may be wrong, the element may not yet be rendered, or an earlier navigation may have replaced the document. Use a locator when its readiness checks fit the page, verify the selector against the current document, and reacquire the element after navigation.

The script processes a page before its content is ready

page.goto() completing is not a guarantee that all application-specific content has appeared. Wait for the specific selector or response required by the next processing step.

The script keeps using the wrong page

If the destination opened in a new tab or popup, your original page object still refers to the original tab. Detect the new page before clicking and perform subsequent work on that page.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Performance, reliability, and cost

Sequential processing is intentionally serialized: it ensures the current navigation and work complete before the next begins, but it does not run destinations concurrently. That is useful when order matters, when one page’s result determines the next action, or when a single page object is being reused. If independent destinations can be processed in parallel, that is a different design with separate page/browser resource and ordering considerations; the documentation cited here does not establish a universal speed advantage or safe concurrency limit.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Best Value
The SQL Programming Language: .
  • Used Book in Good Condition

For reliability, align each wait with the observed event and avoid unnecessary fixed sleeps. Navigation waits are appropriate for document navigation; locators help ensure a target is actionable; in-place updates need a condition reflecting the resulting state. Site-specific behavior and the installed Puppeteer version determine which exact condition and options fit.

Or skip the browser setup

If your goal is to capture the pages rather than interact with them, ScreenshotNeo takes a screenshot or PDF from one GET request. It can accept cookie or consent banners and remove more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers report the page verdict and billing outcome. Its MCP server provides take_screenshot, get_page_info, and capture_pdf for AI agents using Claude, Cursor, or another MCP client. The free plan includes 1,000 shots per month with no card; paid plans start at $5 for 3,000 shots.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com -o shot.webp

See the ScreenshotNeo API documentation for parameters and formats. Sign up for ScreenshotNeo’s free plan to get 1,000 screenshots a month with no card.

Frequently Asked Questions

Can I use page.goto() instead of clicking each link?

Yes, when your array already contains the destination URLs. Use a sequential for...of loop and await each navigation.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Why use Promise.all for a click and navigation wait?

It starts the navigation waiter before the click can trigger navigation, preventing the race that can occur if you register the waiter afterward.

Does a completed navigation mean the page’s application data is ready?

Not necessarily. Wait for the specific selector, response, or other state your next operation requires.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Leave a Reply

Your email address will not be published. Required fields are marked *

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Read next

Recommended PC Tool
Recommended PC Tool
PC Slower Than It Used to Be?Free scan - under a minute
Outdated Drivers Are Slowing You DownFree scan - exact matches

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.