mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
chore: delete recorder in traceviewer experiment (#34347)
This commit is contained in:
parent
8d39c44b69
commit
00bb17751b
@ -595,7 +595,6 @@ async function codegen(options: Options & { target: string, output?: string, tes
|
||||
device: options.device,
|
||||
saveStorage: options.saveStorage,
|
||||
mode: 'recording',
|
||||
codegenMode: process.env.PW_RECORDER_IS_TRACE_VIEWER ? 'trace-events' : 'actions',
|
||||
testIdAttributeName,
|
||||
outputFile: outputFile ? path.resolve(outputFile) : undefined,
|
||||
handleSIGINT: false,
|
||||
|
@ -970,7 +970,6 @@ scheme.BrowserContextPauseResult = tOptional(tObject({}));
|
||||
scheme.BrowserContextEnableRecorderParams = tObject({
|
||||
language: tOptional(tString),
|
||||
mode: tOptional(tEnum(['inspecting', 'recording'])),
|
||||
codegenMode: tOptional(tEnum(['actions', 'trace-events'])),
|
||||
pauseOnNextStatement: tOptional(tBoolean),
|
||||
testIdAttributeName: tOptional(tString),
|
||||
launchOptions: tOptional(tAny),
|
||||
|
@ -130,7 +130,7 @@ export abstract class BrowserContext extends SdkObject {
|
||||
|
||||
// When PWDEBUG=1, show inspector for each context.
|
||||
if (debugMode() === 'inspector')
|
||||
await Recorder.show('actions', this, RecorderApp.factory(this), { pauseOnNextStatement: true });
|
||||
await Recorder.show(this, RecorderApp.factory(this), { pauseOnNextStatement: true });
|
||||
|
||||
// When paused, show inspector.
|
||||
if (this._debugger.isPaused())
|
||||
|
@ -39,7 +39,6 @@ import type { Dialog } from '../dialog';
|
||||
import type { ConsoleMessage } from '../console';
|
||||
import { serializeError } from '../errors';
|
||||
import { ElementHandleDispatcher } from './elementHandlerDispatcher';
|
||||
import { RecorderInTraceViewer } from '../recorder/recorderInTraceViewer';
|
||||
import { RecorderApp } from '../recorder/recorderApp';
|
||||
import { WebSocketRouteDispatcher } from './webSocketRouteDispatcher';
|
||||
|
||||
@ -301,17 +300,7 @@ export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channel
|
||||
}
|
||||
|
||||
async enableRecorder(params: channels.BrowserContextEnableRecorderParams): Promise<void> {
|
||||
if (params.codegenMode === 'trace-events') {
|
||||
await this._context.tracing.start({
|
||||
name: 'trace',
|
||||
snapshots: true,
|
||||
screenshots: true,
|
||||
live: true,
|
||||
});
|
||||
await Recorder.show('trace-events', this._context, RecorderInTraceViewer.factory(this._context), params);
|
||||
} else {
|
||||
await Recorder.show('actions', this._context, RecorderApp.factory(this._context), params);
|
||||
}
|
||||
await Recorder.show(this._context, RecorderApp.factory(this._context), params);
|
||||
}
|
||||
|
||||
async pause(params: channels.BrowserContextPauseParams, metadata: CallMetadata) {
|
||||
|
@ -54,33 +54,33 @@ export class Recorder implements InstrumentationListener, IRecorder {
|
||||
static async showInspector(context: BrowserContext, params: channels.BrowserContextEnableRecorderParams, recorderAppFactory: IRecorderAppFactory) {
|
||||
if (isUnderTest())
|
||||
params.language = process.env.TEST_INSPECTOR_LANGUAGE;
|
||||
return await Recorder.show('actions', context, recorderAppFactory, params);
|
||||
return await Recorder.show(context, recorderAppFactory, params);
|
||||
}
|
||||
|
||||
static showInspectorNoReply(context: BrowserContext, recorderAppFactory: IRecorderAppFactory) {
|
||||
Recorder.showInspector(context, {}, recorderAppFactory).catch(() => {});
|
||||
}
|
||||
|
||||
static show(codegenMode: 'actions' | 'trace-events', context: BrowserContext, recorderAppFactory: IRecorderAppFactory, params: channels.BrowserContextEnableRecorderParams): Promise<Recorder> {
|
||||
static show(context: BrowserContext, recorderAppFactory: IRecorderAppFactory, params: channels.BrowserContextEnableRecorderParams): Promise<Recorder> {
|
||||
let recorderPromise = (context as any)[recorderSymbol] as Promise<Recorder>;
|
||||
if (!recorderPromise) {
|
||||
recorderPromise = Recorder._create(codegenMode, context, recorderAppFactory, params);
|
||||
recorderPromise = Recorder._create(context, recorderAppFactory, params);
|
||||
(context as any)[recorderSymbol] = recorderPromise;
|
||||
}
|
||||
return recorderPromise;
|
||||
}
|
||||
|
||||
private static async _create(codegenMode: 'actions' | 'trace-events', context: BrowserContext, recorderAppFactory: IRecorderAppFactory, params: channels.BrowserContextEnableRecorderParams = {}): Promise<Recorder> {
|
||||
const recorder = new Recorder(codegenMode, context, params);
|
||||
private static async _create(context: BrowserContext, recorderAppFactory: IRecorderAppFactory, params: channels.BrowserContextEnableRecorderParams = {}): Promise<Recorder> {
|
||||
const recorder = new Recorder(context, params);
|
||||
const recorderApp = await recorderAppFactory(recorder);
|
||||
await recorder._install(recorderApp);
|
||||
return recorder;
|
||||
}
|
||||
|
||||
constructor(codegenMode: 'actions' | 'trace-events', context: BrowserContext, params: channels.BrowserContextEnableRecorderParams) {
|
||||
constructor(context: BrowserContext, params: channels.BrowserContextEnableRecorderParams) {
|
||||
this._mode = params.mode || 'none';
|
||||
this.handleSIGINT = params.handleSIGINT;
|
||||
this._contextRecorder = new ContextRecorder(codegenMode, context, params, {});
|
||||
this._contextRecorder = new ContextRecorder(context, params, {});
|
||||
this._context = context;
|
||||
this._omitCallTracking = !!params.omitCallTracking;
|
||||
this._debugger = context.debugger();
|
||||
|
@ -10,6 +10,3 @@
|
||||
../../utils/**
|
||||
../../utilsBundle.ts
|
||||
../../zipBundle.ts
|
||||
|
||||
[recorderInTraceViewer.ts]
|
||||
../trace/viewer/traceViewer.ts
|
||||
|
@ -54,11 +54,9 @@ export class ContextRecorder extends EventEmitter {
|
||||
private _throttledOutputFile: ThrottledFile | null = null;
|
||||
private _orderedLanguages: LanguageGenerator[] = [];
|
||||
private _listeners: RegisteredListener[] = [];
|
||||
private _codegenMode: 'actions' | 'trace-events';
|
||||
|
||||
constructor(codegenMode: 'actions' | 'trace-events', context: BrowserContext, params: channels.BrowserContextEnableRecorderParams, delegate: ContextRecorderDelegate) {
|
||||
constructor(context: BrowserContext, params: channels.BrowserContextEnableRecorderParams, delegate: ContextRecorderDelegate) {
|
||||
super();
|
||||
this._codegenMode = codegenMode;
|
||||
this._context = context;
|
||||
this._params = params;
|
||||
this._delegate = delegate;
|
||||
@ -150,12 +148,6 @@ export class ContextRecorder extends EventEmitter {
|
||||
|
||||
setEnabled(enabled: boolean) {
|
||||
this._collection.setEnabled(enabled);
|
||||
if (this._codegenMode === 'trace-events') {
|
||||
if (enabled)
|
||||
this._context.tracing.startChunk({ name: 'trace', title: 'trace' }).catch(() => {});
|
||||
else
|
||||
this._context.tracing.stopChunk({ mode: 'discard' }).catch(() => {});
|
||||
}
|
||||
}
|
||||
|
||||
dispose() {
|
||||
|
@ -1,126 +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 path from 'path';
|
||||
import type { CallLog, ElementInfo, Mode, Source } from '@recorder/recorderTypes';
|
||||
import { EventEmitter } from 'events';
|
||||
import type { IRecorder, IRecorderApp, IRecorderAppFactory } from './recorderFrontend';
|
||||
import { installRootRedirect, openTraceViewerApp, startTraceViewerServer } from '../trace/viewer/traceViewer';
|
||||
import type { TraceViewerServerOptions } from '../trace/viewer/traceViewer';
|
||||
import type { BrowserContext } from '../browserContext';
|
||||
import type { HttpServer, Transport } from '../../utils/httpServer';
|
||||
import type { Page } from '../page';
|
||||
import { ManualPromise } from '../../utils/manualPromise';
|
||||
import type * as actions from '@recorder/actions';
|
||||
|
||||
export class RecorderInTraceViewer extends EventEmitter implements IRecorderApp {
|
||||
readonly wsEndpointForTest: string | undefined;
|
||||
private _transport: RecorderTransport;
|
||||
private _tracePage: Page;
|
||||
private _traceServer: HttpServer;
|
||||
|
||||
static factory(context: BrowserContext): IRecorderAppFactory {
|
||||
return async (recorder: IRecorder) => {
|
||||
const transport = new RecorderTransport();
|
||||
const trace = path.join(context._browser.options.tracesDir, 'trace');
|
||||
const { wsEndpointForTest, tracePage, traceServer } = await openApp(trace, { transport, headless: !context._browser.options.headful });
|
||||
return new RecorderInTraceViewer(transport, tracePage, traceServer, wsEndpointForTest);
|
||||
};
|
||||
}
|
||||
|
||||
constructor(transport: RecorderTransport, tracePage: Page, traceServer: HttpServer, wsEndpointForTest: string | undefined) {
|
||||
super();
|
||||
this._transport = transport;
|
||||
this._transport.eventSink.resolve(this);
|
||||
this._tracePage = tracePage;
|
||||
this._traceServer = traceServer;
|
||||
this.wsEndpointForTest = wsEndpointForTest;
|
||||
this._tracePage.once('close', () => {
|
||||
this.close();
|
||||
});
|
||||
}
|
||||
|
||||
async close(): Promise<void> {
|
||||
await this._tracePage.context().close({ reason: 'Recorder window closed' });
|
||||
await this._traceServer.stop();
|
||||
}
|
||||
|
||||
async setPaused(paused: boolean): Promise<void> {
|
||||
this._transport.deliverEvent('setPaused', { paused });
|
||||
}
|
||||
|
||||
async setMode(mode: Mode): Promise<void> {
|
||||
this._transport.deliverEvent('setMode', { mode });
|
||||
}
|
||||
|
||||
async setRunningFile(file: string | undefined): Promise<void> {
|
||||
this._transport.deliverEvent('setRunningFile', { file });
|
||||
}
|
||||
|
||||
async elementPicked(elementInfo: ElementInfo, userGesture?: boolean): Promise<void> {
|
||||
this._transport.deliverEvent('elementPicked', { elementInfo, userGesture });
|
||||
}
|
||||
|
||||
async updateCallLogs(callLogs: CallLog[]): Promise<void> {
|
||||
this._transport.deliverEvent('updateCallLogs', { callLogs });
|
||||
}
|
||||
|
||||
async setSources(sources: Source[]): Promise<void> {
|
||||
this._transport.deliverEvent('setSources', { sources });
|
||||
if (process.env.PWTEST_CLI_IS_UNDER_TEST && sources.length) {
|
||||
if ((process as any)._didSetSourcesForTest(sources[0].text))
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
|
||||
async setActions(actions: actions.ActionInContext[], sources: Source[]): Promise<void> {
|
||||
this._transport.deliverEvent('setActions', { actions, sources });
|
||||
}
|
||||
}
|
||||
|
||||
async function openApp(trace: string, options?: TraceViewerServerOptions & { headless?: boolean }): Promise<{ wsEndpointForTest: string | undefined, tracePage: Page, traceServer: HttpServer }> {
|
||||
const traceServer = await startTraceViewerServer(options);
|
||||
await installRootRedirect(traceServer, [trace], { ...options, webApp: 'recorder.html' });
|
||||
const page = await openTraceViewerApp(traceServer.urlPrefix('precise'), 'chromium', options);
|
||||
return { wsEndpointForTest: page.context()._browser.options.wsEndpoint, tracePage: page, traceServer };
|
||||
}
|
||||
|
||||
class RecorderTransport implements Transport {
|
||||
private _connected = new ManualPromise<void>();
|
||||
readonly eventSink = new ManualPromise<EventEmitter>();
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
onconnect() {
|
||||
this._connected.resolve();
|
||||
}
|
||||
|
||||
async dispatch(method: string, params: any): Promise<any> {
|
||||
const eventSink = await this.eventSink;
|
||||
eventSink.emit('event', { event: method, params });
|
||||
}
|
||||
|
||||
onclose() {
|
||||
}
|
||||
|
||||
deliverEvent(method: string, params: any) {
|
||||
this._connected.then(() => this.sendEvent?.(method, params));
|
||||
}
|
||||
|
||||
sendEvent?: (method: string, params: any) => void;
|
||||
close?: () => void;
|
||||
}
|
2
packages/protocol/src/channels.d.ts
vendored
2
packages/protocol/src/channels.d.ts
vendored
@ -1772,7 +1772,6 @@ export type BrowserContextPauseResult = void;
|
||||
export type BrowserContextEnableRecorderParams = {
|
||||
language?: string,
|
||||
mode?: 'inspecting' | 'recording',
|
||||
codegenMode?: 'actions' | 'trace-events',
|
||||
pauseOnNextStatement?: boolean,
|
||||
testIdAttributeName?: string,
|
||||
launchOptions?: any,
|
||||
@ -1786,7 +1785,6 @@ export type BrowserContextEnableRecorderParams = {
|
||||
export type BrowserContextEnableRecorderOptions = {
|
||||
language?: string,
|
||||
mode?: 'inspecting' | 'recording',
|
||||
codegenMode?: 'actions' | 'trace-events',
|
||||
pauseOnNextStatement?: boolean,
|
||||
testIdAttributeName?: string,
|
||||
launchOptions?: any,
|
||||
|
@ -1198,11 +1198,6 @@ BrowserContext:
|
||||
literals:
|
||||
- inspecting
|
||||
- recording
|
||||
codegenMode:
|
||||
type: enum?
|
||||
literals:
|
||||
- actions
|
||||
- trace-events
|
||||
pauseOnNextStatement: boolean?
|
||||
testIdAttributeName: string?
|
||||
launchOptions: json?
|
||||
|
@ -1,28 +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.
|
||||
-->
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" href="/playwright-logo.svg" type="image/svg+xml">
|
||||
<title>Playwright Recorder</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/recorder.tsx"></script>
|
||||
</body>
|
||||
</html>
|
@ -6,7 +6,3 @@ ui/
|
||||
|
||||
[sw-main.ts]
|
||||
sw/**
|
||||
|
||||
|
||||
[recorder.tsx]
|
||||
ui/recorder/**
|
||||
|
@ -1,41 +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 '@web/common.css';
|
||||
import { applyTheme } from '@web/theme';
|
||||
import '@web/third_party/vscode/codicon.css';
|
||||
import * as ReactDOM from 'react-dom/client';
|
||||
import { RecorderView } from './ui/recorder/recorderView';
|
||||
|
||||
(async () => {
|
||||
applyTheme();
|
||||
|
||||
if (window.location.protocol !== 'file:') {
|
||||
if (!navigator.serviceWorker)
|
||||
throw new Error(`Service workers are not supported.\nMake sure to serve the Recorder (${window.location}) via HTTPS or localhost.`);
|
||||
navigator.serviceWorker.register('sw.bundle.js');
|
||||
if (!navigator.serviceWorker.controller) {
|
||||
await new Promise<void>(f => {
|
||||
navigator.serviceWorker.oncontrollerchange = () => f();
|
||||
});
|
||||
}
|
||||
|
||||
// Keep SW running.
|
||||
setInterval(function() { fetch('ping'); }, 10000);
|
||||
}
|
||||
|
||||
ReactDOM.createRoot(document.querySelector('#root')!).render(<RecorderView />);
|
||||
})();
|
@ -1,5 +0,0 @@
|
||||
[*]
|
||||
@isomorphic/**
|
||||
@trace/**
|
||||
@web/**
|
||||
../**
|
@ -1,62 +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 actionTypes from '@recorder/actions';
|
||||
import { ListView } from '@web/components/listView';
|
||||
import * as React from 'react';
|
||||
import '../actionList.css';
|
||||
import { traceParamsForAction } from '@isomorphic/recorderUtils';
|
||||
import { asLocator } from '@isomorphic/locatorGenerators';
|
||||
import type { Language } from '@isomorphic/locatorGenerators';
|
||||
|
||||
const ActionList = ListView<actionTypes.ActionInContext>;
|
||||
|
||||
export const ActionListView: React.FC<{
|
||||
sdkLanguage: Language,
|
||||
actions: actionTypes.ActionInContext[],
|
||||
selectedAction: actionTypes.ActionInContext | undefined,
|
||||
onSelectedAction: (action: actionTypes.ActionInContext | undefined) => void,
|
||||
}> = ({
|
||||
sdkLanguage,
|
||||
actions,
|
||||
selectedAction,
|
||||
onSelectedAction,
|
||||
}) => {
|
||||
const render = React.useCallback((action: actionTypes.ActionInContext) => {
|
||||
return renderAction(sdkLanguage, action);
|
||||
}, [sdkLanguage]);
|
||||
return <div className='vbox'>
|
||||
<ActionList
|
||||
name='actions'
|
||||
items={actions}
|
||||
selectedItem={selectedAction}
|
||||
onSelected={onSelectedAction}
|
||||
render={render} />
|
||||
</div>;
|
||||
};
|
||||
|
||||
export const renderAction = (sdkLanguage: Language, action: actionTypes.ActionInContext) => {
|
||||
const { method, apiName, params } = traceParamsForAction(action);
|
||||
const locator = params.selector ? asLocator(sdkLanguage || 'javascript', params.selector) : undefined;
|
||||
|
||||
return <>
|
||||
<div className='action-title' title={apiName}>
|
||||
<span>{apiName}</span>
|
||||
{locator && <div className='action-selector' title={locator}>{locator}</div>}
|
||||
{method === 'goto' && params.url && <div className='action-url' title={params.url}>{params.url}</div>}
|
||||
</div>
|
||||
</>;
|
||||
};
|
@ -1,118 +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 actionTypes from '@recorder/actions';
|
||||
import type { Mode, Source } from '@recorder/recorderTypes';
|
||||
import * as React from 'react';
|
||||
|
||||
export const BackendContext = React.createContext<Backend | undefined>(undefined);
|
||||
|
||||
export const BackendProvider: React.FunctionComponent<React.PropsWithChildren<{
|
||||
guid: string,
|
||||
}>> = ({ guid, children }) => {
|
||||
const [connection, setConnection] = React.useState<Connection | undefined>(undefined);
|
||||
const [mode, setMode] = React.useState<Mode>('none');
|
||||
const [actions, setActions] = React.useState<{ actions: actionTypes.ActionInContext[], sources: Source[] }>({ actions: [], sources: [] });
|
||||
const callbacks = React.useRef({ setMode, setActions });
|
||||
|
||||
React.useEffect(() => {
|
||||
const wsURL = new URL(`../${guid}`, window.location.toString());
|
||||
wsURL.protocol = (window.location.protocol === 'https:' ? 'wss:' : 'ws:');
|
||||
const webSocket = new WebSocket(wsURL.toString());
|
||||
setConnection(new Connection(webSocket, callbacks.current));
|
||||
return () => {
|
||||
webSocket.close();
|
||||
};
|
||||
}, [guid]);
|
||||
|
||||
const backend = React.useMemo(() => {
|
||||
return connection ? { mode, actions: actions.actions, sources: actions.sources, connection } : undefined;
|
||||
}, [actions, mode, connection]);
|
||||
|
||||
return <BackendContext.Provider value={backend}>
|
||||
{children}
|
||||
</BackendContext.Provider>;
|
||||
};
|
||||
|
||||
export type Backend = {
|
||||
actions: actionTypes.ActionInContext[],
|
||||
sources: Source[],
|
||||
connection: Connection,
|
||||
};
|
||||
|
||||
type ConnectionCallbacks = {
|
||||
setMode: (mode: Mode) => void;
|
||||
setActions: (data: { actions: actionTypes.ActionInContext[], sources: Source[] }) => void;
|
||||
};
|
||||
|
||||
class Connection {
|
||||
private _lastId = 0;
|
||||
private _webSocket: WebSocket;
|
||||
private _callbacks = new Map<number, { resolve: (arg: any) => void, reject: (arg: Error) => void }>();
|
||||
private _options: ConnectionCallbacks;
|
||||
|
||||
constructor(webSocket: WebSocket, options: ConnectionCallbacks) {
|
||||
this._webSocket = webSocket;
|
||||
this._callbacks = new Map();
|
||||
this._options = options;
|
||||
|
||||
this._webSocket.addEventListener('message', event => {
|
||||
const message = JSON.parse(event.data);
|
||||
const { id, result, error, method, params } = message;
|
||||
if (id) {
|
||||
const callback = this._callbacks.get(id);
|
||||
if (!callback)
|
||||
return;
|
||||
this._callbacks.delete(id);
|
||||
if (error)
|
||||
callback.reject(new Error(error));
|
||||
else
|
||||
callback.resolve(result);
|
||||
} else {
|
||||
this._dispatchEvent(method, params);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setMode(mode: Mode) {
|
||||
this._sendMessageNoReply('setMode', { mode });
|
||||
}
|
||||
|
||||
private async _sendMessage(method: string, params?: any): Promise<any> {
|
||||
const id = ++this._lastId;
|
||||
const message = { id, method, params };
|
||||
this._webSocket.send(JSON.stringify(message));
|
||||
return new Promise((resolve, reject) => {
|
||||
this._callbacks.set(id, { resolve, reject });
|
||||
});
|
||||
}
|
||||
|
||||
private _sendMessageNoReply(method: string, params?: any) {
|
||||
this._sendMessage(method, params).catch(() => { });
|
||||
}
|
||||
|
||||
private _dispatchEvent(method: string, params?: any) {
|
||||
if (method === 'setMode') {
|
||||
const { mode } = params as { mode: Mode };
|
||||
this._options.setMode(mode);
|
||||
}
|
||||
if (method === 'setActions') {
|
||||
const { actions, sources } = params as { actions: actionTypes.ActionInContext[], sources: Source[] };
|
||||
this._options.setActions({ actions: actions.filter(a => a.action.name !== 'openPage' && a.action.name !== 'closePage'), sources });
|
||||
(window as any).playwrightSourcesEchoForTest = sources;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,71 +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 { sha1 } from '@web/uiUtils';
|
||||
import * as React from 'react';
|
||||
import type { ContextEntry } from '../../types/entries';
|
||||
import { MultiTraceModel } from '../modelUtil';
|
||||
|
||||
export const ModelContext = React.createContext<MultiTraceModel | undefined>(undefined);
|
||||
|
||||
export const ModelProvider: React.FunctionComponent<React.PropsWithChildren<{
|
||||
trace: string,
|
||||
}>> = ({ trace, children }) => {
|
||||
const [model, setModel] = React.useState<{ model: MultiTraceModel, sha1: string } | undefined>();
|
||||
const [counter, setCounter] = React.useState(0);
|
||||
const pollTimer = React.useRef<NodeJS.Timeout | null>(null);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (pollTimer.current)
|
||||
clearTimeout(pollTimer.current);
|
||||
|
||||
// Start polling running test.
|
||||
pollTimer.current = setTimeout(async () => {
|
||||
try {
|
||||
const result = await loadSingleTraceFile(trace);
|
||||
if (result.sha1 !== model?.sha1)
|
||||
setModel(result);
|
||||
} catch {
|
||||
setModel(undefined);
|
||||
} finally {
|
||||
setCounter(counter + 1);
|
||||
}
|
||||
}, 500);
|
||||
return () => {
|
||||
if (pollTimer.current)
|
||||
clearTimeout(pollTimer.current);
|
||||
};
|
||||
}, [counter, model, trace]);
|
||||
|
||||
return <ModelContext.Provider value={model?.model}>
|
||||
{children}
|
||||
</ModelContext.Provider>;
|
||||
};
|
||||
|
||||
async function loadSingleTraceFile(url: string): Promise<{ model: MultiTraceModel, sha1: string }> {
|
||||
const params = new URLSearchParams();
|
||||
params.set('trace', url);
|
||||
params.set('limit', '1');
|
||||
const response = await fetch(`contexts?${params.toString()}`);
|
||||
const contextEntries = await response.json() as ContextEntry[];
|
||||
|
||||
const tokens: string[] = [];
|
||||
for (const entry of contextEntries) {
|
||||
entry.actions.forEach(a => tokens.push(a.type + '@' + a.startTime + '-' + a.endTime));
|
||||
entry.events.forEach(e => tokens.push(e.type + '@' + e.time));
|
||||
}
|
||||
return { model: new MultiTraceModel(contextEntries), sha1: await sha1(tokens.join('|')) };
|
||||
}
|
@ -1,15 +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.
|
||||
*/
|
@ -1,299 +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 actionTypes from '@recorder/actions';
|
||||
import { SourceChooser } from '@web/components/sourceChooser';
|
||||
import { SplitView } from '@web/components/splitView';
|
||||
import type { TabbedPaneTabModel } from '@web/components/tabbedPane';
|
||||
import { TabbedPane } from '@web/components/tabbedPane';
|
||||
import { Toolbar } from '@web/components/toolbar';
|
||||
import { ToolbarButton, ToolbarSeparator } from '@web/components/toolbarButton';
|
||||
import { copy, useSetting } from '@web/uiUtils';
|
||||
import * as React from 'react';
|
||||
import { ConsoleTab, useConsoleTabModel } from '../consoleTab';
|
||||
import type { Boundaries } from '../geometry';
|
||||
import { InspectorTab } from '../inspectorTab';
|
||||
import type * as modelUtil from '../modelUtil';
|
||||
import type { SourceLocation } from '../modelUtil';
|
||||
import { NetworkTab, useNetworkTabModel } from '../networkTab';
|
||||
import { collectSnapshots, extendSnapshot, SnapshotView } from '../snapshotTab';
|
||||
import { SourceTab } from '../sourceTab';
|
||||
import { ModelContext, ModelProvider } from './modelContext';
|
||||
import './recorderView.css';
|
||||
import { ActionListView } from './actionListView';
|
||||
import { BackendContext, BackendProvider } from './backendContext';
|
||||
import type { Language } from '@isomorphic/locatorGenerators';
|
||||
import { SettingsToolbarButton } from '../settingsToolbarButton';
|
||||
import type { HighlightedElement } from '../snapshotTab';
|
||||
|
||||
export const RecorderView: React.FunctionComponent = () => {
|
||||
const searchParams = new URLSearchParams(window.location.search);
|
||||
const guid = searchParams.get('ws')!;
|
||||
const trace = searchParams.get('trace') + '.json';
|
||||
|
||||
return <BackendProvider guid={guid}>
|
||||
<ModelProvider trace={trace}>
|
||||
<Workbench />
|
||||
</ModelProvider>
|
||||
</BackendProvider>;
|
||||
};
|
||||
|
||||
export const Workbench: React.FunctionComponent = () => {
|
||||
const backend = React.useContext(BackendContext);
|
||||
const model = React.useContext(ModelContext);
|
||||
const [fileId, setFileId] = React.useState<string | undefined>();
|
||||
const [selectedStartTime, setSelectedStartTime] = React.useState<number | undefined>(undefined);
|
||||
const [isInspecting, setIsInspecting] = React.useState(false);
|
||||
const [highlightedElementInProperties, setHighlightedElementInProperties] = React.useState<HighlightedElement>({ lastEdited: 'none' });
|
||||
const [highlightedElementInTrace, setHighlightedElementInTrace] = React.useState<HighlightedElement>({ lastEdited: 'none' });
|
||||
const [traceCallId, setTraceCallId] = React.useState<string | undefined>();
|
||||
|
||||
const setSelectedAction = React.useCallback((action: actionTypes.ActionInContext | undefined) => {
|
||||
setSelectedStartTime(action?.startTime);
|
||||
}, []);
|
||||
|
||||
const selectedAction = React.useMemo(() => {
|
||||
return backend?.actions.find(a => a.startTime === selectedStartTime);
|
||||
}, [backend?.actions, selectedStartTime]);
|
||||
|
||||
React.useEffect(() => {
|
||||
const callId = model?.actions.find(a => a.endTime && a.endTime === selectedAction?.endTime)?.callId;
|
||||
if (callId)
|
||||
setTraceCallId(callId);
|
||||
}, [model, selectedAction]);
|
||||
|
||||
const source = React.useMemo(() => backend?.sources.find(s => s.id === fileId) || backend?.sources[0], [backend?.sources, fileId]);
|
||||
const sourceLocation = React.useMemo(() => {
|
||||
if (!source)
|
||||
return undefined;
|
||||
const sourceLocation: SourceLocation = {
|
||||
file: '',
|
||||
line: 0,
|
||||
column: 0,
|
||||
source: {
|
||||
errors: [],
|
||||
content: source.text
|
||||
}
|
||||
};
|
||||
return sourceLocation;
|
||||
}, [source]);
|
||||
|
||||
const sdkLanguage: Language = source?.language || 'javascript';
|
||||
|
||||
const { boundaries } = React.useMemo(() => {
|
||||
const boundaries = { minimum: model?.startTime || 0, maximum: model?.endTime || 30000 };
|
||||
if (boundaries.minimum > boundaries.maximum) {
|
||||
boundaries.minimum = 0;
|
||||
boundaries.maximum = 30000;
|
||||
}
|
||||
// Leave some nice free space on the right hand side.
|
||||
boundaries.maximum += (boundaries.maximum - boundaries.minimum) / 20;
|
||||
return { boundaries };
|
||||
}, [model]);
|
||||
|
||||
const elementPickedInTrace = React.useCallback((element: HighlightedElement) => {
|
||||
setHighlightedElementInProperties(element);
|
||||
setHighlightedElementInTrace({ lastEdited: 'none' });
|
||||
setIsInspecting(false);
|
||||
}, []);
|
||||
|
||||
const elementTypedInProperties = React.useCallback((element: HighlightedElement) => {
|
||||
setHighlightedElementInTrace(element);
|
||||
setHighlightedElementInProperties(element);
|
||||
}, []);
|
||||
|
||||
const actionList = <ActionListView
|
||||
sdkLanguage={sdkLanguage}
|
||||
actions={backend?.actions || []}
|
||||
selectedAction={selectedAction}
|
||||
onSelectedAction={setSelectedAction}
|
||||
/>;
|
||||
|
||||
const actionsTab: TabbedPaneTabModel = {
|
||||
id: 'actions',
|
||||
title: 'Actions',
|
||||
component: actionList,
|
||||
};
|
||||
|
||||
const toolbar = <Toolbar sidebarBackground>
|
||||
<div style={{ width: 4 }}></div>
|
||||
<ToolbarButton icon='inspect' title='Pick locator' toggled={isInspecting} onClick={() => {
|
||||
setIsInspecting(!isInspecting);
|
||||
}} />
|
||||
<ToolbarButton icon='eye' title='Assert visibility' onClick={() => {
|
||||
}} />
|
||||
<ToolbarButton icon='whole-word' title='Assert text' onClick={() => {
|
||||
}} />
|
||||
<ToolbarButton icon='symbol-constant' title='Assert value' onClick={() => {
|
||||
}} />
|
||||
<ToolbarSeparator />
|
||||
<ToolbarButton icon='files' title='Copy' disabled={!source || !source.text} onClick={() => {
|
||||
if (source?.text)
|
||||
copy(source.text);
|
||||
}}></ToolbarButton>
|
||||
<div style={{ flex: 'auto' }}></div>
|
||||
<div>Target:</div>
|
||||
<SourceChooser fileId={fileId} sources={backend?.sources || []} setFileId={fileId => {
|
||||
setFileId(fileId);
|
||||
}} />
|
||||
<SettingsToolbarButton />
|
||||
</Toolbar>;
|
||||
|
||||
const sidebarTabbedPane = <TabbedPane tabs={[actionsTab]} />;
|
||||
const traceView = <TraceView
|
||||
sdkLanguage={sdkLanguage}
|
||||
callId={traceCallId}
|
||||
isInspecting={isInspecting}
|
||||
setIsInspecting={setIsInspecting}
|
||||
highlightedElement={highlightedElementInTrace}
|
||||
setHighlightedElement={elementPickedInTrace} />;
|
||||
const propertiesView = <PropertiesView
|
||||
sdkLanguage={sdkLanguage}
|
||||
boundaries={boundaries}
|
||||
setIsInspecting={setIsInspecting}
|
||||
highlightedElement={highlightedElementInProperties}
|
||||
setHighlightedElement={elementTypedInProperties}
|
||||
sourceLocation={sourceLocation} />;
|
||||
|
||||
return <div className='vbox workbench'>
|
||||
<SplitView
|
||||
sidebarSize={250}
|
||||
orientation={'horizontal'}
|
||||
settingName='recorderActionListSidebar'
|
||||
sidebarIsFirst
|
||||
main={<SplitView
|
||||
sidebarSize={250}
|
||||
orientation='vertical'
|
||||
settingName='recorderPropertiesSidebar'
|
||||
main={<div className='vbox'>
|
||||
{toolbar}
|
||||
{traceView}
|
||||
</div>}
|
||||
sidebar={propertiesView}
|
||||
/>}
|
||||
sidebar={sidebarTabbedPane}
|
||||
/>
|
||||
</div>;
|
||||
};
|
||||
|
||||
const PropertiesView: React.FunctionComponent<{
|
||||
sdkLanguage: Language,
|
||||
boundaries: Boundaries,
|
||||
setIsInspecting: (value: boolean) => void,
|
||||
highlightedElement: HighlightedElement,
|
||||
setHighlightedElement: (element: HighlightedElement) => void,
|
||||
sourceLocation: modelUtil.SourceLocation | undefined,
|
||||
}> = ({
|
||||
sdkLanguage,
|
||||
boundaries,
|
||||
setIsInspecting,
|
||||
highlightedElement,
|
||||
setHighlightedElement,
|
||||
sourceLocation,
|
||||
}) => {
|
||||
const model = React.useContext(ModelContext);
|
||||
const consoleModel = useConsoleTabModel(model, boundaries);
|
||||
const networkModel = useNetworkTabModel(model, boundaries);
|
||||
const sourceModel = React.useRef(new Map<string, modelUtil.SourceModel>());
|
||||
const [selectedPropertiesTab, setSelectedPropertiesTab] = useSetting<string>('recorderPropertiesTab', 'source');
|
||||
|
||||
const inspectorTab: TabbedPaneTabModel = {
|
||||
id: 'inspector',
|
||||
title: 'Locator',
|
||||
render: () => <InspectorTab
|
||||
sdkLanguage={sdkLanguage}
|
||||
setIsInspecting={setIsInspecting}
|
||||
highlightedElement={highlightedElement}
|
||||
setHighlightedElement={setHighlightedElement} />,
|
||||
};
|
||||
|
||||
const sourceTab: TabbedPaneTabModel = {
|
||||
id: 'source',
|
||||
title: 'Source',
|
||||
render: () => <SourceTab
|
||||
sources={sourceModel.current}
|
||||
stackFrameLocation={'right'}
|
||||
fallbackLocation={sourceLocation}
|
||||
/>
|
||||
};
|
||||
const consoleTab: TabbedPaneTabModel = {
|
||||
id: 'console',
|
||||
title: 'Console',
|
||||
count: consoleModel.entries.length,
|
||||
render: () => <ConsoleTab boundaries={boundaries} consoleModel={consoleModel} />
|
||||
};
|
||||
const networkTab: TabbedPaneTabModel = {
|
||||
id: 'network',
|
||||
title: 'Network',
|
||||
count: networkModel.resources.length,
|
||||
render: () => <NetworkTab boundaries={boundaries} networkModel={networkModel} sdkLanguage={sdkLanguage} />
|
||||
};
|
||||
|
||||
const tabs: TabbedPaneTabModel[] = [
|
||||
sourceTab,
|
||||
inspectorTab,
|
||||
consoleTab,
|
||||
networkTab,
|
||||
];
|
||||
|
||||
return <TabbedPane
|
||||
tabs={tabs}
|
||||
selectedTab={selectedPropertiesTab}
|
||||
setSelectedTab={setSelectedPropertiesTab}
|
||||
/>;
|
||||
};
|
||||
|
||||
const TraceView: React.FunctionComponent<{
|
||||
sdkLanguage: Language,
|
||||
callId: string | undefined,
|
||||
isInspecting: boolean;
|
||||
setIsInspecting: (value: boolean) => void;
|
||||
highlightedElement: HighlightedElement;
|
||||
setHighlightedElement: (element: HighlightedElement) => void;
|
||||
}> = ({
|
||||
sdkLanguage,
|
||||
callId,
|
||||
isInspecting,
|
||||
setIsInspecting,
|
||||
highlightedElement,
|
||||
setHighlightedElement,
|
||||
}) => {
|
||||
const model = React.useContext(ModelContext);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const [shouldPopulateCanvasFromScreenshot, _] = useSetting('shouldPopulateCanvasFromScreenshot', false);
|
||||
|
||||
const action = React.useMemo(() => {
|
||||
return model?.actions.find(a => a.callId === callId);
|
||||
}, [model, callId]);
|
||||
|
||||
const snapshot = React.useMemo(() => {
|
||||
const snapshot = collectSnapshots(action);
|
||||
return snapshot.action || snapshot.after || snapshot.before;
|
||||
}, [action]);
|
||||
const snapshotUrls = React.useMemo(() => {
|
||||
return snapshot ? extendSnapshot(snapshot, shouldPopulateCanvasFromScreenshot) : undefined;
|
||||
}, [snapshot, shouldPopulateCanvasFromScreenshot]);
|
||||
|
||||
return <SnapshotView
|
||||
sdkLanguage={sdkLanguage}
|
||||
testIdAttributeName='data-testid'
|
||||
isInspecting={isInspecting}
|
||||
setIsInspecting={setIsInspecting}
|
||||
highlightedElement={highlightedElement}
|
||||
setHighlightedElement={setHighlightedElement}
|
||||
snapshotUrls={snapshotUrls} />;
|
||||
};
|
@ -46,7 +46,6 @@ export default defineConfig({
|
||||
input: {
|
||||
index: path.resolve(__dirname, 'index.html'),
|
||||
uiMode: path.resolve(__dirname, 'uiMode.html'),
|
||||
recorder: path.resolve(__dirname, 'recorder.html'),
|
||||
snapshot: path.resolve(__dirname, 'snapshot.html'),
|
||||
},
|
||||
output: {
|
||||
|
@ -21,7 +21,6 @@ import * as playwrightLibrary from 'playwright-core';
|
||||
|
||||
export type TestModeWorkerOptions = {
|
||||
mode: TestModeName;
|
||||
codegenMode: 'trace-events' | 'actions';
|
||||
};
|
||||
|
||||
export type TestModeTestFixtures = {
|
||||
@ -49,7 +48,6 @@ export const testModeTest = test.extend<TestModeTestFixtures, TestModeWorkerOpti
|
||||
await run(playwright);
|
||||
await testMode.teardown();
|
||||
}, { scope: 'worker' }],
|
||||
codegenMode: ['actions', { scope: 'worker', option: true }],
|
||||
|
||||
toImplInWorkerScope: [async ({ playwright }, use) => {
|
||||
await use((playwright as any)._toImpl);
|
||||
|
@ -19,7 +19,6 @@ import type { ConsoleMessage } from 'playwright';
|
||||
|
||||
test.describe('cli codegen', () => {
|
||||
test.skip(({ mode }) => mode !== 'default');
|
||||
test.skip(({ trace, codegenMode }) => trace === 'on' && codegenMode === 'trace-events');
|
||||
|
||||
test('should click', async ({ openRecorder }) => {
|
||||
const { page, recorder } = await openRecorder();
|
||||
@ -413,7 +412,7 @@ await page.GetByRole(AriaRole.Textbox).PressAsync("Shift+Enter");`);
|
||||
expect(messages[0].text()).toBe('press');
|
||||
});
|
||||
|
||||
test('should update selected element after pressing Tab', async ({ openRecorder, browserName, codegenMode }) => {
|
||||
test('should update selected element after pressing Tab', async ({ openRecorder }) => {
|
||||
const { page, recorder } = await openRecorder();
|
||||
|
||||
await recorder.setContentAndWait(`
|
||||
|
@ -20,7 +20,6 @@ import fs from 'fs';
|
||||
|
||||
test.describe('cli codegen', () => {
|
||||
test.skip(({ mode }) => mode !== 'default');
|
||||
test.skip(({ trace, codegenMode }) => trace === 'on' && codegenMode === 'trace-events');
|
||||
|
||||
test('should contain open page', async ({ openRecorder }) => {
|
||||
const { recorder } = await openRecorder();
|
||||
@ -310,8 +309,7 @@ await page.GetByRole(AriaRole.Button, new() { Name = "click me" }).ClickAsync();
|
||||
}
|
||||
});
|
||||
|
||||
test('should record open in a new tab with url', async ({ openRecorder, browserName, codegenMode }) => {
|
||||
test.skip(codegenMode === 'trace-events');
|
||||
test('should record open in a new tab with url', async ({ openRecorder, browserName }) => {
|
||||
const { page, recorder } = await openRecorder();
|
||||
await recorder.setContentAndWait(`<a href="about:blank?foo">link</a>`);
|
||||
|
||||
@ -453,8 +451,7 @@ await page1.GotoAsync("about:blank?foo");`);
|
||||
await recorder.waitForOutput('JavaScript', `await page.goto('${server.PREFIX}/page2.html');`);
|
||||
});
|
||||
|
||||
test('should --save-trace', async ({ runCLI, codegenMode }, testInfo) => {
|
||||
test.skip(codegenMode === 'trace-events');
|
||||
test('should --save-trace', async ({ runCLI }, testInfo) => {
|
||||
const traceFileName = testInfo.outputPath('trace.zip');
|
||||
const cli = runCLI([`--save-trace=${traceFileName}`], {
|
||||
autoExitWhen: ' ',
|
||||
@ -463,8 +460,7 @@ await page1.GotoAsync("about:blank?foo");`);
|
||||
expect(fs.existsSync(traceFileName)).toBeTruthy();
|
||||
});
|
||||
|
||||
test('should save assets via SIGINT', async ({ runCLI, platform, codegenMode }, testInfo) => {
|
||||
test.skip(codegenMode === 'trace-events');
|
||||
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');
|
||||
|
@ -21,7 +21,6 @@ import type { Page } from '@playwright/test';
|
||||
|
||||
test.describe('cli codegen', () => {
|
||||
test.skip(({ mode }) => mode !== 'default');
|
||||
test.skip(({ trace, codegenMode }) => trace === 'on' && codegenMode === 'trace-events');
|
||||
|
||||
test('should click locator.first', async ({ openRecorder }) => {
|
||||
const { page, recorder } = await openRecorder();
|
||||
|
@ -19,7 +19,6 @@ import { roundBox } from '../../page/pageTest';
|
||||
|
||||
test.describe(() => {
|
||||
test.skip(({ mode }) => mode !== 'default');
|
||||
test.skip(({ trace, codegenMode }) => trace === 'on' && codegenMode === 'trace-events');
|
||||
|
||||
test('should generate aria snapshot', async ({ openRecorder }) => {
|
||||
const { recorder } = await openRecorder();
|
||||
|
@ -19,7 +19,6 @@ import { roundBox } from '../../page/pageTest';
|
||||
|
||||
test.describe(() => {
|
||||
test.skip(({ mode }) => mode !== 'default');
|
||||
test.skip(({ trace, codegenMode }) => trace === 'on' && codegenMode === 'trace-events');
|
||||
|
||||
test('should inspect locator', async ({ openRecorder }) => {
|
||||
const { recorder } = await openRecorder();
|
||||
|
@ -67,7 +67,7 @@ export const test = contextTest.extend<CLITestArgs>({
|
||||
});
|
||||
},
|
||||
|
||||
runCLI: async ({ childProcess, browserName, channel, headless, mode, launchOptions, codegenMode }, run, testInfo) => {
|
||||
runCLI: async ({ childProcess, browserName, channel, headless, mode, launchOptions }, run, testInfo) => {
|
||||
testInfo.skip(mode.startsWith('service'));
|
||||
|
||||
await run((cliArgs, { autoExitWhen } = {}) => {
|
||||
@ -78,17 +78,15 @@ export const test = contextTest.extend<CLITestArgs>({
|
||||
args: cliArgs,
|
||||
executablePath: launchOptions.executablePath,
|
||||
autoExitWhen,
|
||||
codegenMode
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
openRecorder: async ({ context, recorderPageGetter, codegenMode }, run) => {
|
||||
openRecorder: async ({ context, recorderPageGetter }, run) => {
|
||||
await run(async (options?: { testIdAttributeName?: string }) => {
|
||||
await (context as any)._enableRecorder({
|
||||
language: 'javascript',
|
||||
mode: 'recording',
|
||||
codegenMode,
|
||||
...options
|
||||
});
|
||||
const page = await context.newPage();
|
||||
@ -235,7 +233,7 @@ export class Recorder {
|
||||
class CLIMock {
|
||||
process: TestChildProcess;
|
||||
|
||||
constructor(childProcess: CommonFixtures['childProcess'], options: { browserName: string, channel: string | undefined, headless: boolean | undefined, args: string[], executablePath: string | undefined, autoExitWhen: string | undefined, codegenMode?: 'trace-events' | 'actions'}) {
|
||||
constructor(childProcess: CommonFixtures['childProcess'], options: { browserName: string, channel: string | undefined, headless: boolean | undefined, args: string[], executablePath: string | undefined, autoExitWhen: string | undefined}) {
|
||||
const nodeArgs = [
|
||||
'node',
|
||||
path.join(__dirname, '..', '..', '..', 'packages', 'playwright-core', 'cli.js'),
|
||||
@ -248,7 +246,6 @@ class CLIMock {
|
||||
this.process = childProcess({
|
||||
command: nodeArgs,
|
||||
env: {
|
||||
PW_RECORDER_IS_TRACE_VIEWER: options.codegenMode === 'trace-events' ? '1' : undefined,
|
||||
PWTEST_CLI_AUTO_EXIT_WHEN: options.autoExitWhen,
|
||||
PWTEST_CLI_IS_UNDER_TEST: '1',
|
||||
PWTEST_CLI_HEADLESS: options.headless ? '1' : undefined,
|
||||
|
@ -147,18 +147,6 @@ for (const browserName of browserNames) {
|
||||
testDir: path.join(testDir, 'page'),
|
||||
...projectTemplate,
|
||||
});
|
||||
|
||||
// TODO: figure out reporting to flakiness dashboard (Problem: they get merged, we want to keep them separate)
|
||||
// config.projects.push({
|
||||
// name: `${browserName}-codegen-mode-trace`,
|
||||
// testDir: path.join(testDir, 'library'),
|
||||
// testMatch: '**/cli-codegen-*.spec.ts',
|
||||
// ...projectTemplate,
|
||||
// use: {
|
||||
// ...projectTemplate.use,
|
||||
// codegenMode: 'trace-events',
|
||||
// }
|
||||
// });
|
||||
}
|
||||
|
||||
export default config;
|
||||
|
Loading…
x
Reference in New Issue
Block a user