mirror of
https://github.com/hanzoai/playwright.git
synced 2026-09-24 17:01:53 +00:00
test: switch from codegen cli output to using a file (#36974)
This commit is contained in:
@@ -23,7 +23,7 @@ import path from 'path';
|
||||
import * as playwright from '../..';
|
||||
import { launchBrowserServer, printApiJson, runDriver, runServer } from './driver';
|
||||
import { registry, writeDockerVersion } from '../server';
|
||||
import { gracefullyProcessExitDoNotHang, isLikelyNpxGlobal } from '../utils';
|
||||
import { gracefullyProcessExitDoNotHang, isLikelyNpxGlobal, ManualPromise } from '../utils';
|
||||
import { runTraceInBrowser, runTraceViewerApp } from '../server/trace/viewer/traceViewer';
|
||||
import { assert, getPackageManagerExecCommand } from '../utils';
|
||||
import { wrapInASCIIBox } from '../server/utils/ascii';
|
||||
@@ -70,15 +70,8 @@ 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'],
|
||||
]).action(function(url, options) {
|
||||
codegen(options, url).catch(error => {
|
||||
if (process.env.PWTEST_CLI_AUTO_EXIT_WHEN) {
|
||||
// Tests with PWTEST_CLI_AUTO_EXIT_WHEN might close page too fast, resulting
|
||||
// in a stray navigation aborted error. We should ignore it.
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
]).action(async function(url, options) {
|
||||
await codegen(options, url);
|
||||
}).addHelpText('afterAll', `
|
||||
Examples:
|
||||
|
||||
@@ -440,7 +433,7 @@ type CaptureOptions = {
|
||||
paperFormat?: string;
|
||||
};
|
||||
|
||||
async function launchContext(options: Options, extraOptions: LaunchOptions): Promise<{ browser: Browser, browserName: string, launchOptions: LaunchOptions, contextOptions: BrowserContextOptions, context: BrowserContext }> {
|
||||
async function launchContext(options: Options, extraOptions: LaunchOptions): Promise<{ browser: Browser, browserName: string, launchOptions: LaunchOptions, contextOptions: BrowserContextOptions, context: BrowserContext, closeBrowser: () => Promise<void> }> {
|
||||
validateOptions(options);
|
||||
const browserType = lookupBrowserType(options);
|
||||
const launchOptions: LaunchOptions = extraOptions;
|
||||
@@ -555,31 +548,6 @@ async function launchContext(options: Options, extraOptions: LaunchOptions): Pro
|
||||
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() {
|
||||
// We can come here multiple times. For example, saving storage creates
|
||||
@@ -618,7 +586,7 @@ async function launchContext(options: Options, extraOptions: LaunchOptions): Pro
|
||||
delete launchOptions.executablePath;
|
||||
delete launchOptions.handleSIGINT;
|
||||
delete contextOptions.deviceScaleFactor;
|
||||
return { browser, browserName: browserType.name(), context, contextOptions, launchOptions };
|
||||
return { browser, browserName: browserType.name(), context, contextOptions, launchOptions, closeBrowser };
|
||||
}
|
||||
|
||||
async function openPage(context: BrowserContext, url: string | undefined): Promise<Page> {
|
||||
@@ -643,11 +611,13 @@ async function open(options: Options, url: string | undefined) {
|
||||
async function codegen(options: Options & { target: string, output?: string, testIdAttribute?: string }, url: string | undefined) {
|
||||
const { target: language, output: outputFile, testIdAttribute: testIdAttributeName } = options;
|
||||
const tracesDir = path.join(os.tmpdir(), `playwright-recorder-trace-${Date.now()}`);
|
||||
const { context, launchOptions, contextOptions } = await launchContext(options, {
|
||||
const { context, browser, launchOptions, contextOptions, closeBrowser } = await launchContext(options, {
|
||||
headless: !!process.env.PWTEST_CLI_HEADLESS,
|
||||
executablePath: process.env.PWTEST_CLI_EXECUTABLE_PATH,
|
||||
tracesDir,
|
||||
});
|
||||
const donePromise = new ManualPromise<void>();
|
||||
maybeSetupTestHooks(browser, closeBrowser, donePromise);
|
||||
dotenv.config({ path: 'playwright.env' });
|
||||
await context._enableRecorder({
|
||||
language,
|
||||
@@ -661,6 +631,47 @@ async function codegen(options: Options & { target: string, output?: string, tes
|
||||
handleSIGINT: false,
|
||||
});
|
||||
await openPage(context, url);
|
||||
donePromise.resolve();
|
||||
}
|
||||
|
||||
async function maybeSetupTestHooks(browser: Browser, closeBrowser: () => Promise<void>, donePromise: Promise<void>) {
|
||||
if (!process.env.PWTEST_CLI_IS_UNDER_TEST)
|
||||
return;
|
||||
|
||||
// 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);
|
||||
}
|
||||
});
|
||||
|
||||
const close = async () => {
|
||||
await donePromise;
|
||||
await closeBrowser();
|
||||
};
|
||||
|
||||
if (process.env.PWTEST_CLI_EXIT_AFTER_TIMEOUT) {
|
||||
setTimeout(close, +process.env.PWTEST_CLI_EXIT_AFTER_TIMEOUT);
|
||||
return;
|
||||
}
|
||||
|
||||
// Note: we cannot use SIGINT, as it is not available on Windows.
|
||||
let stdin = '';
|
||||
process.stdin.on('data', data => {
|
||||
stdin += data.toString();
|
||||
if (stdin.startsWith('exit')) {
|
||||
process.stdin.destroy();
|
||||
close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function waitForPage(page: Page, captureOptions: CaptureOptions) {
|
||||
|
||||
@@ -322,13 +322,6 @@ export class RecorderApp {
|
||||
this._page.mainFrame().evaluateExpression((({ sources }: { sources: Source[] }) => {
|
||||
window.playwrightSetSources(sources);
|
||||
}).toString(), { isFunction: true }, { sources }).catch(() => {});
|
||||
|
||||
// Testing harness for runCLI mode.
|
||||
if (process.env.PWTEST_CLI_IS_UNDER_TEST && sources.length) {
|
||||
const primarySource = sources.find(s => s.isPrimary);
|
||||
if ((process as any)._didSetSourcesForTest(primarySource?.text ?? ''))
|
||||
this._page.close().catch(() => {});
|
||||
}
|
||||
}
|
||||
|
||||
private _updateActions(initial: boolean = false) {
|
||||
|
||||
@@ -23,50 +23,62 @@ test('cli should work', async ({ exec, tmpWorkspace }) => {
|
||||
await exec('npx playwright install chromium');
|
||||
|
||||
await test.step('codegen without arguments', async () => {
|
||||
const result = await exec('npx playwright codegen', {
|
||||
const outputFile = test.info().outputPath('codegen.output');
|
||||
await exec(`npx playwright codegen --output=${outputFile}`, {
|
||||
env: {
|
||||
PWTEST_CLI_IS_UNDER_TEST: '1',
|
||||
PWTEST_CLI_AUTO_EXIT_WHEN: '@playwright/test',
|
||||
PWTEST_CLI_EXIT_AFTER_TIMEOUT: '10000',
|
||||
}
|
||||
});
|
||||
expect(result).toContain(`{ page }`);
|
||||
const contents = fs.readFileSync(outputFile, 'utf-8');
|
||||
expect(contents).toContain(`@playwright/test`);
|
||||
expect(contents).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'));
|
||||
const outputFile = test.info().outputPath('codegen.output');
|
||||
|
||||
try {
|
||||
const result = await exec(`npx playwright codegen --user-data-dir ${userDataDir} about:blank`, {
|
||||
await exec(`npx playwright codegen --output=${outputFile} --user-data-dir ${userDataDir} about:blank`, {
|
||||
env: {
|
||||
PWTEST_CLI_IS_UNDER_TEST: '1',
|
||||
PWTEST_CLI_AUTO_EXIT_WHEN: `goto('about:blank')`,
|
||||
PWTEST_CLI_EXIT_AFTER_TIMEOUT: '10000',
|
||||
}
|
||||
});
|
||||
expect(fs.readdirSync(userDataDir).length).toBeGreaterThan(0);
|
||||
expect(result).toContain(`{ page }`);
|
||||
const contents = fs.readFileSync(outputFile, 'utf-8');
|
||||
expect(contents).toContain(`goto('about:blank')`);
|
||||
expect(contents).toContain(`{ page }`);
|
||||
} finally {
|
||||
fs.rmSync(userDataDir, { recursive: true });
|
||||
}
|
||||
});
|
||||
|
||||
await test.step('codegen --target=javascript', async () => {
|
||||
const result = await exec('npx playwright codegen --target=javascript', {
|
||||
const outputFile = test.info().outputPath('codegen.output');
|
||||
await exec(`npx playwright codegen --target=javascript --output=${outputFile}`, {
|
||||
env: {
|
||||
PWTEST_CLI_IS_UNDER_TEST: '1',
|
||||
PWTEST_CLI_AUTO_EXIT_WHEN: 'context.close',
|
||||
PWTEST_CLI_EXIT_AFTER_TIMEOUT: '10000',
|
||||
}
|
||||
});
|
||||
expect(result).toContain(`playwright`);
|
||||
const contents = fs.readFileSync(outputFile, 'utf-8');
|
||||
expect(contents).toContain(`context.close`);
|
||||
expect(contents).toContain(`playwright`);
|
||||
});
|
||||
|
||||
await test.step('codegen --target=python', async () => {
|
||||
const result = await exec('npx playwright codegen --target=python', {
|
||||
const outputFile = test.info().outputPath('codegen.output');
|
||||
await exec(`npx playwright codegen --target=python --output=${outputFile}`, {
|
||||
env: {
|
||||
PWTEST_CLI_IS_UNDER_TEST: '1',
|
||||
PWTEST_CLI_AUTO_EXIT_WHEN: 'chromium.launch',
|
||||
PWTEST_CLI_EXIT_AFTER_TIMEOUT: '10000',
|
||||
},
|
||||
});
|
||||
expect(result).toContain(`browser.close`);
|
||||
const contents = fs.readFileSync(outputFile, 'utf-8');
|
||||
expect(contents).toContain(`chromium.launch`);
|
||||
expect(contents).toContain(`browser.close`);
|
||||
});
|
||||
|
||||
await test.step('screenshot', async () => {
|
||||
|
||||
@@ -463,8 +463,7 @@ await page1.GotoAsync("about:blank?foo");`);
|
||||
// Since our interrupt is non-graceful, we need to wait for the process to settle.
|
||||
// This test should be fixed.
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
await cli.process.kill('SIGINT');
|
||||
const { exitCode, signal } = await cli.process.exited;
|
||||
const { exitCode, signal } = await cli.sigint();
|
||||
if (exitCode !== null) {
|
||||
expect(exitCode).toBe(130);
|
||||
} else {
|
||||
|
||||
@@ -40,12 +40,13 @@ var context = await browser.NewContextAsync();`;
|
||||
await cli.waitFor(expectedResult);
|
||||
});
|
||||
|
||||
test('should print the correct context options for custom settings', async ({ browserName, channel, runCLI, server }) => {
|
||||
test('should print the correct context options for custom settings', async ({ browserName, channel, runCLI, server, proxyServer }) => {
|
||||
proxyServer.forwardTo(server.PORT);
|
||||
const cli = runCLI([
|
||||
'--color-scheme=dark',
|
||||
'--geolocation=37.819722,-122.478611',
|
||||
'--lang=es',
|
||||
'--proxy-server=http://myproxy:3128',
|
||||
'--proxy-server=' + proxyServer.HOST,
|
||||
'--timezone=Europe/Rome',
|
||||
'--user-agent=hardkodemium',
|
||||
'--viewport-size=1280,720',
|
||||
@@ -58,7 +59,7 @@ await using var browser = await playwright.${capitalize(browserName)}.LaunchAsyn
|
||||
${launchOptions(channel)}
|
||||
Proxy = new ProxySettings
|
||||
{
|
||||
Server = "http://myproxy:3128",
|
||||
Server = "${proxyServer.HOST}",
|
||||
},
|
||||
});
|
||||
var context = await browser.NewContextAsync(new BrowserNewContextOptions
|
||||
@@ -169,10 +170,9 @@ await context.StorageStateAsync(new BrowserContextStorageStateOptions
|
||||
test('should work with --save-har', async ({ runCLI }, testInfo) => {
|
||||
const harFileName = testInfo.outputPath('har.har');
|
||||
const expectedResult = `await context.RouteFromHARAsync(${JSON.stringify(harFileName)});`;
|
||||
const cli = runCLI(['--target=csharp', `--save-har=${harFileName}`], {
|
||||
autoExitWhen: expectedResult,
|
||||
});
|
||||
await cli.waitForCleanExit();
|
||||
const cli = runCLI(['--target=csharp', `--save-har=${harFileName}`]);
|
||||
await cli.waitFor(expectedResult);
|
||||
await cli.exit();
|
||||
const json = JSON.parse(fs.readFileSync(harFileName, 'utf-8'));
|
||||
expect(json.log.creator.name).toBe('Playwright');
|
||||
});
|
||||
@@ -183,10 +183,9 @@ test('should work with --save-har and --save-har-glob', async ({ runCLI }, testI
|
||||
{
|
||||
Url = "**/*.js",
|
||||
});`;
|
||||
const cli = runCLI(['--target=csharp', `--save-har=${harFileName}`, '--save-har-glob=**/*.js'], {
|
||||
autoExitWhen: expectedResult,
|
||||
});
|
||||
await cli.waitForCleanExit();
|
||||
const cli = runCLI(['--target=csharp', `--save-har=${harFileName}`, '--save-har-glob=**/*.js']);
|
||||
await cli.waitFor(expectedResult);
|
||||
await cli.exit();
|
||||
const json = JSON.parse(fs.readFileSync(harFileName, 'utf-8'));
|
||||
expect(json.log.creator.name).toBe('Playwright');
|
||||
});
|
||||
@@ -195,13 +194,13 @@ for (const testFramework of ['nunit', 'mstest'] as const) {
|
||||
test(`should not print context options method override in ${testFramework} if no options were passed`, async ({ runCLI, server }) => {
|
||||
const cli = runCLI([`--target=csharp-${testFramework}`, server.EMPTY_PAGE]);
|
||||
await cli.waitFor(`Page.GotoAsync("${server.EMPTY_PAGE}")`);
|
||||
expect(cli.text()).not.toContain('public override BrowserNewContextOptions ContextOptions()');
|
||||
expect(await cli.text()).not.toContain('public override BrowserNewContextOptions ContextOptions()');
|
||||
});
|
||||
|
||||
test(`should print context options method override in ${testFramework} if options were passed`, async ({ runCLI, server }) => {
|
||||
const cli = runCLI([`--target=csharp-${testFramework}`, '--color-scheme=dark', server.EMPTY_PAGE]);
|
||||
await cli.waitFor(`Page.GotoAsync("${server.EMPTY_PAGE}")`);
|
||||
expect(cli.text()).toContain(` public override BrowserNewContextOptions ContextOptions()
|
||||
expect(await cli.text()).toContain(` public override BrowserNewContextOptions ContextOptions()
|
||||
{
|
||||
return new BrowserNewContextOptions
|
||||
{
|
||||
@@ -214,10 +213,9 @@ for (const testFramework of ['nunit', 'mstest'] as const) {
|
||||
test(`should work with --save-har in ${testFramework}`, async ({ runCLI }, testInfo) => {
|
||||
const harFileName = testInfo.outputPath('har.har');
|
||||
const expectedResult = `await Context.RouteFromHARAsync(${JSON.stringify(harFileName)});`;
|
||||
const cli = runCLI([`--target=csharp-${testFramework}`, `--save-har=${harFileName}`], {
|
||||
autoExitWhen: expectedResult,
|
||||
});
|
||||
await cli.waitForCleanExit();
|
||||
const cli = runCLI([`--target=csharp-${testFramework}`, `--save-har=${harFileName}`]);
|
||||
await cli.waitFor(expectedResult);
|
||||
await cli.exit();
|
||||
const json = JSON.parse(fs.readFileSync(harFileName, 'utf-8'));
|
||||
expect(json.log.creator.name).toBe('Playwright');
|
||||
});
|
||||
@@ -228,10 +226,9 @@ for (const testFramework of ['nunit', 'mstest'] as const) {
|
||||
{
|
||||
Url = "**/*.js",
|
||||
});`;
|
||||
const cli = runCLI([`--target=csharp-${testFramework}`, `--save-har=${harFileName}`, '--save-har-glob=**/*.js'], {
|
||||
autoExitWhen: expectedResult,
|
||||
});
|
||||
await cli.waitForCleanExit();
|
||||
const cli = runCLI([`--target=csharp-${testFramework}`, `--save-har=${harFileName}`, '--save-har-glob=**/*.js']);
|
||||
await cli.waitFor(expectedResult);
|
||||
await cli.exit();
|
||||
const json = JSON.parse(fs.readFileSync(harFileName, 'utf-8'));
|
||||
expect(json.log.creator.name).toBe('Playwright');
|
||||
});
|
||||
@@ -260,7 +257,7 @@ public class Tests : PageTest
|
||||
await Page.GotoAsync("${server.EMPTY_PAGE}");
|
||||
}
|
||||
}`;
|
||||
expect(cli.text()).toContain(expected);
|
||||
expect(await cli.text()).toContain(expected);
|
||||
});
|
||||
|
||||
test(`should print a valid basic program in nunit`, async ({ runCLI, server }) => {
|
||||
@@ -287,5 +284,5 @@ public class Tests : PageTest
|
||||
await Page.GotoAsync("${server.EMPTY_PAGE}");
|
||||
}
|
||||
}`;
|
||||
expect(cli.text()).toContain(expected);
|
||||
expect(await cli.text()).toContain(expected);
|
||||
});
|
||||
|
||||
@@ -55,7 +55,8 @@ test('should print the correct context options when using a device', async ({ br
|
||||
.setIsMobile(true)
|
||||
.setUserAgent("Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/XXXX Mobile Safari/537.36")
|
||||
.setViewportSize(411, 731));`;
|
||||
expect(cli.text().replace(/(.*Chrome\/)(.*?)( .*)/m, '$1XXXX$3')).toContain(expectedResult);
|
||||
const text = await cli.text();
|
||||
expect(text.replace(/(.*Chrome\/)(.*?)( .*)/m, '$1XXXX$3')).toContain(expectedResult);
|
||||
});
|
||||
|
||||
test('should print the correct context options when using a device and additional options', async ({ browserName, runCLI, server }) => {
|
||||
@@ -70,7 +71,8 @@ test('should print the correct context options when using a device and additiona
|
||||
.setIsMobile(true)
|
||||
.setUserAgent("Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/XXXX Mobile/15E148 Safari/604.1")
|
||||
.setViewportSize(414, 715));`;
|
||||
expect(cli.text().replace(/(.*Version\/)(.*?)( .*)/m, '$1XXXX$3')).toContain(expectedResult);
|
||||
const text = await cli.text();
|
||||
expect(text.replace(/(.*Version\/)(.*?)( .*)/m, '$1XXXX$3')).toContain(expectedResult);
|
||||
});
|
||||
|
||||
test('should print load/save storage_state', async ({ runCLI, server }, testInfo) => {
|
||||
@@ -91,11 +93,9 @@ test('should work with --save-har and --save-har-glob as java-library', async ({
|
||||
const harFileName = testInfo.outputPath('har.har');
|
||||
const expectedResult = `context.routeFromHAR(Paths.get(${JSON.stringify(harFileName)}), new BrowserContext.RouteFromHAROptions()
|
||||
.setUrl("**/*.js"));`;
|
||||
const cli = runCLI(['--target=java', `--save-har=${harFileName}`, '--save-har-glob=**/*.js'], {
|
||||
autoExitWhen: expectedResult,
|
||||
});
|
||||
|
||||
await cli.waitForCleanExit();
|
||||
const cli = runCLI(['--target=java', `--save-har=${harFileName}`, '--save-har-glob=**/*.js']);
|
||||
await cli.waitFor(expectedResult);
|
||||
await cli.exit();
|
||||
const json = JSON.parse(fs.readFileSync(harFileName, 'utf-8'));
|
||||
expect(json.log.creator.name).toBe('Playwright');
|
||||
});
|
||||
@@ -104,11 +104,9 @@ test('should work with --save-har and --save-har-glob as java-junit', async ({ r
|
||||
const harFileName = testInfo.outputPath('har.har');
|
||||
const expectedResult = `page.routeFromHAR(Paths.get(${JSON.stringify(harFileName)}), new Page.RouteFromHAROptions()
|
||||
.setUrl("**/*.js"));`;
|
||||
const cli = runCLI(['--target=java-junit', `--save-har=${harFileName}`, '--save-har-glob=**/*.js'], {
|
||||
autoExitWhen: expectedResult,
|
||||
});
|
||||
|
||||
await cli.waitForCleanExit();
|
||||
const cli = runCLI(['--target=java-junit', `--save-har=${harFileName}`, '--save-har-glob=**/*.js']);
|
||||
await cli.waitFor(expectedResult);
|
||||
await cli.exit();
|
||||
const json = JSON.parse(fs.readFileSync(harFileName, 'utf-8'));
|
||||
expect(json.log.creator.name).toBe('Playwright');
|
||||
});
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
import fs from 'fs';
|
||||
import { test, expect } from './inspectorTest';
|
||||
import { test } from './inspectorTest';
|
||||
|
||||
const launchOptions = (channel: string) => {
|
||||
return channel ? `channel: '${channel}',\n headless: false` : 'headless: false';
|
||||
@@ -82,13 +82,8 @@ test('should print the correct context options when using a device and additiona
|
||||
});
|
||||
|
||||
test('should save the codegen output to a file if specified', async ({ browserName, channel, runCLI, server }, testInfo) => {
|
||||
const tmpFile = testInfo.outputPath('script.js');
|
||||
const cli = runCLI(['--output', tmpFile, '--target=javascript', server.EMPTY_PAGE], {
|
||||
autoExitWhen: 'await page.goto', // We have to wait for the initial navigation to be recorded.
|
||||
});
|
||||
await cli.waitForCleanExit();
|
||||
const content = fs.readFileSync(tmpFile);
|
||||
expect(content.toString()).toBe(`const { ${browserName} } = require('playwright');
|
||||
const cli = runCLI(['--target=javascript', server.EMPTY_PAGE]);
|
||||
await cli.waitFor(`const { ${browserName} } = require('playwright');
|
||||
|
||||
(async () => {
|
||||
const browser = await ${browserName}.launch({
|
||||
@@ -97,7 +92,6 @@ test('should save the codegen output to a file if specified', async ({ browserNa
|
||||
const context = await browser.newContext();
|
||||
const page = await context.newPage();
|
||||
await page.goto('${server.EMPTY_PAGE}');
|
||||
await page.close();
|
||||
|
||||
// ---------------------
|
||||
await context.close();
|
||||
|
||||
@@ -30,13 +30,8 @@ def test_example(page: Page) -> None:`;
|
||||
test('should print the correct context options when using a device and lang', async ({ browserName, runCLI, server }, testInfo) => {
|
||||
test.skip(browserName !== 'webkit');
|
||||
|
||||
const tmpFile = testInfo.outputPath('script.js');
|
||||
const cli = runCLI(['--target=python-pytest', '--device=iPhone 11', '--lang=en-US', '--output', tmpFile, server.EMPTY_PAGE], {
|
||||
autoExitWhen: 'page.goto',
|
||||
});
|
||||
await cli.waitForCleanExit();
|
||||
const content = fs.readFileSync(tmpFile);
|
||||
expect(content.toString()).toBe(`import pytest
|
||||
const cli = runCLI(['--target=python-pytest', '--device=iPhone 11', '--lang=en-US', server.EMPTY_PAGE]);
|
||||
await cli.waitFor(`import pytest
|
||||
import re
|
||||
from playwright.sync_api import Page, expect
|
||||
|
||||
@@ -52,13 +47,8 @@ def test_example(page: Page) -> None:
|
||||
});
|
||||
|
||||
test('should save the codegen output to a file if specified', async ({ runCLI, server }, testInfo) => {
|
||||
const tmpFile = testInfo.outputPath('test_example.py');
|
||||
const cli = runCLI(['--target=python-pytest', '--output', tmpFile, server.EMPTY_PAGE], {
|
||||
autoExitWhen: 'page.goto',
|
||||
});
|
||||
await cli.waitForCleanExit();
|
||||
const content = fs.readFileSync(tmpFile);
|
||||
expect(content.toString()).toBe(`import re
|
||||
const cli = runCLI(['--target=python-pytest', server.EMPTY_PAGE]);
|
||||
await cli.waitFor(`import re
|
||||
from playwright.sync_api import Page, expect
|
||||
|
||||
|
||||
@@ -70,10 +60,9 @@ def test_example(page: Page) -> None:
|
||||
test('should work with --save-har', async ({ runCLI }, testInfo) => {
|
||||
const harFileName = testInfo.outputPath('har.har');
|
||||
const expectedResult = `page.route_from_har(${JSON.stringify(harFileName)})`;
|
||||
const cli = runCLI(['--target=python-pytest', `--save-har=${harFileName}`], {
|
||||
autoExitWhen: expectedResult,
|
||||
});
|
||||
await cli.waitForCleanExit();
|
||||
const cli = runCLI(['--target=python-pytest', `--save-har=${harFileName}`]);
|
||||
await cli.waitFor(expectedResult);
|
||||
await cli.exit();
|
||||
const json = JSON.parse(fs.readFileSync(harFileName, 'utf-8'));
|
||||
expect(json.log.creator.name).toBe('Playwright');
|
||||
});
|
||||
@@ -81,10 +70,9 @@ test('should work with --save-har', async ({ runCLI }, testInfo) => {
|
||||
test('should work with --save-har and --save-har-glob', async ({ runCLI }, testInfo) => {
|
||||
const harFileName = testInfo.outputPath('har.har');
|
||||
const expectedResult = `page.route_from_har(${JSON.stringify(harFileName)}, url="**/*.js")`;
|
||||
const cli = runCLI(['--target=python-pytest', `--save-har=${harFileName}`, '--save-har-glob=**/*.js'], {
|
||||
autoExitWhen: expectedResult,
|
||||
});
|
||||
await cli.waitForCleanExit();
|
||||
const cli = runCLI(['--target=python-pytest', `--save-har=${harFileName}`, '--save-har-glob=**/*.js']);
|
||||
await cli.waitFor(expectedResult);
|
||||
await cli.exit();
|
||||
const json = JSON.parse(fs.readFileSync(harFileName, 'utf-8'));
|
||||
expect(json.log.creator.name).toBe('Playwright');
|
||||
});
|
||||
|
||||
@@ -78,13 +78,8 @@ async def run(playwright: Playwright) -> None:
|
||||
});
|
||||
|
||||
test('should save the codegen output to a file if specified', async ({ browserName, channel, runCLI, server }, testInfo) => {
|
||||
const tmpFile = testInfo.outputPath('example.py');
|
||||
const cli = runCLI(['--target=python-async', '--output', tmpFile, server.EMPTY_PAGE], {
|
||||
autoExitWhen: 'page.goto',
|
||||
});
|
||||
await cli.waitForCleanExit();
|
||||
const content = fs.readFileSync(tmpFile);
|
||||
expect(content.toString()).toBe(`import asyncio
|
||||
const cli = runCLI(['--target=python-async', server.EMPTY_PAGE]);
|
||||
await cli.waitFor(`import asyncio
|
||||
import re
|
||||
from playwright.async_api import Playwright, async_playwright, expect
|
||||
|
||||
@@ -94,7 +89,6 @@ async def run(playwright: Playwright) -> None:
|
||||
context = await browser.new_context()
|
||||
page = await context.new_page()
|
||||
await page.goto("${server.EMPTY_PAGE}")
|
||||
await page.close()
|
||||
|
||||
# ---------------------
|
||||
await context.close()
|
||||
@@ -145,10 +139,9 @@ asyncio.run(main())
|
||||
test('should work with --save-har', async ({ runCLI }, testInfo) => {
|
||||
const harFileName = testInfo.outputPath('har.har');
|
||||
const expectedResult = `await context.route_from_har(${JSON.stringify(harFileName)})`;
|
||||
const cli = runCLI(['--target=python-async', `--save-har=${harFileName}`], {
|
||||
autoExitWhen: expectedResult,
|
||||
});
|
||||
await cli.waitForCleanExit();
|
||||
const cli = runCLI(['--target=python-async', `--save-har=${harFileName}`]);
|
||||
await cli.waitFor(expectedResult);
|
||||
await cli.exit();
|
||||
const json = JSON.parse(fs.readFileSync(harFileName, 'utf-8'));
|
||||
expect(json.log.creator.name).toBe('Playwright');
|
||||
});
|
||||
@@ -156,10 +149,9 @@ test('should work with --save-har', async ({ runCLI }, testInfo) => {
|
||||
test('should work with --save-har and --save-har-glob', async ({ runCLI }, testInfo) => {
|
||||
const harFileName = testInfo.outputPath('har.har');
|
||||
const expectedResult = `await context.route_from_har(${JSON.stringify(harFileName)}, url="**/*.js")`;
|
||||
const cli = runCLI(['--target=python-async', `--save-har=${harFileName}`, '--save-har-glob=**/*.js'], {
|
||||
autoExitWhen: expectedResult,
|
||||
});
|
||||
await cli.waitForCleanExit();
|
||||
const cli = runCLI(['--target=python-async', `--save-har=${harFileName}`, '--save-har-glob=**/*.js']);
|
||||
await cli.waitFor(expectedResult);
|
||||
await cli.exit();
|
||||
const json = JSON.parse(fs.readFileSync(harFileName, 'utf-8'));
|
||||
expect(json.log.creator.name).toBe('Playwright');
|
||||
});
|
||||
|
||||
@@ -74,13 +74,8 @@ def run(playwright: Playwright) -> None:
|
||||
});
|
||||
|
||||
test('should save the codegen output to a file if specified', async ({ runCLI, channel, browserName, server }, testInfo) => {
|
||||
const tmpFile = testInfo.outputPath('example.py');
|
||||
const cli = runCLI(['--target=python', '--output', tmpFile, server.EMPTY_PAGE], {
|
||||
autoExitWhen: 'page.goto',
|
||||
});
|
||||
await cli.waitForCleanExit();
|
||||
const content = fs.readFileSync(tmpFile);
|
||||
expect(content.toString()).toBe(`import re
|
||||
const cli = runCLI(['--target=python', server.EMPTY_PAGE]);
|
||||
await cli.waitFor(`import re
|
||||
from playwright.sync_api import Playwright, sync_playwright, expect
|
||||
|
||||
|
||||
@@ -89,7 +84,6 @@ def run(playwright: Playwright) -> None:
|
||||
context = browser.new_context()
|
||||
page = context.new_page()
|
||||
page.goto("${server.EMPTY_PAGE}")
|
||||
page.close()
|
||||
|
||||
# ---------------------
|
||||
context.close()
|
||||
@@ -131,10 +125,9 @@ with sync_playwright() as playwright:
|
||||
test('should work with --save-har', async ({ runCLI }, testInfo) => {
|
||||
const harFileName = testInfo.outputPath('har.har');
|
||||
const expectedResult = `context.route_from_har(${JSON.stringify(harFileName)})`;
|
||||
const cli = runCLI(['--target=python-async', `--save-har=${harFileName}`], {
|
||||
autoExitWhen: expectedResult,
|
||||
});
|
||||
await cli.waitForCleanExit();
|
||||
const cli = runCLI(['--target=python-async', `--save-har=${harFileName}`]);
|
||||
await cli.waitFor(expectedResult);
|
||||
await cli.exit();
|
||||
const json = JSON.parse(fs.readFileSync(harFileName, 'utf-8'));
|
||||
expect(json.log.creator.name).toBe('Playwright');
|
||||
});
|
||||
@@ -142,10 +135,9 @@ test('should work with --save-har', async ({ runCLI }, testInfo) => {
|
||||
test('should work with --save-har and --save-har-glob', async ({ runCLI }, testInfo) => {
|
||||
const harFileName = testInfo.outputPath('har.har');
|
||||
const expectedResult = `context.route_from_har(${JSON.stringify(harFileName)}, url="**/*.js")`;
|
||||
const cli = runCLI(['--target=python-async', `--save-har=${harFileName}`, '--save-har-glob=**/*.js'], {
|
||||
autoExitWhen: expectedResult,
|
||||
});
|
||||
await cli.waitForCleanExit();
|
||||
const cli = runCLI(['--target=python-async', `--save-har=${harFileName}`, '--save-har-glob=**/*.js']);
|
||||
await cli.waitFor(expectedResult);
|
||||
await cli.exit();
|
||||
const json = JSON.parse(fs.readFileSync(harFileName, 'utf-8'));
|
||||
expect(json.log.creator.name).toBe('Playwright');
|
||||
});
|
||||
|
||||
@@ -22,6 +22,7 @@ test('should print the correct imports and context options', async ({ runCLI, se
|
||||
const expectedResult = `import { test, expect } from '@playwright/test';
|
||||
|
||||
test('test', async ({ page }) => {
|
||||
await page.goto('${server.EMPTY_PAGE}');
|
||||
});`;
|
||||
await cli.waitFor(expectedResult);
|
||||
});
|
||||
@@ -85,10 +86,9 @@ test('test', async ({ page }) => {`;
|
||||
test('should not generate recordHAR with --save-har', async ({ runCLI }, testInfo) => {
|
||||
const harFileName = testInfo.outputPath('har.har');
|
||||
const expectedResult = ` await page.routeFromHAR('${harFileName.replace(/\\/g, '\\\\')}');`;
|
||||
const cli = runCLI(['--target=playwright-test', `--save-har=${harFileName}`], {
|
||||
autoExitWhen: expectedResult,
|
||||
});
|
||||
await cli.waitForCleanExit();
|
||||
const cli = runCLI(['--target=playwright-test', `--save-har=${harFileName}`]);
|
||||
await cli.waitFor(expectedResult);
|
||||
await cli.exit();
|
||||
const json = JSON.parse(fs.readFileSync(harFileName, 'utf-8'));
|
||||
expect(json.log.creator.name).toBe('Playwright');
|
||||
});
|
||||
@@ -98,10 +98,9 @@ test('should generate routeFromHAR with --save-har', async ({ runCLI }, testInfo
|
||||
const expectedResult = `test('test', async ({ page }) => {
|
||||
await page.routeFromHAR('${harFileName.replace(/\\/g, '\\\\')}');
|
||||
});`;
|
||||
const cli = runCLI(['--target=playwright-test', `--save-har=${harFileName}`], {
|
||||
autoExitWhen: expectedResult,
|
||||
});
|
||||
await cli.waitForCleanExit();
|
||||
const cli = runCLI(['--target=playwright-test', `--save-har=${harFileName}`]);
|
||||
await cli.waitFor(expectedResult);
|
||||
await cli.exit();
|
||||
const json = JSON.parse(fs.readFileSync(harFileName, 'utf-8'));
|
||||
expect(json.log.creator.name).toBe('Playwright');
|
||||
});
|
||||
@@ -113,10 +112,9 @@ test('should generate routeFromHAR with --save-har and --save-har-glob', async (
|
||||
url: '**/*.js'
|
||||
});
|
||||
});`;
|
||||
const cli = runCLI(['--target=playwright-test', `--save-har=${harFileName}`, '--save-har-glob=**/*.js'], {
|
||||
autoExitWhen: expectedResult,
|
||||
});
|
||||
await cli.waitForCleanExit();
|
||||
const cli = runCLI(['--target=playwright-test', `--save-har=${harFileName}`, '--save-har-glob=**/*.js']);
|
||||
await cli.waitFor(expectedResult);
|
||||
await cli.exit();
|
||||
const json = JSON.parse(fs.readFileSync(harFileName, 'utf-8'));
|
||||
expect(json.log.creator.name).toBe('Playwright');
|
||||
});
|
||||
|
||||
@@ -18,9 +18,9 @@ import { contextTest } from '../../config/browserTest';
|
||||
import type { Locator, Page } from 'playwright-core';
|
||||
import { step } from '../../config/baseTest';
|
||||
import * as path from 'path';
|
||||
import fs from 'fs';
|
||||
import type { Source } from '../../../packages/recorder/src/recorderTypes';
|
||||
import type { CommonFixtures, TestChildProcess } from '../../config/commonFixtures';
|
||||
import { stripAnsi } from '../../config/utils';
|
||||
import { expect } from '@playwright/test';
|
||||
import { nodePlatform } from '../../../packages/playwright-core/lib/server/utils/nodePlatform';
|
||||
export { expect } from '@playwright/test';
|
||||
@@ -29,7 +29,7 @@ type CLITestArgs = {
|
||||
recorderPageGetter: () => Promise<Page>;
|
||||
closeRecorder: () => Promise<void>;
|
||||
openRecorder: (options?: { testIdAttributeName?: string, language?: string }) => Promise<{ recorder: Recorder, page: Page }>;
|
||||
runCLI: (args: string[], options?: { autoExitWhen?: string }) => CLIMock;
|
||||
runCLI: (args: string[]) => CLIMock;
|
||||
};
|
||||
|
||||
const codegenLang2Id: Map<string, string> = new Map([
|
||||
@@ -72,16 +72,20 @@ export const test = contextTest.extend<CLITestArgs>({
|
||||
testInfo.slow();
|
||||
testInfo.skip(mode.startsWith('service'));
|
||||
|
||||
await run((cliArgs, { autoExitWhen } = {}) => {
|
||||
return new CLIMock(childProcess, {
|
||||
let cli: CLIMock | undefined;
|
||||
await run(cliArgs => {
|
||||
const outputFile = testInfo.outputPath('codegen.output');
|
||||
cli = new CLIMock(childProcess, {
|
||||
outputFile,
|
||||
browserName,
|
||||
channel,
|
||||
headless,
|
||||
args: cliArgs,
|
||||
executablePath: launchOptions.executablePath,
|
||||
autoExitWhen,
|
||||
});
|
||||
return cli;
|
||||
});
|
||||
await cli?.exit();
|
||||
},
|
||||
|
||||
openRecorder: async ({ context, recorderPageGetter }, use) => {
|
||||
@@ -240,22 +244,25 @@ export class Recorder {
|
||||
}
|
||||
|
||||
class CLIMock {
|
||||
process: TestChildProcess;
|
||||
private _process: TestChildProcess;
|
||||
private _outputFile: string;
|
||||
private _exitPromise: Promise<void> | undefined;
|
||||
|
||||
constructor(childProcess: CommonFixtures['childProcess'], options: { browserName: string, channel: string | undefined, headless: boolean | undefined, args: string[], executablePath: string | undefined, autoExitWhen: string | undefined}) {
|
||||
constructor(childProcess: CommonFixtures['childProcess'], options: { outputFile: string, browserName: string, channel: string | undefined, headless: boolean | undefined, args: string[], executablePath: string | undefined }) {
|
||||
this._outputFile = options.outputFile;
|
||||
const nodeArgs = [
|
||||
'node',
|
||||
path.join(__dirname, '..', '..', '..', 'packages', 'playwright-core', 'cli.js'),
|
||||
'codegen',
|
||||
...options.args,
|
||||
`--browser=${options.browserName}`,
|
||||
`--output=${this._outputFile}`,
|
||||
];
|
||||
if (options.channel)
|
||||
nodeArgs.push(`--channel=${options.channel}`);
|
||||
this.process = childProcess({
|
||||
this._process = childProcess({
|
||||
command: nodeArgs,
|
||||
env: {
|
||||
PWTEST_CLI_AUTO_EXIT_WHEN: options.autoExitWhen,
|
||||
PWTEST_CLI_IS_UNDER_TEST: '1',
|
||||
PWTEST_CLI_HEADLESS: options.headless ? '1' : undefined,
|
||||
PWTEST_CLI_EXECUTABLE_PATH: options.executablePath,
|
||||
@@ -266,17 +273,29 @@ class CLIMock {
|
||||
|
||||
@step
|
||||
async waitFor(text: string): Promise<void> {
|
||||
await expect(() => {
|
||||
expect(this.text()).toContain(text);
|
||||
}).toPass();
|
||||
await expect.poll(() => this.text()).toContain(text);
|
||||
}
|
||||
|
||||
@step
|
||||
async waitForCleanExit() {
|
||||
return this.process.cleanExit();
|
||||
async exit() {
|
||||
if (!this._exitPromise) {
|
||||
this._process.write('exit\n');
|
||||
this._exitPromise = this._process.cleanExit();
|
||||
}
|
||||
await this._exitPromise;
|
||||
}
|
||||
|
||||
text() {
|
||||
return stripAnsi(this.process.output);
|
||||
sigint() {
|
||||
const result = this._process.kill('SIGINT');
|
||||
this._exitPromise = this._process.exited.then(() => undefined); // Avoid double closing.
|
||||
return result;
|
||||
}
|
||||
|
||||
async text() {
|
||||
try {
|
||||
return await fs.promises.readFile(this._outputFile, 'utf-8');
|
||||
} catch {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user