ScreenshotNeo

BlogHow-to

How to Track Indian Fashion Marketplace Prices with Playwright Screenshots

Build a Playwright workflow that records displayed fashion prices with screenshot evidence, structured observations, and marketplace-specific access checks.

By the ScreenshotNeo team4 October 20269 min read

Use Playwright to open a product page, read the displayed price as text, and save a screenshot of the price and its surrounding offer context. Store the parsed value and screenshot together with the marketplace, product identifier, URL, currency, seller or promotion context, and capture timestamp. The screenshot is visual evidence of what the page showed at that moment; it is not a price-history database, proof that an offer remained available, or permission to collect the page.

Before collecting data, check each marketplace’s current terms and authorized data routes. Flipkart documents affiliate product and offer APIs for registered affiliates, and Amazon India documents conditional access to PA-API. The research reviewed for this guide did not establish a current official Myntra price API or automated-access policy, so verify Myntra’s requirements independently.

1. Decide what you are tracking

A product and an offer are different things. The same product can have multiple sellers, sizes, colors, discounts, or delivery conditions. Decide whether your record represents a product’s displayed price, a specific seller’s offer, or the lowest eligible offer you can observe. Do not combine observations that refer to different variants or sellers as if they were the same price.

Prefer a stable product detail URL and retain the marketplace’s product identifier when it is visible or available through an authorized API. For each observation, record:

  • Marketplace and product identifier
  • Product URL and product name
  • Displayed amount and currency, stored separately from the formatted price string
  • Capture time in UTC
  • Visible seller, selected variant, discount, coupon, or other offer context
  • Screenshot path or object key, capture type, and any collection error

This schema is an implementation recommendation. Playwright provides page and screenshot capabilities; it does not define a marketplace price-history schema.

2. Install Playwright

The following example uses Node.js and Playwright’s Chromium browser. In a new project, install the package and browser:

npm init -y
npm install playwright
npx playwright install chromium

Save the runnable script below as track-price.mjs. It accepts a product URL and a CSS selector for the displayed price, reads the text, captures the matching element, and writes a JSON record. Selectors are site-specific: inspect the page you are authorized to access and update the selector and optional context selectors for that product page.

3. Runnable Playwright price capture

import { chromium } from 'playwright';
import { mkdir, writeFile } from 'node:fs/promises';
import { dirname, resolve } from 'node:path';

const url = process.argv[2];
const priceSelector = process.argv[3];
if (!url || !priceSelector) {
  console.error('Usage: node track-price.mjs <product-url> <price-css-selector>');
  process.exit(2);
}

const marketplace = process.env.MARKETPLACE ?? 'unknown';
const productId = process.env.PRODUCT_ID ?? null;
const currency = process.env.CURRENCY ?? 'INR';
const outputDir = resolve(process.env.OUTPUT_DIR ?? 'captures');
const safeId = (productId ?? 'product').replace(/[^a-zA-Z0-9_-]/g, '_');
const capturedAt = new Date().toISOString();
const screenshotPath = resolve(outputDir, `${safeId}-${capturedAt.replaceAll(':', '-')}.png`);
const recordPath = screenshotPath.replace(/\.png$/, '.json');

await mkdir(dirname(screenshotPath), { recursive: true });
const browser = await chromium.launch({ headless: true });
let context;
try {
  context = await browser.newContext({ viewport: { width: 1365, height: 900 } });
  const page = await context.newPage();
  const response = await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 30000 });
  if (!response || !response.ok()) {
    throw new Error(`Navigation returned ${response?.status() ?? 'no response'}`);
  }

  const price = page.locator(priceSelector).first();
  await price.waitFor({ state: 'visible', timeout: 15000 });
  const priceText = (await price.innerText()).trim();
  if (!priceText) throw new Error('Price selector was visible but contained no text');

  // Optional selectors can be supplied for context visible on the page.
  const nameSelector = process.env.NAME_SELECTOR;
  const sellerSelector = process.env.SELLER_SELECTOR;
  const offerSelector = process.env.OFFER_SELECTOR;
  const readOptional = async (selector) => {
    if (!selector) return null;
    const locator = page.locator(selector).first();
    return await locator.count() ? (await locator.innerText().catch(() => '')).trim() || null : null;
  };

  await price.screenshot({ path: screenshotPath, type: 'png', animations: 'disabled' });
  const record = {
    marketplace,
    productId,
    productName: await readOptional(nameSelector),
    url,
    observedAt: capturedAt,
    displayedPrice: priceText,
    amount: null,
    currency,
    sellerOrOfferContext: {
      seller: await readOptional(sellerSelector),
      promotion: await readOptional(offerSelector)
    },
    screenshotPath,
    screenshotScope: 'price-element',
    navigationStatus: response.status()
  };
  await writeFile(recordPath, JSON.stringify(record, null, 2));
  console.log(JSON.stringify(record, null, 2));
} finally {
  await context?.close();
  await browser.close();
}

