How to Use an AI Agent to Screenshot a Website That Requires a One-Time Password
Let an AI agent capture an authenticated page safely: complete the site’s normal OTP flow, save protected browser state, then reuse it for screenshots.
Short answer: An AI agent can screenshot an OTP-protected website if it can control a browser and you are authorized to access the account. Let the site run its normal sign-in flow, complete the one-time password (OTP) challenge yourself or through an integration explicitly approved for that account, and save the authenticated browser state securely after sign-in. Later, load that state, navigate to the target page, verify that it is the intended signed-in page, and capture it. This reuses an authorized session; it does not bypass multi-factor authentication.
The exact agent, site, and OTP channel determine the browser-control steps. There is no universal OTP integration or guarantee that a saved session will work across sites, browsers, or agents. The runnable example below uses Playwright with Node.js as the browser automation layer; connect your AI agent to its supported browser-control interface if it provides one.
1. The safe workflow
- Check authorization. Use an account and site you are permitted to access. Follow the site’s login and MFA requirements.
- Open the login page in a controlled browser. Use your agent’s supported browser interface, or run the Playwright setup script below. Do not paste passwords or OTPs into an agent prompt unless the integration is explicitly approved to handle them.
- Complete the normal OTP challenge. Pause for the account holder to enter the code through the site, or use an explicitly authorized identity integration. Do not try to intercept, guess, replay, or evade the code.
- Confirm successful sign-in. Wait for a final URL or a page-specific signed-in element. A login form disappearing alone may not prove that the intended account is authenticated.
- Save browser state to a private location. Playwright’s storage state can include cookies and other data that may let someone impersonate the account. Keep the file out of source control and restrict access. Playwright authentication guidance recommends a dedicated auth directory and warns against committing its contents.
- Reuse state for the screenshot run. Load the saved state, navigate to the target, check a page-specific marker, then capture. If the session expires, repeat the authorized sign-in and refresh the state.
2. Runnable Playwright example
This example separates the interactive setup from later captures. It assumes you can access the login page and complete its normal OTP challenge in the browser window. Replace the example URLs and success selectors with values for the site you are authorized to use.
Install
mkdir otp-screenshot
cd otp-screenshot
npm init -y
npm install playwright
npx playwright install chromium
mkdir -p playwright/.auth
printf '\nplaywright/.auth/\n' >> .gitignore
Use a login URL and a reliable signed-in selector. The selector below is an example only; choose something that appears only after the intended account has completed sign-in. Save this as setup-auth.mjs:
import { chromium } from 'playwright';
const loginUrl = process.env.LOGIN_URL;
const successSelector = process.env.SUCCESS_SELECTOR;
if (!loginUrl || !successSelector) {
throw new Error('Set LOGIN_URL and SUCCESS_SELECTOR first.');
}
const browser = await chromium.launch({ headless: false });
const context = await browser.newContext();
const page = await context.newPage();
try {
await page.goto(loginUrl, { waitUntil: 'domcontentloaded' });
console.log('Complete the normal sign-in and OTP challenge in the browser.');
await page.locator(successSelector).waitFor({ state: 'visible', timeout: 5 * 60 * 1000 });
await context.storageState({ path: 'playwright/.auth/user.json' });
console.log('Authenticated browser state saved. Protect this file as a credential.');
} finally {
await browser.close();
}
Run it with your site’s values. Keep credentials out of the command and repository; this setup deliberately leaves login and OTP entry to the site’s normal browser flow.
LOGIN_URL='https://example.com/login' \
SUCCESS_SELECTOR='[data-testid="account-menu"]' \
node setup-auth.mjs
For subsequent captures, save this as capture.mjs. Set a target URL, the same kind of signed-in marker, and optionally a selector to conceal sensitive content in the saved image:
import { chromium } from 'playwright';
const targetUrl = process.env.TARGET_URL;
const successSelector = process.env.SUCCESS_SELECTOR;
const maskSelector = process.env.MASK_SELECTOR;
if (!targetUrl || !successSelector) {
throw new Error('Set TARGET_URL and SUCCESS_SELECTOR first.');
}
const browser = await chromium.launch({ headless: true });
const context = await browser.newContext({
storageState: 'playwright/.auth/user.json',
});
const page = await context.newPage();
try {
await page.goto(targetUrl, { waitUntil: 'domcontentloaded', timeout: 60_000 });
await page.locator(successSelector).waitFor({ state: 'visible', timeout: 30_000 });
const options = { path: 'page.png', fullPage: true, animations: 'disabled' };
if (maskSelector) options.mask = [page.locator(maskSelector)];
await page.screenshot(options);
console.log('Saved page.png');
} finally {
await browser.close();
}
TARGET_URL='https://example.com/account/report' \
SUCCESS_SELECTOR='[data-testid="account-menu"]' \
MASK_SELECTOR='[data-testid="account-number"]' \
node capture.mjs
Remove MASK_SELECTOR if no masking is needed. If your chosen selector matches multiple elements, Playwright masks all matched elements. A screenshot can expose account information even if the auth state file is protected, so secure the output too.
Playwright’s API supports full-page screenshots, locator masking, and other capture controls. Its screenshot assertions, including waiting for consecutive stable screenshots, are part of the Playwright Test runner; the standalone capture above uses page.screenshot() instead. See the Playwright screenshot assertion reference.
3. Connect an AI agent without handing it the OTP
The agent’s exact browser tool varies. Keep the authentication boundary clear:
- Ask the agent to navigate and prepare the page, then pause when the site asks for OTP.
- Complete the challenge through the browser’s normal interface, or use an identity integration your organization has explicitly approved.
- Have the agent wait for a final URL or a stable signed-in page marker before proceeding. Avoid vague checks like “wait until it looks ready.”
- Save state through the browser context API only after that success condition is met. For future runs, load the state and have the agent verify the target page before capturing.
- Do not send the state file, cookies, session headers, passwords, or OTP values to an agent unless its credential handling is explicitly approved for the account and environment.
If the agent cannot expose a browser context or reuse authenticated state, use its supported sign-in workflow or run Playwright as the browser layer. An agent that only accepts a URL for remote capture cannot generally access a page behind your authenticated browser session simply because you have logged in elsewhere.
4. Screenshot choices and configuration
| Need | Playwright approach | Notes |
|---|---|---|
| Visible viewport | page.screenshot({ path: 'page.png' }) |
Captures the current viewport. |
| Entire scrollable page | page.screenshot({ path: 'page.png', fullPage: true }) |
Long pages can take longer and produce large images. Lazy content may need scrolling or site-specific waits first. |
| Hide sensitive fields | page.screenshot({ path: 'page.png', mask: [page.locator('.private')] }) |
Masking is visual redaction in the screenshot; it does not remove the data from the page or the browser state. |
| Wait for a page condition | await page.locator('[data-ready="true"]').waitFor() |
Prefer a meaningful page marker over a fixed sleep when possible. |
| Visual regression check | await expect(page).toHaveScreenshot() |
Screenshot assertions require Playwright Test and wait for stable consecutive screenshots before comparison. |
The browser state is browser-context data, not a portable login credential format guaranteed by every agent or site. Some sites bind sessions to device, network, or other signals; some use separate domains or require a fresh challenge. Check the target site’s policy and the agent’s current documentation.
5. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Setup times out waiting for the success selector | The selector is wrong, hidden, or only appears on another page; sign-in may not have completed. | Inspect the page after completing OTP, choose a visible account-specific marker, and verify any redirect or extra consent step. |
| Capture redirects back to login | State was saved before authentication finished, expired, or is not accepted by the site. | Repeat the authorized setup and save only after the success marker appears. Check whether the site requires another domain’s session. |
| “Target page” is actually an error or access-denied page | The navigation succeeded technically but the account lacks access, the session is invalid, or a site interstitial appeared. | Check the final URL and a page-specific marker before saving. Treat access-denied and OTP pages as failed captures, not valid results. |
| Browser state file missing | Setup was not run, the working directory differs, or the path is incorrect. | Run setup once and use a stable absolute or project-relative path. Do not create a blank state file. |
| Browser closes while waiting for OTP | The setup process exited, timed out, or was interrupted. | Keep the setup process running while you complete the challenge; increase the wait timeout if the human flow legitimately takes longer. |
| Screenshot is blank or incomplete | Capture happened before the page rendered, a navigation is still in progress, or the site renders content after an interaction. | Wait for the target-specific element, trigger the required UI action, then capture. Increase navigation timeout only when the site is genuinely slow. |
| Sensitive information remains visible | The mask selector did not match the field or the sensitive content appeared elsewhere. | Check selector matches and inspect the resulting image. Mask every sensitive region or avoid capturing that page. Masking does not secure the underlying page or state. |
| State works locally but not in CI | CI may use a different browser, network, device context, or session policy. | Use the site’s approved CI identity method, create fresh state in the authorized environment, and never copy personal session state into shared logs or artifacts. |
6. Security, reliability, performance, and cost
Protect both artifacts
- Add the auth-state directory to
.gitignore; confirm it is excluded before committing. Restrict file permissions and access to the machine or CI secret store that needs it. - Use a dedicated, least-privilege test account where appropriate. Avoid printing state contents, cookies, OTPs, or credentials in logs.
- Set retention for saved state and screenshots. Delete or refresh state when it expires or access is no longer needed. Microsoft Learn likewise advises keeping passwords out of source control and using environment variables or a secrets manager: Authentication overview.
- Review screenshots before sharing them. A masked screenshot does not sanitize the browser state file.
Make capture predictable
- Wait for the final authenticated URL and a target-specific marker; this helps distinguish a real page from a login redirect or error.
- Use stable selectors and fixed viewport settings in repeatable jobs. Disable animations for less visual variation where appropriate.
- For long pages, full-page capture can increase memory, time, and image size. Capture a specific element or viewport when that meets the requirement.
- Reuse state to avoid repeating interactive login on every run, but expect sessions to expire. Have a controlled reauthentication path rather than assuming indefinite validity.
Understand the cost
With self-hosted Playwright, direct costs depend on where the browser runs, compute and storage usage, and any paid agent or identity service. OTP handling may also require human time. Reusing valid state can avoid repeating the interactive setup on every capture, but it does not eliminate browser runtime or operational costs. No universal runtime or cost figure applies across sites and environments.
Or skip the browser setup
For pages that are publicly accessible without an account, ScreenshotNeo can return an image or PDF with one GET request. See the API documentation for request options:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Important: This URL-based capture is not a way to sign into an OTP-protected account. Do not put OTPs, passwords, cookies, or private session tokens in a capture request. Use the Playwright flow above for pages that require your authenticated browser session.
ScreenshotNeo removes cookie banners, newsletter popups, and chat widgets before the shot. Bot checks, blank pages, failed loads, timeouts, and cache hits are never billed. Its MCP server gives AI agents tools to take screenshots, get page information, and capture PDFs. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000.
Create a free ScreenshotNeo account to try 1,000 screenshots a month with no card.
FAQ
Can an AI agent read my OTP automatically?
Only if an integration is explicitly approved for the account and site. This guide uses the site’s normal challenge with the account holder completing the code; it does not assume an agent can receive OTPs.
Can I reuse one state file for every browser?
Do not assume so. State compatibility and session behavior depend on the browser, site, and authentication system. Verify the supported setup for your agent and target.
Does masking remove private data from the page?
No. It covers selected regions in the output image. The page and browser state still contain their data.
What if I only need a public page screenshot?
You can use a browser automation script or a screenshot API. For a public URL, ScreenshotNeo provides a single-request option; it does not authenticate into private OTP-protected pages.


