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

To capture a loading spinner reliably, locate it with a stable Playwright locator, wait until it is visible, and then call the locator’s screenshot() method. For example:

const spinner = page.getByTestId('loading-spinner');
await spinner.waitFor({ state: 'visible' });
await spinner.screenshot({ path: 'spinner.png', animations: 'allow' });

Replace loading-spinner with your application’s actual test ID or another reliable locator. This captures the spinner element rather than the entire page.

Use a locator, an explicit visibility wait, and an element screenshot

A spinner can appear asynchronously and disappear as soon as an operation finishes. Playwright’s locator screenshot waits for actionability, scrolls the element into view, and fails if the element detaches before the image is taken. It does not, however, prove that a spinner that has not appeared yet will appear. Express that condition yourself:

import { test } from '@playwright/test';

test('captures the loading spinner', async ({ page }) => {
  await page.goto('https://example.test');

  const spinner = page.getByTestId('loading-spinner');
  await spinner.waitFor({ state: 'visible' });
  await spinner.screenshot({
    path: 'artifacts/spinner.png',
    animations: 'allow'
  });
});

If the spinner only starts after a click or form submission, perform that action first and then wait for the resulting visible state. Do not wait for the operation to finish before taking the image, because the spinner may already be gone.

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.
#1 Best Overall
Elebase USB to USB C Adapter for iPhone 18 Pro Max,USBC Car Charger Adapter
  • Read Before You Buy — No Video Output: These adapters support charging and USB 2.0 data transfer, but cannot transmit video signals. Except for standard USB webcams (which use USB data only), they are not compatible with HDMI/DisplayPort cables, video-capable USB-C hubs, or docking stations with video output.
  • Convert USB-A Ports to USB-C: Designed to connect USB-C earphones, cables, flash drives, card readers, and other USB-C accessories to standard USB-A ports. Plug-and-play with no drivers or software required.
  • Aluminum Alloy Housing: Built with a sturdy aluminum alloy shell that aids in heat dissipation and protects against daily wear and scratches. Designed to maintain a stable and secure connection.
  • Compact & Travel-Friendly: The ultra-compact design allows the adapter to stay plugged into your device without blocking adjacent ports or adding bulk, reducing wear and tear on your original USB ports.
  • 12-Month Warranty: Backed by a 12-month manufacturer warranty for peace of mind. Designed to meet strict quality control standards for reliable everyday performance.

Choose a locator that identifies the actual spinner

Playwright provides built-in locators such as getByRole, getByText, getByLabel, getByPlaceholder, getByAltText, getByTitle, and getByTestId. Prefer the one that expresses how a user or an accessibility tree identifies the element.

Test ID

const spinner = page.getByTestId('loading-spinner');

A dedicated test ID is often the least ambiguous choice for a purely visual loader. Add it to the spinner element in your application markup.

Accessible role or label

const spinner = page.getByRole('status', { name: /loading/i });
// or
const spinner = page.getByLabel('Loading');

Use the role and accessible name your markup actually exposes. A CSS selector copied from a generated class can break when the UI is rebuilt.

CSS locator as a last resort

const spinner = page.locator('[data-state="loading"] .spinner');

Scope broad selectors to the relevant component and verify that they match exactly the intended element. If multiple elements match, use a more specific locator or an appropriate filter.

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

Wait for the right loading phase

Use locator-based waiting rather than a guessed timeout:

Rank #2
Anker USB-C Hub, 5-in-1 USB Hub for Laptops, 4K HDMI Multiport Adapter
  • 5-in-1 USB-C Hub: Experience comprehensive connectivity featuring a Power Delivery input, two USB-A 2.0 ports, a USB-A 3.0 port, and an HDMI port. (Note: The USB-C power delivery input port is only for connecting an external wall charger to power your laptop and cannot power peripheral devices.)
  • 90W Pass-Through Charging: Achieve optimal charging with 90W pass-through power to your laptop, supported by a total input of 100W, with the hub reserving 10W for operational efficiency. (Note: Wall charger not included.)
  • Quick Data Transfers: Accelerate your productivity with rapid data transfers using a high-speed 5Gbps USB 3.0 port and two 480Mbps USB 2.0 ports.
  • 4K HDMI Display: Enhance your visual experience with a hub capable of delivering 4K resolution at 30Hz in both mirror and extend modes. Please note that this hub is compatible with MacBook (macOS 12 and newer), Windows 10 and 11, ChromeOS, and laptops equipped with DP Alt Mode and Power Delivery. Note: This device is not compatible with Linux.
  • What You Get: Anker USB-C Hub (5-in-1, 4K HDMI), welcome guide, 18-month warranty, and our friendly customer service.
await spinner.waitFor({ state: 'visible' });

This waits for the element to be attached and visible. Other states are useful when your test has a different objective:

  • state: 'attached' waits for a DOM node even if it is not visible.
  • state: 'hidden' waits for the loading indicator to be hidden.
  • state: 'detached' waits until the node is removed.

page.waitForSelector() is discouraged in the Page API documentation in favor of locator waits and web-first assertions. Playwright generally auto-waits before actions, but that auto-wait is not an assertion that a transient spinner has appeared.

Triggering the spinner

const spinner = page.getByTestId('loading-spinner');
await page.getByRole('button', { name: 'Load report' }).click();
await spinner.waitFor({ state: 'visible' });
await spinner.screenshot({ path: 'report-loading.png' });

If the request completes so quickly that the spinner never becomes visible, decide what your application contract should be. You may need to control the test response, add a test-only synchronization hook, or assert the completed state instead. No generic timeout can determine that application-specific intent.

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

Keep animation or freeze it deliberately

animations: 'allow' is Playwright’s documented default and leaves the spinner moving. It is the correct choice when the screenshot must represent the animated loading state:

await spinner.screenshot({ path: 'spinner.png', animations: 'allow' });

With animations: 'disabled', finite animations are fast-forwarded and infinite animations are canceled to their initial state during capture, then played again. A circular loader can therefore look frozen, empty, or unlike what a user sees:

Rank #3
Sale
Anker USB C Hub, 7in1 Multi-Port USB Adapter, 4K@60Hz USBC to HDMI Splitter
  • Sleek 7-in-1 USB-C Hub: Features an HDMI port, two USB-A 3.0 ports, and a USB-C data port, each providing 5Gbps transfer speeds. It also includes a USB-C PD input port for charging up to 100W and dual SD and TF card slots, all in a compact design.
  • Flawless 4K@60Hz Video with HDMI: Delivers exceptional clarity and smoothness with its 4K@60Hz HDMI port, making it ideal for high-definition presentations and entertainment. (Note: Only the HDMI port supports video projection; the USB-C port is for data transfer only.)
  • Double Up on Efficiency: The two USB-A 3.0 ports and a USB-C port support a fast 5Gbps data rate, significantly boosting your transfer speeds and improving productivity.
  • Fast and Reliable 85W Charging: Offers high-capacity, speedy charging for laptops up to 85W, so you spend less time tethered to an outlet and more time being productive.
  • What You Get: Anker USB-C Hub (7-in-1), welcome guide, 18-month warranty, and our friendly customer service.
await spinner.screenshot({
  path: 'spinner-static.png',
  animations: 'disabled'
});

Disable animation only when a deterministic visual baseline is more important than animation fidelity.

Element screenshot versus page screenshot

Method What it captures Best use
locator.screenshot() The matched spinner element One-off evidence or a focused image of the loader
page.screenshot() The viewport or full page, depending on options Spinner plus surrounding layout, overlay, and page context
expect(locator).toHaveScreenshot() A locator image used in a visual assertion Playwright Test visual regression checks

Capture the surrounding page

await page.screenshot({
  path: 'loading-page.png',
  fullPage: false
});

Use a page screenshot when the position of the loader, a dimmed background, or an overlay is part of what you need to inspect. A correct element locator does not make a covered element visible in the resulting image.

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.

Use a visual regression assertion

import { test, expect } from '@playwright/test';

test('spinner visual stays consistent', async ({ page }) => {
  await page.goto('https://example.test');
  const spinner = page.getByTestId('loading-spinner');
  await page.getByRole('button', { name: 'Load report' }).click();
  await spinner.waitFor({ state: 'visible' });
  await expect(spinner).toHaveScreenshot('spinner.png');
});

toHaveScreenshot is provided by the Playwright Test runner. It waits for two consecutive locator screenshots to match before comparing them with the stored expectation, which helps avoid saving a frame while layout is still changing.

Complete TypeScript and JavaScript examples

TypeScript with a controlled response

import { test, expect } from '@playwright/test';

test('captures a report loader', async ({ page }) => {
  await page.route('**/api/report', async route => {
    await new Promise(resolve => setTimeout(resolve, 1500));
    await route.fulfill({
      status: 200,
      contentType: 'application/json',
      body: JSON.stringify({ ok: true })
    });
  });

  await page.goto('https://example.test/report');
  const spinner = page.getByTestId('loading-spinner');
  await page.getByRole('button', { name: 'Load report' }).click();
  await spinner.waitFor({ state: 'visible' });
  await spinner.screenshot({ path: 'artifacts/report-spinner.png' });
  await expect(spinner).toBeVisible();
});

JavaScript

const { test } = require('@playwright/test');

test('captures a loader', async ({ page }) => {
  await page.goto('https://example.test');
  const spinner = page.getByTestId('loading-spinner');
  await page.getByRole('button', { name: 'Load report' }).click();
  await spinner.waitFor({ state: 'visible' });
  await spinner.screenshot({ path: 'spinner.png', animations: 'allow' });
});

Troubleshoot missing or incorrect spinner images

The screenshot is taken before the spinner appears

Symptom: the file is empty, the locator times out, or the captured state is already complete. Fix it by waiting for state: 'visible' immediately after the action that starts loading. If the operation can finish instantly, control the test data or test the completed state instead.

The locator matches nothing or the wrong element

Inspect the rendered accessibility tree and DOM, then choose a stable test ID, role, label, or text locator. Generated class names and selectors copied from a single run are especially fragile. Narrow a locator that matches several nodes.

Rank #4
Sale
UGREEN USB to USB C Adapter Combo 4-Pack, 10Gbps USB C Converter Space Gray
  • Dual Converters, Infinite Potential:Includes 2× USB C male to USB A female adapters and 2× USB A male to USB C female adapters. Perfect for a wide range of uses—tablets with Bluetooth keyboards, expand USB ports on macbook, and more. Two different converters for all your daily needs
  • Next-Level 10Gbps & 3A Charging: No more slow 480Mbps, this usb to usb c adapter has a transfer speed of up to 10Gbps, allowing you to do more transferring in less time. This usb adapter fits both USB A and USB C charger, supporting up to 3A fast charging
  • Upgraded Exquisite Craftsmanship: With an aluminum alloy housing and metal connector, the usbc to usb adapter is extremely durable and sturdy. Rigorously tested to withstand more than 10,000 times of plugging and unplugging, ensuring long-lasting performance
  • Broad Compatible: The usb c to usb adapter widely supports all USB C/ USB A devices like laptops, tablets, cellphones, car chargers, and phone chargers. Such as compatible with MacBook Pro/Air 2023/2022, Thunderbolt 4/3 Devices,Apple MagSafe Watch 9/8/7/SE/Ultra, iPad Pro 2022/2021, Samsung Galaxy S23/S20/S10, and iPhone 17/16/15 Pro. Plug and play
  • Please Note: To reach 10Gbps speed, keep the cable under 3.3 ft. For USB A Male to USB C adapters, try flipping the USB C connector. USB C Male to USB A adapters support bidirectional 10Gbps transfer within 3.3 ft

The spinner detaches during capture

Locator screenshots throw when the element is removed before capture. Coordinate the screenshot with the loading phase: trigger loading, wait for visibility, and capture without an unnecessary intermediate delay. If the product intentionally replaces the node, add a stable loading container or a test hook.

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

An overlay covers the spinner

An element can satisfy the locator while another element paints over it. Capture the page to inspect stacking and overlays, or fix the application’s z-index and visibility behavior. Do not use forced interaction as a substitute for a screenshot that should show what a user sees.

The animation looks frozen

Check that you did not pass animations: 'disabled'. Infinite animations are canceled to their initial frame for that capture mode. Use animations: 'allow' when motion is part of the evidence.

The visual assertion is flaky

Wait for the spinner’s visible state, keep viewport and browser settings consistent, and avoid capturing while surrounding layout is still changing. The assertion’s consecutive-image stabilization helps, but it cannot repair an unstable application state or a locator that changes between frames.

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

Reliability and maintenance checklist

  • Give the loading component a stable semantic locator or test ID.
  • Trigger the exact action that starts loading before waiting.
  • Wait on the UI state, not an arbitrary sleep.
  • Choose allow for motion fidelity and disabled only for a deliberate static baseline.
  • Use an element screenshot for the spinner and a page screenshot for context.
  • Keep the screenshot path in a predictable artifacts directory.
  • For regression tests, use toHaveScreenshot in Playwright Test and keep rendering conditions consistent.

Or skip the browser setup

If you need a URL screenshot rather than a Playwright test artifact, ScreenshotNeo provides a GET API and an MCP server. It accepts cookie and consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be disabled. 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. Its MCP tools—take_screenshot, get_page_info, and capture_pdf—work with Claude, Cursor, and other MCP clients.

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

The API can capture PNG, JPEG, or WebP. Options include full-page and CSS-selector element capture, dark mode, device presets or custom viewports, retina scale, PDF paper and page settings, custom CSS and JavaScript, clicks, waits, blocked resources, headers, cookies, user agent, authorization, timezone, geolocation, transparent backgrounds, resizing, chosen cache TTL, signed image links, asynchronous webhooks, bulk capture of up to 100 URLs per call, usage data, and an OpenAPI specification.

Best Value
Anker USB C Hub, 5-in-1 USBC to HDMI Splitter with 4K Display
  • 5-in-1 Connectivity: Equipped with a 4K HDMI port, a 5 Gbps USB-C data port, two 5 Gbps USB-A ports, and a USB C 100W PD-IN port. Note: The USB C 100W PD-IN port supports only charging and does not support data transfer devices such as headphones or speakers.
  • Powerful Pass-Through Charging: Supports up to 85W pass-through charging so you can power up your laptop while you use the hub. Note: Pass-through charging requires a charger (not included). Note: To achieve full power for iPad, we recommend using a 45W wall charger.
  • Transfer Files in Seconds: Move files to and from your laptop at speeds of up to 5 Gbps via the USB-C and USB-A data ports. Note: The USB C 5Gbps Data port does not support video output.
  • HD Display: Connect to the HDMI port to stream or mirror content to an external monitor in resolutions of up to 4K@30Hz. Note: The USB-C ports do not support video output.
  • What You Get: Anker 332 USB-C Hub (5-in-1), welcome guide, our worry-free 18-month warranty, and friendly customer service.

See the ScreenshotNeo API documentation for parameter details. A direct call is:

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

Equivalent clients:

import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

One thousand screenshots per month are free with no card. Paid plans start at $5 for 3,000 screenshots, and every feature is included on every plan. Create a free ScreenshotNeo account.

Frequently Asked Questions

Can I capture a spinner that is already hidden?

No. A visibility wait requires the element to become visible. Trigger the loading state first or test the application’s completed state instead.

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

Should I use a fixed timeout before the screenshot?

Use a locator wait for the visible loading state. A fixed delay can be too short on a slow run and unnecessarily long on a fast one.

Why does my spinner screenshot contain only its initial frame?

The capture likely used animations: 'disabled'. Use animations: 'allow' when the moving indicator must remain representative.

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.