diff --git a/docs/src/codegen.md b/docs/src/codegen.md
index 641383d7b4e0a..7f2358bdaaafd 100644
--- a/docs/src/codegen.md
+++ b/docs/src/codegen.md
@@ -414,6 +414,30 @@ pwsh bin/Debug/netX/playwright.ps1 codegen --load-storage=auth.json github.com/m
+#### Use existing userDataDir
+
+Run `codegen` with `--user-data-dir` to set a fixed [user data directory](https://playwright.dev/docs/api/class-browsertype#browser-type-launch-persistent-context-option-user-data-dir) for the browser session. If you create a custom browser user data directory, codegen will use this existing browser profile and have access to any authentication state present in that profile.
+
+:::warning
+[As of Chrome 136, the default user data directory cannot be accessed via automated tooling](https://developer.chrome.com/blog/remote-debugging-port), such as Playwright. You must create a separate user data directory for use in testing.
+:::
+
+```bash js
+npx playwright codegen --user-data-dir=/path/to/your/browser/data/ github.com/microsoft/playwright
+```
+
+```bash java
+mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="codegen --user-data-dir=/path/to/your/browser/data/ github.com/microsoft/playwright"
+```
+
+```bash python
+playwright codegen --user-data-dir=/path/to/your/browser/data/ github.com/microsoft/playwright
+```
+
+```bash csharp
+pwsh bin/Debug/netX/playwright.ps1 codegen --user-data-dir=/path/to/your/browser/data/ github.com/microsoft/playwright
+```
+
## Record using custom setup
If you would like to use codegen in some non-standard setup (for example, use [`method: BrowserContext.route`]), it is possible to call [`method: Page.pause`] that will open a separate window with codegen controls.
diff --git a/packages/playwright-core/src/cli/program.ts b/packages/playwright-core/src/cli/program.ts
index a1927b912de50..28971acbc50ac 100644
--- a/packages/playwright-core/src/cli/program.ts
+++ b/packages/playwright-core/src/cli/program.ts
@@ -70,6 +70,7 @@ commandWithOpenOptions('codegen [url]', 'open page and generate code for user ac
['-o, --output ', 'saves the generated script to a file'],
['--target ', `language to generate, one of javascript, playwright-test, python, python-async, python-pytest, csharp, csharp-mstest, csharp-nunit, java, java-junit`, codegenId()],
['--test-id-attribute ', 'use the specified attribute to generate data test ID selectors'],
+ ['--user-data-dir ', 'use the specified user data directory instead of a new context'],
]).action(function(url, options) {
codegen(options, url).catch(logErrorAndExit);
}).addHelpText('afterAll', `
@@ -423,6 +424,7 @@ type Options = {
timezone?: string;
viewportSize?: string;
userAgent?: string;
+ userDataDir?: string;
};
type CaptureOptions = {
@@ -472,33 +474,6 @@ async function launchContext(options: Options, extraOptions: LaunchOptions): Pro
launchOptions.proxy.bypass = options.proxyBypass;
}
- const browser = await browserType.launch(launchOptions);
-
- if (process.env.PWTEST_CLI_IS_UNDER_TEST) {
- (process as any)._didSetSourcesForTest = (text: string) => {
- process.stdout.write('\n-------------8<-------------\n');
- process.stdout.write(text);
- process.stdout.write('\n-------------8<-------------\n');
- const autoExitCondition = process.env.PWTEST_CLI_AUTO_EXIT_WHEN;
- if (autoExitCondition && text.includes(autoExitCondition))
- closeBrowser();
- };
- // Make sure we exit abnormally when browser crashes.
- const logs: string[] = [];
- require('playwright-core/lib/utilsBundle').debug.log = (...args: any[]) => {
- const line = require('util').format(...args) + '\n';
- logs.push(line);
- process.stderr.write(line);
- };
- browser.on('disconnected', () => {
- const hasCrashLine = logs.some(line => line.includes('process did exit:') && !line.includes('process did exit: exitCode=0, signal=null'));
- if (hasCrashLine) {
- process.stderr.write('Detected browser crash.\n');
- gracefullyProcessExitDoNotHang(1);
- }
- });
- }
-
// Viewport size
if (options.viewportSize) {
try {
@@ -563,9 +538,43 @@ async function launchContext(options: Options, extraOptions: LaunchOptions): Pro
contextOptions.serviceWorkers = 'block';
}
- // Close app when the last window closes.
+ let browser: Browser;
+ let context: BrowserContext;
+
+ if (options.userDataDir) {
+ context = await browserType.launchPersistentContext(options.userDataDir, { ...launchOptions, ...contextOptions });
+ const persistentBrowser = context.browser();
+ assert(persistentBrowser, 'Could not launch persistent browser context');
+ browser = persistentBrowser;
+ } else {
+ browser = await browserType.launch(launchOptions);
+ context = await browser.newContext(contextOptions);
+ }
- const context = await browser.newContext(contextOptions);
+ if (process.env.PWTEST_CLI_IS_UNDER_TEST) {
+ (process as any)._didSetSourcesForTest = (text: string) => {
+ process.stdout.write('\n-------------8<-------------\n');
+ process.stdout.write(text);
+ process.stdout.write('\n-------------8<-------------\n');
+ const autoExitCondition = process.env.PWTEST_CLI_AUTO_EXIT_WHEN;
+ if (autoExitCondition && text.includes(autoExitCondition))
+ closeBrowser();
+ };
+ // Make sure we exit abnormally when browser crashes.
+ const logs: string[] = [];
+ require('playwright-core/lib/utilsBundle').debug.log = (...args: any[]) => {
+ const line = require('util').format(...args) + '\n';
+ logs.push(line);
+ process.stderr.write(line);
+ };
+ browser.on('disconnected', () => {
+ const hasCrashLine = logs.some(line => line.includes('process did exit:') && !line.includes('process did exit: exitCode=0, signal=null'));
+ if (hasCrashLine) {
+ process.stderr.write('Detected browser crash.\n');
+ gracefullyProcessExitDoNotHang(1);
+ }
+ });
+ }
let closingBrowser = false;
async function closeBrowser() {
@@ -608,8 +617,10 @@ async function launchContext(options: Options, extraOptions: LaunchOptions): Pro
return { browser, browserName: browserType.name(), context, contextOptions, launchOptions };
}
-async function openPage(context: BrowserContext, url: string | undefined): Promise {
- const page = await context.newPage();
+async function openPage(context: BrowserContext, url: string | undefined, allowPageReuse: boolean = false): Promise {
+ let page = allowPageReuse ? context.pages()[0] : undefined;
+ if (!page)
+ page = await context.newPage();
if (url) {
if (fs.existsSync(url))
url = 'file://' + path.resolve(url);
@@ -660,7 +671,7 @@ async function codegen(options: Options & { target: string, output?: string, tes
outputFile: outputFile ? path.resolve(outputFile) : undefined,
handleSIGINT: false,
});
- await openPage(context, url);
+ await openPage(context, url, true);
}
async function waitForPage(page: Page, captureOptions: CaptureOptions) {
diff --git a/tests/installation/playwright-cli.spec.ts b/tests/installation/playwright-cli.spec.ts
index 02e65a0fe8e2b..de278ac9defeb 100755
--- a/tests/installation/playwright-cli.spec.ts
+++ b/tests/installation/playwright-cli.spec.ts
@@ -16,6 +16,7 @@
import { test, expect } from './npmTest';
import path from 'path';
import fs from 'fs';
+import os from 'os';
test('cli should work', async ({ exec, tmpWorkspace }) => {
await exec('npm i playwright');
@@ -31,6 +32,23 @@ test('cli should work', async ({ exec, tmpWorkspace }) => {
expect(result).toContain(`{ page }`);
});
+ await test.step('codegen with user data dir', async () => {
+ const userDataDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'playwright-test-custom-user-data-dir'));
+
+ try {
+ const result = await exec(`npx playwright codegen --user-data-dir ${userDataDir} about:blank`, {
+ env: {
+ PWTEST_CLI_IS_UNDER_TEST: '1',
+ PWTEST_CLI_AUTO_EXIT_WHEN: `goto('about:blank')`,
+ }
+ });
+ expect(fs.readdirSync(userDataDir).length).toBeGreaterThan(0);
+ expect(result).toContain(`{ page }`);
+ } finally {
+ fs.rmdirSync(userDataDir, { recursive: true });
+ }
+ });
+
await test.step('codegen --target=javascript', async () => {
const result = await exec('npx playwright codegen --target=javascript', {
env: {