Skip to content

feat(codegen): add user-data-dir option #35814

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions docs/src/codegen.md
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,30 @@ pwsh bin/Debug/netX/playwright.ps1 codegen --load-storage=auth.json github.com/m

<img width="1394" alt="github signed in showing use of load storage scharp" src="https://user-images.githubusercontent.com/13063165/220928354-caa0e958-fe09-4125-9b54-67483064da51.png" />

#### 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.
Expand Down
75 changes: 43 additions & 32 deletions packages/playwright-core/src/cli/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ commandWithOpenOptions('codegen [url]', 'open page and generate code for user ac
['-o, --output <file name>', 'saves the generated script to a file'],
['--target <language>', `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 <attributeName>', 'use the specified attribute to generate data test ID selectors'],
['--user-data-dir <directory>', 'use the specified user data directory instead of a new context'],
]).action(function(url, options) {
codegen(options, url).catch(logErrorAndExit);
}).addHelpText('afterAll', `
Expand Down Expand Up @@ -423,6 +424,7 @@ type Options = {
timezone?: string;
viewportSize?: string;
userAgent?: string;
userDataDir?: string;
};

type CaptureOptions = {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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<Page> {
const page = await context.newPage();
async function openPage(context: BrowserContext, url: string | undefined, allowPageReuse: boolean = false): Promise<Page> {
let page = allowPageReuse ? context.pages()[0] : undefined;
if (!page)
page = await context.newPage();
if (url) {
if (fs.existsSync(url))
url = 'file://' + path.resolve(url);
Expand Down Expand Up @@ -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) {
Expand Down
18 changes: 18 additions & 0 deletions tests/installation/playwright-cli.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand All @@ -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: {
Expand Down
Loading