How to Capture a Full Product Grid Screenshot on an Indian E-commerce Page with Playwright
Capture a full product listing or just its grid with Playwright. Handle lazy-loaded products, scrolling, overlays, and stable screenshots.
To capture the entire scrollable listing page with Playwright, use fullPage: true. To capture only the product grid, take a screenshot of a locator for the grid. First make sure the products you need—including lazy-loaded rows—have rendered: a screenshot does not guarantee that the site has fetched products that have not loaded yet.
await page.screenshot({ path: 'product-listing.png', fullPage: true });
For grid-only output:
await page.locator('[data-testid="product-grid"]').screenshot({
path: 'product-grid.png',
animations: 'disabled',
});
The URL and selector in the examples below are placeholders. Replace them with the listing URL and a stable selector from the retailer page you are allowed to access. See the official Playwright screenshot guide for the screenshot API.
1. Choose what the screenshot should include
| Need | Use | What it captures |
|---|---|---|
| Listing page, including header and other page content | page.screenshot({ fullPage: true }) |
The full scrollable document, as if it were displayed on a very tall screen. |
| Only the product grid | locator.screenshot() |
The selected element, cropped to its bounds. A scrollable element captures its currently visible portion. |
| A specific rectangular area | page.screenshot({ clip: { x, y, width, height } }) |
The requested viewport rectangle. Use this when the desired crop is fixed and known. |
| Repeatable visual regression check | Playwright Test’s toHaveScreenshot() |
A screenshot assertion that waits for consecutive screenshots to stabilize and compares with the expected image. It requires the Playwright Test runner. |
fullPage defaults to false. A full-page capture includes more than the grid; it may include navigation, filters, promotional content, and footer. Use an element screenshot if those are unwanted and the grid element itself is not an independently scrollable container.
2. Install Playwright and capture the listing
In a new Node.js project, install Playwright and its Chromium browser:
npm init -y
npm install playwright
npx playwright install chromium
Save this as capture-grid.mjs and run node capture-grid.mjs. Set LISTING_URL to a permitted listing page. Set GRID_SELECTOR to the actual grid selector; data-testid is only an example.
import { chromium } from 'playwright';
const url = process.env.LISTING_URL ?? 'https://example.com/category';
const gridSelector = process.env.GRID_SELECTOR ?? '[data-testid="product-grid"]';
const expectedCards = Number(process.env.EXPECTED_CARDS ?? 0);
const browser = await chromium.launch({ headless: true });
try {
const context = await browser.newContext({
viewport: { width: 1440, height: 900 },
deviceScaleFactor: 1,
});
const page = await context.newPage();
await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 45_000 });
// Handle a consent, location, or other modal only according to your test policy.
const grid = page.locator(gridSelector);
await grid.waitFor({ state: 'visible', timeout: 20_000 });
// Optional guard: verify the expected number of product cards exists.
if (expectedCards > 0) {
const cards = grid.locator('[data-testid="product-card"]');
await page.waitForFunction(
({ selector, count }) => document.querySelectorAll(`${selector} [data-testid="product-card"]`).length >= count,
{ selector: gridSelector, count: expectedCards },
{ timeout: 20_000 },
);
console.log(`Product cards found: ${await cards.count()}`);
}
// Save the full document. Use grid.screenshot() below for grid-only output.
await page.screenshot({ path: 'product-listing-full.png', fullPage: true });
// await grid.screenshot({ path: 'product-grid.png', animations: 'disabled' });
} finally {
await browser.close();
}
The optional count guard assumes each card uses data-testid="product-card"; adapt the card selector to the site DOM. A count of zero disables that guard. Visibility of the grid alone does not mean every card or image is ready.
3. Load lazy rows before capture
Many listing pages add products as the user scrolls, or load images only when they approach the viewport. fullPage: true captures the document’s current content; it does not promise that the site has requested the next product page or rendered every lazy row.
- Identify the expected product count, last product, or another page-specific completion condition.
- Scroll the page or the grid’s own scroll container in controlled increments to trigger loading.
- Wait for the count or last-item condition to become true, then capture.
- If images matter, wait for the relevant image elements to finish loading or for a known visual readiness condition. Do not assume the network becoming quiet proves that the listing is complete.
For a grid that scrolls inside a fixed-height panel, scroll that panel itself. A locator screenshot of a scrollable grid shows the visible area at its current scroll position, not every item hidden inside its scrollbar. If the goal is a long grid image, use an appropriate page-level strategy or capture deliberate segments after scrolling; verify the result because fixed-height and overflow styles can affect what is visible.
4. Capture just the grid with a stable locator
Prefer a stable, page-specific selector such as a test ID or a semantic locator. Avoid brittle selectors based on generated class names or a broad selector that matches multiple sections. The locator screenshot crops to the element’s bounds; it does not automatically remove banners that overlap the grid.
const grid = page.locator('[data-testid="product-grid"]');
await grid.waitFor({ state: 'visible' });
await grid.screenshot({
path: 'product-grid.png',
animations: 'disabled',
caret: 'hide',
});
If the grid is a nested scrolling region, decide whether you need only the current viewport or several segments. Scroll the container to the desired position before each segment capture. For a whole-document view, use page.screenshot({ fullPage: true }), then crop or inspect the result as needed.
5. Screenshot options that matter
| Option | Use | Watch for |
|---|---|---|
path |
Write the image to a file; the extension determines the image format. | Ensure the process can write to the destination directory. |
fullPage |
Capture the full scrollable page rather than just the viewport. | It includes the rest of the page and does not itself fetch content that has not loaded. |
clip |
Capture a specific rectangle with x, y, width, and height. |
Coordinates are in CSS pixels; keep the rectangle within the intended capture area. |
animations |
Set to 'disabled' for less movement in the capture. |
Animations can affect repeatability and the moment a transition is captured. |
mask |
Cover dynamic or private elements with locator masks. | Mask only the elements that should not appear in the artifact or visual baseline. |
scale |
Choose CSS-sized output with 'css' or device-scaled output with 'device'. |
Keep scale consistent across visual baselines; device scale can increase pixel dimensions. |
Also keep viewport width and device scale factor fixed when comparing screenshots. Changing them can alter responsive columns, line wrapping, and output dimensions. Consult the Page screenshot API reference for the current option definitions.
6. Python example: full listing page
The Python API uses full_page=True with an underscore. Install the Python package and browser, then run this script:
python -m pip install playwright
python -m playwright install chromium
from playwright.sync_api import sync_playwright
url = "https://example.com/category" # Replace with an allowed listing URL.
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
try:
page = browser.new_page(
viewport={"width": 1440, "height": 900},
device_scale_factor=1,
)
page.goto(url, wait_until="domcontentloaded", timeout=45_000)
page.locator('[data-testid="product-grid"]').wait_for(
state="visible", timeout=20_000
)
page.screenshot(path="product-listing-full.png", full_page=True)
finally:
browser.close()
As in the Node.js example, this captures the page’s current content. Add site-specific scrolling and readiness checks before the screenshot if the listing loads rows or images on demand.
7. Visual regression checks
For a baseline comparison, use Playwright Test rather than treating a manually saved image as an assertion. Example test:
import { test, expect } from '@playwright/test';
test('product listing stays visually stable', async ({ page }) => {
await page.setViewportSize({ width: 1440, height: 900 });
await page.goto('https://example.com/category', { waitUntil: 'domcontentloaded' });
const grid = page.locator('[data-testid="product-grid"]');
await grid.waitFor({ state: 'visible' });
await expect(grid).toHaveScreenshot('product-grid.png', {
animations: 'disabled',
});
});
Install @playwright/test and run the test with the Playwright Test runner. Screenshot assertions wait for two consecutive screenshots to match before comparing with the expected image. Keep test data, viewport, browser version, fonts, and image readiness stable to reduce unrelated diffs. See Playwright’s screenshot assertion documentation.
8. Indian e-commerce page details
- Consent and location prompts: A cookie dialog, pincode prompt, or location modal may cover the product grid. Handle it according to your test policy and the permitted access you have. Do not assume a real account or checkout action is necessary for a screenshot.
- Promotional overlays: Newsletter popups, chat widgets, and sticky offers can obscure content. For visual tests, dismiss them through an approved test setup or mask known dynamic elements; document any dismissal so the capture remains meaningful.
- Changing inventory and prices: Listing content may vary over time or by location. Use a stable test environment or mask volatile fields only when those fields are outside the test’s purpose.
- Infinite scroll and pagination: A screenshot captures rendered content, not every product that could be reached through another scroll or page request. Decide whether the target is one loaded listing, all loaded rows, or several pages.
- Image readiness: A product card can exist while its image is still loading. Wait on relevant image load state or a site-specific ready signal when image completeness matters.
9. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Bottom product rows are missing | Rows were lazy-loaded only after scrolling, or pagination/infinite scroll has not fetched them. | Scroll in controlled increments and wait for the expected count or final card before capture. |
| Only part of a long grid appears | The grid is its own scroll container, so its locator screenshot captures the visible portion. | Scroll the grid container, take planned segment captures, or use a page-level capture strategy suitable for the desired output. |
| The image is much taller than expected | fullPage: true captured the entire document, including header, filters, and other sections. |
Capture the grid locator or use a deliberate clip rectangle. |
| The grid is cut off | The locator targets a wrapper with fixed height or overflow clipping, or the selector points to the wrong element. | Inspect the DOM and layout styles, select the actual grid, and check whether a nested scrollbar must be scrolled. |
| Grid locator times out | The selector is wrong, the page has not reached the listing, or a consent/location step blocks it. | Inspect the page DOM and URL, choose a stable selector, and handle the prompt under your test policy. |
| Images are blank | Image requests or lazy rendering have not completed, or the browser cannot load the resource. | Wait for the relevant images or site-specific readiness condition and inspect failed requests and browser errors. |
| Overlay or animation varies between runs | Dynamic content or movement changes the captured pixels. | Disable animations, mask known variable elements, or dismiss an overlay through a consistent approved setup. |
| Visual assertion produces noisy diffs | Viewport, device scale, browser, fonts, images, data, or timing differ between runs. | Pin the capture environment and data, wait for readiness, and keep screenshot options consistent. |
| Browser launch fails in CI | Playwright’s browser binary may not be installed or required system dependencies may be missing. | Install Chromium with Playwright’s installer and follow the installation guidance for the CI operating system. |
10. Performance, reliability, and cost
A browser screenshot’s time and memory use grow with the amount of page content and image data. Very tall full-page captures can produce large image files and may take longer to render and encode. Capture only the grid when that is the required artifact; for exceptionally long grids, consider segment captures and validate their boundaries.
Reliability depends on the page’s own loading behavior and on stable capture conditions. Prefer explicit conditions such as an expected card count, final item, or image readiness over an assumption that networkidle means the listing is complete. Sites may maintain analytics or polling connections, and images may load independently. Set timeouts that fit the page, fail clearly when the readiness condition is not met, and close the browser in a finally block.
Playwright is open-source browser automation software; the examples do not call a paid screenshot API. Your costs come from the machine, browser runtime, and any infrastructure or test service you choose. Large captures use more runtime and storage than a viewport-sized image.
11. Or skip the browser setup
If you want a screenshot from one request instead of managing a browser, ScreenshotNeo is a website screenshot API and MCP server for developers. Its API can return PNG, JPEG, WebP, or PDF. For a listing page, call it with the URL:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com/category -o listing.webp
See the ScreenshotNeo API documentation for request details. The service accepts cookie or consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be turned off. Bot checks, blank pages, failed loads, timeouts, and cache hits cost nothing, with the response identifying the page verdict and billing status in headers. Its MCP server provides take_screenshot, get_page_info, and capture_pdf for AI agents. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots.
Create a free ScreenshotNeo account and start with 1,000 screenshots a month at no charge and no card.
12. FAQ
Does fullPage: true capture only the product grid?
No. It captures the full scrollable page. Use a grid locator screenshot when you want only that element.
Can a locator screenshot capture every item in a scrollable grid?
It captures the locator’s visible area at its current scroll position. Scroll the container or choose another capture strategy for off-screen rows.
Does Playwright automatically load products that have not been requested yet?
No. Trigger the page’s loading behavior and verify the expected products are present before taking the screenshot.
Which option should I use for visual tests?
Use Playwright Test’s screenshot assertion with a stable viewport, data, and readiness condition. It is available through the Playwright Test runner.
Sources
- Playwright screenshot guide — full-page and element screenshot examples.
- Page screenshot API — screenshot options including clipping, masking, animation handling, and scale.
- Playwright screenshot assertions — visual assertion behavior and runner guidance.


