chore: migrate injected scripts to esbuild (#13143)
This commit is contained in:
parent
de0af27837
commit
1961959dcb
3098
package-lock.json
generated
3098
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -65,7 +65,6 @@
|
||||||
"@types/resize-observer-browser": "^0.1.6",
|
"@types/resize-observer-browser": "^0.1.6",
|
||||||
"@types/rimraf": "^3.0.2",
|
"@types/rimraf": "^3.0.2",
|
||||||
"@types/source-map-support": "^0.5.4",
|
"@types/source-map-support": "^0.5.4",
|
||||||
"@types/webpack": "^5.28.0",
|
|
||||||
"@types/ws": "8.2.2",
|
"@types/ws": "8.2.2",
|
||||||
"@types/xml2js": "^0.4.9",
|
"@types/xml2js": "^0.4.9",
|
||||||
"@types/yazl": "^2.4.2",
|
"@types/yazl": "^2.4.2",
|
||||||
|
|
@ -74,22 +73,17 @@
|
||||||
"@vitejs/plugin-react": "^1.0.7",
|
"@vitejs/plugin-react": "^1.0.7",
|
||||||
"@zip.js/zip.js": "^2.4.2",
|
"@zip.js/zip.js": "^2.4.2",
|
||||||
"ansi-to-html": "^0.7.2",
|
"ansi-to-html": "^0.7.2",
|
||||||
"babel-loader": "^8.2.3",
|
|
||||||
"chokidar": "^3.5.3",
|
"chokidar": "^3.5.3",
|
||||||
"commonmark": "^0.30.0",
|
"commonmark": "^0.30.0",
|
||||||
"concurrently": "^6.2.1",
|
"concurrently": "^6.2.1",
|
||||||
"copy-webpack-plugin": "^9.1.0",
|
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"css-loader": "^6.5.1",
|
|
||||||
"dotenv": "^16.0.0",
|
"dotenv": "^16.0.0",
|
||||||
"electron": "^12.2.1",
|
"electron": "^12.2.1",
|
||||||
"enquirer": "^2.3.6",
|
"enquirer": "^2.3.6",
|
||||||
"eslint": "^8.8.0",
|
"eslint": "^8.8.0",
|
||||||
"eslint-plugin-notice": "^0.9.10",
|
"eslint-plugin-notice": "^0.9.10",
|
||||||
"eslint-plugin-react-hooks": "^4.3.0",
|
"eslint-plugin-react-hooks": "^4.3.0",
|
||||||
"file-loader": "^6.2.0",
|
|
||||||
"formidable": "^2.0.1",
|
"formidable": "^2.0.1",
|
||||||
"html-webpack-plugin": "^5.5.0",
|
|
||||||
"mime": "^3.0.0",
|
"mime": "^3.0.0",
|
||||||
"ncp": "^2.0.0",
|
"ncp": "^2.0.0",
|
||||||
"node-stream-zip": "^1.15.0",
|
"node-stream-zip": "^1.15.0",
|
||||||
|
|
@ -97,11 +91,8 @@
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
"socksv5": "0.0.6",
|
"socksv5": "0.0.6",
|
||||||
"style-loader": "^3.3.1",
|
|
||||||
"typescript": "^4.5.5",
|
"typescript": "^4.5.5",
|
||||||
"vite": "^2.8.0",
|
"vite": "^2.8.0",
|
||||||
"webpack": "^5.68.0",
|
|
||||||
"webpack-cli": "^4.9.2",
|
|
||||||
"xml2js": "^0.4.23",
|
"xml2js": "^0.4.23",
|
||||||
"yaml": "^1.10.2"
|
"yaml": "^1.10.2"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -113,7 +113,7 @@ export function serializeValue(value: any, handleSerializer: (value: any) => Han
|
||||||
return { s: value };
|
return { s: value };
|
||||||
if (isError(value)) {
|
if (isError(value)) {
|
||||||
const error = value;
|
const error = value;
|
||||||
if ('captureStackTrace' in global.Error) {
|
if ('captureStackTrace' in globalThis.Error) {
|
||||||
// v8
|
// v8
|
||||||
return { s: error.stack || '' };
|
return { s: error.stack || '' };
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -110,7 +110,7 @@ function serialize(value: any, handleSerializer: (value: any) => HandleOrValue,
|
||||||
|
|
||||||
if (isError(value)) {
|
if (isError(value)) {
|
||||||
const error = value;
|
const error = value;
|
||||||
if ('captureStackTrace' in global.Error) {
|
if ('captureStackTrace' in globalThis.Error) {
|
||||||
// v8
|
// v8
|
||||||
return error.stack || '';
|
return error.stack || '';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -99,8 +99,9 @@ export class FrameExecutionContext extends js.ExecutionContext {
|
||||||
custom.push(`{ name: '${name}', engine: (${source}) }`);
|
custom.push(`{ name: '${name}', engine: (${source}) }`);
|
||||||
const source = `
|
const source = `
|
||||||
(() => {
|
(() => {
|
||||||
|
const module = {};
|
||||||
${injectedScriptSource.source}
|
${injectedScriptSource.source}
|
||||||
return new pwExport(
|
return new module.exports(
|
||||||
${isUnderTest()},
|
${isUnderTest()},
|
||||||
${this.frame._page._delegate.rafCountForStablePosition()},
|
${this.frame._page._delegate.rafCountForStablePosition()},
|
||||||
"${this.frame._page._browserContext._browser.options.name}",
|
"${this.frame._page._browserContext._browser.options.name}",
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ import { assert, constructURLBasedOnBaseURL, makeWaitForNextTask } from '../util
|
||||||
import { ManualPromise } from '../utils/async';
|
import { ManualPromise } from '../utils/async';
|
||||||
import { debugLogger } from '../utils/debugLogger';
|
import { debugLogger } from '../utils/debugLogger';
|
||||||
import { CallMetadata, serverSideCallMetadata, SdkObject } from './instrumentation';
|
import { CallMetadata, serverSideCallMetadata, SdkObject } from './instrumentation';
|
||||||
import type InjectedScript from './injected/injectedScript';
|
import { type InjectedScript } from './injected/injectedScript';
|
||||||
import type { ElementStateWithoutStable, FrameExpectParams, InjectedScriptPoll, InjectedScriptProgress } from './injected/injectedScript';
|
import type { ElementStateWithoutStable, FrameExpectParams, InjectedScriptPoll, InjectedScriptProgress } from './injected/injectedScript';
|
||||||
import { isSessionClosedError } from './protocolError';
|
import { isSessionClosedError } from './protocolError';
|
||||||
import { isInvalidSelectorError, splitSelectorByFrame, stringifySelector, ParsedSelector } from './common/selectorParser';
|
import { isInvalidSelectorError, splitSelectorByFrame, stringifySelector, ParsedSelector } from './common/selectorParser';
|
||||||
|
|
|
||||||
|
|
@ -121,7 +121,7 @@ export class InjectedScript {
|
||||||
}
|
}
|
||||||
|
|
||||||
eval(expression: string): any {
|
eval(expression: string): any {
|
||||||
return global.eval(expression);
|
return globalThis.eval(expression);
|
||||||
}
|
}
|
||||||
|
|
||||||
parseSelector(selector: string): ParsedSelector {
|
parseSelector(selector: string): ParsedSelector {
|
||||||
|
|
@ -303,10 +303,11 @@ export class InjectedScript {
|
||||||
}
|
}
|
||||||
|
|
||||||
extend(source: string, params: any): any {
|
extend(source: string, params: any): any {
|
||||||
const constrFunction = global.eval(`
|
const constrFunction = globalThis.eval(`
|
||||||
(() => {
|
(() => {
|
||||||
|
const module = {};
|
||||||
${source}
|
${source}
|
||||||
return pwExport;
|
return module.exports;
|
||||||
})()`);
|
})()`);
|
||||||
return new constrFunction(this, params);
|
return new constrFunction(this, params);
|
||||||
}
|
}
|
||||||
|
|
@ -1257,4 +1258,4 @@ function deepEquals(a: any, b: any): boolean {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default InjectedScript;
|
module.exports = InjectedScript;
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type InjectedScript from './injectedScript';
|
import { type InjectedScript } from './injectedScript';
|
||||||
import { elementText } from './selectorEvaluator';
|
import { elementText } from './selectorEvaluator';
|
||||||
|
|
||||||
type SelectorToken = {
|
type SelectorToken = {
|
||||||
|
|
|
||||||
|
|
@ -16,12 +16,12 @@
|
||||||
|
|
||||||
import { serializeAsCallArgument, parseEvaluationResultValue } from '../common/utilityScriptSerializers';
|
import { serializeAsCallArgument, parseEvaluationResultValue } from '../common/utilityScriptSerializers';
|
||||||
|
|
||||||
export default class UtilityScript {
|
export class UtilityScript {
|
||||||
evaluate(isFunction: boolean | undefined, returnByValue: boolean, expression: string, argCount: number, ...argsAndHandles: any[]) {
|
evaluate(isFunction: boolean | undefined, returnByValue: boolean, expression: string, argCount: number, ...argsAndHandles: any[]) {
|
||||||
const args = argsAndHandles.slice(0, argCount);
|
const args = argsAndHandles.slice(0, argCount);
|
||||||
const handles = argsAndHandles.slice(argCount);
|
const handles = argsAndHandles.slice(argCount);
|
||||||
const parameters = args.map(a => parseEvaluationResultValue(a, handles));
|
const parameters = args.map(a => parseEvaluationResultValue(a, handles));
|
||||||
let result = global.eval(expression);
|
let result = globalThis.eval(expression);
|
||||||
if (isFunction === true) {
|
if (isFunction === true) {
|
||||||
result = result(...parameters);
|
result = result(...parameters);
|
||||||
} else if (isFunction === false) {
|
} else if (isFunction === false) {
|
||||||
|
|
@ -63,3 +63,5 @@ export default class UtilityScript {
|
||||||
return safeJson(value);
|
return safeJson(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
module.exports = UtilityScript;
|
||||||
|
|
|
||||||
|
|
@ -1,80 +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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
const path = require('path');
|
|
||||||
const fs = require('fs');
|
|
||||||
|
|
||||||
class InlineSource {
|
|
||||||
/**
|
|
||||||
* @param {string[]} outFiles
|
|
||||||
*/
|
|
||||||
constructor(outFiles) {
|
|
||||||
this.outFiles = outFiles;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('webpack').Compiler} compiler
|
|
||||||
*/
|
|
||||||
apply(compiler) {
|
|
||||||
compiler.hooks.emit.tapAsync('InlineSource', (compilation, callback) => {
|
|
||||||
for (const outFile of this.outFiles) {
|
|
||||||
const source = compilation.assets[path.basename(outFile).replace('.ts', '.js')].source();
|
|
||||||
fs.mkdirSync(path.dirname(outFile), { recursive: true });
|
|
||||||
const newSource = 'export const source = ' + JSON.stringify(source) + ';';
|
|
||||||
fs.writeFileSync(outFile, newSource);
|
|
||||||
}
|
|
||||||
callback();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const entry = {
|
|
||||||
utilityScriptSource: path.join(__dirname, 'utilityScript.ts'),
|
|
||||||
injectedScriptSource: path.join(__dirname, 'injectedScript.ts'),
|
|
||||||
consoleApiSource: path.join(__dirname, '..', 'supplements', 'injected', 'consoleApi.ts'),
|
|
||||||
recorderSource: path.join(__dirname, '..', 'supplements', 'injected', 'recorder.ts'),
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @type {import('webpack').Configuration} */
|
|
||||||
module.exports = {
|
|
||||||
entry,
|
|
||||||
mode: process.env.NODE_ENV === 'production' ? 'production' : 'development',
|
|
||||||
devtool: false,
|
|
||||||
module: {
|
|
||||||
rules: [
|
|
||||||
{
|
|
||||||
test: /\.(j|t)sx?$/,
|
|
||||||
loader: 'babel-loader',
|
|
||||||
exclude: /node_modules/
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
resolve: {
|
|
||||||
extensions: [ '.tsx', '.ts', '.js' ]
|
|
||||||
},
|
|
||||||
output: {
|
|
||||||
libraryTarget: 'var',
|
|
||||||
library: 'pwExport',
|
|
||||||
libraryExport: 'default',
|
|
||||||
filename: '[name].js',
|
|
||||||
path: path.resolve(__dirname, '../../../lib/server/injected/packed')
|
|
||||||
},
|
|
||||||
plugins: [
|
|
||||||
new InlineSource(
|
|
||||||
Object.keys(entry).map(x => path.join(__dirname, '..', '..', 'generated', x + '.ts'))
|
|
||||||
),
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
import * as dom from './dom';
|
import * as dom from './dom';
|
||||||
import * as utilityScriptSource from '../generated/utilityScriptSource';
|
import * as utilityScriptSource from '../generated/utilityScriptSource';
|
||||||
import { serializeAsCallArgument } from './common/utilityScriptSerializers';
|
import { serializeAsCallArgument } from './common/utilityScriptSerializers';
|
||||||
import type UtilityScript from './injected/utilityScript';
|
import { type UtilityScript } from './injected/utilityScript';
|
||||||
import { SdkObject } from './instrumentation';
|
import { SdkObject } from './instrumentation';
|
||||||
import { ManualPromise } from '../utils/async';
|
import { ManualPromise } from '../utils/async';
|
||||||
|
|
||||||
|
|
@ -114,8 +114,9 @@ export class ExecutionContext extends SdkObject {
|
||||||
if (!this._utilityScriptPromise) {
|
if (!this._utilityScriptPromise) {
|
||||||
const source = `
|
const source = `
|
||||||
(() => {
|
(() => {
|
||||||
|
const module = {};
|
||||||
${utilityScriptSource.source}
|
${utilityScriptSource.source}
|
||||||
return new pwExport();
|
return new module.exports();
|
||||||
})();`;
|
})();`;
|
||||||
this._utilityScriptPromise = this._raceAgainstContextDestroyed(this._delegate.rawEvaluateHandle(source).then(objectId => new JSHandle(this, 'object', undefined, objectId)));
|
this._utilityScriptPromise = this._raceAgainstContextDestroyed(this._delegate.rawEvaluateHandle(source).then(objectId => new JSHandle(this, 'object', undefined, objectId)));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { escapeWithQuotes } from '../../../utils/stringUtils';
|
import { escapeWithQuotes } from '../../../utils/stringUtils';
|
||||||
import type InjectedScript from '../../injected/injectedScript';
|
import { type InjectedScript } from '../../injected/injectedScript';
|
||||||
import { generateSelector } from '../../injected/selectorGenerator';
|
import { generateSelector } from '../../injected/selectorGenerator';
|
||||||
|
|
||||||
function createLocator(injectedScript: InjectedScript, initial: string, options?: { hasText?: string | RegExp }) {
|
function createLocator(injectedScript: InjectedScript, initial: string, options?: { hasText?: string | RegExp }) {
|
||||||
|
|
@ -64,7 +64,7 @@ declare global {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ConsoleAPI {
|
class ConsoleAPI {
|
||||||
private _injectedScript: InjectedScript;
|
private _injectedScript: InjectedScript;
|
||||||
|
|
||||||
constructor(injectedScript: InjectedScript) {
|
constructor(injectedScript: InjectedScript) {
|
||||||
|
|
@ -112,4 +112,4 @@ export class ConsoleAPI {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ConsoleAPI;
|
module.exports = ConsoleAPI;
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type * as actions from '../recorder/recorderActions';
|
import type * as actions from '../recorder/recorderActions';
|
||||||
import type InjectedScript from '../../injected/injectedScript';
|
import { type InjectedScript } from '../../injected/injectedScript';
|
||||||
import { generateSelector, querySelector } from '../../injected/selectorGenerator';
|
import { generateSelector, querySelector } from '../../injected/selectorGenerator';
|
||||||
import type { Point } from '../../../common/types';
|
import type { Point } from '../../../common/types';
|
||||||
import type { UIState } from '../recorder/recorderTypes';
|
import type { UIState } from '../recorder/recorderTypes';
|
||||||
|
|
@ -30,7 +30,7 @@ declare module globalThis {
|
||||||
let _playwrightRefreshOverlay: () => void;
|
let _playwrightRefreshOverlay: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Recorder {
|
class Recorder {
|
||||||
private _injectedScript: InjectedScript;
|
private _injectedScript: InjectedScript;
|
||||||
private _performingAction = false;
|
private _performingAction = false;
|
||||||
private _listeners: (() => void)[] = [];
|
private _listeners: (() => void)[] = [];
|
||||||
|
|
@ -473,4 +473,4 @@ function removeEventListeners(listeners: (() => void)[]) {
|
||||||
listeners.splice(0, listeners.length);
|
listeners.splice(0, listeners.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Recorder;
|
module.exports = Recorder;
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,6 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { BrowserContext } from '../../browserContext';
|
import { BrowserContext } from '../../browserContext';
|
||||||
import { eventsHelper } from '../../../utils/eventsHelper';
|
|
||||||
import { Page } from '../../page';
|
import { Page } from '../../page';
|
||||||
import { FrameSnapshot } from '../common/snapshotTypes';
|
import { FrameSnapshot } from '../common/snapshotTypes';
|
||||||
import { SnapshotRenderer } from '../../../../../trace-viewer/src/snapshotRenderer';
|
import { SnapshotRenderer } from '../../../../../trace-viewer/src/snapshotRenderer';
|
||||||
|
|
@ -61,9 +60,9 @@ export class InMemorySnapshotter extends BaseSnapshotStorage implements Snapshot
|
||||||
|
|
||||||
this._snapshotter.captureSnapshot(page, snapshotName, element).catch(() => {});
|
this._snapshotter.captureSnapshot(page, snapshotName, element).catch(() => {});
|
||||||
return new Promise<SnapshotRenderer>(fulfill => {
|
return new Promise<SnapshotRenderer>(fulfill => {
|
||||||
const listener = eventsHelper.addEventListener(this, 'snapshot', (renderer: SnapshotRenderer) => {
|
const disposable = this.onSnapshotEvent((renderer: SnapshotRenderer) => {
|
||||||
if (renderer.snapshotName === snapshotName) {
|
if (renderer.snapshotName === snapshotName) {
|
||||||
eventsHelper.removeEventListeners([listener]);
|
disposable.dispose();
|
||||||
fulfill(renderer);
|
fulfill(renderer);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
77
packages/trace-viewer/src/events.ts
Normal file
77
packages/trace-viewer/src/events.ts
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export namespace Disposable {
|
||||||
|
export function disposeAll(disposables: Disposable[]): void {
|
||||||
|
for (const disposable of disposables.splice(0))
|
||||||
|
disposable.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Disposable = {
|
||||||
|
dispose(): void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface Event<T> {
|
||||||
|
(listener: (e: T) => any, disposables?: Disposable[]): Disposable;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class EventEmitter<T> {
|
||||||
|
public event: Event<T>;
|
||||||
|
|
||||||
|
private _deliveryQueue?: {listener: (e: T) => void, event: T}[];
|
||||||
|
private _listeners = new Set<(e: T) => void>();
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.event = (listener: (e: T) => any, disposables?: Disposable[]) => {
|
||||||
|
this._listeners.add(listener);
|
||||||
|
let disposed = false;
|
||||||
|
const self = this;
|
||||||
|
const result: Disposable = {
|
||||||
|
dispose() {
|
||||||
|
if (!disposed) {
|
||||||
|
disposed = true;
|
||||||
|
self._listeners.delete(listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (disposables)
|
||||||
|
disposables.push(result);
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fire(event: T): void {
|
||||||
|
const dispatch = !this._deliveryQueue;
|
||||||
|
if (!this._deliveryQueue)
|
||||||
|
this._deliveryQueue = [];
|
||||||
|
for (const listener of this._listeners)
|
||||||
|
this._deliveryQueue.push({ listener, event });
|
||||||
|
if (!dispatch)
|
||||||
|
return;
|
||||||
|
for (let index = 0; index < this._deliveryQueue.length; index++) {
|
||||||
|
const { listener, event } = this._deliveryQueue[index];
|
||||||
|
listener.call(null, event);
|
||||||
|
}
|
||||||
|
this._deliveryQueue = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose() {
|
||||||
|
this._listeners.clear();
|
||||||
|
if (this._deliveryQueue)
|
||||||
|
this._deliveryQueue = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { FrameSnapshot, ResourceSnapshot } from '@playwright-core/server/trace/common/snapshotTypes';
|
import type { FrameSnapshot, ResourceSnapshot } from '@playwright-core/server/trace/common/snapshotTypes';
|
||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from './events';
|
||||||
import { SnapshotRenderer } from './snapshotRenderer';
|
import { SnapshotRenderer } from './snapshotRenderer';
|
||||||
|
|
||||||
export interface SnapshotStorage {
|
export interface SnapshotStorage {
|
||||||
|
|
@ -25,12 +25,14 @@ export interface SnapshotStorage {
|
||||||
snapshotByIndex(frameId: string, index: number): SnapshotRenderer | undefined;
|
snapshotByIndex(frameId: string, index: number): SnapshotRenderer | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export abstract class BaseSnapshotStorage extends EventEmitter implements SnapshotStorage {
|
export abstract class BaseSnapshotStorage implements SnapshotStorage {
|
||||||
protected _resources: ResourceSnapshot[] = [];
|
protected _resources: ResourceSnapshot[] = [];
|
||||||
protected _frameSnapshots = new Map<string, {
|
protected _frameSnapshots = new Map<string, {
|
||||||
raw: FrameSnapshot[],
|
raw: FrameSnapshot[],
|
||||||
renderer: SnapshotRenderer[]
|
renderer: SnapshotRenderer[]
|
||||||
}>();
|
}>();
|
||||||
|
private _didSnapshot = new EventEmitter<SnapshotRenderer>();
|
||||||
|
readonly onSnapshotEvent = this._didSnapshot.event;
|
||||||
|
|
||||||
clear() {
|
clear() {
|
||||||
this._resources = [];
|
this._resources = [];
|
||||||
|
|
@ -55,7 +57,7 @@ export abstract class BaseSnapshotStorage extends EventEmitter implements Snapsh
|
||||||
frameSnapshots.raw.push(snapshot);
|
frameSnapshots.raw.push(snapshot);
|
||||||
const renderer = new SnapshotRenderer(this._resources, frameSnapshots.raw, frameSnapshots.raw.length - 1);
|
const renderer = new SnapshotRenderer(this._resources, frameSnapshots.raw, frameSnapshots.raw.length - 1);
|
||||||
frameSnapshots.renderer.push(renderer);
|
frameSnapshots.renderer.push(renderer);
|
||||||
this.emit('snapshot', renderer);
|
this._didSnapshot.fire(renderer);
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract resourceContent(sha1: string): Promise<Blob | undefined>;
|
abstract resourceContent(sha1: string): Promise<Blob | undefined>;
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,7 @@ it.describe('snapshots', () => {
|
||||||
it('should collect multiple', async ({ page, toImpl, snapshotter }) => {
|
it('should collect multiple', async ({ page, toImpl, snapshotter }) => {
|
||||||
await page.setContent('<button>Hello</button>');
|
await page.setContent('<button>Hello</button>');
|
||||||
const snapshots = [];
|
const snapshots = [];
|
||||||
snapshotter.on('snapshot', snapshot => snapshots.push(snapshot));
|
snapshotter.onSnapshotEvent(snapshot => snapshots.push(snapshot));
|
||||||
await snapshotter.captureSnapshot(toImpl(page), 'snapshot1');
|
await snapshotter.captureSnapshot(toImpl(page), 'snapshot1');
|
||||||
await snapshotter.captureSnapshot(toImpl(page), 'snapshot2');
|
await snapshotter.captureSnapshot(toImpl(page), 'snapshot2');
|
||||||
expect(snapshots.length).toBe(2);
|
expect(snapshots.length).toBe(2);
|
||||||
|
|
|
||||||
|
|
@ -190,19 +190,11 @@ steps.push({
|
||||||
});
|
});
|
||||||
|
|
||||||
// Build injected scripts.
|
// Build injected scripts.
|
||||||
const webPackFiles = [
|
steps.push({
|
||||||
'packages/playwright-core/src/server/injected/webpack.config.js',
|
command: 'node',
|
||||||
];
|
args: ['utils/generate_injected.js'],
|
||||||
for (const file of webPackFiles) {
|
|
||||||
steps.push({
|
|
||||||
command: 'npx',
|
|
||||||
args: ['webpack', '--config', quotePath(filePath(file)), ...(watchMode ? ['--watch', '--stats', 'none'] : [])],
|
|
||||||
shell: true,
|
shell: true,
|
||||||
env: {
|
});
|
||||||
NODE_ENV: watchMode ? 'development' : 'production'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run Babel.
|
// Run Babel.
|
||||||
for (const pkg of workspace.packages()) {
|
for (const pkg of workspace.packages()) {
|
||||||
|
|
@ -216,11 +208,22 @@ for (const pkg of workspace.packages()) {
|
||||||
'--extensions', '.ts',
|
'--extensions', '.ts',
|
||||||
'--out-dir', quotePath(path.join(pkg.path, 'lib')),
|
'--out-dir', quotePath(path.join(pkg.path, 'lib')),
|
||||||
'--ignore', '"packages/playwright-core/src/server/injected/**/*"',
|
'--ignore', '"packages/playwright-core/src/server/injected/**/*"',
|
||||||
|
'--ignore', '"packages/playwright-core/src/server/supplements/injected/**/*"',
|
||||||
quotePath(path.join(pkg.path, 'src'))],
|
quotePath(path.join(pkg.path, 'src'))],
|
||||||
shell: true,
|
shell: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Generate injected.
|
||||||
|
onChanges.push({
|
||||||
|
committed: false,
|
||||||
|
inputs: [
|
||||||
|
'packages/playwright-core/src/server/injected/**',
|
||||||
|
'packages/playwright-core/src/supplements/injected/**',
|
||||||
|
'utils/generate_injected.js',
|
||||||
|
],
|
||||||
|
script: 'utils/generate_injected.js',
|
||||||
|
});
|
||||||
|
|
||||||
// Generate channels.
|
// Generate channels.
|
||||||
onChanges.push({
|
onChanges.push({
|
||||||
|
|
|
||||||
50
utils/generate_injected.js
Normal file
50
utils/generate_injected.js
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
/**
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// @ts-check
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const ROOT = path.join(__dirname, '..');
|
||||||
|
const esbuild = require('esbuild');
|
||||||
|
|
||||||
|
const injectedScripts = [
|
||||||
|
path.join(ROOT, 'packages', 'playwright-core', 'src', 'server', 'injected', 'utilityScript.ts'),
|
||||||
|
path.join(ROOT, 'packages', 'playwright-core', 'src', 'server', 'injected', 'injectedScript.ts'),
|
||||||
|
path.join(ROOT, 'packages', 'playwright-core', 'src', 'server', 'supplements', 'injected', 'consoleApi.ts'),
|
||||||
|
path.join(ROOT, 'packages', 'playwright-core', 'src', 'server', 'supplements', 'injected', 'recorder.ts'),
|
||||||
|
];
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const generatedFolder = path.join(ROOT, 'packages', 'playwright-core', 'src', 'generated');
|
||||||
|
await fs.promises.mkdir(generatedFolder, { recursive: true });
|
||||||
|
for (const injected of injectedScripts) {
|
||||||
|
const outdir = path.join(ROOT, 'packages', 'playwright-core', 'lib', 'server', 'injected', 'packed');
|
||||||
|
await esbuild.build({
|
||||||
|
entryPoints: [injected],
|
||||||
|
bundle: true,
|
||||||
|
outdir,
|
||||||
|
format: 'cjs',
|
||||||
|
platform: 'browser',
|
||||||
|
target: 'ES2019'
|
||||||
|
});
|
||||||
|
const baseName = path.basename(injected);
|
||||||
|
const content = await fs.promises.readFile(path.join(outdir, baseName.replace('.ts', '.js')), 'utf-8');
|
||||||
|
const newContent = `export const source = ${JSON.stringify(content)};`;
|
||||||
|
await fs.promises.writeFile(path.join(generatedFolder, baseName.replace('.ts', 'Source.ts')), newContent);
|
||||||
|
}
|
||||||
|
})();
|
||||||
Loading…
Reference in a new issue