chore: remove --save-trace codegen option (#34362)
This commit is contained in:
parent
b339d457e3
commit
1b21ec9cd8
|
|
@ -65,7 +65,6 @@ commandWithOpenOptions('codegen [url]', 'open page and generate code for user ac
|
||||||
[
|
[
|
||||||
['-o, --output <file name>', 'saves the generated script to a file'],
|
['-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()],
|
['--target <language>', `language to generate, one of javascript, playwright-test, python, python-async, python-pytest, csharp, csharp-mstest, csharp-nunit, java, java-junit`, codegenId()],
|
||||||
['--save-trace <filename>', 'record a trace for the session and save it to a file'],
|
|
||||||
['--test-id-attribute <attributeName>', 'use the specified attribute to generate data test ID selectors'],
|
['--test-id-attribute <attributeName>', 'use the specified attribute to generate data test ID selectors'],
|
||||||
]).action(function(url, options) {
|
]).action(function(url, options) {
|
||||||
codegen(options, url).catch(logErrorAndExit);
|
codegen(options, url).catch(logErrorAndExit);
|
||||||
|
|
@ -353,7 +352,6 @@ type Options = {
|
||||||
saveHar?: string;
|
saveHar?: string;
|
||||||
saveHarGlob?: string;
|
saveHarGlob?: string;
|
||||||
saveStorage?: string;
|
saveStorage?: string;
|
||||||
saveTrace?: string;
|
|
||||||
timeout: string;
|
timeout: string;
|
||||||
timezone?: string;
|
timezone?: string;
|
||||||
viewportSize?: string;
|
viewportSize?: string;
|
||||||
|
|
@ -508,8 +506,6 @@ async function launchContext(options: Options, extraOptions: LaunchOptions): Pro
|
||||||
if (closingBrowser)
|
if (closingBrowser)
|
||||||
return;
|
return;
|
||||||
closingBrowser = true;
|
closingBrowser = true;
|
||||||
if (options.saveTrace)
|
|
||||||
await context.tracing.stop({ path: options.saveTrace });
|
|
||||||
if (options.saveStorage)
|
if (options.saveStorage)
|
||||||
await context.storageState({ path: options.saveStorage }).catch(e => null);
|
await context.storageState({ path: options.saveStorage }).catch(e => null);
|
||||||
if (options.saveHar)
|
if (options.saveHar)
|
||||||
|
|
@ -536,9 +532,6 @@ async function launchContext(options: Options, extraOptions: LaunchOptions): Pro
|
||||||
context.setDefaultTimeout(timeout);
|
context.setDefaultTimeout(timeout);
|
||||||
context.setDefaultNavigationTimeout(timeout);
|
context.setDefaultNavigationTimeout(timeout);
|
||||||
|
|
||||||
if (options.saveTrace)
|
|
||||||
await context.tracing.start({ screenshots: true, snapshots: true });
|
|
||||||
|
|
||||||
// Omit options that we add automatically for presentation purpose.
|
// Omit options that we add automatically for presentation purpose.
|
||||||
delete launchOptions.headless;
|
delete launchOptions.headless;
|
||||||
delete launchOptions.executablePath;
|
delete launchOptions.executablePath;
|
||||||
|
|
|
||||||
|
|
@ -27,9 +27,8 @@ import { Debugger } from './debugger';
|
||||||
import type { CallMetadata, InstrumentationListener, SdkObject } from './instrumentation';
|
import type { CallMetadata, InstrumentationListener, SdkObject } from './instrumentation';
|
||||||
import { ContextRecorder, generateFrameSelector } from './recorder/contextRecorder';
|
import { ContextRecorder, generateFrameSelector } from './recorder/contextRecorder';
|
||||||
import type { IRecorderAppFactory, IRecorderApp, IRecorder } from './recorder/recorderFrontend';
|
import type { IRecorderAppFactory, IRecorderApp, IRecorder } from './recorder/recorderFrontend';
|
||||||
import { metadataToCallLog } from './recorder/recorderUtils';
|
import { buildFullSelector, metadataToCallLog } from './recorder/recorderUtils';
|
||||||
import type * as actions from '@recorder/actions';
|
import type * as actions from '@recorder/actions';
|
||||||
import { buildFullSelector } from '../utils/isomorphic/recorderUtils';
|
|
||||||
import { stringifySelector } from '../utils/isomorphic/selectorParser';
|
import { stringifySelector } from '../utils/isomorphic/selectorParser';
|
||||||
import type { Frame } from './frames';
|
import type { Frame } from './frames';
|
||||||
import type { AriaTemplateNode } from '@isomorphic/ariaSnapshot';
|
import type { AriaTemplateNode } from '@isomorphic/ariaSnapshot';
|
||||||
|
|
|
||||||
|
|
@ -20,10 +20,8 @@ import type { Page } from '../page';
|
||||||
import type { Signal } from '../../../../recorder/src/actions';
|
import type { Signal } from '../../../../recorder/src/actions';
|
||||||
import type * as actions from '@recorder/actions';
|
import type * as actions from '@recorder/actions';
|
||||||
import { monotonicTime } from '../../utils/time';
|
import { monotonicTime } from '../../utils/time';
|
||||||
import { callMetadataForAction, collapseActions } from './recorderUtils';
|
import { collapseActions } from './recorderUtils';
|
||||||
import { serializeError } from '../errors';
|
|
||||||
import { performAction } from './recorderRunner';
|
import { performAction } from './recorderRunner';
|
||||||
import type { CallMetadata } from '@protocol/callMetadata';
|
|
||||||
import { isUnderTest } from '../../utils/debug';
|
import { isUnderTest } from '../../utils/debug';
|
||||||
|
|
||||||
export class RecorderCollection extends EventEmitter {
|
export class RecorderCollection extends EventEmitter {
|
||||||
|
|
@ -46,8 +44,8 @@ export class RecorderCollection extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
async performAction(actionInContext: actions.ActionInContext) {
|
async performAction(actionInContext: actions.ActionInContext) {
|
||||||
await this._addAction(actionInContext, async callMetadata => {
|
await this._addAction(actionInContext, async () => {
|
||||||
await performAction(callMetadata, this._pageAliases, actionInContext);
|
await performAction(this._pageAliases, actionInContext);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -60,7 +58,7 @@ export class RecorderCollection extends EventEmitter {
|
||||||
this._addAction(actionInContext).catch(() => {});
|
this._addAction(actionInContext).catch(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _addAction(actionInContext: actions.ActionInContext, callback?: (callMetadata: CallMetadata) => Promise<void>) {
|
private async _addAction(actionInContext: actions.ActionInContext, callback?: () => Promise<void>) {
|
||||||
if (!this._enabled)
|
if (!this._enabled)
|
||||||
return;
|
return;
|
||||||
if (actionInContext.action.name === 'openPage' || actionInContext.action.name === 'closePage') {
|
if (actionInContext.action.name === 'openPage' || actionInContext.action.name === 'closePage') {
|
||||||
|
|
@ -69,18 +67,10 @@ export class RecorderCollection extends EventEmitter {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { callMetadata, mainFrame } = callMetadataForAction(this._pageAliases, actionInContext);
|
|
||||||
await mainFrame.instrumentation.onBeforeCall(mainFrame, callMetadata);
|
|
||||||
this._actions.push(actionInContext);
|
this._actions.push(actionInContext);
|
||||||
this._fireChange();
|
this._fireChange();
|
||||||
const error = await callback?.(callMetadata).catch((e: Error) => e);
|
await callback?.().catch();
|
||||||
callMetadata.endTime = monotonicTime();
|
actionInContext.endTime = monotonicTime();
|
||||||
actionInContext.endTime = callMetadata.endTime;
|
|
||||||
callMetadata.error = error ? serializeError(error) : undefined;
|
|
||||||
// Do not wait for onAfterCall so that performAction returned immediately after the action.
|
|
||||||
mainFrame.instrumentation.onAfterCall(mainFrame, callMetadata).then(() => {
|
|
||||||
this._fireChange();
|
|
||||||
}).catch(() => {});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
signal(pageAlias: string, frame: Frame, signal: Signal) {
|
signal(pageAlias: string, frame: Frame, signal: Signal) {
|
||||||
|
|
|
||||||
|
|
@ -16,14 +16,14 @@
|
||||||
|
|
||||||
import { serializeExpectedTextValues } from '../../utils';
|
import { serializeExpectedTextValues } from '../../utils';
|
||||||
import { toKeyboardModifiers } from '../codegen/language';
|
import { toKeyboardModifiers } from '../codegen/language';
|
||||||
import type { CallMetadata } from '../instrumentation';
|
import { serverSideCallMetadata } from '../instrumentation';
|
||||||
import type { Page } from '../page';
|
import type { Page } from '../page';
|
||||||
import type * as actions from '@recorder/actions';
|
import type * as actions from '@recorder/actions';
|
||||||
import type * as types from '../types';
|
import type * as types from '../types';
|
||||||
import { mainFrameForAction } from './recorderUtils';
|
import { buildFullSelector, mainFrameForAction } from './recorderUtils';
|
||||||
import { buildFullSelector } from '../../utils/isomorphic/recorderUtils';
|
|
||||||
|
|
||||||
export async function performAction(callMetadata: CallMetadata, pageAliases: Map<Page, string>, actionInContext: actions.ActionInContext) {
|
export async function performAction(pageAliases: Map<Page, string>, actionInContext: actions.ActionInContext) {
|
||||||
|
const callMetadata = serverSideCallMetadata();
|
||||||
const mainFrame = mainFrameForAction(pageAliases, actionInContext);
|
const mainFrame = mainFrameForAction(pageAliases, actionInContext);
|
||||||
const { action } = actionInContext;
|
const { action } = actionInContext;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,10 @@ import type { CallLog, CallLogStatus } from '@recorder/recorderTypes';
|
||||||
import type { Page } from '../page';
|
import type { Page } from '../page';
|
||||||
import type { Frame } from '../frames';
|
import type { Frame } from '../frames';
|
||||||
import type * as actions from '@recorder/actions';
|
import type * as actions from '@recorder/actions';
|
||||||
import { createGuid } from '../../utils';
|
|
||||||
import { buildFullSelector, traceParamsForAction } from '../../utils/isomorphic/recorderUtils';
|
export function buildFullSelector(framePath: string[], selector: string) {
|
||||||
|
return [...framePath, selector].join(' >> internal:control=enter-frame >> ');
|
||||||
|
}
|
||||||
|
|
||||||
export function metadataToCallLog(metadata: CallMetadata, status: CallLogStatus): CallLog {
|
export function metadataToCallLog(metadata: CallMetadata, status: CallLogStatus): CallLog {
|
||||||
let title = metadata.apiName || metadata.method;
|
let title = metadata.apiName || metadata.method;
|
||||||
|
|
@ -70,26 +72,6 @@ export async function frameForAction(pageAliases: Map<Page, string>, actionInCon
|
||||||
return result.frame;
|
return result.frame;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function callMetadataForAction(pageAliases: Map<Page, string>, actionInContext: actions.ActionInContext): { callMetadata: CallMetadata, mainFrame: Frame } {
|
|
||||||
const mainFrame = mainFrameForAction(pageAliases, actionInContext);
|
|
||||||
const { method, apiName, params } = traceParamsForAction(actionInContext);
|
|
||||||
|
|
||||||
const callMetadata: CallMetadata = {
|
|
||||||
id: `call@${createGuid()}`,
|
|
||||||
apiName,
|
|
||||||
objectId: mainFrame.guid,
|
|
||||||
pageId: mainFrame._page.guid,
|
|
||||||
frameId: mainFrame.guid,
|
|
||||||
startTime: actionInContext.startTime,
|
|
||||||
endTime: 0,
|
|
||||||
type: 'Frame',
|
|
||||||
method,
|
|
||||||
params,
|
|
||||||
log: [],
|
|
||||||
};
|
|
||||||
return { callMetadata, mainFrame };
|
|
||||||
}
|
|
||||||
|
|
||||||
export function collapseActions(actions: actions.ActionInContext[]): actions.ActionInContext[] {
|
export function collapseActions(actions: actions.ActionInContext[]): actions.ActionInContext[] {
|
||||||
const result: actions.ActionInContext[] = [];
|
const result: actions.ActionInContext[] = [];
|
||||||
for (const action of actions) {
|
for (const action of actions) {
|
||||||
|
|
|
||||||
|
|
@ -1,163 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) Microsoft Corporation.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import type * as recorderActions from '@recorder/actions';
|
|
||||||
import type * as channels from '@protocol/channels';
|
|
||||||
import type * as types from '../../server/types';
|
|
||||||
|
|
||||||
export function buildFullSelector(framePath: string[], selector: string) {
|
|
||||||
return [...framePath, selector].join(' >> internal:control=enter-frame >> ');
|
|
||||||
}
|
|
||||||
|
|
||||||
const kDefaultTimeout = 5_000;
|
|
||||||
|
|
||||||
export function traceParamsForAction(actionInContext: recorderActions.ActionInContext): { method: string, apiName: string, params: any } {
|
|
||||||
const { action } = actionInContext;
|
|
||||||
|
|
||||||
switch (action.name) {
|
|
||||||
case 'navigate': {
|
|
||||||
const params: channels.FrameGotoParams = {
|
|
||||||
url: action.url,
|
|
||||||
};
|
|
||||||
return { method: 'goto', apiName: 'page.goto', params };
|
|
||||||
}
|
|
||||||
case 'openPage':
|
|
||||||
case 'closePage':
|
|
||||||
throw new Error('Not reached');
|
|
||||||
}
|
|
||||||
|
|
||||||
const selector = buildFullSelector(actionInContext.frame.framePath, action.selector);
|
|
||||||
switch (action.name) {
|
|
||||||
case 'click': {
|
|
||||||
const params: channels.FrameClickParams = {
|
|
||||||
selector,
|
|
||||||
strict: true,
|
|
||||||
modifiers: toKeyboardModifiers(action.modifiers),
|
|
||||||
button: action.button,
|
|
||||||
clickCount: action.clickCount,
|
|
||||||
position: action.position,
|
|
||||||
};
|
|
||||||
return { method: 'click', apiName: 'locator.click', params };
|
|
||||||
}
|
|
||||||
case 'press': {
|
|
||||||
const params: channels.FramePressParams = {
|
|
||||||
selector,
|
|
||||||
strict: true,
|
|
||||||
key: [...toKeyboardModifiers(action.modifiers), action.key].join('+'),
|
|
||||||
};
|
|
||||||
return { method: 'press', apiName: 'locator.press', params };
|
|
||||||
}
|
|
||||||
case 'fill': {
|
|
||||||
const params: channels.FrameFillParams = {
|
|
||||||
selector,
|
|
||||||
strict: true,
|
|
||||||
value: action.text,
|
|
||||||
};
|
|
||||||
return { method: 'fill', apiName: 'locator.fill', params };
|
|
||||||
}
|
|
||||||
case 'setInputFiles': {
|
|
||||||
const params: channels.FrameSetInputFilesParams = {
|
|
||||||
selector,
|
|
||||||
strict: true,
|
|
||||||
localPaths: action.files,
|
|
||||||
};
|
|
||||||
return { method: 'setInputFiles', apiName: 'locator.setInputFiles', params };
|
|
||||||
}
|
|
||||||
case 'check': {
|
|
||||||
const params: channels.FrameCheckParams = {
|
|
||||||
selector,
|
|
||||||
strict: true,
|
|
||||||
};
|
|
||||||
return { method: 'check', apiName: 'locator.check', params };
|
|
||||||
}
|
|
||||||
case 'uncheck': {
|
|
||||||
const params: channels.FrameUncheckParams = {
|
|
||||||
selector,
|
|
||||||
strict: true,
|
|
||||||
};
|
|
||||||
return { method: 'uncheck', apiName: 'locator.uncheck', params };
|
|
||||||
}
|
|
||||||
case 'select': {
|
|
||||||
const params: channels.FrameSelectOptionParams = {
|
|
||||||
selector,
|
|
||||||
strict: true,
|
|
||||||
options: action.options.map(option => ({ value: option })),
|
|
||||||
};
|
|
||||||
return { method: 'selectOption', apiName: 'locator.selectOption', params };
|
|
||||||
}
|
|
||||||
case 'assertChecked': {
|
|
||||||
const params: channels.FrameExpectParams = {
|
|
||||||
selector: action.selector,
|
|
||||||
expression: 'to.be.checked',
|
|
||||||
isNot: !action.checked,
|
|
||||||
timeout: kDefaultTimeout,
|
|
||||||
};
|
|
||||||
return { method: 'expect', apiName: 'expect.toBeChecked', params };
|
|
||||||
}
|
|
||||||
case 'assertText': {
|
|
||||||
const params: channels.FrameExpectParams = {
|
|
||||||
selector,
|
|
||||||
expression: 'to.have.text',
|
|
||||||
expectedText: [],
|
|
||||||
isNot: false,
|
|
||||||
timeout: kDefaultTimeout,
|
|
||||||
};
|
|
||||||
return { method: 'expect', apiName: 'expect.toContainText', params };
|
|
||||||
}
|
|
||||||
case 'assertValue': {
|
|
||||||
const params: channels.FrameExpectParams = {
|
|
||||||
selector,
|
|
||||||
expression: 'to.have.value',
|
|
||||||
expectedValue: undefined,
|
|
||||||
isNot: false,
|
|
||||||
timeout: kDefaultTimeout,
|
|
||||||
};
|
|
||||||
return { method: 'expect', apiName: 'expect.toHaveValue', params };
|
|
||||||
}
|
|
||||||
case 'assertVisible': {
|
|
||||||
const params: channels.FrameExpectParams = {
|
|
||||||
selector,
|
|
||||||
expression: 'to.be.visible',
|
|
||||||
isNot: false,
|
|
||||||
timeout: kDefaultTimeout,
|
|
||||||
};
|
|
||||||
return { method: 'expect', apiName: 'expect.toBeVisible', params };
|
|
||||||
}
|
|
||||||
case 'assertSnapshot': {
|
|
||||||
const params: channels.FrameExpectParams = {
|
|
||||||
selector,
|
|
||||||
expression: 'to.match.snapshot',
|
|
||||||
expectedText: [],
|
|
||||||
isNot: false,
|
|
||||||
timeout: kDefaultTimeout,
|
|
||||||
};
|
|
||||||
return { method: 'expect', apiName: 'expect.toMatchAriaSnapshot', params };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function toKeyboardModifiers(modifiers: number): types.SmartKeyboardModifier[] {
|
|
||||||
const result: types.SmartKeyboardModifier[] = [];
|
|
||||||
if (modifiers & 1)
|
|
||||||
result.push('Alt');
|
|
||||||
if (modifiers & 2)
|
|
||||||
result.push('ControlOrMeta');
|
|
||||||
if (modifiers & 4)
|
|
||||||
result.push('ControlOrMeta');
|
|
||||||
if (modifiers & 8)
|
|
||||||
result.push('Shift');
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
@ -451,27 +451,16 @@ await page1.GotoAsync("about:blank?foo");`);
|
||||||
await recorder.waitForOutput('JavaScript', `await page.goto('${server.PREFIX}/page2.html');`);
|
await recorder.waitForOutput('JavaScript', `await page.goto('${server.PREFIX}/page2.html');`);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should --save-trace', async ({ runCLI }, testInfo) => {
|
|
||||||
const traceFileName = testInfo.outputPath('trace.zip');
|
|
||||||
const cli = runCLI([`--save-trace=${traceFileName}`], {
|
|
||||||
autoExitWhen: ' ',
|
|
||||||
});
|
|
||||||
await cli.waitForCleanExit();
|
|
||||||
expect(fs.existsSync(traceFileName)).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should save assets via SIGINT', async ({ runCLI, platform }, testInfo) => {
|
test('should save assets via SIGINT', async ({ runCLI, platform }, testInfo) => {
|
||||||
test.skip(platform === 'win32', 'SIGINT not supported on Windows');
|
test.skip(platform === 'win32', 'SIGINT not supported on Windows');
|
||||||
|
|
||||||
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-storage=${storageFileName}`, `--save-har=${harFileName}`]);
|
||||||
await cli.waitFor(`import { test, expect } from '@playwright/test'`);
|
await cli.waitFor(`import { test, expect } from '@playwright/test'`);
|
||||||
await cli.process.kill('SIGINT');
|
await 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(storageFileName)).toBeTruthy();
|
expect(fs.existsSync(storageFileName)).toBeTruthy();
|
||||||
expect(fs.existsSync(harFileName)).toBeTruthy();
|
expect(fs.existsSync(harFileName)).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue