ScreenshotNeo

BlogHow-to

Why does Playwright capture the old price on a product page?

A product price can change after navigation or variant selection. Learn how to identify the cause and wait for the correct price reliably.

By the ScreenshotNeo team4 October 20268 min read

Playwright does not inherently choose an old price. Most often, the test reads the price before the product page finishes updating, or it reads a value associated with a previous product or variant state. Wait for the application event that changes the price, then assert the final visible price with a retryable locator assertion. Without the page URL, selector, and test code, the exact cause remains uncertain.

For a product page that renders HTML first and updates through client-side JavaScript, navigation completing does not necessarily mean the price is final. Selecting a size or color may also trigger a request that returns a new price. A one-time read can capture the value before either update is reflected. Playwright explains the difference between navigation and application readiness; its Page API documents waiting for a response, and its assertions retry until the expected condition is met.

Diagnose which price the test should capture

First define the intended value: the default variant price, the price after selecting a particular option, the sale price, or another amount. Product pages often show more than one price, such as a crossed-out list price and a current price. A selector matching several elements can read the wrong one even when the page has finished updating.

  1. Inspect the locator count and identify the exact element that displays the intended price.
  2. Observe the price immediately after navigation and again after the product page settles. Check whether client-side rendering replaces or updates the node.
  3. If a variant selection is involved, identify whether it sends a request. Compare the request payload and response with the price rendered in the page.
  4. Check when the test reads the price. A read before hydration or before the variant response is applied can preserve a provisional value.
  5. Use a retryable assertion for the expected price. A fixed sleep can be too short on a slow run and waste time on a fast one; it does not establish that the application reached the intended state.

Keep cache as a hypothesis, not a diagnosis. Playwright documents that enabling request routing disables HTTP cache, but that does not show cache caused a particular stale price. Inspect the actual request, response, browser context, and page state before changing cache or routing behavior. Playwright’s issue tracker may contain reports of similar symptoms, but another report is not evidence for the cause on your page.

Wait for the variant response, then assert the displayed price

When choosing a variant triggers a request that supplies the new price, begin waiting for the matching response before the action. Otherwise the request may finish before the test starts waiting. The endpoint predicate and expected amount below are examples: adapt them to the store and variant under test.

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

test('shows the selected variant price', async ({ page }) => {
  await page.goto('https://shop.example/product');

  const priceResponsePromise = page.waitForResponse(response =>
    response.url().includes('/product-price') && response.status() === 200
  );

  await page.getByLabel('Size').selectOption('large');
  const priceResponse = await priceResponsePromise;

  // Inspect the response if it helps diagnose the store's behavior.
  const payload = await priceResponse.json();
  console.log('Price response:', payload);

  // This locator assertion retries until the displayed state matches.
  await expect(page.getByTestId('product-price')).toHaveText('$42.00');
});

The response wait confirms that a matching successful response arrived; it does not by itself prove the UI applied the response or that the response belongs to the intended variant. The final locator assertion checks the displayed state. If the site updates from a different event, wait for that event or assert the user-visible final state directly.

Read a price that is rendered after hydration

If no specific response drives the update, use a locator assertion for the known final price instead of reading text once and assuming navigation made the application ready.

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

test('shows the hydrated product price', async ({ page }) => {
  await page.goto('https://shop.example/product');
  await expect(page.getByTestId('product-price')).toHaveText('$42.00');
});

Use an expected value tied to the test data and selected variant. A broad wait for a visible price is not enough if the provisional and final values are both visible during the page transition.

Complete runnable examples in Python and cURL

Python with Playwright

Install the Playwright package and its browser, then save this as test_price.py. Replace the example URL, option label and value, response predicate, locator, and expected price with those for the store. This example waits for the variant request before selecting the option, then checks the rendered price.

from playwright.sync_api import sync_playwright, expect

with sync_playwright() as p:
    browser = p.chromium.launch()
    page = browser.new_page()
    page.goto('https://shop.example/product')

    with page.expect_response(
        lambda response: '/product-price' in response.url and response.status == 200
    ) as response_info:
        page.get_by_label('Size').select_option('large')

    response = response_info.value
    print('Price response:', response.json())
    expect(page.get_by_test_id('product-price')).to_have_text('$42.00')
    browser.close()

Node.js with Playwright