Run it with a page and a selector you have verified:

MARKETPLACE=example PRODUCT_ID=item-123 CURRENCY=INR \
NAME_SELECTOR='h1' SELLER_SELECTOR='.seller-name' OFFER_SELECTOR='.offer' \
node track-price.mjs 'https://shop.example/product' '.price'

The example deliberately preserves displayedPrice as text and leaves amount null. Price strings may include currency symbols, separators, ranges, discounts, or variant-dependent values. Parse them only with marketplace- and locale-aware rules that you can validate; retain the original text so a later parser change does not destroy the observation.

4. Choose the right screenshot scope

Capture Use it for Trade-off
Price element A compact image of the amount May omit product, seller, or offer context
Viewport Price plus nearby context as seen on screen Content below the viewport is omitted
Full page Reviewing the complete scrollable page Can produce large images and include unrelated content

Playwright supports viewport, locator-element, and full-page screenshots, with options such as file path, image type, scale, and clipping. A focused element image is useful for a compact record, but for a price audit it can be better to save a viewport screenshot as well so the product and offer context are visible.

// Viewport screenshot
await page.screenshot({ path: 'viewport.png', type: 'png' });

// Full scrollable page screenshot
await page.screenshot({ path: 'full-page.png', fullPage: true, type: 'png' });

// Element screenshot
await page.locator('.price').screenshot({ path: 'price.png', type: 'png' });

// Image bytes instead of a file
const imageBytes = await page.screenshot({ type: 'jpeg', quality: 80 });

The screenshot API also supports scale choices for device-pixel or CSS-pixel output and clipping to a defined region. Use PNG when pixel-level fidelity matters; JPEG can reduce storage at the cost of compression artifacts. Keep capture dimensions and type consistent if comparing images over time.

5. Make repeated observations comparable

Rendered pages can change between captures because prices, promotions, seller selection, variant selection, or page content changed. Wait for the specific price locator to become visible rather than relying only on a fixed delay. Store the exact capture time and relevant visible context with each observation.

For screenshot comparison in a Playwright test, its screenshot assertion waits for two consecutive page screenshots to match before comparing against an expected image. These screenshot assertions are part of the Playwright test runner. They help with visual checks, but they do not establish why a price changed or whether an offer was purchasable.

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

test('price region stays visually consistent', async ({ page }) => {
  await page.goto('https://shop.example/product');
  const price = page.locator('.price');
  await expect(price).toBeVisible();
  await expect(price).toHaveScreenshot('price-region.png');
});

For a history table or alerts, compare normalized structured fields, not screenshots alone. Keep the raw displayed string and screenshot as evidence alongside any normalized amount.

6. Check official marketplace data routes

Flipkart

Flipkart describes product and offer APIs for registered Affiliate Program users, and identifies comparison sites and apps as a potential use case. Review the current Flipkart Affiliate API overview and API terms for eligibility, permitted use, and current conditions before building on those endpoints. Registration does not imply unrestricted access.

Amazon India

Amazon India says PA-API use requires an open Associates account, compliance with the Associates Operating Agreement, a PA-API application, and compliance with the API License Agreement. The API documents offer-price information. See the Amazon.in Product Advertising API documentation and Associates Operating Agreement. Amazon India’s help page describes an initial allowance of one request per second per account, with increases tied to attributed shipped revenue and a stated maximum. These limits can depend on account status and may change; verify current guidance in Amazon’s PA-API rate limit help.

