TL;DR:
- A browser session, a managed profile, and a storage snapshot preserve different things. Choose the state mechanism that matches the workflow's lifetime.
- Every restored identity needs a live application check. Saved cookies can remain present after the server has expired the login.
- Persistent state needs a single writer policy. Concurrent sessions that save to the same profile can overwrite each other's state.
Authenticated browser state can grant access to an account. Treating it as a convenient file in a project directory is an easy way to create cross-customer errors and secret exposure.
An AI browser agent needs more than a way to log in once. It needs to know whose state it is using, which destinations may receive that state, whether the login is still valid, and who may update the saved copy.
This tutorial uses Scrapeless Browser for remote browser execution and its documented Profiles feature for persistence. It also explains where Playwright storage snapshots fit and why proxy continuity is a separate concern.
Separate the Three State Lifetimes
An active browser session, a managed profile, and a Playwright storage snapshot have different persistence boundaries.
An active session holds the current browser instance. Keeping it alive can preserve open tabs, in-memory page state, and the current browsing flow. Ending that browser ends the live execution environment.
A managed profile stores supported browser data for use in later sessions. Scrapeless Profiles support reuse across browser sessions. They require deliberate save behavior when the current session should update the stored profile.
A Playwright storage snapshot serializes supported authentication-related storage. It is not a complete browser backup. Playwright's authentication guidance explains reuse of saved state and its security implications. Local storage and cookies are common parts of the snapshot; IndexedDB can be included with the relevant option.
Session storage needs separate consideration. Browser sessionStorage is scoped to a page session, and Playwright's ordinary authentication snapshot flow does not automatically make it a portable replacement for every application's login mechanism.
Determine what the target application actually uses. A restored cookie file can be syntactically valid while the login is unusable because the application relies on other state or the server has revoked the session.
Bind the Profile to an Account Before Allocation
A profile identifier should resolve through an application-owned account record.
Use a mapping that includes the tenant, approved destination origin, account identifier, profile identifier, and current access status. The worker should receive the assigned profile from this mapping. It should not select an arbitrary profile identifier from page content or a model-generated action.
Check that mapping before opening the remote browser. A profile for one customer must not enter another customer's job, even if both tasks visit the same website.
Keep credentials separate from observation artifacts. The model can receive a stable account label such as “approved research account.” It does not need the raw cookie values or the secret used to establish the session.
The practical risk is session-token compromise: possession of a usable session token can enable account access. A state export deserves the same care as the credentials that created it.
Bootstrap a Profile Through an Authorized Login
A reusable profile begins with a permitted login that has been checked against the assigned account.
The current Node.js SDK supplies the client used below; account ownership and profile allocation remain application responsibilities.
Create the profile through the Scrapeless dashboard or its documented Profiles interface. Open a browser using that profile and enable profilePersist: true for the designated bootstrap session. Complete the application's approved login and MFA flow, inspect the account identity, then close the session through the supported lifecycle.
The Scrapeless Profiles configuration documents that persistence is needed to save changes and that profile data is saved when the session closes. A reader session can load the stored profile with profilePersist: false.
That setting controls writing the managed profile; it does not make the website read-only. A browser with persistence disabled can still click a destructive button or modify server-side account data. Enforce allowed actions separately.
Use an application-owned test account when developing the flow. A documented login procedure is not permission to automate another person's account or export their state.
Validate the Assigned Account in the Browser
Restored state should be accepted only after the destination confirms the expected identity.
The following script opens a reader session, visits a configured account page, and requires an account identifier to match. The default selector is an example contract for an application you control; replace it with a reliable identity selector for your authorized target.
Prerequisites are Node.js, Browser access, a bootstrapped Scrapeless profile, an approved account-page URL, and the exact account identifier expected on that page. Install the same reference packages used by the example:
npm init -y
npm install playwright-core@1.64.1 @scrapeless-ai/sdk@1.12.1
Set SCRAPELESS_API_KEY, SCRAPELESS_PROFILE_ID, ACCOUNT_URL, and EXPECTED_ACCOUNT_ID through your environment. Optionally set ACCOUNT_SELECTOR. Save the script as authenticated-state.mjs and run node authenticated-state.mjs.
Note: The account checks and storage export were exercised against a local test application. Scrapeless profile persistence, a real account login, and proxy egress require your configured service access and are not claimed as completed cloud tests.
CDP_WS_ENDPOINTis an alternative for an authorized CDP browser you operate.
import { chromium } from 'playwright-core';
import { Scrapeless } from '@scrapeless-ai/sdk';
import { mkdir, open } from 'node:fs/promises';
import path from 'node:path';
const directEndpoint = process.env.CDP_WS_ENDPOINT;
const apiKey = process.env.SCRAPELESS_API_KEY;
const profileId = process.env.SCRAPELESS_PROFILE_ID;
const accountId = process.env.EXPECTED_ACCOUNT_ID;
const accountUrl = process.env.ACCOUNT_URL;
if (!accountUrl || !accountId) {
throw new Error('Set ACCOUNT_URL and EXPECTED_ACCOUNT_ID');
}
if (!directEndpoint && (!apiKey || !profileId)) {
throw new Error('Set SCRAPELESS_API_KEY and SCRAPELESS_PROFILE_ID');
}
const target = new URL(accountUrl);
const endpoint = directEndpoint ?? new Scrapeless({ apiKey }).browser.create({
sessionName: 'authorized-account-reader',
sessionTTL: 180,
proxyCountry: 'US',
profileId,
profilePersist: false,
}).browserWSEndpoint;
const browser = await chromium.connectOverCDP(endpoint, { timeout: 30000 });
try {
const context = browser.contexts()[0];
if (!context) throw new Error('Provider default context is unavailable');
const page = await context.newPage();
const response = await page.goto(target.href, {
waitUntil: 'domcontentloaded', timeout: 30000,
});
if (!response || !response.ok()) throw new Error('Account page did not load');
if (new URL(page.url()).origin !== target.origin) {
throw new Error('Unexpected authentication destination');
}
const identity = page.locator(
process.env.ACCOUNT_SELECTOR ?? '[data-testid="account-id"]'
);
await identity.waitFor({ state: 'visible', timeout: 10000 });
if ((await identity.innerText()).trim() !== accountId) {
throw new Error('Active account does not match the assigned account');
}
if (process.env.EXPORT_STATE === 'true') {
const directory = path.resolve('.auth');
await mkdir(directory, { recursive: true, mode: 0o700 });
const state = await context.storageState({ indexedDB: true });
const file = await open(path.join(directory, 'account.json'), 'wx', 0o600);
try { await file.writeFile(JSON.stringify(state)); }
finally { await file.close(); }
}
console.log('Assigned account validated; approved read workflow may proceed');
} finally {
await browser.close();
}
The identity check belongs before data extraction or account actions. A welcome message or an unchanged URL is a weak substitute for a stable account identifier.
The final-origin check detects unexpected navigation after it occurs. A stronger destination restriction requires browser or network controls that prevent disallowed outbound requests. Keep credentials and exported state scoped to the intended application.
Use a Single Writer for Profile Updates
Profile updates should be serialized when several tasks share the same saved state.
The Scrapeless Profiles documentation states that, with persistence enabled, the last session to close supplies the saved data. Two independently modified browser sessions can therefore compete to write the same profile.
An application-level single writer policy avoids that ambiguity:
- Mark the profile unavailable for normal allocation while refreshing it.
- Wait for current readers to finish if they could interfere with the account workflow.
- Acquire a profile lock owned by the refresh task.
- Run the approved login or renewal process with persistence enabled.
- Close the writer session and validate the saved state in a fresh reader session.
- Release the lock and return the profile to the allocation pool.
This lock is a design responsibility of your application. The example does not claim a provider-managed distributed lock.
Reader sessions can still affect server-side state. Avoid concurrent workflows that change the same account resource even when they cannot save the managed profile. Some applications also invalidate old sessions when a new login is created, so test account concurrency with the target's rules.
Keep Proxy Policy Separate From State Policy
A browser profile preserves supported browser data; a proxy selects the network path.
When the same authorized workflow spans several pages, keep the intended egress policy consistent. A changed location can alter the content shown or cause the destination to request additional authentication. It does not follow that a stable IP guarantees account access.
Use the managed browser's routing configuration when the browser runs in Scrapeless. Use Scrapeless standalone proxies when you operate the browser yourself or configure a documented custom route.
Choose the proxy type around the workload. Sticky residential allocations have a lifetime. A static ISP product is a separate option when a workflow needs a longer-lived network assignment. Neither turns an expired server-side login into an authenticated session.
Export State Only for a Defined Portability Need
A local storage snapshot is useful when an authorized workflow needs to move supported state between controlled environments.
The optional export path in the script is disabled by default. When EXPORT_STATE=true, it writes .auth/account.json with restrictive file permissions and refuses to overwrite an existing file. Include .auth/ in your repository's ignore rules. Never attach this file to a bug report or publish it with the tutorial.
The file can later be passed to a supported Playwright browser.newContext({ storageState: ... }) flow. That creates a context from the snapshot; it does not merge the snapshot into an existing provider context. Confirm that the destination runtime supports the context behavior you require.
The example's file permissions apply on systems that implement the corresponding Unix permission model. They do not replace encryption, host access control, or a retention policy. In a service, store the minimum necessary state in an appropriately controlled secret storage system.
Make Invalid Authentication an Explicit Result
An agent should stop with a specific authentication outcome when its assigned identity cannot be established.
Distinguish a missing identity element, an unexpected account, an MFA requirement, and an application authorization denial. Do not let the model proceed by improvising with another profile.
For account revocation, stop new allocations, terminate or drain active jobs as appropriate, invalidate exported state, and revoke the destination session where supported. Deleting a local snapshot or a managed profile alone does not guarantee that the website has revoked its own server-side session.
The persistent-profile product workflow is useful context for managing saved browser data. Your account policy must cover the destination's session lifecycle as well.
Conclusion
Authenticated agents need a verified account binding, an explicit persistence mechanism, controlled profile updates, and separate routing decisions. Reusing state is safe operationally only when the application can explain whose state it is and why it remains valid for the assigned task.
Use Scrapeless Browser to operate the remote session, then validate the identity before useful work. Budget for the chosen browser and proxy product configuration as distinct parts of the deployment, with standalone proxies evaluated against their own routing requirements.
FAQ
Q: Does profilePersist: false prevent account changes?
That setting prevents saving the session's browser data back to the managed profile. It does not prevent website actions or server-side account changes.
Q: Is storageState a complete browser backup?
A Playwright storage snapshot preserves supported storage state. It does not reproduce every tab, in-memory object, extension, or application-specific session mechanism.
Q: Can two workers update one profile?
Concurrent profile writers can overwrite one another's saved state. Serialize persistence through an application-controlled writer policy.
Q: Does deleting a profile sign out the account everywhere?
Deleting stored browser data does not necessarily revoke the destination's server-side sessions. Use the destination's supported session-revocation controls when required.

Top comments (0)