test: unflake inspector-cli tests (#22347)
This patch: - changes the `childProcess` fixture to reliably SIGKILL all descendants (children and grand-children, regardless of their process group). This is achieved using the `ps` command to build the process tree, and then send `SIGKILL` to the descendant process groups. - changes the `runCLI` fixture to **not** auto-close codegen by default; the `childProcess` fixture will clean up all processes. This makes sure that all `runCLI.waitFor()` commands actually wait until the necessary output. - for a handful of tests that do actually want to auto-close codegen, introduce an optional `autoCloseWhen` flag for the `runCLI` fixture that makes sure to close the codegen once a certain output was reached.
This commit is contained in:
parent
56dcab844a
commit
8bb708be70
|
|
@ -414,7 +414,16 @@ async function launchContext(options: Options, headless: boolean, executablePath
|
||||||
|
|
||||||
const browser = await browserType.launch(launchOptions);
|
const browser = await browserType.launch(launchOptions);
|
||||||
|
|
||||||
if (process.env.PWTEST_CLI_EXIT) {
|
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))
|
||||||
|
Promise.all(context.pages().map(async p => p.close()));
|
||||||
|
};
|
||||||
|
// Make sure we exit abnormally when browser crashes.
|
||||||
const logs: string[] = [];
|
const logs: string[] = [];
|
||||||
require('playwright-core/lib/utilsBundle').debug.log = (...args: any[]) => {
|
require('playwright-core/lib/utilsBundle').debug.log = (...args: any[]) => {
|
||||||
const line = require('util').format(...args) + '\n';
|
const line = require('util').format(...args) + '\n';
|
||||||
|
|
@ -425,7 +434,6 @@ async function launchContext(options: Options, headless: boolean, executablePath
|
||||||
const hasCrashLine = logs.some(line => line.includes('process did exit:') && !line.includes('process did exit: exitCode=0, signal=null'));
|
const hasCrashLine = logs.some(line => line.includes('process did exit:') && !line.includes('process did exit: exitCode=0, signal=null'));
|
||||||
if (hasCrashLine) {
|
if (hasCrashLine) {
|
||||||
process.stderr.write('Detected browser crash.\n');
|
process.stderr.write('Detected browser crash.\n');
|
||||||
// Make sure we exit abnormally when browser crashes.
|
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -552,7 +560,14 @@ async function openPage(context: BrowserContext, url: string | undefined): Promi
|
||||||
url = 'file://' + path.resolve(url);
|
url = 'file://' + path.resolve(url);
|
||||||
else if (!url.startsWith('http') && !url.startsWith('file://') && !url.startsWith('about:') && !url.startsWith('data:'))
|
else if (!url.startsWith('http') && !url.startsWith('file://') && !url.startsWith('about:') && !url.startsWith('data:'))
|
||||||
url = 'http://' + url;
|
url = 'http://' + url;
|
||||||
await page.goto(url);
|
await page.goto(url).catch(error => {
|
||||||
|
if (process.env.PWTEST_CLI_AUTO_EXIT_WHEN && error.message.includes('Navigation failed because page was closed')) {
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return page;
|
return page;
|
||||||
}
|
}
|
||||||
|
|
@ -567,8 +582,6 @@ async function open(options: Options, url: string | undefined, language: string)
|
||||||
saveStorage: options.saveStorage,
|
saveStorage: options.saveStorage,
|
||||||
});
|
});
|
||||||
await openPage(context, url);
|
await openPage(context, url);
|
||||||
if (process.env.PWTEST_CLI_EXIT)
|
|
||||||
await Promise.all(context.pages().map(p => p.close()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function codegen(options: Options, url: string | undefined, language: string, outputFile?: string) {
|
async function codegen(options: Options, url: string | undefined, language: string, outputFile?: string) {
|
||||||
|
|
@ -584,8 +597,6 @@ async function codegen(options: Options, url: string | undefined, language: stri
|
||||||
handleSIGINT: false,
|
handleSIGINT: false,
|
||||||
});
|
});
|
||||||
await openPage(context, url);
|
await openPage(context, url);
|
||||||
if (process.env.PWTEST_CLI_EXIT)
|
|
||||||
await Promise.all(context.pages().map(p => p.close()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function waitForPage(page: Page, captureOptions: CaptureOptions) {
|
async function waitForPage(page: Page, captureOptions: CaptureOptions) {
|
||||||
|
|
|
||||||
|
|
@ -170,13 +170,8 @@ export class RecorderApp extends EventEmitter implements IRecorderApp {
|
||||||
}).toString(), true, sources, 'main').catch(() => {});
|
}).toString(), true, sources, 'main').catch(() => {});
|
||||||
|
|
||||||
// Testing harness for runCLI mode.
|
// Testing harness for runCLI mode.
|
||||||
{
|
if (process.env.PWTEST_CLI_IS_UNDER_TEST && sources.length)
|
||||||
if ((process.env.PWTEST_CLI_IS_UNDER_TEST || process.env.PWTEST_CLI_EXIT) && sources.length) {
|
(process as any)._didSetSourcesForTest(sources[0].text);
|
||||||
process.stdout.write('\n-------------8<-------------\n');
|
|
||||||
process.stdout.write(sources[0].text);
|
|
||||||
process.stdout.write('\n-------------8<-------------\n');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async setSelector(selector: string, focus?: boolean): Promise<void> {
|
async setSelector(selector: string, focus?: boolean): Promise<void> {
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,38 @@ type TestChildParams = {
|
||||||
onOutput?: () => void;
|
onOutput?: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
import childProcess from 'child_process';
|
||||||
|
|
||||||
|
type ProcessData = {
|
||||||
|
pid: number, // process ID
|
||||||
|
pgid: number, // process groupd ID
|
||||||
|
children: Set<ProcessData>, // direct children of the process
|
||||||
|
};
|
||||||
|
|
||||||
|
function buildProcessTreePosix(pid: number): ProcessData {
|
||||||
|
const processTree = childProcess.spawnSync('ps', ['-eo', 'pid,pgid,ppid']);
|
||||||
|
const lines = processTree.stdout.toString().trim().split('\n');
|
||||||
|
|
||||||
|
const pidToProcess = new Map<number, ProcessData>();
|
||||||
|
const edges: { pid: number, ppid: number }[] = [];
|
||||||
|
for (const line of lines) {
|
||||||
|
const [pid, pgid, ppid] = line.trim().split(/\s+/).map(token => +token);
|
||||||
|
// On linux, the very first line of `ps` is the header with "PID PGID PPID".
|
||||||
|
if (isNaN(pid) || isNaN(pgid) || isNaN(ppid))
|
||||||
|
continue;
|
||||||
|
pidToProcess.set(pid, { pid, pgid, children: new Set() });
|
||||||
|
edges.push({ pid, ppid });
|
||||||
|
}
|
||||||
|
for (const { pid, ppid } of edges) {
|
||||||
|
const parent = pidToProcess.get(ppid);
|
||||||
|
const child = pidToProcess.get(pid);
|
||||||
|
// On POSIX, certain processes might not have parent (e.g. PID=1 and occasionally PID=2).
|
||||||
|
if (parent && child)
|
||||||
|
parent.children.add(child);
|
||||||
|
}
|
||||||
|
return pidToProcess.get(pid);
|
||||||
|
}
|
||||||
|
|
||||||
export class TestChildProcess {
|
export class TestChildProcess {
|
||||||
params: TestChildParams;
|
params: TestChildParams;
|
||||||
process: ChildProcess;
|
process: ChildProcess;
|
||||||
|
|
@ -72,7 +104,7 @@ export class TestChildProcess {
|
||||||
this.process.stderr.on('data', appendChunk);
|
this.process.stderr.on('data', appendChunk);
|
||||||
this.process.stdout.on('data', appendChunk);
|
this.process.stdout.on('data', appendChunk);
|
||||||
|
|
||||||
const killProcessGroup = this._killProcessGroup.bind(this);
|
const killProcessGroup = this._killProcessTree.bind(this, 'SIGKILL');
|
||||||
process.on('exit', killProcessGroup);
|
process.on('exit', killProcessGroup);
|
||||||
this.exited = new Promise(f => {
|
this.exited = new Promise(f => {
|
||||||
this.process.on('exit', (exitCode, signal) => f({ exitCode, signal }));
|
this.process.on('exit', (exitCode, signal) => f({ exitCode, signal }));
|
||||||
|
|
@ -86,28 +118,49 @@ export class TestChildProcess {
|
||||||
return strippedOutput.split('\n').filter(line => line.startsWith('%%')).map(line => line.substring(2).trim());
|
return strippedOutput.split('\n').filter(line => line.startsWith('%%')).map(line => line.substring(2).trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
async close() {
|
async kill(signal: 'SIGINT' | 'SIGKILL' = 'SIGKILL') {
|
||||||
if (this.process.kill(0))
|
this._killProcessTree(signal);
|
||||||
this._killProcessGroup('SIGINT');
|
|
||||||
return this.exited;
|
return this.exited;
|
||||||
}
|
}
|
||||||
|
|
||||||
async kill() {
|
private _killProcessTree(signal: 'SIGINT' | 'SIGKILL') {
|
||||||
if (this.process.kill(0))
|
|
||||||
this._killProcessGroup('SIGKILL');
|
|
||||||
return this.exited;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _killProcessGroup(signal: 'SIGINT' | 'SIGKILL') {
|
|
||||||
if (!this.process.pid || !this.process.kill(0))
|
if (!this.process.pid || !this.process.kill(0))
|
||||||
return;
|
return;
|
||||||
try {
|
|
||||||
if (process.platform === 'win32')
|
// On Windows, we always call `taskkill` no matter signal.
|
||||||
|
if (process.platform === 'win32') {
|
||||||
|
try {
|
||||||
execSync(`taskkill /pid ${this.process.pid} /T /F /FI "MEMUSAGE gt 0"`, { stdio: 'ignore' });
|
execSync(`taskkill /pid ${this.process.pid} /T /F /FI "MEMUSAGE gt 0"`, { stdio: 'ignore' });
|
||||||
else
|
} catch (e) {
|
||||||
process.kill(-this.process.pid, signal);
|
// the process might have already stopped
|
||||||
} catch (e) {
|
}
|
||||||
// the process might have already stopped
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// In case of POSIX and `SIGINT` signal, send it to the main process group only.
|
||||||
|
if (signal === 'SIGINT') {
|
||||||
|
try {
|
||||||
|
process.kill(-this.process.pid, 'SIGINT');
|
||||||
|
} catch (e) {
|
||||||
|
// the process might have already stopped
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// In case of POSIX and `SIGKILL` signal, we should send it to all descendant process groups.
|
||||||
|
const rootProcess = buildProcessTreePosix(this.process.pid);
|
||||||
|
const descendantProcessGroups = (function flatten(processData: ProcessData, result: Set<number> = new Set()) {
|
||||||
|
// Process can nullify its own process group with `setpgid`. Use its PID instead.
|
||||||
|
result.add(processData.pgid || processData.pid);
|
||||||
|
processData.children.forEach(child => flatten(child, result));
|
||||||
|
return result;
|
||||||
|
})(rootProcess);
|
||||||
|
for (const pgid of descendantProcessGroups) {
|
||||||
|
try {
|
||||||
|
process.kill(-pgid, 'SIGKILL');
|
||||||
|
} catch (e) {
|
||||||
|
// the process might have already stopped
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -150,16 +203,7 @@ export const commonFixtures: Fixtures<CommonFixtures, CommonWorkerFixtures> = {
|
||||||
processes.push(process);
|
processes.push(process);
|
||||||
return process;
|
return process;
|
||||||
});
|
});
|
||||||
await Promise.all(processes.map(async child => {
|
await Promise.all(processes.map(async child => child.kill()));
|
||||||
await Promise.race([
|
|
||||||
child.exited,
|
|
||||||
new Promise(f => setTimeout(f, 3_000)),
|
|
||||||
]);
|
|
||||||
if (child.process.kill(0)) {
|
|
||||||
await child.kill();
|
|
||||||
throw new Error(`Process ${child.params.command.join(' ')} is still running. Leaking process?\nOutput:${child.output}`);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
if (testInfo.status !== 'passed' && testInfo.status !== 'skipped' && !process.env.PWTEST_DEBUG) {
|
if (testInfo.status !== 'passed' && testInfo.status !== 'skipped' && !process.env.PWTEST_DEBUG) {
|
||||||
for (const process of processes) {
|
for (const process of processes) {
|
||||||
console.log('====== ' + process.params.command.join(' '));
|
console.log('====== ' + process.params.command.join(' '));
|
||||||
|
|
@ -176,7 +220,7 @@ export const commonFixtures: Fixtures<CommonFixtures, CommonWorkerFixtures> = {
|
||||||
processes.push(process);
|
processes.push(process);
|
||||||
return process;
|
return process;
|
||||||
});
|
});
|
||||||
await Promise.all(processes.map(child => child.close()));
|
await Promise.all(processes.map(child => child.kill('SIGINT')));
|
||||||
}, { scope: 'worker' }],
|
}, { scope: 'worker' }],
|
||||||
|
|
||||||
waitForPort: async ({}, use) => {
|
waitForPort: async ({}, use) => {
|
||||||
|
|
|
||||||
|
|
@ -49,8 +49,7 @@ export class RunServer implements PlaywrightServer {
|
||||||
}
|
}
|
||||||
|
|
||||||
async close() {
|
async close() {
|
||||||
await this._process.close();
|
await this._process.kill('SIGINT');
|
||||||
await this._process.exitCode;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -150,7 +149,7 @@ export class RemoteServer implements PlaywrightServer {
|
||||||
await this._browser.close();
|
await this._browser.close();
|
||||||
this._browser = undefined;
|
this._browser = undefined;
|
||||||
}
|
}
|
||||||
await this._process.close();
|
await this._process.kill('SIGINT');
|
||||||
await this.childExitCode();
|
await this.childExitCode();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,20 +19,32 @@ test('codegen should work', async ({ exec }) => {
|
||||||
await exec('npm i --foreground-scripts playwright');
|
await exec('npm i --foreground-scripts playwright');
|
||||||
|
|
||||||
await test.step('codegen without arguments', async () => {
|
await test.step('codegen without arguments', async () => {
|
||||||
const result = await exec('npx playwright codegen', { env: { PWTEST_CLI_EXIT: '1' } });
|
const result = await exec('npx playwright codegen', {
|
||||||
expect(result).toContain(`@playwright/test`);
|
env: {
|
||||||
|
PWTEST_CLI_IS_UNDER_TEST: '1',
|
||||||
|
PWTEST_CLI_AUTO_EXIT_WHEN: '@playwright/test',
|
||||||
|
}
|
||||||
|
});
|
||||||
expect(result).toContain(`{ page }`);
|
expect(result).toContain(`{ page }`);
|
||||||
});
|
});
|
||||||
|
|
||||||
await test.step('codegen --target=javascript', async () => {
|
await test.step('codegen --target=javascript', async () => {
|
||||||
const result = await exec('npx playwright codegen --target=javascript', { env: { PWTEST_CLI_EXIT: '1' } });
|
const result = await exec('npx playwright codegen --target=javascript', {
|
||||||
|
env: {
|
||||||
|
PWTEST_CLI_IS_UNDER_TEST: '1',
|
||||||
|
PWTEST_CLI_AUTO_EXIT_WHEN: 'context.close',
|
||||||
|
}
|
||||||
|
});
|
||||||
expect(result).toContain(`playwright`);
|
expect(result).toContain(`playwright`);
|
||||||
expect(result).toContain(`page.close`);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await test.step('codegen --target=python', async () => {
|
await test.step('codegen --target=python', async () => {
|
||||||
const result = await exec('npx playwright codegen --target=python', { env: { PWTEST_CLI_EXIT: '1' } });
|
const result = await exec('npx playwright codegen --target=python', {
|
||||||
expect(result).toContain(`chromium.launch`);
|
env: {
|
||||||
|
PWTEST_CLI_IS_UNDER_TEST: '1',
|
||||||
|
PWTEST_CLI_AUTO_EXIT_WHEN: 'chromium.launch',
|
||||||
|
},
|
||||||
|
});
|
||||||
expect(result).toContain(`browser.close`);
|
expect(result).toContain(`browser.close`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -481,8 +481,10 @@ test.describe('cli codegen', () => {
|
||||||
|
|
||||||
test('should --save-trace', async ({ runCLI }, testInfo) => {
|
test('should --save-trace', async ({ runCLI }, testInfo) => {
|
||||||
const traceFileName = testInfo.outputPath('trace.zip');
|
const traceFileName = testInfo.outputPath('trace.zip');
|
||||||
const cli = runCLI([`--save-trace=${traceFileName}`]);
|
const cli = runCLI([`--save-trace=${traceFileName}`], {
|
||||||
await cli.exited;
|
autoExitWhen: ' ',
|
||||||
|
});
|
||||||
|
await cli.waitForCleanExit();
|
||||||
expect(fs.existsSync(traceFileName)).toBeTruthy();
|
expect(fs.existsSync(traceFileName)).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -492,11 +494,9 @@ test.describe('cli codegen', () => {
|
||||||
const traceFileName = testInfo.outputPath('trace.zip');
|
const traceFileName = testInfo.outputPath('trace.zip');
|
||||||
const storageFileName = testInfo.outputPath('auth.json');
|
const storageFileName = testInfo.outputPath('auth.json');
|
||||||
const harFileName = testInfo.outputPath('har.har');
|
const harFileName = testInfo.outputPath('har.har');
|
||||||
const cli = runCLI([`--save-trace=${traceFileName}`, `--save-storage=${storageFileName}`, `--save-har=${harFileName}`], {
|
const cli = runCLI([`--save-trace=${traceFileName}`, `--save-storage=${storageFileName}`, `--save-har=${harFileName}`]);
|
||||||
noAutoExit: true,
|
|
||||||
});
|
|
||||||
await cli.waitFor(`import { test, expect } from '@playwright/test'`);
|
await cli.waitFor(`import { test, expect } from '@playwright/test'`);
|
||||||
cli.exit('SIGINT');
|
cli.process.kill('SIGINT');
|
||||||
const { exitCode } = await cli.process.exited;
|
const { exitCode } = await cli.process.exited;
|
||||||
expect(exitCode).toBe(130);
|
expect(exitCode).toBe(130);
|
||||||
expect(fs.existsSync(traceFileName)).toBeTruthy();
|
expect(fs.existsSync(traceFileName)).toBeTruthy();
|
||||||
|
|
|
||||||
|
|
@ -43,8 +43,7 @@ class Program
|
||||||
${launchOptions(channel)}
|
${launchOptions(channel)}
|
||||||
});
|
});
|
||||||
var context = await browser.NewContextAsync();`;
|
var context = await browser.NewContextAsync();`;
|
||||||
await cli.waitFor(expectedResult).catch(e => e);
|
await cli.waitFor(expectedResult);
|
||||||
expect(cli.text()).toContain(expectedResult);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should print the correct context options for custom settings', async ({ browserName, channel, runCLI }) => {
|
test('should print the correct context options for custom settings', async ({ browserName, channel, runCLI }) => {
|
||||||
|
|
@ -87,7 +86,6 @@ test('should print the correct context options for custom settings', async ({ br
|
||||||
},
|
},
|
||||||
});`;
|
});`;
|
||||||
await cli.waitFor(expectedResult);
|
await cli.waitFor(expectedResult);
|
||||||
expect(cli.text()).toContain(expectedResult);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should print the correct context options when using a device', async ({ browserName, channel, runCLI }) => {
|
test('should print the correct context options when using a device', async ({ browserName, channel, runCLI }) => {
|
||||||
|
|
@ -102,7 +100,6 @@ test('should print the correct context options when using a device', async ({ br
|
||||||
});
|
});
|
||||||
var context = await browser.NewContextAsync(playwright.Devices["Pixel 2"]);`;
|
var context = await browser.NewContextAsync(playwright.Devices["Pixel 2"]);`;
|
||||||
await cli.waitFor(expectedResult);
|
await cli.waitFor(expectedResult);
|
||||||
expect(cli.text()).toContain(expectedResult);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should print the correct context options when using a device and additional options', async ({ browserName, channel, runCLI }) => {
|
test('should print the correct context options when using a device and additional options', async ({ browserName, channel, runCLI }) => {
|
||||||
|
|
@ -147,9 +144,7 @@ test('should print the correct context options when using a device and additiona
|
||||||
Width = 1280,
|
Width = 1280,
|
||||||
},
|
},
|
||||||
});`;
|
});`;
|
||||||
|
|
||||||
await cli.waitFor(expectedResult);
|
await cli.waitFor(expectedResult);
|
||||||
expect(cli.text()).toContain(expectedResult);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should print load/save storageState', async ({ browserName, channel, runCLI }, testInfo) => {
|
test('should print load/save storageState', async ({ browserName, channel, runCLI }, testInfo) => {
|
||||||
|
|
@ -179,7 +174,6 @@ test('should print load/save storageState', async ({ browserName, channel, runCL
|
||||||
|
|
||||||
test('should work with --save-har', async ({ runCLI }, testInfo) => {
|
test('should work with --save-har', async ({ runCLI }, testInfo) => {
|
||||||
const harFileName = testInfo.outputPath('har.har');
|
const harFileName = testInfo.outputPath('har.har');
|
||||||
const cli = runCLI(['--target=csharp', `--save-har=${harFileName}`]);
|
|
||||||
const expectedResult = `
|
const expectedResult = `
|
||||||
var context = await browser.NewContextAsync(new BrowserNewContextOptions
|
var context = await browser.NewContextAsync(new BrowserNewContextOptions
|
||||||
{
|
{
|
||||||
|
|
@ -187,9 +181,10 @@ test('should work with --save-har', async ({ runCLI }, testInfo) => {
|
||||||
RecordHarPath = ${JSON.stringify(harFileName)},
|
RecordHarPath = ${JSON.stringify(harFileName)},
|
||||||
ServiceWorkers = ServiceWorkerPolicy.Block,
|
ServiceWorkers = ServiceWorkerPolicy.Block,
|
||||||
});`;
|
});`;
|
||||||
await cli.waitFor(expectedResult).catch(e => e);
|
const cli = runCLI(['--target=csharp', `--save-har=${harFileName}`], {
|
||||||
expect(cli.text()).toContain(expectedResult);
|
autoExitWhen: expectedResult,
|
||||||
await cli.exited;
|
});
|
||||||
|
await cli.waitForCleanExit();
|
||||||
const json = JSON.parse(fs.readFileSync(harFileName, 'utf-8'));
|
const json = JSON.parse(fs.readFileSync(harFileName, 'utf-8'));
|
||||||
expect(json.log.creator.name).toBe('Playwright');
|
expect(json.log.creator.name).toBe('Playwright');
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,6 @@ public class Example {
|
||||||
${launchOptions(channel)});
|
${launchOptions(channel)});
|
||||||
BrowserContext context = browser.newContext();`;
|
BrowserContext context = browser.newContext();`;
|
||||||
await cli.waitFor(expectedResult);
|
await cli.waitFor(expectedResult);
|
||||||
expect(cli.text()).toContain(expectedResult);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should print the correct context options for custom settings', async ({ runCLI, browserName }) => {
|
test('should print the correct context options for custom settings', async ({ runCLI, browserName }) => {
|
||||||
|
|
@ -45,7 +44,6 @@ test('should print the correct context options for custom settings', async ({ ru
|
||||||
const expectedResult = `BrowserContext context = browser.newContext(new Browser.NewContextOptions()
|
const expectedResult = `BrowserContext context = browser.newContext(new Browser.NewContextOptions()
|
||||||
.setColorScheme(ColorScheme.LIGHT));`;
|
.setColorScheme(ColorScheme.LIGHT));`;
|
||||||
await cli.waitFor(expectedResult);
|
await cli.waitFor(expectedResult);
|
||||||
expect(cli.text()).toContain(expectedResult);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should print the correct context options when using a device', async ({ browserName, runCLI }) => {
|
test('should print the correct context options when using a device', async ({ browserName, runCLI }) => {
|
||||||
|
|
@ -93,14 +91,15 @@ test('should print load/save storage_state', async ({ runCLI, browserName }, tes
|
||||||
|
|
||||||
test('should work with --save-har', async ({ runCLI }, testInfo) => {
|
test('should work with --save-har', async ({ runCLI }, testInfo) => {
|
||||||
const harFileName = testInfo.outputPath('har.har');
|
const harFileName = testInfo.outputPath('har.har');
|
||||||
const cli = runCLI(['--target=java', `--save-har=${harFileName}`]);
|
|
||||||
const expectedResult = `BrowserContext context = browser.newContext(new Browser.NewContextOptions()
|
const expectedResult = `BrowserContext context = browser.newContext(new Browser.NewContextOptions()
|
||||||
.setRecordHarMode(HarMode.MINIMAL)
|
.setRecordHarMode(HarMode.MINIMAL)
|
||||||
.setRecordHarPath(Paths.get(${JSON.stringify(harFileName)}))
|
.setRecordHarPath(Paths.get(${JSON.stringify(harFileName)}))
|
||||||
.setServiceWorkers(ServiceWorkerPolicy.BLOCK));`;
|
.setServiceWorkers(ServiceWorkerPolicy.BLOCK));`;
|
||||||
await cli.waitFor(expectedResult).catch(e => e);
|
const cli = runCLI(['--target=java', `--save-har=${harFileName}`], {
|
||||||
expect(cli.text()).toContain(expectedResult);
|
autoExitWhen: expectedResult,
|
||||||
await cli.exited;
|
});
|
||||||
|
|
||||||
|
await cli.waitForCleanExit();
|
||||||
const json = JSON.parse(fs.readFileSync(harFileName, 'utf-8'));
|
const json = JSON.parse(fs.readFileSync(harFileName, 'utf-8'));
|
||||||
expect(json.log.creator.name).toBe('Playwright');
|
expect(json.log.creator.name).toBe('Playwright');
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,6 @@ test('should print the correct imports and context options', async ({ browserNam
|
||||||
});
|
});
|
||||||
const context = await browser.newContext();`;
|
const context = await browser.newContext();`;
|
||||||
await cli.waitFor(expectedResult);
|
await cli.waitFor(expectedResult);
|
||||||
expect(cli.text()).toContain(expectedResult);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should print the correct context options for custom settings', async ({ browserName, channel, runCLI }) => {
|
test('should print the correct context options for custom settings', async ({ browserName, channel, runCLI }) => {
|
||||||
|
|
@ -49,7 +48,6 @@ test('should print the correct context options for custom settings', async ({ br
|
||||||
colorScheme: 'light'
|
colorScheme: 'light'
|
||||||
});`;
|
});`;
|
||||||
await cli.waitFor(expectedResult);
|
await cli.waitFor(expectedResult);
|
||||||
expect(cli.text()).toContain(expectedResult);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -67,7 +65,6 @@ test('should print the correct context options when using a device', async ({ br
|
||||||
...devices['Pixel 2'],
|
...devices['Pixel 2'],
|
||||||
});`;
|
});`;
|
||||||
await cli.waitFor(expectedResult);
|
await cli.waitFor(expectedResult);
|
||||||
expect(cli.text()).toContain(expectedResult);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should print the correct context options when using a device and additional options', async ({ browserName, channel, runCLI }) => {
|
test('should print the correct context options when using a device and additional options', async ({ browserName, channel, runCLI }) => {
|
||||||
|
|
@ -85,13 +82,14 @@ test('should print the correct context options when using a device and additiona
|
||||||
colorScheme: 'light'
|
colorScheme: 'light'
|
||||||
});`;
|
});`;
|
||||||
await cli.waitFor(expectedResult);
|
await cli.waitFor(expectedResult);
|
||||||
expect(cli.text()).toContain(expectedResult);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should save the codegen output to a file if specified', async ({ browserName, channel, runCLI }, testInfo) => {
|
test('should save the codegen output to a file if specified', async ({ browserName, channel, runCLI }, testInfo) => {
|
||||||
const tmpFile = testInfo.outputPath('script.js');
|
const tmpFile = testInfo.outputPath('script.js');
|
||||||
const cli = runCLI(['--output', tmpFile, '--target=javascript', emptyHTML]);
|
const cli = runCLI(['--output', tmpFile, '--target=javascript', emptyHTML], {
|
||||||
await cli.exited;
|
autoExitWhen: 'await page.goto', // We have to wait for the initial navigation to be recorded.
|
||||||
|
});
|
||||||
|
await cli.waitForCleanExit();
|
||||||
const content = fs.readFileSync(tmpFile);
|
const content = fs.readFileSync(tmpFile);
|
||||||
expect(content.toString()).toBe(`const { ${browserName} } = require('playwright');
|
expect(content.toString()).toBe(`const { ${browserName} } = require('playwright');
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,15 +27,16 @@ test('should print the correct imports and context options', async ({ runCLI })
|
||||||
|
|
||||||
def test_example(page: Page) -> None:`;
|
def test_example(page: Page) -> None:`;
|
||||||
await cli.waitFor(expectedResult);
|
await cli.waitFor(expectedResult);
|
||||||
expect(cli.text()).toContain(expectedResult);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should print the correct context options when using a device and lang', async ({ browserName, runCLI }, testInfo) => {
|
test('should print the correct context options when using a device and lang', async ({ browserName, runCLI }, testInfo) => {
|
||||||
test.skip(browserName !== 'webkit');
|
test.skip(browserName !== 'webkit');
|
||||||
|
|
||||||
const tmpFile = testInfo.outputPath('script.js');
|
const tmpFile = testInfo.outputPath('script.js');
|
||||||
const cli = runCLI(['--target=python-pytest', '--device=iPhone 11', '--lang=en-US', '--output', tmpFile, emptyHTML]);
|
const cli = runCLI(['--target=python-pytest', '--device=iPhone 11', '--lang=en-US', '--output', tmpFile, emptyHTML], {
|
||||||
await cli.exited;
|
autoExitWhen: 'page.goto',
|
||||||
|
});
|
||||||
|
await cli.waitForCleanExit();
|
||||||
const content = fs.readFileSync(tmpFile);
|
const content = fs.readFileSync(tmpFile);
|
||||||
expect(content.toString()).toBe(`import pytest
|
expect(content.toString()).toBe(`import pytest
|
||||||
|
|
||||||
|
|
@ -54,8 +55,10 @@ def test_example(page: Page) -> None:
|
||||||
|
|
||||||
test('should save the codegen output to a file if specified', async ({ runCLI }, testInfo) => {
|
test('should save the codegen output to a file if specified', async ({ runCLI }, testInfo) => {
|
||||||
const tmpFile = testInfo.outputPath('test_example.py');
|
const tmpFile = testInfo.outputPath('test_example.py');
|
||||||
const cli = runCLI(['--target=python-pytest', '--output', tmpFile, emptyHTML]);
|
const cli = runCLI(['--target=python-pytest', '--output', tmpFile, emptyHTML], {
|
||||||
await cli.exited;
|
autoExitWhen: 'page.goto',
|
||||||
|
});
|
||||||
|
await cli.waitForCleanExit();
|
||||||
const content = fs.readFileSync(tmpFile);
|
const content = fs.readFileSync(tmpFile);
|
||||||
expect(content.toString()).toBe(`from playwright.sync_api import Page, expect
|
expect(content.toString()).toBe(`from playwright.sync_api import Page, expect
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,6 @@ async def run(playwright: Playwright) -> None:
|
||||||
browser = await playwright.${browserName}.launch(${launchOptions(channel)})
|
browser = await playwright.${browserName}.launch(${launchOptions(channel)})
|
||||||
context = await browser.new_context()`;
|
context = await browser.new_context()`;
|
||||||
await cli.waitFor(expectedResult);
|
await cli.waitFor(expectedResult);
|
||||||
expect(cli.text()).toContain(expectedResult);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should print the correct context options for custom settings', async ({ browserName, channel, runCLI }) => {
|
test('should print the correct context options for custom settings', async ({ browserName, channel, runCLI }) => {
|
||||||
|
|
@ -48,7 +47,6 @@ async def run(playwright: Playwright) -> None:
|
||||||
browser = await playwright.${browserName}.launch(${launchOptions(channel)})
|
browser = await playwright.${browserName}.launch(${launchOptions(channel)})
|
||||||
context = await browser.new_context(color_scheme="light")`;
|
context = await browser.new_context(color_scheme="light")`;
|
||||||
await cli.waitFor(expectedResult);
|
await cli.waitFor(expectedResult);
|
||||||
expect(cli.text()).toContain(expectedResult);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should print the correct context options when using a device', async ({ browserName, channel, runCLI }) => {
|
test('should print the correct context options when using a device', async ({ browserName, channel, runCLI }) => {
|
||||||
|
|
@ -64,7 +62,6 @@ async def run(playwright: Playwright) -> None:
|
||||||
browser = await playwright.chromium.launch(${launchOptions(channel)})
|
browser = await playwright.chromium.launch(${launchOptions(channel)})
|
||||||
context = await browser.new_context(**playwright.devices["Pixel 2"])`;
|
context = await browser.new_context(**playwright.devices["Pixel 2"])`;
|
||||||
await cli.waitFor(expectedResult);
|
await cli.waitFor(expectedResult);
|
||||||
expect(cli.text()).toContain(expectedResult);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should print the correct context options when using a device and additional options', async ({ browserName, channel, runCLI }) => {
|
test('should print the correct context options when using a device and additional options', async ({ browserName, channel, runCLI }) => {
|
||||||
|
|
@ -80,13 +77,14 @@ async def run(playwright: Playwright) -> None:
|
||||||
browser = await playwright.webkit.launch(${launchOptions(channel)})
|
browser = await playwright.webkit.launch(${launchOptions(channel)})
|
||||||
context = await browser.new_context(**playwright.devices["iPhone 11"], color_scheme="light")`;
|
context = await browser.new_context(**playwright.devices["iPhone 11"], color_scheme="light")`;
|
||||||
await cli.waitFor(expectedResult);
|
await cli.waitFor(expectedResult);
|
||||||
expect(cli.text()).toContain(expectedResult);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should save the codegen output to a file if specified', async ({ browserName, channel, runCLI }, testInfo) => {
|
test('should save the codegen output to a file if specified', async ({ browserName, channel, runCLI }, testInfo) => {
|
||||||
const tmpFile = testInfo.outputPath('example.py');
|
const tmpFile = testInfo.outputPath('example.py');
|
||||||
const cli = runCLI(['--target=python-async', '--output', tmpFile, emptyHTML]);
|
const cli = runCLI(['--target=python-async', '--output', tmpFile, emptyHTML], {
|
||||||
await cli.exited;
|
autoExitWhen: 'page.goto',
|
||||||
|
});
|
||||||
|
await cli.waitForCleanExit();
|
||||||
const content = fs.readFileSync(tmpFile);
|
const content = fs.readFileSync(tmpFile);
|
||||||
expect(content.toString()).toBe(`import asyncio
|
expect(content.toString()).toBe(`import asyncio
|
||||||
|
|
||||||
|
|
@ -148,11 +146,11 @@ asyncio.run(main())
|
||||||
|
|
||||||
test('should work with --save-har', async ({ runCLI }, testInfo) => {
|
test('should work with --save-har', async ({ runCLI }, testInfo) => {
|
||||||
const harFileName = testInfo.outputPath('har.har');
|
const harFileName = testInfo.outputPath('har.har');
|
||||||
const cli = runCLI(['--target=python-async', `--save-har=${harFileName}`]);
|
|
||||||
const expectedResult = `context = await browser.new_context(record_har_mode="minimal", record_har_path=${JSON.stringify(harFileName)}, service_workers="block")`;
|
const expectedResult = `context = await browser.new_context(record_har_mode="minimal", record_har_path=${JSON.stringify(harFileName)}, service_workers="block")`;
|
||||||
await cli.waitFor(expectedResult).catch(e => e);
|
const cli = runCLI(['--target=python-async', `--save-har=${harFileName}`], {
|
||||||
expect(cli.text()).toContain(expectedResult);
|
autoExitWhen: expectedResult,
|
||||||
await cli.exited;
|
});
|
||||||
|
await cli.waitForCleanExit();
|
||||||
const json = JSON.parse(fs.readFileSync(harFileName, 'utf-8'));
|
const json = JSON.parse(fs.readFileSync(harFileName, 'utf-8'));
|
||||||
expect(json.log.creator.name).toBe('Playwright');
|
expect(json.log.creator.name).toBe('Playwright');
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,6 @@ def run(playwright: Playwright) -> None:
|
||||||
browser = playwright.${browserName}.launch(${launchOptions(channel)})
|
browser = playwright.${browserName}.launch(${launchOptions(channel)})
|
||||||
context = browser.new_context()`;
|
context = browser.new_context()`;
|
||||||
await cli.waitFor(expectedResult);
|
await cli.waitFor(expectedResult);
|
||||||
expect(cli.text()).toContain(expectedResult);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should print the correct context options for custom settings', async ({ runCLI, channel, browserName }) => {
|
test('should print the correct context options for custom settings', async ({ runCLI, channel, browserName }) => {
|
||||||
|
|
@ -44,7 +43,6 @@ def run(playwright: Playwright) -> None:
|
||||||
browser = playwright.${browserName}.launch(${launchOptions(channel)})
|
browser = playwright.${browserName}.launch(${launchOptions(channel)})
|
||||||
context = browser.new_context(color_scheme="light")`;
|
context = browser.new_context(color_scheme="light")`;
|
||||||
await cli.waitFor(expectedResult);
|
await cli.waitFor(expectedResult);
|
||||||
expect(cli.text()).toContain(expectedResult);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should print the correct context options when using a device', async ({ browserName, channel, runCLI }) => {
|
test('should print the correct context options when using a device', async ({ browserName, channel, runCLI }) => {
|
||||||
|
|
@ -58,7 +56,6 @@ def run(playwright: Playwright) -> None:
|
||||||
browser = playwright.chromium.launch(${launchOptions(channel)})
|
browser = playwright.chromium.launch(${launchOptions(channel)})
|
||||||
context = browser.new_context(**playwright.devices["Pixel 2"])`;
|
context = browser.new_context(**playwright.devices["Pixel 2"])`;
|
||||||
await cli.waitFor(expectedResult);
|
await cli.waitFor(expectedResult);
|
||||||
expect(cli.text()).toContain(expectedResult);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should print the correct context options when using a device and additional options', async ({ browserName, channel, runCLI }) => {
|
test('should print the correct context options when using a device and additional options', async ({ browserName, channel, runCLI }) => {
|
||||||
|
|
@ -72,13 +69,14 @@ def run(playwright: Playwright) -> None:
|
||||||
browser = playwright.webkit.launch(${launchOptions(channel)})
|
browser = playwright.webkit.launch(${launchOptions(channel)})
|
||||||
context = browser.new_context(**playwright.devices["iPhone 11"], color_scheme="light")`;
|
context = browser.new_context(**playwright.devices["iPhone 11"], color_scheme="light")`;
|
||||||
await cli.waitFor(expectedResult);
|
await cli.waitFor(expectedResult);
|
||||||
expect(cli.text()).toContain(expectedResult);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should save the codegen output to a file if specified', async ({ runCLI, channel, browserName }, testInfo) => {
|
test('should save the codegen output to a file if specified', async ({ runCLI, channel, browserName }, testInfo) => {
|
||||||
const tmpFile = testInfo.outputPath('example.py');
|
const tmpFile = testInfo.outputPath('example.py');
|
||||||
const cli = runCLI(['--target=python', '--output', tmpFile, emptyHTML]);
|
const cli = runCLI(['--target=python', '--output', tmpFile, emptyHTML], {
|
||||||
await cli.exited;
|
autoExitWhen: 'page.goto',
|
||||||
|
});
|
||||||
|
await cli.waitForCleanExit();
|
||||||
const content = fs.readFileSync(tmpFile);
|
const content = fs.readFileSync(tmpFile);
|
||||||
expect(content.toString()).toBe(`from playwright.sync_api import Playwright, sync_playwright, expect
|
expect(content.toString()).toBe(`from playwright.sync_api import Playwright, sync_playwright, expect
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,6 @@ test('should print the correct imports and context options', async ({ runCLI })
|
||||||
test('test', async ({ page }) => {
|
test('test', async ({ page }) => {
|
||||||
});`;
|
});`;
|
||||||
await cli.waitFor(expectedResult);
|
await cli.waitFor(expectedResult);
|
||||||
expect(cli.text()).toContain(expectedResult);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should print the correct context options for custom settings', async ({ browserName, channel, runCLI }) => {
|
test('should print the correct context options for custom settings', async ({ browserName, channel, runCLI }) => {
|
||||||
|
|
@ -40,7 +39,6 @@ test.use({
|
||||||
|
|
||||||
test('test', async ({ page }) => {`;
|
test('test', async ({ page }) => {`;
|
||||||
await cli.waitFor(expectedResult);
|
await cli.waitFor(expectedResult);
|
||||||
expect(cli.text()).toContain(expectedResult);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -56,7 +54,6 @@ test.use({
|
||||||
|
|
||||||
test('test', async ({ page }) => {`;
|
test('test', async ({ page }) => {`;
|
||||||
await cli.waitFor(expectedResult);
|
await cli.waitFor(expectedResult);
|
||||||
expect(cli.text()).toContain(expectedResult);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should print the correct context options when using a device and additional options', async ({ browserName, channel, runCLI }) => {
|
test('should print the correct context options when using a device and additional options', async ({ browserName, channel, runCLI }) => {
|
||||||
|
|
@ -72,7 +69,6 @@ test.use({
|
||||||
|
|
||||||
test('test', async ({ page }) => {`;
|
test('test', async ({ page }) => {`;
|
||||||
await cli.waitFor(expectedResult);
|
await cli.waitFor(expectedResult);
|
||||||
expect(cli.text()).toContain(expectedResult);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should print load storageState', async ({ browserName, channel, runCLI }, testInfo) => {
|
test('should print load storageState', async ({ browserName, channel, runCLI }, testInfo) => {
|
||||||
|
|
@ -86,21 +82,20 @@ test.use({
|
||||||
});
|
});
|
||||||
|
|
||||||
test('test', async ({ page }) => {`;
|
test('test', async ({ page }) => {`;
|
||||||
|
|
||||||
await cli.waitFor(expectedResult);
|
await cli.waitFor(expectedResult);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should work with --save-har', async ({ runCLI }, testInfo) => {
|
test('should work with --save-har', async ({ runCLI }, testInfo) => {
|
||||||
const harFileName = testInfo.outputPath('har.har');
|
const harFileName = testInfo.outputPath('har.har');
|
||||||
const cli = runCLI(['--target=playwright-test', `--save-har=${harFileName}`]);
|
|
||||||
const expectedResult = `
|
const expectedResult = `
|
||||||
recordHar: {
|
recordHar: {
|
||||||
mode: 'minimal',
|
mode: 'minimal',
|
||||||
path: '${harFileName.replace(/\\/g, '\\\\')}'
|
path: '${harFileName.replace(/\\/g, '\\\\')}'
|
||||||
}`;
|
}`;
|
||||||
await cli.waitFor(expectedResult).catch(e => e);
|
const cli = runCLI(['--target=playwright-test', `--save-har=${harFileName}`], {
|
||||||
expect(cli.text()).toContain(expectedResult);
|
autoExitWhen: expectedResult,
|
||||||
await cli.exited;
|
});
|
||||||
|
await cli.waitForCleanExit();
|
||||||
const json = JSON.parse(fs.readFileSync(harFileName, 'utf-8'));
|
const json = JSON.parse(fs.readFileSync(harFileName, 'utf-8'));
|
||||||
expect(json.log.creator.name).toBe('Playwright');
|
expect(json.log.creator.name).toBe('Playwright');
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -16,9 +16,11 @@
|
||||||
|
|
||||||
import { contextTest } from '../../config/browserTest';
|
import { contextTest } from '../../config/browserTest';
|
||||||
import type { Page } from 'playwright-core';
|
import type { Page } from 'playwright-core';
|
||||||
|
import { step } from '../../config/baseTest';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import type { Source } from '../../../packages/recorder/src/recorderTypes';
|
import type { Source } from '../../../packages/recorder/src/recorderTypes';
|
||||||
import type { CommonFixtures, TestChildProcess } from '../../config/commonFixtures';
|
import type { CommonFixtures, TestChildProcess } from '../../config/commonFixtures';
|
||||||
|
import { stripAnsi } from '../../config/utils';
|
||||||
import { expect } from '@playwright/test';
|
import { expect } from '@playwright/test';
|
||||||
export { expect } from '@playwright/test';
|
export { expect } from '@playwright/test';
|
||||||
|
|
||||||
|
|
@ -26,7 +28,7 @@ type CLITestArgs = {
|
||||||
recorderPageGetter: () => Promise<Page>;
|
recorderPageGetter: () => Promise<Page>;
|
||||||
closeRecorder: () => Promise<void>;
|
closeRecorder: () => Promise<void>;
|
||||||
openRecorder: () => Promise<Recorder>;
|
openRecorder: () => Promise<Recorder>;
|
||||||
runCLI: (args: string[], options?: { noAutoExit?: boolean }) => CLIMock;
|
runCLI: (args: string[], options?: { autoExitWhen?: string }) => CLIMock;
|
||||||
};
|
};
|
||||||
|
|
||||||
const codegenLang2Id: Map<string, string> = new Map([
|
const codegenLang2Id: Map<string, string> = new Map([
|
||||||
|
|
@ -68,13 +70,9 @@ export const test = contextTest.extend<CLITestArgs>({
|
||||||
process.env.PWTEST_RECORDER_PORT = String(10907 + testInfo.workerIndex);
|
process.env.PWTEST_RECORDER_PORT = String(10907 + testInfo.workerIndex);
|
||||||
testInfo.skip(mode === 'service');
|
testInfo.skip(mode === 'service');
|
||||||
|
|
||||||
let cli: CLIMock | undefined;
|
await run((cliArgs, { autoExitWhen } = {}) => {
|
||||||
await run((cliArgs, { noAutoExit } = {}) => {
|
return new CLIMock(childProcess, browserName, channel, headless, cliArgs, launchOptions.executablePath, autoExitWhen);
|
||||||
cli = new CLIMock(childProcess, browserName, channel, headless, cliArgs, launchOptions.executablePath, noAutoExit);
|
|
||||||
return cli;
|
|
||||||
});
|
});
|
||||||
// Discard any exit error and let childProcess fixture report leaking processes (processwes which do not exit).
|
|
||||||
cli?.exited.catch(() => {});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
openRecorder: async ({ page, recorderPageGetter }, run) => {
|
openRecorder: async ({ page, recorderPageGetter }, run) => {
|
||||||
|
|
@ -135,7 +133,7 @@ class Recorder {
|
||||||
const w = window as any;
|
const w = window as any;
|
||||||
const source = (w.playwrightSourcesEchoForTest || []).find((s: Source) => s.id === params.languageId);
|
const source = (w.playwrightSourcesEchoForTest || []).find((s: Source) => s.id === params.languageId);
|
||||||
return source && source.text.includes(params.text) ? w.playwrightSourcesEchoForTest : null;
|
return source && source.text.includes(params.text) ? w.playwrightSourcesEchoForTest : null;
|
||||||
}, { text, languageId: codegenLang2Id.get(file) }, { timeout: 8000, polling: 300 });
|
}, { text, languageId: codegenLang2Id.get(file) }, { timeout: 0, polling: 300 });
|
||||||
const sources: Source[] = await handle.jsonValue();
|
const sources: Source[] = await handle.jsonValue();
|
||||||
for (const source of sources) {
|
for (const source of sources) {
|
||||||
if (!codegenLangId2lang.has(source.id))
|
if (!codegenLangId2lang.has(source.id))
|
||||||
|
|
@ -199,11 +197,8 @@ class Recorder {
|
||||||
|
|
||||||
class CLIMock {
|
class CLIMock {
|
||||||
process: TestChildProcess;
|
process: TestChildProcess;
|
||||||
private waitForText: string;
|
|
||||||
private waitForCallback: () => void;
|
|
||||||
exited: Promise<void>;
|
|
||||||
|
|
||||||
constructor(childProcess: CommonFixtures['childProcess'], browserName: string, channel: string | undefined, headless: boolean | undefined, args: string[], executablePath: string | undefined, noAutoExit: boolean | undefined) {
|
constructor(childProcess: CommonFixtures['childProcess'], browserName: string, channel: string | undefined, headless: boolean | undefined, args: string[], executablePath: string | undefined, autoExitWhen: string | undefined) {
|
||||||
const nodeArgs = [
|
const nodeArgs = [
|
||||||
'node',
|
'node',
|
||||||
path.join(__dirname, '..', '..', '..', 'packages', 'playwright-core', 'lib', 'cli', 'cli.js'),
|
path.join(__dirname, '..', '..', '..', 'packages', 'playwright-core', 'lib', 'cli', 'cli.js'),
|
||||||
|
|
@ -216,47 +211,28 @@ class CLIMock {
|
||||||
this.process = childProcess({
|
this.process = childProcess({
|
||||||
command: nodeArgs,
|
command: nodeArgs,
|
||||||
env: {
|
env: {
|
||||||
|
PWTEST_CLI_AUTO_EXIT_WHEN: autoExitWhen,
|
||||||
PWTEST_CLI_IS_UNDER_TEST: '1',
|
PWTEST_CLI_IS_UNDER_TEST: '1',
|
||||||
PWTEST_CLI_EXIT: !noAutoExit ? '1' : undefined,
|
|
||||||
PWTEST_CLI_HEADLESS: headless ? '1' : undefined,
|
PWTEST_CLI_HEADLESS: headless ? '1' : undefined,
|
||||||
PWTEST_CLI_EXECUTABLE_PATH: executablePath,
|
PWTEST_CLI_EXECUTABLE_PATH: executablePath,
|
||||||
DEBUG: (process.env.DEBUG ?? '') + ',pw:browser*',
|
DEBUG: (process.env.DEBUG ?? '') + ',pw:browser*',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
this.process.onOutput = () => {
|
|
||||||
if (this.waitForCallback && this.process.output.includes(this.waitForText))
|
|
||||||
this.waitForCallback();
|
|
||||||
};
|
|
||||||
this.exited = this.process.cleanExit();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async waitFor(text: string, timeout = 10_000): Promise<void> {
|
@step
|
||||||
if (this.process.output.includes(text))
|
async waitFor(text: string): Promise<void> {
|
||||||
return Promise.resolve();
|
await expect(() => {
|
||||||
this.waitForText = text;
|
expect(this.text()).toContain(text);
|
||||||
return new Promise((f, r) => {
|
}).toPass();
|
||||||
this.waitForCallback = f;
|
}
|
||||||
if (timeout) {
|
|
||||||
setTimeout(() => {
|
@step
|
||||||
r(new Error('Timed out waiting for text:\n' + text + '\n\nReceived:\n' + this.text()));
|
async waitForCleanExit() {
|
||||||
}, timeout);
|
return this.process.cleanExit();
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
text() {
|
text() {
|
||||||
return removeAnsiColors(this.process.output);
|
return stripAnsi(this.process.output);
|
||||||
}
|
|
||||||
|
|
||||||
exit(signal: NodeJS.Signals | number) {
|
|
||||||
this.process.process.kill(signal);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeAnsiColors(input: string): string {
|
|
||||||
const pattern = [
|
|
||||||
'[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)',
|
|
||||||
'(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))'
|
|
||||||
].join('|');
|
|
||||||
return input.replace(new RegExp(pattern, 'g'), '');
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -109,7 +109,7 @@ export const test = base
|
||||||
return { page, testProcess };
|
return { page, testProcess };
|
||||||
});
|
});
|
||||||
await browser?.close();
|
await browser?.close();
|
||||||
await testProcess?.close();
|
await testProcess?.kill('SIGINT');
|
||||||
await removeFolderAsync(cacheDir);
|
await removeFolderAsync(cacheDir);
|
||||||
},
|
},
|
||||||
createLatch: async ({}, use, testInfo) => {
|
createLatch: async ({}, use, testInfo) => {
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,6 @@ export const webView2Test = baseTest.extend<TraceViewerFixtures>(traceViewerFixt
|
||||||
const browser = await playwright.chromium.connectOverCDP(`http://127.0.0.1:${cdpPort}`);
|
const browser = await playwright.chromium.connectOverCDP(`http://127.0.0.1:${cdpPort}`);
|
||||||
await use(browser);
|
await use(browser);
|
||||||
await browser.close();
|
await browser.close();
|
||||||
await spawnedProcess.close();
|
await spawnedProcess.kill('SIGINT');
|
||||||
}, { scope: 'worker' }],
|
}, { scope: 'worker' }],
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue