@memlab/api 1.0.22 → 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.
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.
@@ -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
- const browser = yield APIUtils_1.default.getBrowser();
343
+ browser = yield APIUtils_1.default.getBrowser();
315
344
  const pages = yield browser.pages();
316
- const page = pages.length > 0 ? pages[0] : yield browser.newPage();
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
- const error = core_1.utils.getError(ex);
332
- core_1.utils.checkUninstalledLibrary(error);
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memlab/api",
3
- "version": "1.0.22",
3
+ "version": "1.0.23",
4
4
  "license": "MIT",
5
5
  "description": "memlab API",
6
6
  "author": "Liang Gong <lgong@fb.com>",