fix(cli): store trace/storage/har on SIGINT (#16339)
This commit is contained in:
parent
57fcb590f7
commit
1ca6635bb8
|
|
@ -376,6 +376,7 @@ async function launchContext(options: Options, headless: boolean, executablePath
|
|||
const launchOptions: LaunchOptions = { headless, executablePath };
|
||||
if (options.channel)
|
||||
launchOptions.channel = options.channel as any;
|
||||
launchOptions.handleSIGINT = false;
|
||||
|
||||
const contextOptions: BrowserContextOptions =
|
||||
// Copy the device descriptor since we have to compare and modify the options.
|
||||
|
|
@ -522,6 +523,11 @@ async function launchContext(options: Options, headless: boolean, executablePath
|
|||
closeBrowser().catch(e => null);
|
||||
});
|
||||
});
|
||||
process.on('SIGINT', async () => {
|
||||
await closeBrowser();
|
||||
process.exit(130);
|
||||
});
|
||||
|
||||
const timeout = options.timeout ? parseInt(options.timeout, 10) : 0;
|
||||
context.setDefaultTimeout(timeout);
|
||||
context.setDefaultNavigationTimeout(timeout);
|
||||
|
|
@ -532,6 +538,7 @@ async function launchContext(options: Options, headless: boolean, executablePath
|
|||
// Omit options that we add automatically for presentation purpose.
|
||||
delete launchOptions.headless;
|
||||
delete launchOptions.executablePath;
|
||||
delete launchOptions.handleSIGINT;
|
||||
delete contextOptions.deviceScaleFactor;
|
||||
return { browser, browserName: browserType.name(), context, contextOptions, launchOptions };
|
||||
}
|
||||
|
|
@ -571,7 +578,8 @@ async function codegen(options: Options, url: string | undefined, language: stri
|
|||
device: options.device,
|
||||
saveStorage: options.saveStorage,
|
||||
mode: 'recording',
|
||||
outputFile: outputFile ? path.resolve(outputFile) : undefined
|
||||
outputFile: outputFile ? path.resolve(outputFile) : undefined,
|
||||
handleSIGINT: false,
|
||||
});
|
||||
await openPage(context, url);
|
||||
if (process.env.PWTEST_CLI_EXIT)
|
||||
|
|
|
|||
|
|
@ -374,7 +374,8 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
|
|||
device?: string,
|
||||
saveStorage?: string,
|
||||
mode?: 'recording' | 'inspecting',
|
||||
outputFile?: string
|
||||
outputFile?: string,
|
||||
handleSIGINT?: boolean,
|
||||
}) {
|
||||
await this._channel.recorderSupplementEnable(params);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1423,6 +1423,7 @@ export type BrowserContextRecorderSupplementEnableParams = {
|
|||
device?: string,
|
||||
saveStorage?: string,
|
||||
outputFile?: string,
|
||||
handleSIGINT?: boolean,
|
||||
};
|
||||
export type BrowserContextRecorderSupplementEnableOptions = {
|
||||
language?: string,
|
||||
|
|
@ -1433,6 +1434,7 @@ export type BrowserContextRecorderSupplementEnableOptions = {
|
|||
device?: string,
|
||||
saveStorage?: string,
|
||||
outputFile?: string,
|
||||
handleSIGINT?: boolean,
|
||||
};
|
||||
export type BrowserContextRecorderSupplementEnableResult = void;
|
||||
export type BrowserContextNewCDPSessionParams = {
|
||||
|
|
|
|||
|
|
@ -948,6 +948,7 @@ BrowserContext:
|
|||
device: string?
|
||||
saveStorage: string?
|
||||
outputFile: string?
|
||||
handleSIGINT: boolean?
|
||||
|
||||
newCDPSession:
|
||||
parameters:
|
||||
|
|
|
|||
|
|
@ -772,6 +772,7 @@ scheme.BrowserContextRecorderSupplementEnableParams = tObject({
|
|||
device: tOptional(tString),
|
||||
saveStorage: tOptional(tString),
|
||||
outputFile: tOptional(tString),
|
||||
handleSIGINT: tOptional(tBoolean),
|
||||
});
|
||||
scheme.BrowserContextRecorderSupplementEnableResult = tOptional(tObject({}));
|
||||
scheme.BrowserContextNewCDPSessionParams = tObject({
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ export class Recorder implements InstrumentationListener {
|
|||
private _allMetadatas = new Map<string, CallMetadata>();
|
||||
private _debugger: Debugger;
|
||||
private _contextRecorder: ContextRecorder;
|
||||
private _handleSIGINT: boolean | undefined;
|
||||
private _recorderAppFactory: (recorder: Recorder) => Promise<IRecorderApp>;
|
||||
|
||||
static showInspector(context: BrowserContext) {
|
||||
|
|
@ -78,13 +79,14 @@ export class Recorder implements InstrumentationListener {
|
|||
this._contextRecorder = new ContextRecorder(context, params);
|
||||
this._context = context;
|
||||
this._debugger = Debugger.lookup(context)!;
|
||||
this._handleSIGINT = params.handleSIGINT;
|
||||
context.instrumentation.addListener(this, context);
|
||||
}
|
||||
|
||||
private static async defaultRecorderAppFactory(recorder: Recorder) {
|
||||
if (process.env.PW_CODEGEN_NO_INSPECTOR)
|
||||
return new EmptyRecorderApp();
|
||||
return await RecorderApp.open(recorder, recorder._context);
|
||||
return await RecorderApp.open(recorder, recorder._context, recorder._handleSIGINT);
|
||||
}
|
||||
|
||||
async install() {
|
||||
|
|
|
|||
|
|
@ -109,7 +109,7 @@ export class RecorderApp extends EventEmitter implements IRecorderApp {
|
|||
await mainFrame.goto(serverSideCallMetadata(), 'https://playwright/index.html');
|
||||
}
|
||||
|
||||
static async open(recorder: Recorder, inspectedContext: BrowserContext): Promise<IRecorderApp> {
|
||||
static async open(recorder: Recorder, inspectedContext: BrowserContext, handleSIGINT: boolean | undefined): Promise<IRecorderApp> {
|
||||
const sdkLanguage = inspectedContext._browser.options.sdkLanguage;
|
||||
const headed = !!inspectedContext._browser.options.headful;
|
||||
const recorderPlaywright = (require('../playwright').createPlaywright as typeof import('../playwright').createPlaywright)('javascript', true);
|
||||
|
|
@ -127,7 +127,8 @@ export class RecorderApp extends EventEmitter implements IRecorderApp {
|
|||
noDefaultViewport: true,
|
||||
ignoreDefaultArgs: ['--enable-automation'],
|
||||
headless: !!process.env.PWTEST_CLI_HEADLESS || (isUnderTest() && !headed),
|
||||
useWebSocket: !!process.env.PWTEST_RECORDER_PORT
|
||||
useWebSocket: !!process.env.PWTEST_RECORDER_PORT,
|
||||
handleSIGINT,
|
||||
});
|
||||
const controller = new ProgressController(serverSideCallMetadata(), context._browser);
|
||||
await controller.run(async progress => {
|
||||
|
|
@ -165,7 +166,7 @@ export class RecorderApp extends EventEmitter implements IRecorderApp {
|
|||
|
||||
// Testing harness for runCLI mode.
|
||||
{
|
||||
if (process.env.PWTEST_CLI_EXIT && sources.length) {
|
||||
if ((process.env.PWTEST_CLI_IS_UNDER_TEST || process.env.PWTEST_CLI_EXIT) && sources.length) {
|
||||
process.stdout.write('\n-------------8<-------------\n');
|
||||
process.stdout.write(sources[0].text);
|
||||
process.stdout.write('\n-------------8<-------------\n');
|
||||
|
|
|
|||
|
|
@ -544,6 +544,24 @@ test.describe('cli codegen', () => {
|
|||
expect(fs.existsSync(traceFileName)).toBeTruthy();
|
||||
});
|
||||
|
||||
test('should save assets via SIGINT', async ({ runCLI, platform }, testInfo) => {
|
||||
test.skip(platform === 'win32', 'SIGINT not supported on Windows');
|
||||
|
||||
const traceFileName = testInfo.outputPath('trace.zip');
|
||||
const storageFileName = testInfo.outputPath('auth.json');
|
||||
const harFileName = testInfo.outputPath('har.har');
|
||||
const cli = runCLI([`--save-trace=${traceFileName}`, `--save-storage=${storageFileName}`, `--save-har=${harFileName}`], {
|
||||
noAutoExit: true,
|
||||
});
|
||||
await cli.waitFor(`import { test, expect } from '@playwright/test'`);
|
||||
cli.exit('SIGINT');
|
||||
const { exitCode } = await cli.process.exited;
|
||||
expect(exitCode).toBe(130);
|
||||
expect(fs.existsSync(traceFileName)).toBeTruthy();
|
||||
expect(fs.existsSync(storageFileName)).toBeTruthy();
|
||||
expect(fs.existsSync(harFileName)).toBeTruthy();
|
||||
});
|
||||
|
||||
test('should fill tricky characters', async ({ page, openRecorder }) => {
|
||||
const recorder = await openRecorder();
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ type CLITestArgs = {
|
|||
recorderPageGetter: () => Promise<Page>;
|
||||
closeRecorder: () => Promise<void>;
|
||||
openRecorder: () => Promise<Recorder>;
|
||||
runCLI: (args: string[]) => CLIMock;
|
||||
runCLI: (args: string[], options?: { noAutoExit?: boolean }) => CLIMock;
|
||||
};
|
||||
|
||||
const playwrightToAutomateInspector = require('../../../packages/playwright-core/lib/inProcessFactory').createInProcessPlaywright();
|
||||
|
|
@ -55,8 +55,8 @@ export const test = contextTest.extend<CLITestArgs>({
|
|||
testInfo.skip(mode === 'service');
|
||||
|
||||
let cli: CLIMock | undefined;
|
||||
await run(cliArgs => {
|
||||
cli = new CLIMock(childProcess, browserName, channel, headless, cliArgs, launchOptions.executablePath);
|
||||
await run((cliArgs, { noAutoExit } = {}) => {
|
||||
cli = new CLIMock(childProcess, browserName, channel, headless, cliArgs, launchOptions.executablePath, noAutoExit);
|
||||
return cli;
|
||||
});
|
||||
if (cli)
|
||||
|
|
@ -178,12 +178,12 @@ class Recorder {
|
|||
}
|
||||
|
||||
class CLIMock {
|
||||
private 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) {
|
||||
constructor(childProcess: CommonFixtures['childProcess'], browserName: string, channel: string | undefined, headless: boolean | undefined, args: string[], executablePath: string | undefined, noAutoExit: boolean | undefined) {
|
||||
const nodeArgs = [
|
||||
'node',
|
||||
path.join(__dirname, '..', '..', '..', 'packages', 'playwright-core', 'lib', 'cli', 'cli.js'),
|
||||
|
|
@ -196,7 +196,8 @@ class CLIMock {
|
|||
this.process = childProcess({
|
||||
command: nodeArgs,
|
||||
env: {
|
||||
PWTEST_CLI_EXIT: '1',
|
||||
PWTEST_CLI_IS_UNDER_TEST: '1',
|
||||
PWTEST_CLI_EXIT: !noAutoExit ? '1' : undefined,
|
||||
PWTEST_CLI_HEADLESS: headless ? '1' : undefined,
|
||||
PWTEST_CLI_EXECUTABLE_PATH: executablePath,
|
||||
DEBUG: (process.env.DEBUG ?? '') + ',pw:browser*',
|
||||
|
|
@ -226,6 +227,10 @@ class CLIMock {
|
|||
text() {
|
||||
return removeAnsiColors(this.process.output);
|
||||
}
|
||||
|
||||
exit(signal: NodeJS.Signals | number) {
|
||||
this.process.process.kill(signal);
|
||||
}
|
||||
}
|
||||
|
||||
function removeAnsiColors(input: string): string {
|
||||
|
|
|
|||
Loading…
Reference in a new issue