ScreenshotNeo

BlogHow-to

How to Capture a Shopify Product Page Screenshot with Playwright and Wait for Price Updates

Select a Shopify variant, wait for its visible price to update with Playwright, then capture a reliable screenshot without arbitrary sleeps.

By the ScreenshotNeo team4 October 20268 min read

To capture a Shopify product page after a variant changes its price, select the variant, wait for the storefront’s visible price to equal the expected value with a Playwright web-first assertion, and only then take the screenshot. The price selector, variant control, currency, and formatting depend on the store’s theme; inspect the actual storefront rather than assuming Shopify provides universal markup.

Use page.screenshot() for a one-off image. In a Playwright Test visual regression test, use expect(page).toHaveScreenshot() after the price assertion. Avoid using networkidle or a fixed sleep as proof that the price update finished: the assertion checks the state the screenshot actually depends on. See the official Playwright Page API, assertions guide, and Shopify’s variant documentation.

1. Inspect the storefront and choose reliable locators

Shopify themes can render product forms and variant selectors differently. Find the control a shopper uses and the price element that is visible after selection. Prefer, in order:

  • An accessible label or role, such as a labeled select or named button.
  • A stable test ID intentionally provided by the theme.
  • A specific CSS locator verified against the target theme.

Do not copy the placeholder selectors below as if they were Shopify defaults. Inspect the page, verify the locator resolves to the currently displayed price, and check whether the theme uses a select, radio buttons, swatches, or another control. If price is displayed in more than one place, scope the locator to the product’s main price area.

2. Install Playwright Test

For a new Node.js project, install Playwright Test and its browser:

npm init -y
npm install --save-dev @playwright/test
npx playwright install chromium

Save the following as shopify-price.spec.ts. Replace the URL, option label, test ID, and expected price with values from the store. This example assumes the variant control is a labeled native select and the theme exposes a product-price test ID; neither is universal.

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

test('captures the selected Shopify variant price', async ({ page }) => {
  await page.goto('https://store.example/products/example');

  // Store-specific: use the real accessible label and variant option.
  await page.getByLabel('Size').selectOption({ label: 'Large' });

  // Store-specific: assert the shopper-visible price and exact formatting.
  const price = page.getByTestId('product-price');
  await expect(price).toHaveText('$29.00');

  await page.screenshot({ path: 'product-price.png', fullPage: true });
});

Run the test with npx playwright test shopify-price.spec.ts. The assertion retries while the page changes and fails if the expected text does not appear before its timeout. Taking the screenshot after the assertion means the test cannot silently capture the old price when the update never arrived.

3. Pick the right price assertion

Use the narrowest assertion that represents the expected storefront state:

  • toHaveText('$29.00') checks exact visible text. Account for whitespace only if the theme includes it in the locator’s text.
  • toContainText('29.00') is useful when the element includes additional text, but can pass on a partial or unintended match.
  • toHaveText(/29[.,]00/) can allow known formatting variations. Make the pattern specific enough to distinguish the intended price.
  • toBeVisible() alone does not prove the price changed. Pair it with a text assertion.

Currency symbols, decimal separators, spacing, and localized number formats vary by store and locale. Read the displayed value from the target storefront and assert its actual formatting. If the page has both a current price and a compare-at price, locate and assert the current-price element specifically.

When the selected variant affects availability or compare-at pricing, assert those visible outcomes too. Shopify’s theme guidance expects the displayed product information to reflect the selected variant, including price and sold-out messaging where applicable. For example, use a separate locator for the availability message and assert its expected text before capture. This also checks what a shopper sees rather than relying on an internal variant value alone.

4. Adapt the interaction to the theme

For a native select, use selectOption as in the main example. For radio buttons or variant buttons, click the option by its accessible name, then make the same price assertion:

await page.getByRole('radio', { name: 'Large' }).check();
await expect(page.getByTestId('product-price')).toHaveText('$29.00');
await page.screenshot({ path: 'product-price.png', fullPage: true });

If the theme uses a button or swatch instead of a radio input, inspect its role and accessible name and use the matching locator, for example getByRole('button', { name: 'Large' }).click(). Do not select a variant by a guessed position when a name or stable identifier is available; option ordering can change.

If the store requires a consent choice, region, or other storefront setup before the product form is usable, perform that setup explicitly in the test before selecting the variant. Keep setup separate from the price check so a consent dialog or region prompt is not mistaken for a failed price update.

5. One-off screenshot or visual regression

One-off capture

For a bug report or review artifact, use page.screenshot() after the expected price appears:

await expect(page.getByTestId('product-price')).toHaveText('$29.00');
await page.screenshot({ path: 'product-price.png', fullPage: true });

Use fullPage: true when you need the entire document. Omit it for a viewport screenshot. A full-page image can include content below the product and may be taller or slower to render than a viewport capture.

