/** * Converts a Playwright storageState (the format ZeTa already captures via * session-capture.ts/live-view.ts and stores per-project) into snippets for * other automation frameworks. Playwright's storageState is the de-facto * standard for browser session export — see docs/UNIVERSAL-SESSION-CAPTURE- * STRATEGY.md — so every other framework's import mechanism is a small, * mechanical conversion away, not a new capture flow. * * Pure, dependency-free functions — no I/O, no network, fully unit-testable. */ export interface StorageStateCookie { name: string; value: string; domain: string; path: string; expires?: number; httpOnly?: boolean; secure?: boolean; sameSite?: 'Strict' | 'Lax' | 'None'; } export interface StorageStateOrigin { origin: string; localStorage: Array<{ name: string; value: string }>; } export interface StorageState { cookies: StorageStateCookie[]; origins: StorageStateOrigin[]; } export type SessionExportFormat = 'cypress' | 'selenium-python' | 'webdriverio'; function jsStringLiteral(s: string): string { return JSON.stringify(s); } /** Python dict-literal repr — JSON.stringify's `true`/`false`/`null` aren't * valid Python (Python needs `True`/`False`/`None`), so a plain * JSON.stringify would emit syntactically invalid Python for any cookie * with a boolean field. */ function pythonDictLiteral(obj: Record): string { const parts = Object.entries(obj).map(([k, v]) => { let repr: string; if (typeof v === 'boolean') repr = v ? 'True' : 'False'; else if (v === null || v === undefined) repr = 'None'; else if (typeof v === 'number') repr = String(v); else repr = JSON.stringify(v); return `${JSON.stringify(k)}: ${repr}`; }); return `{${parts.join(', ')}}`; } /** cy.setCookie() + localStorage.setItem() inside a reusable custom command, * following Cypress's documented cy.session() pattern (see references in * docs/UNIVERSAL-SESSION-CAPTURE-STRATEGY.md). */ export function toCypressSnippet(state: StorageState): string { const cookieLines = state.cookies.map((c) => { const opts: string[] = [`domain: ${jsStringLiteral(c.domain)}`, `path: ${jsStringLiteral(c.path)}`]; if (c.secure) opts.push('secure: true'); if (c.httpOnly) opts.push('httpOnly: true'); if (c.sameSite) opts.push(`sameSite: ${jsStringLiteral(c.sameSite.toLowerCase())}`); return ` cy.setCookie(${jsStringLiteral(c.name)}, ${jsStringLiteral(c.value)}, { ${opts.join(', ')} });`; }); const localStorageBlocks = state.origins .filter((o) => o.localStorage.length > 0) .map((o) => { const sets = o.localStorage .map((kv) => ` window.localStorage.setItem(${jsStringLiteral(kv.name)}, ${jsStringLiteral(kv.value)});`) .join('\n'); return ` cy.visit(${jsStringLiteral(o.origin)});\n cy.window().then((window) => {\n${sets}\n });`; }); return [ '// Generated by ZeTa-AI — restores a captured browser session in Cypress.', '// Usage: import this file and call restoreZetaSession() inside cy.session(), e.g.:', "// cy.session('zeta-session', restoreZetaSession)", "Cypress.Commands.add('restoreZetaSession', () => {", ...cookieLines, ...localStorageBlocks, '});', ].join('\n'); } /** driver.add_cookie() (Selenium requires the browser already be on the * cookie's domain before adding it) + execute_script() for localStorage, * per Selenium's documented Cookie API. */ export function toSeleniumPythonDict(state: StorageState): string { const byDomain = new Map(); for (const c of state.cookies) { const list = byDomain.get(c.domain) ?? []; list.push(c); byDomain.set(c.domain, list); } const lines: string[] = [ '# Generated by ZeTa-AI — restores a captured browser session in Selenium.', '# Selenium requires the driver to already be on a matching domain before', '# add_cookie() will accept a cookie for it — navigate first, then restore.', 'def restore_zeta_session(driver):', ]; for (const [domain, cookies] of byDomain) { lines.push(` driver.get(${JSON.stringify(`https://${domain.replace(/^\./, '')}`)})`); for (const c of cookies) { const cookieDict: Record = { name: c.name, value: c.value, path: c.path }; if (c.expires && c.expires > 0) cookieDict.expiry = Math.floor(c.expires); if (c.secure) cookieDict.secure = true; lines.push(` driver.add_cookie(${pythonDictLiteral(cookieDict)})`); } } for (const o of state.origins) { if (o.localStorage.length === 0) continue; lines.push(` driver.get(${JSON.stringify(o.origin)})`); for (const kv of o.localStorage) { lines.push( ` driver.execute_script(${JSON.stringify(`window.localStorage.setItem(arguments[0], arguments[1]);`)}, ${JSON.stringify(kv.name)}, ${JSON.stringify(kv.value)})`, ); } } return lines.join('\n'); } /** browser.setCookies() accepts an array shape very close to Playwright's own * cookie objects (per WebdriverIO's documented API) — closest 1:1 mapping * of the three target formats. */ export function toWebdriverIOSnippet(state: StorageState): string { const cookies = state.cookies.map((c) => ({ name: c.name, value: c.value, domain: c.domain, path: c.path, secure: !!c.secure, httpOnly: !!c.httpOnly, ...(c.expires && c.expires > 0 ? { expiry: Math.floor(c.expires) } : {}), })); const localStorageSets = state.origins .filter((o) => o.localStorage.length > 0) .map((o) => { const sets = o.localStorage .map((kv) => ` localStorage.setItem(${jsStringLiteral(kv.name)}, ${jsStringLiteral(kv.value)});`) .join('\n'); return ` await browser.url(${jsStringLiteral(o.origin)});\n await browser.execute(() => {\n${sets}\n });`; }); return [ '// Generated by ZeTa-AI — restores a captured browser session in WebdriverIO.', 'async function restoreZetaSession(browser) {', ` await browser.setCookies(${JSON.stringify(cookies, null, 2).split('\n').join('\n ')});`, ...localStorageSets, '}', 'module.exports = { restoreZetaSession };', ].join('\n'); } export function exportSessionAs(format: SessionExportFormat, state: StorageState): { content: string; filename: string; mimeType: string } { switch (format) { case 'cypress': return { content: toCypressSnippet(state), filename: 'zeta-session.cy-commands.js', mimeType: 'application/javascript' }; case 'selenium-python': return { content: toSeleniumPythonDict(state), filename: 'zeta_session.py', mimeType: 'text/x-python' }; case 'webdriverio': return { content: toWebdriverIOSnippet(state), filename: 'zeta-session.wdio.js', mimeType: 'application/javascript' }; default: throw new Error(`Unknown session export format: ${format}`); } }