@memlab/api 1.0.21 → 1.0.23
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/dist/API.d.ts
CHANGED
|
@@ -138,7 +138,7 @@ export declare function run(runOptions?: RunOptions): Promise<RunResult>;
|
|
|
138
138
|
*/
|
|
139
139
|
export declare function takeSnapshots(options?: RunOptions): Promise<BrowserInteractionResultReader>;
|
|
140
140
|
/**
|
|
141
|
-
* This API finds memory leaks by analyzing heap snapshot(s)
|
|
141
|
+
* This API finds memory leaks by analyzing heap snapshot(s).
|
|
142
142
|
* This is equivalent to `memlab find-leaks` in CLI.
|
|
143
143
|
*
|
|
144
144
|
* @param runResult return value of a browser interaction run
|
|
@@ -157,6 +157,21 @@ export declare function takeSnapshots(options?: RunOptions): Promise<BrowserInte
|
|
|
157
157
|
* ```
|
|
158
158
|
*/
|
|
159
159
|
export declare function findLeaks(runResult: BrowserInteractionResultReader): Promise<ISerializedInfo[]>;
|
|
160
|
+
/**
|
|
161
|
+
* This API finds memory leaks by analyzing specified heap snapshots.
|
|
162
|
+
* This is equivalent to `memlab find-leaks` with
|
|
163
|
+
* the `--baseline`, `--target`, and `--final` flags in CLI.
|
|
164
|
+
*
|
|
165
|
+
* @param baselineSnapshot the file path of the baseline heap snapshot
|
|
166
|
+
* @param targetSnapshot the file path of the target heap snapshot
|
|
167
|
+
* @param finalSnapshot the file path of the final heap snapshot
|
|
168
|
+
* @param options optionally, you can specify a working
|
|
169
|
+
* directory (other than the default one) for heap analysis
|
|
170
|
+
* @returns leak traces detected and clustered from the browser interaction
|
|
171
|
+
*/
|
|
172
|
+
export declare function findLeaksBySnapshotFilePaths(baselineSnapshot: string, targetSnapshot: string, finalSnapshot: string, options?: {
|
|
173
|
+
workDir?: string;
|
|
174
|
+
}): Promise<ISerializedInfo[]>;
|
|
160
175
|
/**
|
|
161
176
|
* This API analyzes heap snapshot(s) with a specified heap analysis.
|
|
162
177
|
* This is equivalent to `memlab analyze` in CLI.
|
|
@@ -169,7 +184,7 @@ export declare function findLeaks(runResult: BrowserInteractionResultReader): Pr
|
|
|
169
184
|
* analysis class you are using for `heapAnalyzer`.
|
|
170
185
|
* * **Examples**:
|
|
171
186
|
* ```javascript
|
|
172
|
-
* const {takeSnapshots, StringAnalysis} = require('@memlab/api');
|
|
187
|
+
* const {analyze, takeSnapshots, StringAnalysis} = require('@memlab/api');
|
|
173
188
|
*
|
|
174
189
|
* (async function () {
|
|
175
190
|
* const scenario = {
|
package/dist/API.js
CHANGED
|
@@ -21,7 +21,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
21
21
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
22
22
|
};
|
|
23
23
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
-
exports.testInBrowser = exports.warmup = exports.analyze = exports.findLeaks = exports.takeSnapshots = exports.run = exports.warmupAndTakeSnapshots = void 0;
|
|
24
|
+
exports.testInBrowser = exports.warmup = exports.analyze = exports.findLeaksBySnapshotFilePaths = exports.findLeaks = exports.takeSnapshots = exports.run = exports.warmupAndTakeSnapshots = void 0;
|
|
25
25
|
const core_1 = require("@memlab/core");
|
|
26
26
|
const e2e_1 = require("@memlab/e2e");
|
|
27
27
|
const APIUtils_1 = __importDefault(require("./lib/APIUtils"));
|
|
@@ -125,7 +125,7 @@ function takeSnapshots(options = {}) {
|
|
|
125
125
|
}
|
|
126
126
|
exports.takeSnapshots = takeSnapshots;
|
|
127
127
|
/**
|
|
128
|
-
* This API finds memory leaks by analyzing heap snapshot(s)
|
|
128
|
+
* This API finds memory leaks by analyzing heap snapshot(s).
|
|
129
129
|
* This is equivalent to `memlab find-leaks` in CLI.
|
|
130
130
|
*
|
|
131
131
|
* @param runResult return value of a browser interaction run
|
|
@@ -152,6 +152,32 @@ function findLeaks(runResult) {
|
|
|
152
152
|
});
|
|
153
153
|
}
|
|
154
154
|
exports.findLeaks = findLeaks;
|
|
155
|
+
/**
|
|
156
|
+
* This API finds memory leaks by analyzing specified heap snapshots.
|
|
157
|
+
* This is equivalent to `memlab find-leaks` with
|
|
158
|
+
* the `--baseline`, `--target`, and `--final` flags in CLI.
|
|
159
|
+
*
|
|
160
|
+
* @param baselineSnapshot the file path of the baseline heap snapshot
|
|
161
|
+
* @param targetSnapshot the file path of the target heap snapshot
|
|
162
|
+
* @param finalSnapshot the file path of the final heap snapshot
|
|
163
|
+
* @param options optionally, you can specify a working
|
|
164
|
+
* directory (other than the default one) for heap analysis
|
|
165
|
+
* @returns leak traces detected and clustered from the browser interaction
|
|
166
|
+
*/
|
|
167
|
+
function findLeaksBySnapshotFilePaths(baselineSnapshot, targetSnapshot, finalSnapshot, options = {}) {
|
|
168
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
169
|
+
core_1.config.useExternalSnapshot = true;
|
|
170
|
+
core_1.config.externalSnapshotFilePaths = [
|
|
171
|
+
baselineSnapshot,
|
|
172
|
+
targetSnapshot,
|
|
173
|
+
finalSnapshot,
|
|
174
|
+
];
|
|
175
|
+
core_1.fileManager.initDirs(core_1.config, { workDir: options.workDir });
|
|
176
|
+
core_1.config.chaseWeakMapEdge = false;
|
|
177
|
+
return yield core_1.analysis.checkLeak();
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
exports.findLeaksBySnapshotFilePaths = findLeaksBySnapshotFilePaths;
|
|
155
181
|
/**
|
|
156
182
|
* This API analyzes heap snapshot(s) with a specified heap analysis.
|
|
157
183
|
* This is equivalent to `memlab analyze` in CLI.
|
|
@@ -164,7 +190,7 @@ exports.findLeaks = findLeaks;
|
|
|
164
190
|
* analysis class you are using for `heapAnalyzer`.
|
|
165
191
|
* * **Examples**:
|
|
166
192
|
* ```javascript
|
|
167
|
-
* const {takeSnapshots, StringAnalysis} = require('@memlab/api');
|
|
193
|
+
* const {analyze, takeSnapshots, StringAnalysis} = require('@memlab/api');
|
|
168
194
|
*
|
|
169
195
|
* (async function () {
|
|
170
196
|
* const scenario = {
|
|
@@ -309,30 +335,37 @@ function testInBrowser(options = {}) {
|
|
|
309
335
|
const testPlanner = (_b = options.testPlanner) !== null && _b !== void 0 ? _b : e2e_1.defaultTestPlanner;
|
|
310
336
|
let interactionManager = null;
|
|
311
337
|
let xvfb = null;
|
|
338
|
+
let browser = null;
|
|
339
|
+
let page = null;
|
|
340
|
+
let maybeError = null;
|
|
312
341
|
try {
|
|
313
342
|
xvfb = e2e_1.Xvfb.startIfEnabled();
|
|
314
|
-
|
|
343
|
+
browser = yield APIUtils_1.default.getBrowser();
|
|
315
344
|
const pages = yield browser.pages();
|
|
316
|
-
|
|
345
|
+
page = pages.length > 0 ? pages[0] : yield browser.newPage();
|
|
346
|
+
// create and configure web page interaction manager
|
|
317
347
|
interactionManager = new e2e_1.E2EInteractionManager(page, browser);
|
|
318
348
|
if (options.evalInBrowserAfterInitLoad) {
|
|
319
349
|
interactionManager.setEvalFuncAfterInitLoad(options.evalInBrowserAfterInitLoad);
|
|
320
350
|
}
|
|
321
351
|
const visitPlan = testPlanner.getVisitPlan();
|
|
352
|
+
// setup page configuration
|
|
322
353
|
config.setDevice(visitPlan.device);
|
|
323
354
|
autoDismissDialog(page);
|
|
324
355
|
yield initBrowserInfoInConfig(browser);
|
|
325
356
|
core_1.browserInfo.monitorWebConsole(page);
|
|
326
357
|
yield setupPage(page, options);
|
|
358
|
+
// interact with the web page and take heap snapshots
|
|
327
359
|
yield interactionManager.visitAndGetSnapshots(options);
|
|
328
|
-
yield core_1.utils.closePuppeteer(browser, [page]);
|
|
329
360
|
}
|
|
330
361
|
catch (ex) {
|
|
331
|
-
|
|
332
|
-
core_1.utils.checkUninstalledLibrary(
|
|
333
|
-
core_1.info.error(error.message);
|
|
362
|
+
maybeError = core_1.utils.getError(ex);
|
|
363
|
+
core_1.utils.checkUninstalledLibrary(maybeError);
|
|
334
364
|
}
|
|
335
365
|
finally {
|
|
366
|
+
if (browser && page) {
|
|
367
|
+
yield core_1.utils.closePuppeteer(browser, [page]);
|
|
368
|
+
}
|
|
336
369
|
if (interactionManager) {
|
|
337
370
|
interactionManager.clearCDPSession();
|
|
338
371
|
}
|
|
@@ -343,6 +376,9 @@ function testInBrowser(options = {}) {
|
|
|
343
376
|
}
|
|
344
377
|
});
|
|
345
378
|
}
|
|
379
|
+
if (maybeError != null) {
|
|
380
|
+
core_1.utils.haltOrThrow(maybeError);
|
|
381
|
+
}
|
|
346
382
|
}
|
|
347
383
|
});
|
|
348
384
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
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 {};
|
|
11
|
+
//# sourceMappingURL=E2EFindMemoryLeaksWithSnapshotFiles.test.d.ts.map
|
|
@@ -0,0 +1,100 @@
|
|
|
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 path_1 = __importDefault(require("path"));
|
|
26
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
27
|
+
const index_1 = require("../../index");
|
|
28
|
+
const E2ETestSettings_1 = require("./lib/E2ETestSettings");
|
|
29
|
+
beforeEach(E2ETestSettings_1.testSetup);
|
|
30
|
+
function injectDetachedDOMElements() {
|
|
31
|
+
// @ts-ignore
|
|
32
|
+
window.injectHookForLink4 = () => {
|
|
33
|
+
class TestObject {
|
|
34
|
+
}
|
|
35
|
+
const arr = [];
|
|
36
|
+
for (let i = 0; i < 23; ++i) {
|
|
37
|
+
arr.push(document.createElement('div'));
|
|
38
|
+
}
|
|
39
|
+
// @ts-ignore
|
|
40
|
+
window.__injectedValue = arr;
|
|
41
|
+
// @ts-ignore
|
|
42
|
+
window._path_1 = { x: { y: document.createElement('div') } };
|
|
43
|
+
// @ts-ignore
|
|
44
|
+
window._path_2 = new Set([document.createElement('div')]);
|
|
45
|
+
// @ts-ignore
|
|
46
|
+
window._randomObject = [new TestObject()];
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
test('leak detector can find detached DOM elements', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
50
|
+
const result = yield (0, index_1.takeSnapshots)({
|
|
51
|
+
scenario: E2ETestSettings_1.scenario,
|
|
52
|
+
evalInBrowserAfterInitLoad: injectDetachedDOMElements,
|
|
53
|
+
});
|
|
54
|
+
// copy heap snapshots to a different location
|
|
55
|
+
const snapshotFiles = result.getSnapshotFiles();
|
|
56
|
+
const tmpDir = core_1.fileManager.generateTmpHeapDir();
|
|
57
|
+
const newSnapshotFiles = [];
|
|
58
|
+
snapshotFiles.forEach(file => {
|
|
59
|
+
const newFile = path_1.default.join(tmpDir, path_1.default.basename(file));
|
|
60
|
+
newSnapshotFiles.push(newFile);
|
|
61
|
+
fs_extra_1.default.moveSync(file, newFile);
|
|
62
|
+
});
|
|
63
|
+
fs_extra_1.default.rmdirSync(result.getRootDirectory(), { recursive: true });
|
|
64
|
+
// find memory leaks with the new snapshot files
|
|
65
|
+
const leaks = yield (0, index_1.findLeaksBySnapshotFilePaths)(newSnapshotFiles[0], newSnapshotFiles[1], newSnapshotFiles[2]);
|
|
66
|
+
// detected all different leak trace cluster
|
|
67
|
+
expect(leaks.length >= 1).toBe(true);
|
|
68
|
+
// expect all traces are found
|
|
69
|
+
expect(leaks.some(leak => JSON.stringify(leak).includes('__injectedValue')));
|
|
70
|
+
expect(leaks.some(leak => JSON.stringify(leak).includes('_path_1')));
|
|
71
|
+
expect(leaks.some(leak => JSON.stringify(leak).includes('_path_2')));
|
|
72
|
+
// finally clean up the temporary directory
|
|
73
|
+
fs_extra_1.default.rmdirSync(tmpDir, { recursive: true });
|
|
74
|
+
}), E2ETestSettings_1.testTimeout);
|
|
75
|
+
test('takeSnapshot API allows to throw and catch exceptions from scenario', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
76
|
+
const scenarioThatThrows = Object.assign({}, E2ETestSettings_1.scenario);
|
|
77
|
+
const errorMessage = 'throw from scenario.action';
|
|
78
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
79
|
+
scenarioThatThrows.action = (_page) => __awaiter(void 0, void 0, void 0, function* () {
|
|
80
|
+
throw new Error(errorMessage);
|
|
81
|
+
});
|
|
82
|
+
expect.assertions(1);
|
|
83
|
+
yield expect((0, index_1.takeSnapshots)({
|
|
84
|
+
scenario: scenarioThatThrows,
|
|
85
|
+
evalInBrowserAfterInitLoad: injectDetachedDOMElements,
|
|
86
|
+
})).rejects.toThrow(errorMessage);
|
|
87
|
+
}), E2ETestSettings_1.testTimeout);
|
|
88
|
+
test('run API allows to throw and catch exceptions from scenario', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
89
|
+
const scenarioThatThrows = Object.assign({}, E2ETestSettings_1.scenario);
|
|
90
|
+
const errorMessage = 'throw from scenario.action';
|
|
91
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
92
|
+
scenarioThatThrows.action = (_page) => __awaiter(void 0, void 0, void 0, function* () {
|
|
93
|
+
throw new Error(errorMessage);
|
|
94
|
+
});
|
|
95
|
+
expect.assertions(1);
|
|
96
|
+
yield expect((0, index_1.run)({
|
|
97
|
+
scenario: scenarioThatThrows,
|
|
98
|
+
evalInBrowserAfterInitLoad: injectDetachedDOMElements,
|
|
99
|
+
})).rejects.toThrow(errorMessage);
|
|
100
|
+
}), E2ETestSettings_1.testTimeout);
|