Visual regression

For a baseline comparison in Playwright Test, use a screenshot assertion after waiting for the price:

await expect(page.getByTestId('product-price')).toHaveText('$29.00');
await expect(page).toHaveScreenshot('shopify-product.png');

Playwright’s screenshot assertion waits for two consecutive page screenshots to match before comparing the result with the expected snapshot. That stabilization is separate from the price assertion: keep the explicit price check so the test explains which product state it requires. If only the price area matters, Playwright Test also supports locator screenshot assertions; use a locator for the price or product region and give the assertion a meaningful snapshot name.

Generate and compare baselines in a consistent environment. Browser version, operating system, fonts, rendering settings, and headless mode can affect screenshot output. A baseline created on one environment can differ from a run on another even when the page behavior is correct.

6. Waiting, timeouts, and readiness

Playwright’s web-first assertions keep checking the locator until the condition passes or the assertion timeout is reached. The locator is evaluated against the current page, which suits themes that replace or update price markup dynamically. Set a test or assertion timeout to fit the store’s normal response time, but keep it bounded so a genuine failure is reported promptly.

A page navigation event only tells you about the chosen navigation lifecycle. It does not prove that client-side variant logic has updated the price. Likewise, a fixed waitForTimeout can be too short on a slow run and unnecessarily long on a fast one. Playwright marks networkidle as discouraged for test readiness and recommends web assertions for the expected state. Wait for the actual price, not an indirect proxy.

7. Troubleshooting

Symptom Likely cause Fix
Price assertion times out The selection did not take effect, the option is unavailable, or the expected price/format is wrong. Check the selected option and storefront manually. Confirm the variant exists and is available, then assert the exact visible value for that store.
Locator matches multiple prices The page has a sticky price, compare-at price, recommendation card, or duplicate responsive markup. Scope the locator to the main product form or price container. Prefer a theme-owned test ID or accessible locator that uniquely identifies the current price.
Assertion passes against the wrong price A broad text locator or partial match found a price elsewhere on the page. Use a specific locator and exact expected text. Avoid broad page-wide text assertions.
selectOption fails The theme uses buttons, radios, or custom swatches rather than a native select, or the label is different. Inspect the actual control and use its role and accessible name with the matching Playwright action.
Screenshot shows an old price The screenshot was taken before the update assertion, or the assertion targeted stale or hidden markup. Place the screenshot directly after the visible price assertion and verify the locator points to the rendered current price.
Price is correct but screenshot comparison fails Other dynamic content or environment differences changed the image. Keep browser and host settings consistent, stabilize content that is irrelevant to the test, and compare the specific region when appropriate.
Test waits for networkidle but still captures incorrectly Network quiet does not establish that the theme has committed the expected displayed state. Replace the readiness check with an assertion for the expected visible price.

8. Performance, reliability, and cost

The main reliability gain comes from waiting on the meaningful state. Arbitrary sleeps add time to every run and still cannot guarantee readiness; a retried assertion can proceed as soon as the correct price appears and fail with a useful locator error if it does not. Keep locators specific, avoid unnecessary full-page captures when a viewport or product region is enough, and reuse the same browser setup for a suite rather than repeatedly installing or launching browsers per test.

Browser screenshots have no per-capture API charge, but your CI or machine pays the cost of browser execution, storage, and test runtime. Visual baselines also need maintenance as the theme intentionally changes. No universal wait duration or performance benchmark applies across stores; measure the actual storefront and CI environment.

Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server from Yorker Media. For a screenshot of a Shopify product page, make one GET request with the product URL. See the ScreenshotNeo API documentation for request options. This captures the page as it loads; it does not select a Shopify variant or wait for a particular price state, so use the Playwright flow above when the capture must follow a specific variant update.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://store.example/products/example -o product.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://store.example/products/example"}, timeout=90)
open("product.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://store.example/products/example' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
  • Cookie and consent banners, newsletter popups, and chat widgets are removed before the shot; each step can be turned off.
  • Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed; response headers identify the page verdict and billing status.
  • An MCP server lets AI agents use take_screenshot, get_page_info, and capture_pdf.
  • The Free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots. Every feature is on every plan.

Create a free ScreenshotNeo account to get 1,000 screenshots a month with no card.

FAQ

Does Shopify provide one standard selector for product price?

No universal selector is established by the supplied Shopify theme guidance. Inspect the target theme and use its accessible markup or a stable theme-owned test ID.

Should I use toHaveScreenshot for a one-off image?

Use page.screenshot() when you only need an artifact. Use toHaveScreenshot when the test should compare the rendering with a stored visual baseline.

Can this flow prove a price update happened for every shopper?

It verifies the selected state rendered in the tested browser session. It does not establish behavior for every locale, inventory state, or storefront configuration; add cases for the variants and conditions your project supports.