Myntra

The reviewed sources do not establish a current official Myntra product-price API or policy authorizing automated access. That is an unresolved research point, not evidence that no authorized route exists. Verify Myntra’s current terms and any available authorized data route before collecting its pages. Do not infer Myntra’s rules from Amazon or Flipkart.

7. Reliability, performance, and storage

  • Use bounded waits. Set navigation and locator timeouts, and record timeouts as failed observations rather than substituting a guessed price.
  • Keep screenshots useful. Element captures are compact; viewport and full-page captures provide more context and consume more storage. Choose the smallest scope that supports later review.
  • Separate capture from parsing. Save the raw price text and image even if normalization fails. A parser should not silently turn an ambiguous range or unavailable offer into a numeric price.
  • Control concurrency. Launching many browser pages consumes memory and CPU. Begin with a small, bounded queue and account for each marketplace’s terms and any API rate limits.
  • Record failures explicitly. Store navigation status, timestamp, and error category. A missing page or blocked request is not a zero price and should not overwrite the last valid observation.
  • Manage retention. Screenshots can dominate storage. Define retention, compression, and access controls based on your audit needs, and avoid retaining unnecessary personal or account data.

Playwright is browser automation software, not authorization to access a site. If a page presents a bot check, CAPTCHA, access denial, or other restriction, do not build a bypass into this workflow. Stop or use an authorized API or data route. The collection schedule and volume should follow the applicable terms and account limits.

8. Troubleshooting

Symptom Likely cause Fix
Navigation returns no response or a non-success status The page failed to load, redirected, or returned an error Record the status and URL, inspect the response and page state, and retry only within an authorized, bounded policy. Do not record it as a price.
Price locator times out The selector is stale, the page structure changed, or the price is not visible for the selected variant Inspect the current page structure, update the selector, and verify the intended product and variant. Avoid broad selectors that can match crossed-out prices.
Price text is empty or ambiguous The selected element is a wrapper, content is not rendered, or multiple amounts appear Target the displayed amount more precisely; store the raw text and relevant seller/discount labels. Treat ranges and unavailable states as non-numeric.
Screenshot omits context An element-only capture contains only the selected node Capture the viewport or full page in addition to the element, or target a containing card that includes product and offer details.
Images or price are missing on first load Lazy content has not appeared or a client-rendered section is still loading Wait for the relevant locator or a documented page state with a bounded timeout. Do not rely on an arbitrary long sleep as the only readiness check.
Repeated screenshots differ unexpectedly Dynamic banners, rotating content, changing offers, or viewport differences Keep viewport and capture options stable; record changing offer context; compare the intended region only. Playwright test screenshot assertions wait for consecutive stable screenshots.
Browser launch fails The Playwright browser binary is not installed for the package Run npx playwright install chromium in the project environment and check that the runtime can launch the installed browser.
Access denial or bot check appears The site restricts the request or requires an authorized route Do not attempt to evade the restriction. Check the marketplace’s terms and official APIs, and stop automated page collection unless you have authorization.

9. Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server. A single request captures a URL as an image or PDF; use the response image as visual evidence and keep price extraction and structured history in your own authorized workflow. It does not turn a screenshot into structured price data or grant marketplace access.

cURL:

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

Python:

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)

Node.js:

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

See the ScreenshotNeo API documentation for request options. Cookie banners, popups, and chat widgets are removed before the shot; bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000.

Start with 1,000 free screenshots a month, no card required.

10. Frequently asked questions

Does a screenshot prove that the price was available for purchase?

No. It records what was visible at capture time. It does not prove stock, checkout eligibility, delivery terms, or continued availability.

Should I store price as text or a number?

Store both the original displayed text and a separately normalized amount when parsing is unambiguous. Retaining the source text makes later corrections auditable.

Can one marketplace’s API permission be applied to another?

No. Eligibility, terms, and technical routes are marketplace-specific. Verify each one independently.

Can Playwright screenshots create a price history on their own?

No. Your application must schedule authorized observations and store structured records over time. Screenshots provide visual context for those records.