ScreenshotNeo

BlogHow-to

How to screenshot a logged-in Shopify store admin page with Playwright

Save an authorized Playwright session, restore it for a Shopify admin page, verify you are signed in, and capture a viewport or full-page screenshot safely.

By the ScreenshotNeo team4 October 20269 min read

To screenshot a logged-in Shopify admin page with Playwright, first sign in through an account authorized for the store, save the browser context’s storage state, then load that state into a new context. Navigate to the intended admin page, verify a page-specific landmark so you do not capture a login redirect, and call page.screenshot(). Treat the saved state file like a credential: it may let someone impersonate the account.

This workflow is for authorized browser automation. It does not bypass MFA or guarantee that every store’s login policy permits unattended reuse. The store URL, target route, login flow, and confirmation landmark depend on your setup.

1. Install Playwright and prepare a private state directory

In a Node.js project, install Playwright and its Chromium browser:

npm init -y
npm install -D playwright
npx playwright install chromium

Use a dedicated test or staff account with permission to view the page. Add the state directory and screenshot output to .gitignore so they are not committed:

mkdir -p playwright/.auth
printf '\nplaywright/.auth/\nshopify-admin.png\n' >> .gitignore

Set the actual admin URL in an environment variable rather than hard-coding it in source:

export SHOPIFY_ADMIN_URL='https://your-store-admin-url.example/path'

Use the store’s known, authorized admin URL and target route. There is no single route that applies to every store and page.

2. Sign in once and save storage state

Run a headed browser for the initial authentication so you can complete the store’s permitted login flow and any required human MFA step. Do not automate around a security challenge. Create save-shopify-auth.mjs:

import { chromium } from 'playwright';

const adminUrl = process.env.SHOPIFY_ADMIN_URL;
if (!adminUrl) throw new Error('Set SHOPIFY_ADMIN_URL first.');

const browser = await chromium.launch({ headless: false });
const context = await browser.newContext();
const page = await context.newPage();

try {
  await page.goto(adminUrl, { waitUntil: 'domcontentloaded' });
  console.log('Complete the authorized sign-in in the browser.');
  console.log('When the intended admin page is visible, press Enter here.');
  await new Promise((resolve) => process.stdin.once('data', resolve));

  // Replace this check with a reliable landmark for your page.
  await page.getByRole('heading', { name: /products/i }).waitFor();
  await context.storageState({ path: 'playwright/.auth/shopify-staff.json' });
  console.log('Saved browser state. Keep the file private.');
} finally {
  await context.close();
  await browser.close();
}

Run it with node save-shopify-auth.mjs. The example expects a Products heading after sign-in; change it to a landmark that actually identifies your target page. If your login flow lands on a different page first, navigate to the target page before confirming and saving state.

Playwright’s authentication guide explains saving state with browserContext.storageState() and warns that the file may contain sensitive cookies and headers that could be used to impersonate you or your test account. Store it outside source control and do not print, publish, or share it. See the Playwright authentication guide.

3. Restore the session and capture the admin page

Create screenshot-shopify-admin.mjs to open a fresh context with the saved state. Wait for a meaningful page condition before saving the image:

import { chromium } from 'playwright';

const adminUrl = process.env.SHOPIFY_ADMIN_URL;
if (!adminUrl) throw new Error('Set SHOPIFY_ADMIN_URL first.');

const browser = await chromium.launch();
const context = await browser.newContext({
  storageState: 'playwright/.auth/shopify-staff.json',
});
const page = await context.newPage();

try {
  await page.goto(adminUrl, { waitUntil: 'domcontentloaded', timeout: 60_000 });

  // Replace this with a stable, page-specific landmark in your admin view.
  await page.getByRole('heading', { name: /products/i }).waitFor({ timeout: 30_000 });

  await page.screenshot({ path: 'shopify-admin.png', fullPage: true });
  console.log('Saved shopify-admin.png');
} finally {
  await context.close();
  await browser.close();
}

Run node screenshot-shopify-admin.mjs. The image is written to shopify-admin.png. The official Playwright Page API documents page.screenshot() and its options; confirm option availability against the Playwright version in your project.

Choose a page readiness condition

A fixed delay is usually a weaker signal than waiting for the state your task needs. Prefer a URL assertion or a visible, page-specific element. For example:

// UI landmark
await page.getByRole('heading', { name: /products/i }).waitFor();

// Or, if the route is stable in your store:
await page.waitForURL(/\/products(?:\/|$)/);

Use a condition that distinguishes the intended page from a login redirect, permission error, or partially loaded admin shell. Avoid asserting only a generic header that is also visible on unrelated admin pages.

4. Configure the capture

Need Playwright approach Notes
Visible viewport only page.screenshot({ path: 'admin.png' }) Captures the current viewport.
Entire page page.screenshot({ path: 'admin.png', fullPage: true }) Captures the full scrollable page; long pages can produce large images.
Specific screen size browser.newContext({ viewport: { width: 1440, height: 1000 } }) Set the viewport when creating the context, before opening the page.
Higher pixel density browser.newContext({ deviceScaleFactor: 2 }) Creates a higher-resolution viewport capture and increases image dimensions.
Image format Use a .png, .jpeg, or .webp path and supported screenshot options for your Playwright version. Check the API reference for the installed version and format-specific options.
Hide a sensitive or noisy element page.screenshot({ path: 'admin.png', mask: [page.locator('.selector')] }) Use a selector verified on the target page; masking changes the captured pixels, not page access.

For element-only capture, locate the element and call its screenshot method, such as await page.locator('main').screenshot({ path: 'admin-main.png' }). Check that the selector matches exactly one intended region and is visible. Review the Page API reference for current screenshot options.