Install @playwright/test and the required browser, then save this as price.spec.js. Run it with the Playwright test runner. Adjust the endpoint and expected price for the actual product.

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

test('shows the selected variant price', async ({ page }) => {
  await page.goto('https://shop.example/product');

  const responsePromise = page.waitForResponse(response =>
    response.url().includes('/product-price') && response.status() === 200
  );
  await page.getByLabel('Size').selectOption('large');
  const response = await responsePromise;
  console.log('Price response:', await response.json());

  await expect(page.getByTestId('product-price')).toHaveText('$42.00');
});

cURL for inspecting the suspected price endpoint

cURL does not run Playwright or render a storefront. It can help inspect a known endpoint after you identify it in the browser’s network activity. Supply the correct method, URL, cookies, headers, and request body for the store; this generic GET example is only a starting point.

curl -i 'https://shop.example/product-price?variant=large'

If the endpoint requires session cookies or a POST body, reproduce those request details from the page. A successful endpoint response is useful evidence, but the browser test should still assert the final rendered price.

Common causes and fixes

Symptom Likely cause Fix
The default price appears even after selecting a variant The test reads before the variant update completes, or the selected option did not trigger the expected change. Verify the selected option, wait for the matching response if there is one, and assert the variant’s visible price.
The test sometimes passes and sometimes captures the previous amount A one-time read races with hydration or an asynchronous update. Replace the read-and-compare with a retryable locator assertion for the intended final value.
The assertion sees the list price instead of the sale price The page contains multiple price elements and the locator is too broad. Inspect matching elements and target the current-price element using a stable, page-specific locator.
The response wait times out The endpoint predicate does not match the real request, the action did not trigger it, or the request failed. Inspect network activity, check the option action and status, and make the predicate specific to the actual request.
The response is correct but the assertion sees an old amount The UI has not applied the response yet, or the response is for a different variant. Verify the variant in the request and response, then keep a retryable assertion on the displayed price.
Routing changes what the test observes Playwright documents that request routing disables HTTP cache, which can affect request behavior. Compare the routed and non-routed request behavior; do not conclude that cache caused the original issue without evidence.
The expected amount differs by locale or currency The page formats price according to locale, currency, or customer context. Set the intended test context and assert the price format the page should display in that context.

Reliability, speed, and cost considerations

Prefer waiting for the state that matters over sleeping for an arbitrary number of milliseconds. A response wait is useful when the network response is the trigger you are diagnosing; a locator assertion is useful for verifying what the shopper sees. Keep the response predicate narrow enough to identify the correct request, but match stable parts of the URL rather than transient query ordering when possible.

For reliable diagnostics, record the selected option, matching response status and relevant payload, and final rendered text. If retries are enabled in your test runner, distinguish a genuinely transient timing issue from a deterministic wrong locator or wrong variant: retries can hide flakiness without fixing its cause. Avoid logging session credentials or personal data while inspecting requests.

Browser-based capture and testing incur browser startup, navigation, and page execution work; reusing a browser process across tests can reduce repeated startup overhead, while independent contexts help isolate cookies and state. A screenshot can document what was visible at a particular moment, but it cannot establish that the price was correct unless the test also checks the intended application state.

Or skip the browser setup

For a screenshot of a page after you have established its state, ScreenshotNeo is a website screenshot API and MCP server. Its API accepts a URL in one GET request and returns an image or PDF. For a basic capture, see the ScreenshotNeo API documentation:

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

Cookie banners are accepted and removed before the shot, along with supported newsletter popups and chat widgets; each cleanup step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers say the page verdict and billing status. An MCP server gives AI agents screenshot, page-info, and PDF capture tools. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots.

Sign up free for 1,000 screenshots a month, with no card required.

FAQ

Does page.goto() guarantee the final product price is ready?

No. A page can continue hydrating or updating after navigation. Verify the application state that determines the price.

Should I add a fixed timeout?

Usually not as the readiness check. Wait for the relevant response or use a retryable assertion for the final displayed price.

Can a screenshot tell me why the old price appeared?

It can show what was visible at capture time. To diagnose why, compare the selected variant, network response, DOM updates, and test read timing.

Is browser cache the likely explanation?

It is one possibility to investigate, but the available information does not identify it as the cause. Examine the request and response before changing cache behavior.