2020-12-28 23:50:12 +01:00
|
|
|
/**
|
|
|
|
|
* 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.
|
|
|
|
|
*/
|
|
|
|
|
|
2021-01-25 04:21:19 +01:00
|
|
|
import type { BrowserContextOptions, LaunchOptions } from '../../../..';
|
|
|
|
|
import { Frame } from '../../frames';
|
|
|
|
|
import { LanguageGenerator } from './language';
|
2020-12-28 23:50:12 +01:00
|
|
|
import { Action, Signal } from './recorderActions';
|
2021-01-27 22:19:36 +01:00
|
|
|
import { describeFrame } from './utils';
|
2020-12-28 23:50:12 +01:00
|
|
|
|
|
|
|
|
export type ActionInContext = {
|
|
|
|
|
pageAlias: string;
|
2021-01-27 22:19:36 +01:00
|
|
|
frameName?: string;
|
|
|
|
|
frameUrl: string;
|
|
|
|
|
isMainFrame: boolean;
|
2020-12-28 23:50:12 +01:00
|
|
|
action: Action;
|
|
|
|
|
committed?: boolean;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface CodeGeneratorOutput {
|
|
|
|
|
printLn(text: string): void;
|
|
|
|
|
popLn(text: string): void;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export class CodeGenerator {
|
2021-01-28 02:02:09 +01:00
|
|
|
private _currentAction: ActionInContext | null = null;
|
|
|
|
|
private _lastAction: ActionInContext | null = null;
|
2020-12-28 23:50:12 +01:00
|
|
|
private _lastActionText: string | undefined;
|
|
|
|
|
private _languageGenerator: LanguageGenerator;
|
|
|
|
|
private _output: CodeGeneratorOutput;
|
2021-01-28 02:02:09 +01:00
|
|
|
private _headerText = '';
|
2021-01-25 23:49:26 +01:00
|
|
|
private _footerText = '';
|
2020-12-28 23:50:12 +01:00
|
|
|
|
2021-01-25 23:49:26 +01:00
|
|
|
constructor(browserName: string, generateHeaders: boolean, launchOptions: LaunchOptions, contextOptions: BrowserContextOptions, output: CodeGeneratorOutput, languageGenerator: LanguageGenerator, deviceName: string | undefined, saveStorage: string | undefined) {
|
2020-12-28 23:50:12 +01:00
|
|
|
this._output = output;
|
|
|
|
|
this._languageGenerator = languageGenerator;
|
|
|
|
|
|
|
|
|
|
launchOptions = { headless: false, ...launchOptions };
|
2021-01-25 23:49:26 +01:00
|
|
|
if (generateHeaders) {
|
2021-01-28 02:02:09 +01:00
|
|
|
this._headerText = this._languageGenerator.generateHeader(browserName, launchOptions, contextOptions, deviceName);
|
2021-01-25 23:49:26 +01:00
|
|
|
this._footerText = '\n' + this._languageGenerator.generateFooter(saveStorage);
|
2021-01-28 02:02:09 +01:00
|
|
|
}
|
|
|
|
|
this.restart();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
restart() {
|
|
|
|
|
this._currentAction = null;
|
|
|
|
|
this._lastAction = null;
|
|
|
|
|
if (this._headerText) {
|
|
|
|
|
this._output.printLn(this._headerText);
|
2021-01-25 23:49:26 +01:00
|
|
|
this._output.printLn(this._footerText);
|
|
|
|
|
}
|
2020-12-28 23:50:12 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
addAction(action: ActionInContext) {
|
|
|
|
|
this.willPerformAction(action);
|
|
|
|
|
this.didPerformAction(action);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
willPerformAction(action: ActionInContext) {
|
|
|
|
|
this._currentAction = action;
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-28 00:57:28 +01:00
|
|
|
performedActionFailed(action: ActionInContext) {
|
|
|
|
|
if (this._currentAction === action)
|
2021-01-28 02:02:09 +01:00
|
|
|
this._currentAction = null;
|
2021-01-28 00:57:28 +01:00
|
|
|
}
|
|
|
|
|
|
2020-12-28 23:50:12 +01:00
|
|
|
didPerformAction(actionInContext: ActionInContext) {
|
|
|
|
|
const { action, pageAlias } = actionInContext;
|
|
|
|
|
let eraseLastAction = false;
|
|
|
|
|
if (this._lastAction && this._lastAction.pageAlias === pageAlias) {
|
|
|
|
|
const { action: lastAction } = this._lastAction;
|
|
|
|
|
// We augment last action based on the type.
|
|
|
|
|
if (this._lastAction && action.name === 'fill' && lastAction.name === 'fill') {
|
|
|
|
|
if (action.selector === lastAction.selector)
|
|
|
|
|
eraseLastAction = true;
|
|
|
|
|
}
|
|
|
|
|
if (lastAction && action.name === 'click' && lastAction.name === 'click') {
|
|
|
|
|
if (action.selector === lastAction.selector && action.clickCount > lastAction.clickCount)
|
|
|
|
|
eraseLastAction = true;
|
|
|
|
|
}
|
|
|
|
|
if (lastAction && action.name === 'navigate' && lastAction.name === 'navigate') {
|
2021-01-28 02:05:56 +01:00
|
|
|
if (action.url === lastAction.url) {
|
2021-01-28 02:45:27 +01:00
|
|
|
this._currentAction = null;
|
2020-12-28 23:50:12 +01:00
|
|
|
return;
|
2021-01-28 02:05:56 +01:00
|
|
|
}
|
2020-12-28 23:50:12 +01:00
|
|
|
}
|
|
|
|
|
for (const name of ['check', 'uncheck']) {
|
|
|
|
|
if (lastAction && action.name === name && lastAction.name === 'click') {
|
|
|
|
|
if ((action as any).selector === (lastAction as any).selector)
|
|
|
|
|
eraseLastAction = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
this._printAction(actionInContext, eraseLastAction);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
commitLastAction() {
|
|
|
|
|
const action = this._lastAction;
|
|
|
|
|
if (action)
|
|
|
|
|
action.committed = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_printAction(actionInContext: ActionInContext, eraseLastAction: boolean) {
|
2021-01-25 23:49:26 +01:00
|
|
|
if (this._footerText)
|
|
|
|
|
this._output.popLn(this._footerText);
|
2020-12-28 23:50:12 +01:00
|
|
|
if (eraseLastAction && this._lastActionText)
|
|
|
|
|
this._output.popLn(this._lastActionText);
|
|
|
|
|
const performingAction = !!this._currentAction;
|
2021-01-28 02:02:09 +01:00
|
|
|
this._currentAction = null;
|
2020-12-28 23:50:12 +01:00
|
|
|
this._lastAction = actionInContext;
|
|
|
|
|
this._lastActionText = this._languageGenerator.generateAction(actionInContext, performingAction);
|
|
|
|
|
this._output.printLn(this._lastActionText);
|
2021-01-25 23:49:26 +01:00
|
|
|
if (this._footerText)
|
|
|
|
|
this._output.printLn(this._footerText);
|
2020-12-28 23:50:12 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
signal(pageAlias: string, frame: Frame, signal: Signal) {
|
|
|
|
|
// Signal either arrives while action is being performed or shortly after.
|
|
|
|
|
if (this._currentAction) {
|
|
|
|
|
this._currentAction.action.signals.push(signal);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (this._lastAction && !this._lastAction.committed) {
|
|
|
|
|
this._lastAction.action.signals.push(signal);
|
|
|
|
|
this._printAction(this._lastAction, true);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (signal.name === 'navigation') {
|
|
|
|
|
this.addAction({
|
|
|
|
|
pageAlias,
|
2021-01-27 22:19:36 +01:00
|
|
|
...describeFrame(frame),
|
2020-12-28 23:50:12 +01:00
|
|
|
committed: true,
|
|
|
|
|
action: {
|
|
|
|
|
name: 'navigate',
|
|
|
|
|
url: frame.url(),
|
|
|
|
|
signals: [],
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|