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

To load a Vue.js and Vuetify app with Puppeteer, navigate to the app with page.goto(), then wait for a rendered element or another app-specific readiness signal. Do not treat the existence of #app as proof that Vue has finished rendering: it may be an empty mount container. A short network-idle wait can help settle late resources, but it should usually supplement—not replace—a check that the UI or data you need is ready.

Load the app and wait for a rendered Vuetify element

Use domcontentloaded to reach the initial document state, then wait for a child element that the running app renders. The selector below is illustrative; inspect your own application and substitute a stable element such as its toolbar, page heading, or main content container.

import puppeteer from 'puppeteer';

const browser = await puppeteer.launch();

try {
  const page = await browser.newPage();

  const response = await page.goto('http://localhost:5173/', {
    waitUntil: 'domcontentloaded',
    timeout: 30000,
  });

  console.log('HTTP status:', response?.status());
  console.log('Page URL:', page.url());

  await page.waitForSelector('#app .v-application', {
    visible: true,
    timeout: 15000,
  });

  // Optional: allow a short quiet period for late-loading resources.
  await page.waitForNetworkIdle({
    idleTime: 500,
    timeout: 10000,
  });

  await page.screenshot({ path: 'vuetify.png', fullPage: true });
} finally {
  await browser.close();
}

This example uses ES modules. Save it as .mjs or set "type": "module" in the project’s package.json. Replace the local URL with the app address reachable from the environment running Puppeteer. The optional network-idle wait has a finite timeout so a persistent connection or recurring request cannot hold the script indefinitely.

Why waiting for #app alone can capture a blank page

Vue’s createApp() creates an application instance; calling app.mount() mounts it into the selected element and renders the component tree. That makes a rendered child a more meaningful readiness condition than the mount container itself. See the Vue Application API.

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

A typical Vuetify setup imports its styles, creates a Vuetify instance, registers it with the Vue app, and then mounts the app:

import { createApp } from 'vue';
import { createVuetify } from 'vuetify';
import 'vuetify/styles';
import App from './App.vue';

const vuetify = createVuetify();

createApp(App)
  .use(vuetify)
  .mount('#app');

Vuetify’s installation guide documents this registration-and-mount pattern. Depending on the app, the Vuetify shell, styles, API data, and individual components may become ready at different times. Pick a signal that matches the task: a shell may be enough for a layout screenshot, while a populated table may be necessary for data extraction.

Choose a wait that matches what “ready” means

Puppeteer provides separate state-based waits for selectors, functions, navigation, requests, responses, frames, and network activity. Its page interactions guide describes these as distinct tools; choose the narrowest condition that establishes the state you actually need.

Readiness condition Use it when Important limitation
page.waitForSelector() A specific visible component proves the page is usable, such as a toolbar, form, or card. The selector must represent readiness for this task; a generic shell may appear before its data.
page.waitForFunction() Readiness depends on a value, item count, app-defined flag, or other condition in the page. Make the condition specific and ensure it can become true; otherwise it times out.
page.waitForResponse() A known API response gates the UI or contains the data being tested. A response arriving does not guarantee that Vue has finished rendering it.
page.waitForNetworkIdle() You need a brief quiet period after the relevant UI appears. Analytics, polling, or WebSockets can keep activity going; network quiet alone does not prove the intended UI is ready.
page.waitForNavigation() An action causes a new document navigation. Vue Router transitions commonly update the route without loading a new document.

The Puppeteer API describes page.waitForNetworkIdle() as a method that “Waits for the network to be idle.” It also waits at least the configured idle time. See the Page.waitForNetworkIdle API reference. Treat it as a settling window, not as a universal application-ready event.

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

Wait for a component or overlay

For a visible component, use a stable selector tied to the page’s purpose:

await page.waitForSelector('[data-testid="orders-table"]', {
  visible: true,
  timeout: 15000,
});

If the app displays a loading overlay, waiting for it to disappear can be more accurate than waiting for a broad shell:

await page.waitForSelector('.loading-overlay', {
  hidden: true,
  timeout: 15000,
});

Confirm that the selector matches the actual app and that the overlay is present when loading begins. A selector that never exists can behave differently from one that exists and becomes hidden, so inspect the page’s markup and loading lifecycle.

Wait for application state

When a specific state matters, express it as a predicate. For example, if the page exposes an app-owned readiness flag:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
await page.waitForFunction(() => window.__APP_READY__ === true, {
  timeout: 15000,
});

This only works if your app deliberately sets that flag. For a known rendered count, test the DOM instead:

await page.waitForFunction(() => {
  const rows = document.querySelectorAll('[data-testid="orders-table"] tbody tr');
  return rows.length > 0;
}, { timeout: 15000 });

Use an application-owned test attribute or explicit readiness contract where possible, rather than coupling automation to incidental styling classes that may change.

Wait for the API request that drives the view

If a known request gates the component, wait for its response and then assert the rendered result:

const ordersResponse = page.waitForResponse(response =>
  response.url().includes('/api/orders') && response.status() === 200
);

