How to Automate OTP Login for Scheduled Screenshots of an Indian Web Portal
Use the portal’s approved OTP flow to create protected Playwright state, then reuse it for scheduled screenshots while it remains valid.
For an Indian web portal that permits browser automation, the practical pattern is to complete its real OTP login in a Playwright setup step, save the authenticated browser state as a protected secret, and load that state in scheduled capture jobs. Verify that the session is still authenticated before saving each screenshot; if the portal requires another OTP, mark the run as failed and reauthenticate through the approved flow.
There is no universal OTP automation recipe. OTP may be application-generated, Aadhaar-based, TOTP, or another portal-specific method. The portal’s delivery channel, expiry rules, CAPTCHA, terms, and approved automation methods are unknown here. Obtain the portal owner’s authorization and check its rules before automating access. Never bypass OTP, CAPTCHA, rate limits, or other access controls.
1. Confirm the portal allows the workflow
Before writing a job, ask the portal owner or administrator to confirm:
- Which account and pages may be accessed, and whether browser automation is allowed.
- Whether there is an officially supported API, test environment, or service account. Prefer an approved API when one is provided.
- Which OTP method is used, how long the authenticated session lasts, and whether reauthentication is expected.
- Permitted capture frequency, screenshot retention, and who may access the resulting files.
- Whether the portal has CAPTCHA, MFA, or anti-automation controls that require an operator or an approved integration.
Indian government website guidance describes multiple authentication patterns, including custom application OTP, Aadhaar-based OTP, and TOTP. That does not establish which method a particular portal uses or grant permission to automate it. See the [GIGW guidelines](https://guidelines.india.gov.in/guidelines-3/).
2. Install Playwright
This example uses Node.js and Playwright. It opens the actual portal login page, lets an authorized operator complete its normal OTP flow, and saves browser storage state after the operator confirms login. It does not read an inbox or SMS account, extract OTPs, or evade a challenge.
mkdir portal-screenshots
cd portal-screenshots
npm init -y
npm install playwright
npx playwright install chromium
Create a private directory for the state file and exclude it from version control:
mkdir -p .auth captures
printf '\n.auth/\ncaptures/\n' >> .gitignore
chmod 700 .auth captures
Use a dedicated, least-privilege portal account if the owner supports one. Restrict the machine account and job permissions that can read .auth; do not print cookies, OTP values, or tokens in logs.
3. Authenticate through the real OTP flow and save state
Save this as setup-auth.mjs. Set PORTAL_LOGIN_URL to the portal’s known login page. Run this step interactively whenever the state expires or the portal asks for a fresh login. Once signed in, return to the terminal and press Enter to save state.
import { chromium } from 'playwright';
import { mkdir } from 'node:fs/promises';
import { createInterface } from 'node:readline/promises';
import { stdin as input, stdout as output } from 'node:process';
const loginUrl = process.env.PORTAL_LOGIN_URL;
if (!loginUrl) throw new Error('Set PORTAL_LOGIN_URL to the authorized portal login page.');
await mkdir('.auth', { recursive: true, mode: 0o700 });
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 portal’s normal login and OTP steps in the browser.');
const rl = createInterface({ input, output });
await rl.question('After the portal shows you are signed in, press Enter here. ');
rl.close();
await context.storageState({ path: '.auth/portal-state.json', indexedDB: true });
console.log('Saved browser state to the protected .auth directory.');
} finally {
await browser.close();
}
Run it with:
PORTAL_LOGIN_URL='https://portal.example/login' node setup-auth.mjs
Replace the example host with the portal URL you are authorized to use. The operator should confirm the signed-in page before saving. If the portal requires an OTP for every access, or does not permit this reuse, do not try to work around that requirement; arrange an approved interactive run or supported integration.
4. Reuse state, verify login, and capture
Save as capture.mjs. Set the destination URL and a portal-specific success selector that only appears after successful authentication. The example deliberately fails if that selector is absent, including when the portal redirects to login or displays an OTP challenge. Choose a selector that is meaningful for the page being captured.
import { chromium } from 'playwright';
import { mkdir } from 'node:fs/promises';
const targetUrl = process.env.PORTAL_TARGET_URL;
const successSelector = process.env.PORTAL_SUCCESS_SELECTOR;
const outputPath = process.env.SCREENSHOT_PATH ?? `captures/portal-${new Date().toISOString().replaceAll(':', '-')}.png`;
if (!targetUrl || !successSelector) {
throw new Error('Set PORTAL_TARGET_URL and PORTAL_SUCCESS_SELECTOR.');
}
await mkdir('captures', { recursive: true, mode: 0o700 });
const browser = await chromium.launch({ headless: true });
const context = await browser.newContext({ storageState: '.auth/portal-state.json' });
const page = await context.newPage();
try {
await page.goto(targetUrl, { waitUntil: 'domcontentloaded', timeout: 60_000 });
await page.locator(successSelector).waitFor({ state: 'visible', timeout: 20_000 });
await page.screenshot({ path: outputPath, fullPage: true, animations: 'disabled' });
console.log(`Saved authorized page capture to ${outputPath}`);
} catch (error) {
console.error('Capture failed; no screenshot was reported as successful.', error.message);
process.exitCode = 1;
} finally {
await context.close();
await browser.close();
}
Run a single capture:
PORTAL_TARGET_URL='https://portal.example/reports' \
PORTAL_SUCCESS_SELECTOR='main .report-table' \
node capture.mjs
Use a portal-specific selector, title, or other authenticated-page condition. A generic selector such as body is not a useful login check: login pages also have a body. If the page is dynamic, wait for its actual report or data region before capturing. Avoid saving a screenshot when the expected content is missing.
5. Schedule the capture and handle expiration
Run the job under a dedicated operating-system account with access only to the required script, protected state, and output location. For example, a Unix cron entry for a daily run at 06:15 is:
15 6 * * * cd /srv/portal-screenshots && PORTAL_TARGET_URL='https://portal.example/reports' PORTAL_SUCCESS_SELECTOR='main .report-table' /usr/bin/node capture.mjs >> /var/log/portal-screenshots.log 2>&1
Use the real absolute paths and portal values for your environment. Configure your scheduler or monitoring to detect a nonzero exit code and alert an operator. The capture script does not implement a scheduler, upload destination, retention policy, or alerting system; provide those through your approved job platform.
- Run
setup-auth.mjsinteractively and confirm the resulting state works with one capture. - Schedule the job at a frequency allowed by the portal owner.
- Alert on failed navigation, missing authenticated-page selector, missing output, or unexpected login/OTP screen.
- When the portal expires state, revoke the old state file and repeat the approved interactive login. Do not loop retries against an OTP or CAPTCHA challenge.
- Delete state when the account is revoked or the scheduled job no longer needs it. Follow the owner’s data-retention rules for screenshots.
Playwright’s authentication guidance says saved state may contain sensitive cookies and headers that could impersonate the account, and recommends keeping it out of source control. It supports cookies, local storage, and IndexedDB state; passkey-based authentication is also covered in its guidance. The usual storageState mechanism does not persist sessionStorage. If the portal depends on session storage, investigate that portal-specific behavior and the security implications of injecting it into a new context rather than assuming the saved file is complete. Read [Playwright authentication](https://playwright.dev/docs/auth).
6. Make captures stable and useful
For production captures, wait for the specific content you need, use a suitable timeout, and disable animations when that produces the intended view. Full-page captures can be large and may trigger lazy-loaded content differently from a viewport capture; confirm the result visually and choose the capture scope deliberately. If the portal has frequently changing data, record the capture time and avoid treating a changing page as a failed authentication.
Playwright’s screenshot assertions belong to its test runner: they compare against a baseline and wait for consecutive screenshots to stabilize. They are useful for visual tests, but do not provide a scheduled production pipeline, retention, alerting, or job orchestration by themselves. Screenshot assertion options include animation handling, caret behavior, and scale. See [Playwright PageAssertions](https://playwright.dev/docs/api/class-pageassertions).
7. Session, security, and cost considerations
- Session expiry: Stored state is a snapshot, not a permanent login. Expiration, revocation, password changes, or portal policy can invalidate it. Plan for an operator to refresh it.
- Secret handling: Treat the state file like a password. Keep it out of source control and build artifacts, limit filesystem and job access, encrypt storage and backups where appropriate, and remove old copies on rotation.
- Logs and screenshots: Avoid logging authentication material. Screenshots may contain personal or confidential data; restrict access and retention to the portal owner’s requirements.
- Reliability: Classify outcomes as authenticated capture, login/OTP challenge, navigation failure, or missing expected content. Retry transient navigation failures cautiously; do not blindly retry authentication challenges.
- Performance: Reusing a browser state avoids repeating interactive login on every run, but the browser still loads and renders the target page. Capture less content when a viewport image is enough, and keep scheduling frequency proportionate to the portal’s limits.
- Cost: A self-hosted job’s cost depends on its compute, storage, and monitoring environment; no portal-specific or provider cost can be inferred here. A supported API may have its own terms and charges.
GIGW security guidance discusses MFA, secure and HTTP-only cookies, encrypted communication, access control, and security audit practices. These are security recommendations, not permission to bypass portal controls. See [GIGW security guidelines](https://guidelines.india.gov.in/security-guidelines-and-attributes/).
8. Troubleshooting
| Symptom | Likely cause | What to do |
|---|---|---|
| Capture lands on login page | Saved state expired, was not saved after login, or the portal requires a fresh OTP. | Stop treating the image as a successful capture. Confirm the account state, then rerun the authorized interactive setup. Do not automate an undocumented OTP retrieval path. |
| Success selector times out | Wrong selector, page content is slower than expected, or the session is not authenticated. | Inspect the page manually in an authorized session. Choose a selector unique to the signed-in content and adjust the timeout only when the page’s normal load warrants it. |
| Browser state file is missing or unreadable | Wrong working directory, file permissions, or state was never created. | Run the job from its configured project directory, verify the protected file exists for the job account, and repeat setup if needed. Do not loosen permissions broadly. |
| Login succeeds but a new context is unauthenticated | The portal may rely on session storage, a device-bound factor, or state not covered by the saved context. | Check the portal’s supported method and Playwright’s documented state behavior. Request an approved API or automation path from the owner if state reuse is unsupported. |
| Navigation times out or screenshot is incomplete | Slow response, network issue, or delayed client-side content. | Wait for the page’s specific content condition, use a reasonable navigation timeout, and distinguish network failure from authentication failure before retrying. |
| CAPTCHA or repeated OTP challenge appears | The portal is enforcing an access control, session policy, or automation restriction. | Stop the unattended run and contact the portal owner for an approved integration or human-operated process. Do not bypass the challenge or increase retries. |
| Image differs between runs | Dynamic content, animation, timestamps, or lazy loading changed the page. | Wait for the intended content, disable animations if appropriate, and define whether the job needs a viewport or full-page capture. For visual assertions, configure the test runner’s screenshot comparison options. |
Or skip the browser setup
For public pages that do not require portal authentication, ScreenshotNeo can return an image or PDF from one GET request. It does not replace an authorized login to a private portal or bypass OTP; use the Playwright pattern above for an authenticated page unless the portal owner provides another supported route.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
See the ScreenshotNeo API documentation for request options. 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}`);
- Cookie banners, popups, and chat widgets are removed before the shot.
- Bot checks, blank pages, failed loads, timeouts, and cache hits are never billed; response headers report page verdict and billing status.
- An MCP server lets AI agents use
take_screenshot,get_page_info, andcapture_pdf. - 1,000 screenshots a month are free with no card; paid plans start at $5 for 3,000. Every feature is on every plan.
Sign up for 1,000 free screenshots a month, with no card required.
FAQ
Can I automatically read OTPs from SMS or email?
Only if the portal owner explicitly provides and approves that integration. This guide does not assume an OTP channel or authorize access to a personal inbox or phone account.
Can I use the saved Playwright state indefinitely?
No. The portal can expire or revoke it at any time. The job needs a clear failure path and an authorized reauthentication process.
Can ScreenshotNeo capture a page after this OTP login?
This API example captures a URL and does not establish an authenticated portal session. Use a portal-approved authenticated browser workflow for private pages.
Should I use Playwright screenshot assertions in the scheduled job?
Use assertions when the goal is comparison against a visual baseline in a test. For scheduled image delivery, use page screenshot capture and add your own scheduling, output handling, and alerting.


