@memlab/e2e 1.0.13 → 1.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/BaseSynthesizer.d.ts +1 -1
- package/dist/E2EInteractionManager.d.ts +2 -1
- package/dist/E2EInteractionManager.js +17 -7
- package/dist/NetworkManager.d.ts +21 -0
- package/dist/NetworkManager.js +110 -0
- package/dist/ScriptManager.d.ts +24 -0
- package/dist/ScriptManager.js +104 -0
- package/dist/TestRunnerLoader.d.ts +1 -1
- package/dist/TestRunnerLoader.js +1 -1
- package/dist/TypesThirdParty.d.ts +1 -1
- package/dist/TypesThirdParty.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/instrumentation/BaseAstTransform.d.ts +16 -0
- package/dist/instrumentation/BaseAstTransform.js +31 -0
- package/dist/instrumentation/ScriptRewriteManager.d.ts +17 -0
- package/dist/instrumentation/ScriptRewriteManager.js +45 -0
- package/dist/instrumentation/TransformLoader.d.ts +14 -0
- package/dist/instrumentation/TransformLoader.js +21 -0
- package/dist/instrumentation/transforms/InjectSourceInfoTranform.d.ts +17 -0
- package/dist/instrumentation/transforms/InjectSourceInfoTranform.js +62 -0
- package/dist/instrumentation/transforms/InjectSourceInfoTransform.d.ts +17 -0
- package/dist/instrumentation/transforms/InjectSourceInfoTransform.js +63 -0
- package/dist/lib/E2EUtils.d.ts +1 -1
- package/dist/lib/E2EUtils.js +1 -1
- package/dist/lib/SynthesisUtils.d.ts +1 -1
- package/dist/lib/SynthesisUtils.js +1 -1
- package/dist/lib/operations/BackOperation.d.ts +1 -1
- package/dist/lib/operations/BackOperation.js +1 -1
- package/dist/lib/operations/BackspaceOperation.d.ts +1 -1
- package/dist/lib/operations/BackspaceOperation.js +1 -1
- package/dist/lib/operations/BaseOperation.d.ts +1 -1
- package/dist/lib/operations/BaseOperation.js +1 -1
- package/dist/lib/operations/CachePageContent.d.ts +1 -1
- package/dist/lib/operations/CachePageContent.js +1 -1
- package/dist/lib/operations/ClickOperation.d.ts +1 -1
- package/dist/lib/operations/ClickOperation.js +1 -1
- package/dist/lib/operations/CompoundOperation.d.ts +1 -1
- package/dist/lib/operations/CompoundOperation.js +1 -1
- package/dist/lib/operations/EnterOperation.d.ts +1 -1
- package/dist/lib/operations/EnterOperation.js +1 -1
- package/dist/lib/operations/EscOperation.d.ts +1 -1
- package/dist/lib/operations/EscOperation.js +1 -1
- package/dist/lib/operations/HoverOperation.d.ts +1 -1
- package/dist/lib/operations/HoverOperation.js +1 -1
- package/dist/lib/operations/InteractionUtils.d.ts +1 -1
- package/dist/lib/operations/InteractionUtils.js +1 -1
- package/dist/lib/operations/Operations.d.ts +1 -1
- package/dist/lib/operations/Operations.js +1 -1
- package/dist/lib/operations/RevertOperation.d.ts +1 -1
- package/dist/lib/operations/RevertOperation.js +1 -1
- package/dist/lib/operations/RunJSCode.d.ts +1 -1
- package/dist/lib/operations/RunJSCode.js +1 -1
- package/dist/lib/operations/ScrollOperation.d.ts +1 -1
- package/dist/lib/operations/ScrollOperation.js +1 -1
- package/dist/lib/operations/TargetExtraOperation.d.ts +1 -1
- package/dist/lib/operations/TargetExtraOperation.js +1 -1
- package/dist/lib/operations/TestPlanner.d.ts +1 -1
- package/dist/lib/operations/TestPlanner.js +1 -1
- package/dist/lib/operations/TypeOperation.d.ts +1 -1
- package/dist/lib/operations/TypeOperation.js +1 -1
- package/dist/lib/operations/UploadOperation.d.ts +1 -1
- package/dist/lib/operations/UploadOperation.js +1 -1
- package/dist/lib/operations/WaitForElementOperation.d.ts +1 -1
- package/dist/lib/operations/WaitForElementOperation.js +1 -1
- package/dist/lib/operations/WaitOperation.d.ts +1 -1
- package/dist/lib/operations/WaitOperation.js +1 -1
- package/dist/lib/operations/WithCachedPageContent.d.ts +1 -1
- package/dist/lib/operations/WithCachedPageContent.js +1 -1
- package/dist/lib/operations/XVirtualFrameBuffer.d.ts +1 -1
- package/dist/lib/operations/XVirtualFrameBuffer.js +1 -1
- package/dist/plugins/DefaultScenarioSynthesizer.d.ts +1 -1
- package/dist/plugins/DefaultScenarioSynthesizer.js +1 -1
- package/dist/plugins/TestSPAVisitSynthesizer.d.ts +1 -1
- package/dist/plugins/TestSPAVisitSynthesizer.js +2 -2
- package/dist/plugins/scenarios/test-airbnb.d.ts +1 -1
- package/dist/plugins/scenarios/test-airbnb.js +1 -1
- package/dist/plugins/scenarios/test-databricks.d.ts +1 -1
- package/dist/plugins/scenarios/test-databricks.js +1 -1
- package/dist/plugins/scenarios/test-ebay.d.ts +1 -1
- package/dist/plugins/scenarios/test-ebay.js +1 -1
- package/dist/plugins/scenarios/test-google-maps.d.ts +1 -1
- package/dist/plugins/scenarios/test-google-maps.js +1 -1
- package/dist/plugins/scenarios/test-shopify.d.ts +1 -1
- package/dist/plugins/scenarios/test-shopify.js +1 -1
- package/dist/plugins/scenarios/test-youtube.d.ts +1 -1
- package/dist/plugins/scenarios/test-youtube.js +1 -1
- package/package.json +11 -5
- package/static/example/components/layout.jsx +1 -1
- package/static/example/leak-filters/filter-by-retained-size.js +7 -1
- package/static/example/next.config.js +4 -3
- package/static/example/pages/_app.js +1 -1
- package/static/example/pages/_document.js +1 -1
- package/static/example/pages/examples/detached-dom.jsx +9 -3
- package/static/example/pages/examples/oversized-object.jsx +8 -2
- package/static/example/pages/index.js +1 -1
- package/static/example/scenarios/detached-dom.js +7 -6
- package/static/example/scenarios/oversized-leak-filter.js +7 -2
- package/static/example/scenarios/oversized-object-with-filter.js +7 -2
- package/static/example/scenarios/oversized-object.js +4 -4
- package/static/links/min.js +1 -0
- package/static/links/src/App.js +1 -1
- package/static/links/src/index.js +1 -1
package/README.md
CHANGED
|
@@ -4,5 +4,5 @@ This is the memlab E2E testing framework for headless/headful Chromium. It is ma
|
|
|
4
4
|
for controling/interacting with browser, recording heap snapshots, and logging other execution meta data.
|
|
5
5
|
|
|
6
6
|
## Online Resources
|
|
7
|
-
* [Official Website and Demo](https://
|
|
8
|
-
* [Documentation](https://
|
|
7
|
+
* [Official Website and Demo](https://facebook.github.io/memlab)
|
|
8
|
+
* [Documentation](https://facebook.github.io/memlab/docs/intro)
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @format
|
|
8
8
|
* @lightSyntaxTransform
|
|
9
|
-
* @oncall
|
|
9
|
+
* @oncall web_perf_infra
|
|
10
10
|
*/
|
|
11
11
|
import type { AnyOptions, Config, E2EOperation, E2EStepInfo, IE2EScenarioVisitPlan, E2ESynthesizerOptions, IScenario, IE2EStepBasic, IE2EScenarioSynthesizer, CheckPageLoadCallback, Nullable } from '@memlab/core';
|
|
12
12
|
declare class BaseSynthesizer implements IE2EScenarioSynthesizer {
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*
|
|
7
7
|
* @format
|
|
8
|
-
* @oncall
|
|
8
|
+
* @oncall web_perf_infra
|
|
9
9
|
*/
|
|
10
10
|
import type { AnyFunction, OperationArgs, MemLabConfig } from '@memlab/core';
|
|
11
11
|
import type { CDPSession, Page } from 'puppeteer';
|
|
@@ -19,6 +19,7 @@ export default class E2EInteractionManager {
|
|
|
19
19
|
private page;
|
|
20
20
|
private pageHistoryLength;
|
|
21
21
|
private evalFuncAfterInitLoad;
|
|
22
|
+
private networkManager;
|
|
22
23
|
constructor(page: Page);
|
|
23
24
|
getCDPSession(): Promise<CDPSession>;
|
|
24
25
|
clearCDPSession(): void;
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* LICENSE file in the root directory of this source tree.
|
|
7
7
|
*
|
|
8
8
|
* @format
|
|
9
|
-
* @oncall
|
|
9
|
+
* @oncall web_perf_infra
|
|
10
10
|
*/
|
|
11
11
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
12
12
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
@@ -27,17 +27,20 @@ const E2EUtils_1 = __importDefault(require("./lib/E2EUtils"));
|
|
|
27
27
|
const core_1 = require("@memlab/core");
|
|
28
28
|
const InteractionUtils_1 = __importDefault(require("./lib/operations/InteractionUtils"));
|
|
29
29
|
const TestPlanner_1 = __importDefault(require("./lib/operations/TestPlanner"));
|
|
30
|
+
const NetworkManager_1 = __importDefault(require("./NetworkManager"));
|
|
30
31
|
const { logMetaData, setPermissions, logTabProgress, maybeWaitForConsoleInput, applyAsyncWithRetry, compareURL, waitExtraForTab, checkURL, injectPageReloadChecker, checkPageReload, dispatchOperation, clearConsole, getNavigationHistoryLength, checkLastSnapshotChunk, getURLParameter, } = E2EUtils_1.default;
|
|
31
32
|
class E2EInteractionManager {
|
|
32
33
|
constructor(page) {
|
|
33
34
|
this.pageHistoryLength = [];
|
|
34
35
|
this.evalFuncAfterInitLoad = null;
|
|
35
36
|
this.page = page;
|
|
37
|
+
this.networkManager = new NetworkManager_1.default(page);
|
|
36
38
|
}
|
|
37
39
|
getCDPSession() {
|
|
38
40
|
return __awaiter(this, void 0, void 0, function* () {
|
|
39
41
|
if (!this.cdpsession) {
|
|
40
42
|
this.cdpsession = yield this.page.target().createCDPSession();
|
|
43
|
+
this.networkManager.setCDPSession(this.cdpsession);
|
|
41
44
|
}
|
|
42
45
|
return this.cdpsession;
|
|
43
46
|
});
|
|
@@ -70,6 +73,9 @@ class E2EInteractionManager {
|
|
|
70
73
|
}
|
|
71
74
|
beforeInteractions() {
|
|
72
75
|
return __awaiter(this, void 0, void 0, function* () {
|
|
76
|
+
const session = yield this.getCDPSession();
|
|
77
|
+
this.networkManager.setCDPSession(session);
|
|
78
|
+
yield this.networkManager.interceptScript();
|
|
73
79
|
if (core_1.config.verbose) {
|
|
74
80
|
const browserVersion = yield this.page.browser().version();
|
|
75
81
|
core_1.info.lowLevel(`Browser version: ${browserVersion}`);
|
|
@@ -98,7 +104,7 @@ class E2EInteractionManager {
|
|
|
98
104
|
core_1.info.lowLevel(new Date().toString());
|
|
99
105
|
}
|
|
100
106
|
const tab = visitPlan.tabsOrder[i];
|
|
101
|
-
const subUrl = tab.url.
|
|
107
|
+
const subUrl = tab.url.substring(tab.url.startsWith('/') ? 1 : 0);
|
|
102
108
|
const url = `${baseURL}${subUrl}` + getURLParameter(tab, visitPlan);
|
|
103
109
|
logTabProgress(i, visitPlan);
|
|
104
110
|
yield maybeWaitForConsoleInput(i + 1);
|
|
@@ -179,13 +185,17 @@ class E2EInteractionManager {
|
|
|
179
185
|
});
|
|
180
186
|
}
|
|
181
187
|
getPageStatistics(url, tabInfo, opArgs = {}) {
|
|
182
|
-
var _a;
|
|
188
|
+
var _a, _b;
|
|
183
189
|
return __awaiter(this, void 0, void 0, function* () {
|
|
184
190
|
if (core_1.config.verbose) {
|
|
185
191
|
core_1.info.lowLevel('url: ' + url);
|
|
186
192
|
}
|
|
187
193
|
// visit the URL of the first step
|
|
188
194
|
if (tabInfo.idx === 1) {
|
|
195
|
+
const beforeInitLoad = (_a = opArgs.scenario) === null || _a === void 0 ? void 0 : _a.beforeInitialPageLoad;
|
|
196
|
+
if (beforeInitLoad) {
|
|
197
|
+
yield beforeInitLoad(this.page);
|
|
198
|
+
}
|
|
189
199
|
yield applyAsyncWithRetry(this.initialLoad, this, [this.page, url, opArgs], {
|
|
190
200
|
retry: core_1.config.initialLoadFailRetry,
|
|
191
201
|
delayBeforeRetry: 3000,
|
|
@@ -202,7 +212,7 @@ class E2EInteractionManager {
|
|
|
202
212
|
// inject marker, which checks if the page is reloaded
|
|
203
213
|
if (tabInfo.idx === 1) {
|
|
204
214
|
// call setup callback if the scenario has one
|
|
205
|
-
const setup = (
|
|
215
|
+
const setup = (_b = opArgs.scenario) === null || _b === void 0 ? void 0 : _b.setup;
|
|
206
216
|
if (setup) {
|
|
207
217
|
yield setup(this.page);
|
|
208
218
|
}
|
|
@@ -267,14 +277,14 @@ class E2EInteractionManager {
|
|
|
267
277
|
writeStream.write(data.chunk);
|
|
268
278
|
lastChunk = data.chunk;
|
|
269
279
|
};
|
|
270
|
-
const
|
|
280
|
+
const progressHandler = (data) => {
|
|
271
281
|
const percent = ((100 * data.done) / data.total) | 0;
|
|
272
282
|
if (!core_1.config.isContinuousTest) {
|
|
273
283
|
core_1.info.overwrite(`heap snapshot ${percent}% complete`);
|
|
274
284
|
}
|
|
275
285
|
};
|
|
276
286
|
session.on('HeapProfiler.addHeapSnapshotChunk', dataHandler);
|
|
277
|
-
session.on('HeapProfiler.reportHeapSnapshotProgress',
|
|
287
|
+
session.on('HeapProfiler.reportHeapSnapshotProgress', progressHandler);
|
|
278
288
|
// start taking heap snapshot
|
|
279
289
|
yield session.send('HeapProfiler.takeHeapSnapshot', {
|
|
280
290
|
reportProgress: true,
|
|
@@ -282,7 +292,7 @@ class E2EInteractionManager {
|
|
|
282
292
|
});
|
|
283
293
|
checkLastSnapshotChunk(lastChunk);
|
|
284
294
|
session.removeListener('HeapProfiler.addHeapSnapshotChunk', dataHandler);
|
|
285
|
-
session.removeListener('HeapProfiler.reportHeapSnapshotProgress',
|
|
295
|
+
session.removeListener('HeapProfiler.reportHeapSnapshotProgress', progressHandler);
|
|
286
296
|
writeStream.end();
|
|
287
297
|
});
|
|
288
298
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*
|
|
7
|
+
* @format
|
|
8
|
+
* @oncall web_perf_infra
|
|
9
|
+
*/
|
|
10
|
+
import type { CDPSession, Page } from 'puppeteer';
|
|
11
|
+
export default class NetworkManager {
|
|
12
|
+
private cdpSession;
|
|
13
|
+
private page;
|
|
14
|
+
private requestCache;
|
|
15
|
+
private scriptManager;
|
|
16
|
+
constructor(page: Page);
|
|
17
|
+
setCDPSession(session: CDPSession): void;
|
|
18
|
+
private networkLog;
|
|
19
|
+
interceptScript(): Promise<void>;
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=NetworkManager.d.ts.map
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the MIT license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*
|
|
8
|
+
* @format
|
|
9
|
+
* @oncall web_perf_infra
|
|
10
|
+
*/
|
|
11
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
12
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
13
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
14
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
15
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
16
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
17
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
18
|
+
});
|
|
19
|
+
};
|
|
20
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
21
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
22
|
+
};
|
|
23
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
+
const core_1 = require("@memlab/core");
|
|
25
|
+
const ScriptManager_1 = __importDefault(require("./ScriptManager"));
|
|
26
|
+
const patterns = ['*'];
|
|
27
|
+
class NetworkManager {
|
|
28
|
+
constructor(page) {
|
|
29
|
+
this.cdpSession = null;
|
|
30
|
+
this.page = page;
|
|
31
|
+
this.requestCache = new Map();
|
|
32
|
+
this.scriptManager = new ScriptManager_1.default();
|
|
33
|
+
}
|
|
34
|
+
setCDPSession(session) {
|
|
35
|
+
this.cdpSession = session;
|
|
36
|
+
}
|
|
37
|
+
networkLog(msg) {
|
|
38
|
+
if (core_1.config.verbose) {
|
|
39
|
+
core_1.info.lowLevel(msg);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
interceptScript() {
|
|
43
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
44
|
+
const session = this.cdpSession;
|
|
45
|
+
if (!session) {
|
|
46
|
+
core_1.info.warning('Network manager does not have connection to Chrome DevTools.');
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const requestCache = this.requestCache;
|
|
50
|
+
yield session.send('Network.enable');
|
|
51
|
+
yield session.send('Network.setRequestInterception', {
|
|
52
|
+
patterns: patterns.map(pattern => ({
|
|
53
|
+
urlPattern: pattern,
|
|
54
|
+
resourceType: 'Script',
|
|
55
|
+
interceptionStage: 'HeadersReceived',
|
|
56
|
+
})),
|
|
57
|
+
});
|
|
58
|
+
session.on('Network.requestIntercepted', ({ interceptionId, request, responseHeaders, resourceType }) => __awaiter(this, void 0, void 0, function* () {
|
|
59
|
+
var _a;
|
|
60
|
+
this.networkLog(`Intercepted ${request.url} {interception id: ${interceptionId}}`);
|
|
61
|
+
const response = (yield session.send('Network.getResponseBodyForInterception', { interceptionId }));
|
|
62
|
+
// responseHeaders could be null if requesting from
|
|
63
|
+
// local files (i.e., urls starting with 'file:///')
|
|
64
|
+
let contentType = null;
|
|
65
|
+
if (responseHeaders) {
|
|
66
|
+
const contentTypeHeader = (_a = Object.keys(responseHeaders).find(k => k.toLowerCase() === 'content-type')) !== null && _a !== void 0 ? _a : '';
|
|
67
|
+
contentType = responseHeaders[contentTypeHeader];
|
|
68
|
+
}
|
|
69
|
+
let newBody = '';
|
|
70
|
+
if (requestCache.has(response.body)) {
|
|
71
|
+
newBody = requestCache.get(response.body);
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
const bodyData = response.base64Encoded
|
|
75
|
+
? atob(response.body)
|
|
76
|
+
: response.body;
|
|
77
|
+
newBody = bodyData;
|
|
78
|
+
try {
|
|
79
|
+
if (resourceType === 'Script') {
|
|
80
|
+
newBody = yield this.scriptManager.rewriteScript(bodyData, {
|
|
81
|
+
url: request.url,
|
|
82
|
+
resourceType,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
catch (e) {
|
|
87
|
+
this.networkLog(`Failed to process ${request.url} {interception id: ${interceptionId}}: ${e}`);
|
|
88
|
+
}
|
|
89
|
+
requestCache.set(response.body, newBody);
|
|
90
|
+
this.scriptManager.logScript(request.url, newBody);
|
|
91
|
+
}
|
|
92
|
+
const newHeaders = [
|
|
93
|
+
'Date: ' + new Date().toUTCString(),
|
|
94
|
+
'Connection: closed',
|
|
95
|
+
'Content-Length: ' + newBody.length,
|
|
96
|
+
];
|
|
97
|
+
if (contentType) {
|
|
98
|
+
newHeaders.push('Content-Type: ' + contentType);
|
|
99
|
+
}
|
|
100
|
+
this.networkLog(`Continuing interception ${interceptionId}`);
|
|
101
|
+
const rawResponse = btoa(['HTTP/1.1 200 OK', ...newHeaders, '', newBody].join('\r\n'));
|
|
102
|
+
session.send('Network.continueInterceptedRequest', {
|
|
103
|
+
interceptionId,
|
|
104
|
+
rawResponse,
|
|
105
|
+
});
|
|
106
|
+
}));
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
exports.default = NetworkManager;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*
|
|
7
|
+
* @format
|
|
8
|
+
* @oncall web_perf_infra
|
|
9
|
+
*/
|
|
10
|
+
import type { RewriteScriptOption } from './instrumentation/ScriptRewriteManager';
|
|
11
|
+
export default class ScriptManager {
|
|
12
|
+
private fileId;
|
|
13
|
+
private metaFileWriteTimeout;
|
|
14
|
+
private scriptRewriteManager;
|
|
15
|
+
private urlToScriptMap;
|
|
16
|
+
private scriptInfos;
|
|
17
|
+
constructor();
|
|
18
|
+
private init;
|
|
19
|
+
loadFromFiles(): boolean;
|
|
20
|
+
rewriteScript(code: string, options?: RewriteScriptOption): Promise<string>;
|
|
21
|
+
logScript(url: string, code: string): Promise<void>;
|
|
22
|
+
private debounce;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=ScriptManager.d.ts.map
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the MIT license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*
|
|
8
|
+
* @format
|
|
9
|
+
* @oncall web_perf_infra
|
|
10
|
+
*/
|
|
11
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
12
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
13
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
14
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
15
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
16
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
17
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
18
|
+
});
|
|
19
|
+
};
|
|
20
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
21
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
22
|
+
};
|
|
23
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
+
const fs_1 = __importDefault(require("fs"));
|
|
25
|
+
const path_1 = __importDefault(require("path"));
|
|
26
|
+
const core_1 = require("@memlab/core");
|
|
27
|
+
const ScriptRewriteManager_1 = __importDefault(require("./instrumentation/ScriptRewriteManager"));
|
|
28
|
+
class ScriptManager {
|
|
29
|
+
constructor() {
|
|
30
|
+
this.fileId = 0;
|
|
31
|
+
this.metaFileWriteTimeout = null;
|
|
32
|
+
this.init();
|
|
33
|
+
}
|
|
34
|
+
init() {
|
|
35
|
+
this.urlToScriptMap = new Map();
|
|
36
|
+
this.scriptInfos = [];
|
|
37
|
+
this.scriptRewriteManager = new ScriptRewriteManager_1.default();
|
|
38
|
+
}
|
|
39
|
+
loadFromFiles() {
|
|
40
|
+
this.init();
|
|
41
|
+
const webSourceMetaFile = core_1.fileManager.getWebSourceMetaFile();
|
|
42
|
+
const webSourceDir = core_1.fileManager.getWebSourceDir();
|
|
43
|
+
if (!fs_1.default.existsSync(webSourceMetaFile) || !fs_1.default.existsSync(webSourceDir)) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
const metaContent = fs_1.default.readFileSync(webSourceMetaFile, 'UTF-8');
|
|
48
|
+
this.scriptInfos = JSON.parse(metaContent);
|
|
49
|
+
for (const scriptInfo of this.scriptInfos) {
|
|
50
|
+
if (!fs_1.default.existsSync(scriptInfo.codePath)) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
this.urlToScriptMap.set(scriptInfo.url, scriptInfo);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
catch (_a) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
rewriteScript(code, options = {}) {
|
|
62
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
63
|
+
if (!core_1.config.instrumentJS) {
|
|
64
|
+
return code;
|
|
65
|
+
}
|
|
66
|
+
const newCode = yield this.scriptRewriteManager.rewriteScript(code, options);
|
|
67
|
+
return newCode;
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
logScript(url, code) {
|
|
71
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
72
|
+
if (this.urlToScriptMap.has(url)) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const metaFile = core_1.fileManager.getWebSourceMetaFile();
|
|
76
|
+
const file = path_1.default.join(core_1.fileManager.getWebSourceDir(), `${++this.fileId}.js`);
|
|
77
|
+
const scriptInfo = {
|
|
78
|
+
url,
|
|
79
|
+
fileId: this.fileId,
|
|
80
|
+
codePath: file,
|
|
81
|
+
};
|
|
82
|
+
this.urlToScriptMap.set(url, scriptInfo);
|
|
83
|
+
this.scriptInfos.push(scriptInfo);
|
|
84
|
+
fs_1.default.writeFile(file, code, 'UTF-8', () => {
|
|
85
|
+
// write file asynchroniously
|
|
86
|
+
});
|
|
87
|
+
// only write the latest version of the meta file state
|
|
88
|
+
this.debounce(() => {
|
|
89
|
+
fs_1.default.writeFileSync(metaFile, JSON.stringify(this.scriptInfos, undefined, 2), 'UTF-8');
|
|
90
|
+
}, 1000);
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
debounce(callback, timeout) {
|
|
94
|
+
if (this.metaFileWriteTimeout) {
|
|
95
|
+
clearTimeout(this.metaFileWriteTimeout);
|
|
96
|
+
this.metaFileWriteTimeout = null;
|
|
97
|
+
}
|
|
98
|
+
this.metaFileWriteTimeout = setTimeout(() => {
|
|
99
|
+
callback();
|
|
100
|
+
this.metaFileWriteTimeout = null;
|
|
101
|
+
}, timeout);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
exports.default = ScriptManager;
|
package/dist/TestRunnerLoader.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* LICENSE file in the root directory of this source tree.
|
|
7
7
|
*
|
|
8
8
|
* @format
|
|
9
|
-
* @oncall
|
|
9
|
+
* @oncall web_perf_infra
|
|
10
10
|
*/
|
|
11
11
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
12
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
package/dist/TypesThirdParty.js
CHANGED
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*
|
|
7
|
+
* @format
|
|
8
|
+
* @oncall web_perf_infra
|
|
9
|
+
*/
|
|
10
|
+
import type { ParseResult } from '@babel/parser';
|
|
11
|
+
import type { File } from '@babel/types';
|
|
12
|
+
import type { RewriteScriptOption } from './ScriptRewriteManager';
|
|
13
|
+
export default abstract class BaseAstTransform {
|
|
14
|
+
transform(ast: ParseResult<File>, options?: RewriteScriptOption): Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=BaseAstTransform.d.ts.map
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the MIT license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*
|
|
8
|
+
* @format
|
|
9
|
+
* @oncall web_perf_infra
|
|
10
|
+
*/
|
|
11
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
12
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
13
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
14
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
15
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
16
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
17
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
18
|
+
});
|
|
19
|
+
};
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
const core_1 = require("@memlab/core");
|
|
22
|
+
class BaseAstTransform {
|
|
23
|
+
transform(ast,
|
|
24
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
25
|
+
options = {}) {
|
|
26
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
27
|
+
throw core_1.utils.haltOrThrow('BaseAstTransform.transform is not implemented');
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
exports.default = BaseAstTransform;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*
|
|
7
|
+
* @format
|
|
8
|
+
* @oncall web_perf_infra
|
|
9
|
+
*/
|
|
10
|
+
export declare type RewriteScriptOption = {
|
|
11
|
+
url?: string;
|
|
12
|
+
resourceType?: string;
|
|
13
|
+
};
|
|
14
|
+
export default class ScriptRewriteManager {
|
|
15
|
+
rewriteScript(code: string, options?: RewriteScriptOption): Promise<string>;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=ScriptRewriteManager.d.ts.map
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the MIT license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*
|
|
8
|
+
* @format
|
|
9
|
+
* @oncall web_perf_infra
|
|
10
|
+
*/
|
|
11
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
12
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
13
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
14
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
15
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
16
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
17
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
18
|
+
});
|
|
19
|
+
};
|
|
20
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
21
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
22
|
+
};
|
|
23
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
+
const parser_1 = require("@babel/parser");
|
|
25
|
+
const generator_1 = __importDefault(require("@babel/generator"));
|
|
26
|
+
const TransformLoader_1 = __importDefault(require("./TransformLoader"));
|
|
27
|
+
class ScriptRewriteManager {
|
|
28
|
+
rewriteScript(code, options = {}) {
|
|
29
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
30
|
+
// parse code
|
|
31
|
+
const ast = (0, parser_1.parse)(code, {
|
|
32
|
+
sourceType: 'script',
|
|
33
|
+
});
|
|
34
|
+
// transform ast
|
|
35
|
+
const transforms = TransformLoader_1.default.loadAllTransforms();
|
|
36
|
+
for (const transform of transforms) {
|
|
37
|
+
yield transform.transform(ast, options);
|
|
38
|
+
}
|
|
39
|
+
// generate code
|
|
40
|
+
const generateResult = (0, generator_1.default)(ast);
|
|
41
|
+
return generateResult.code;
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
exports.default = ScriptRewriteManager;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*
|
|
7
|
+
* @format
|
|
8
|
+
* @oncall web_perf_infra
|
|
9
|
+
*/
|
|
10
|
+
import type BaseAstTransform from './BaseAstTransform';
|
|
11
|
+
export default class TransformLoader {
|
|
12
|
+
static loadAllTransforms(): BaseAstTransform[];
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=TransformLoader.d.ts.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the MIT license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*
|
|
8
|
+
* @format
|
|
9
|
+
* @oncall web_perf_infra
|
|
10
|
+
*/
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
const InjectSourceInfoTransform_1 = __importDefault(require("./transforms/InjectSourceInfoTransform"));
|
|
16
|
+
class TransformLoader {
|
|
17
|
+
static loadAllTransforms() {
|
|
18
|
+
return [new InjectSourceInfoTransform_1.default()];
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
exports.default = TransformLoader;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*
|
|
7
|
+
* @format
|
|
8
|
+
* @oncall web_perf_infra
|
|
9
|
+
*/
|
|
10
|
+
import type { File } from '@babel/types';
|
|
11
|
+
import type { ParseResult } from '@babel/parser';
|
|
12
|
+
import type { RewriteScriptOption } from '../ScriptRewriteManager';
|
|
13
|
+
import BaseAstTransform from '../BaseAstTransform';
|
|
14
|
+
export default class InjectSourceInfoTranform extends BaseAstTransform {
|
|
15
|
+
transform(ast: ParseResult<File>, options?: RewriteScriptOption): Promise<void>;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=InjectSourceInfoTranform.d.ts.map
|