await page.goto('http://localhost:5173/', { waitUntil: 'domcontentloaded' });
await ordersResponse;
await page.waitForSelector('[data-testid="orders-table"] tbody tr', {
  visible: true,
  timeout: 15000,
});

Arm the response wait before the action or navigation that triggers the request; otherwise, a fast response might arrive before Puppeteer starts waiting. A successful HTTP response establishes that the request completed, not necessarily that the app accepted its data or rendered it.

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

Handle Vue Router transitions without waiting for document navigation

A Vue Router link may change the URL and rendered view while keeping the same document alive. In that case, wait for a route-specific element or URL condition, not page.waitForNavigation(). For example:

await page.click('a[href="/reports"]');
await page.waitForSelector('[data-testid="reports-page"]', {
  visible: true,
  timeout: 15000,
});

If a click causes a real document navigation, arm the navigation wait before clicking:

await Promise.all([
  page.waitForNavigation({ waitUntil: 'domcontentloaded' }),
  page.click('a[href="/next"]'),
]);

The ordering matters: Puppeteer’s Page API warns that incorrectly arranging a click and a separately awaited navigation promise can create a race. For an in-app route transition, replace the navigation wait with the route’s rendered-state assertion.

Install Puppeteer and ensure a browser is available

Puppeteer controls Chrome or Firefox using the DevTools Protocol or WebDriver BiDi and runs headless by default; see the Puppeteer documentation. The package you install affects whether a browser is downloaded:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  • npm i puppeteer downloads a compatible Chrome as part of the documented installation.
  • npm i puppeteer-core does not download a browser. Use it when you provide and configure the browser runtime separately.

If installation scripts are blocked by your package manager and the browser is missing, Puppeteer documents npx puppeteer browsers install as the manual installation remedy. See the installation guide. In CI or a container, check both that the browser binary exists and that the runtime can launch it; installing the JavaScript package alone is not always sufficient.

Debug blank pages, premature screenshots, and hangs

Capture diagnostics before navigation so they include errors emitted while the app loads:

page.on('console', message => {
  console.log('BROWSER:', message.type(), message.text());
});

page.on('pageerror', error => {
  console.error('PAGE ERROR:', error);
});

page.on('requestfailed', request => {
  console.error('REQUEST FAILED:', request.url(), request.failure()?.errorText);
});

Then check the failure according to the symptom:

  • The script exits after finding #app, but the screenshot is empty. Wait for a rendered child, such as a page-specific Vuetify component, rather than the mount container.
  • The shell renders, but content is missing. Check browser console output and failed requests, then wait for the API response and the resulting UI element. Verify that the API is reachable from the browser process.
  • Styles or fonts are missing. Look for failed stylesheet, font, or bundle requests. Confirm the Vuetify style import and app build are present, and that the resources are reachable at the URLs the browser requests.
  • waitForNetworkIdle() times out. The page may poll, load analytics, or maintain a WebSocket. Use a finite idle timeout and a more specific UI or API condition instead of requiring the whole page to become quiet.
  • The wait times out despite an apparently loaded page. Confirm the selector exists in the current route, is visible if visible: true is required, and is not inside a different frame. Increase a timeout only when the app legitimately needs longer; do not mask a selector or application error.
  • The route changed but the navigation wait failed. If Vue Router handled the transition in the same document, wait for the new route’s content or URL rather than document navigation.
  • The browser will not launch. Check whether you installed puppeteer or puppeteer-core, whether installation scripts ran, and whether the corresponding browser is installed. Use the documented browser-install command if needed.

For a blank or premature page, also record the response returned by page.goto() and page.url(). Use page.waitForRequest() or page.waitForResponse() when you need to isolate the exact API call that controls rendering.

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

Or skip the browser setup

If your goal is simply to capture the rendered site, ScreenshotNeo is a website screenshot API and MCP server for developers. Its one-request endpoint can return PNG, JPEG, WebP, or PDF; it also provides an MCP server with take_screenshot, get_page_info, and capture_pdf tools for AI agents.

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.

For example, this cURL request saves a WebP screenshot of the target URL. See the ScreenshotNeo API documentation for parameters and response details.

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

ScreenshotNeo accepts cookie and consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; those steps can be turned off. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing status. The free plan includes 1,000 screenshots per month without a card; paid plans start at $5 for 3,000 shots.

Sign up for ScreenshotNeo’s free plan to try 1,000 screenshots a month with no card.

Frequently Asked Questions

Does Puppeteer wait for Vue to finish rendering after `page.goto()`?

No. `page.goto()` waits for the navigation condition you request; use a separate app-specific selector, function, or response wait to establish that the needed Vue UI is ready.

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.

Can I use Puppeteer with a Vue Router URL change?

Yes. For an in-app route transition that keeps the document alive, wait for the route’s rendered element or URL rather than a document navigation.

Why can an app’s network never become idle?

Polling, analytics, and WebSocket connections can keep network activity going. Use a finite timeout and wait for the relevant component or API-driven state instead.

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.