5. Reuse the state in Playwright Test

If this is part of an end-to-end test suite, configure a project to use the saved state rather than manually constructing a context in each test:

// playwright.config.js
import { defineConfig } from '@playwright/test';

export default defineConfig({
  use: {
    baseURL: process.env.SHOPIFY_ADMIN_URL,
    storageState: 'playwright/.auth/shopify-staff.json',
  },
});
// tests/shopify-admin-screenshot.spec.js
import { test, expect } from '@playwright/test';

test('capture the authorized Products admin page', async ({ page }) => {
  await page.goto('/');
  await expect(page.getByRole('heading', { name: /products/i })).toBeVisible();
  await page.screenshot({ path: 'shopify-admin.png', fullPage: true });
});

For parallel workers that modify shared server-side state, Playwright recommends separate accounts per worker to avoid interference. A screenshot-only workflow should still use an account with only the access it needs. Refer to Playwright’s guidance on authentication and parallel tests.

Session storage and expired sessions

Playwright’s storage state covers cookies and local storage, and can include IndexedDB when requested using the supported API options. Session storage is a special case: it is not automatically persisted in the same way. If the sign-in flow depends on session storage, follow Playwright’s documented save-and-restore approach for that storage type. See the authentication guide and BrowserContext storageState API.

Saved state can expire or be invalidated. When the page returns to sign-in or the expected landmark never appears, complete the authorized sign-in flow again and regenerate the state file. Do not assume a fixed session lifetime for every account.

Shopify API authentication is not browser sign-in

A Shopify app’s API credentials and an interactive staff browser session solve different problems. An API access token is for API calls; an ID token representing a user is not itself an API access token. Neither should be treated as a browser cookie or a direct replacement for signing into the admin UI. Shopify’s authentication overview and Remix Admin authentication guide describe app authentication and framework integration, not a universal Playwright recipe for staff sign-in.

Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server. Its endpoint captures a URL in one GET request. For a protected admin page, use only a supported, authorized URL and authentication setup; a URL alone does not make an authenticated staff session. 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
import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
    timeout=90,
)
r.raise_for_status()
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}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await Bun.write('shot.webp', res);

The examples use the documented sample target; replace it with a URL you are authorized to capture and configure any supported authentication as described in the docs. ScreenshotNeo accepts cookie and consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each step can be turned off. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed. Responses identify the page verdict and billing status in headers. Its MCP server offers take_screenshot, get_page_info, and capture_pdf for AI agents. The free plan includes 1,000 screenshots per month without a card; paid plans start at $5 for 3,000.

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

Troubleshooting

Symptom Likely cause What to do
You see a sign-in page in the screenshot The state expired, was not saved after sign-in, or the target URL redirected. Repeat the authorized headed setup, verify the target page landmark, save fresh state, and retry.
The landmark wait times out The locator is wrong for this page, content has not loaded, or the account lacks access. Inspect the page manually, choose a page-specific landmark that exists, and confirm account permissions and URL.
Navigation times out The page is slow, an external resource is stalled, or the chosen load event is too strict. Use a suitable navigation condition such as domcontentloaded, then wait for the specific UI condition your capture requires. Increase timeout only when justified.
State works in one run but not another Session expiry, changed account policy, or authentication stored outside the state mechanisms being restored. Re-authenticate through the permitted flow; check whether session storage needs separate handling.
State file is missing The setup script did not reach the save step or the path differs. Run the setup again and check that the state directory exists and is writable.
Screenshot is clipped or unexpectedly large Viewport capture was used where full-page was needed, or full-page dimensions are very long. Choose viewport versus fullPage deliberately; inspect page dimensions and capture a specific element when suitable.
Parallel captures affect each other Workers share an account or alter common store data. Use separate authorized accounts for parallel workers when tests change shared server-side state, as Playwright recommends.

Performance, reliability, and cost

Local Playwright has no per-screenshot API charge described here, but you operate the browser, runtime, and execution environment. Browser startup, page loading, and image size affect elapsed time and resource use. Reuse browser processes for batches when appropriate, while keeping contexts isolated where sessions or state should not mix. Use readiness checks that reflect the content you need instead of arbitrary long sleeps.

For reliability, close contexts and browsers in a finally block, set a bounded navigation timeout, check a page-specific landmark, and regenerate state when it expires. Protect the state file and screenshot output according to the store’s data policies. For shared data changes in parallel runs, use separate accounts. Do not treat a successful navigation alone as proof that the browser is signed in to the right page.

ScreenshotNeo’s published tiers are Free: 1,000 shots/month; Starter: $5 for 3,000; Growth: $15 for 15,000; Pro: $39 for 60,000; Scale: $99 for 250,000; Business: $249 for 1,000,000. Yearly billing gives two months free, and every feature is on every plan. Only clean shots are billed; inspect the response’s X-Page-Verdict and X-Billed headers to understand an individual result. See ScreenshotNeo for product details and the docs for options.

FAQ

Can Playwright bypass Shopify MFA?

No. This workflow uses an authorized sign-in and allows any required human MFA step. It does not provide a method to bypass MFA or other security checks.

Can I use an Admin API token as the browser session?

No. App API authentication and the browser’s logged-in staff session are different mechanisms.

Will this work unattended forever?

No universal session lifetime or unattended MFA behavior is established for every store. Plan to refresh state through the account’s permitted login flow when it expires.

Should I capture the whole admin page?

Use a viewport screenshot for the visible state and a full-page screenshot when the complete scrollable page matters. For one panel, capture the element directly.