@memlab/api 1.0.22 → 1.0.24

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.
Files changed (48) hide show
  1. package/dist/API.d.ts +26 -6
  2. package/dist/API.d.ts.map +1 -0
  3. package/dist/API.js +50 -10
  4. package/dist/__tests__/API/E2EBasicAnalysis.test.d.ts.map +1 -0
  5. package/dist/__tests__/API/E2EDetachedDOMAnalysis.test.d.ts.map +1 -0
  6. package/dist/__tests__/API/E2EDuplicateObjectAnalysis.test.d.ts.map +1 -0
  7. package/dist/__tests__/API/E2EFindLeaks.example.d.ts.map +1 -0
  8. package/dist/__tests__/API/E2EFindMemoryLeaks.test.d.ts.map +1 -0
  9. package/dist/__tests__/API/E2EFindMemoryLeaksWithSnapshotFiles.test.d.ts +11 -0
  10. package/dist/__tests__/API/E2EFindMemoryLeaksWithSnapshotFiles.test.d.ts.map +1 -0
  11. package/dist/__tests__/API/E2EFindMemoryLeaksWithSnapshotFiles.test.js +100 -0
  12. package/dist/__tests__/API/E2EInjectLeaksWithSetup.test.d.ts.map +1 -0
  13. package/dist/__tests__/API/E2EReloadInSetup.test.d.ts.map +1 -0
  14. package/dist/__tests__/API/E2EResultReader.test.d.ts.map +1 -0
  15. package/dist/__tests__/API/E2EResultReader.test.js +36 -0
  16. package/dist/__tests__/API/E2ERunMultipleSnapshots.example.d.ts.map +1 -0
  17. package/dist/__tests__/API/E2ERunSingleSnapshot.example.d.ts.map +1 -0
  18. package/dist/__tests__/API/E2EShapeUnboundGrowthAnalysis.test.d.ts.map +1 -0
  19. package/dist/__tests__/API/E2EStringAnalysis.test.d.ts.map +1 -0
  20. package/dist/__tests__/API/{E2EFindWebWorkerLeaks.test.d.ts → E2ETestScenarioCallback.test.d.ts} +1 -1
  21. package/dist/__tests__/API/E2ETestScenarioCallback.test.d.ts.map +1 -0
  22. package/dist/__tests__/API/E2ETestScenarioCallback.test.js +135 -0
  23. package/dist/__tests__/API/lib/E2ETestSettings.d.ts.map +1 -0
  24. package/dist/__tests__/heap/E2EHeapParser.test.d.ts.map +1 -0
  25. package/dist/__tests__/heap/examples/example-1.d.ts.map +1 -0
  26. package/dist/__tests__/heap/examples/example-2.d.ts.map +1 -0
  27. package/dist/__tests__/heap/examples/example-3.d.ts.map +1 -0
  28. package/dist/__tests__/heap/examples/example-4.d.ts.map +1 -0
  29. package/dist/__tests__/heap/examples/example-5.test.d.ts.map +1 -0
  30. package/dist/__tests__/heap/examples/example-6.d.ts.map +1 -0
  31. package/dist/__tests__/heap/examples/example-7.test.d.ts.map +1 -0
  32. package/dist/__tests__/heap/lib/HeapParserTestUtils.d.ts.map +1 -0
  33. package/dist/__tests__/heap/lib/HeapParserTestUtils.js +1 -0
  34. package/dist/__tests__/packages/heap-analysis.test.d.ts.map +1 -0
  35. package/dist/index.d.ts +1 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +3 -1
  38. package/dist/lib/APIUtils.d.ts.map +1 -0
  39. package/dist/lib/APIUtils.js +1 -0
  40. package/dist/result-reader/BaseResultReader.d.ts.map +1 -0
  41. package/dist/result-reader/BrowserInteractionResultReader.d.ts +1 -1
  42. package/dist/result-reader/BrowserInteractionResultReader.d.ts.map +1 -0
  43. package/dist/result-reader/BrowserInteractionResultReader.js +1 -1
  44. package/dist/result-reader/SnapshotResultReader.d.ts +115 -0
  45. package/dist/result-reader/SnapshotResultReader.d.ts.map +1 -0
  46. package/dist/result-reader/SnapshotResultReader.js +156 -0
  47. package/package.json +5 -4
  48. package/dist/__tests__/API/E2EFindWebWorkerLeaks.test.js +0 -69
package/dist/API.d.ts CHANGED
@@ -13,10 +13,11 @@ import { MemLabConfig } from '@memlab/core';
13
13
  import { TestPlanner } from '@memlab/e2e';
14
14
  import { BaseAnalysis } from '@memlab/heap-analysis';
15
15
  import BrowserInteractionResultReader from './result-reader/BrowserInteractionResultReader';
16
+ import BaseResultReader from './result-reader/BaseResultReader';
16
17
  /**
17
18
  * Options for configuring browser interaction run, all fields are optional
18
19
  */
19
- export declare type RunOptions = {
20
+ export type RunOptions = {
20
21
  /**
21
22
  * test scenario specifying how to interact with browser
22
23
  * (for more details view {@link IScenario})
@@ -52,11 +53,15 @@ export declare type RunOptions = {
52
53
  * means analyzing the heap of the web worker with name: `'workerTitle'`.
53
54
  */
54
55
  webWorker?: Optional<string>;
56
+ /**
57
+ * skip warmup page load for the target web app
58
+ */
59
+ skipWarmup?: boolean;
55
60
  };
56
61
  /**
57
62
  * A data structure holding the result of the {@link run} API call.
58
63
  */
59
- export declare type RunResult = {
64
+ export type RunResult = {
60
65
  /**
61
66
  * leak traces detected and clustered from the browser interaction
62
67
  */
@@ -70,7 +75,7 @@ export declare type RunResult = {
70
75
  * Options for memlab inter-package API calls
71
76
  * @internal
72
77
  */
73
- export declare type APIOptions = {
78
+ export type APIOptions = {
74
79
  testPlanner?: TestPlanner;
75
80
  cache?: boolean;
76
81
  config?: MemLabConfig;
@@ -138,7 +143,7 @@ export declare function run(runOptions?: RunOptions): Promise<RunResult>;
138
143
  */
139
144
  export declare function takeSnapshots(options?: RunOptions): Promise<BrowserInteractionResultReader>;
140
145
  /**
141
- * This API finds memory leaks by analyzing heap snapshot(s)
146
+ * This API finds memory leaks by analyzing heap snapshot(s).
142
147
  * This is equivalent to `memlab find-leaks` in CLI.
143
148
  *
144
149
  * @param runResult return value of a browser interaction run
@@ -156,7 +161,22 @@ export declare function takeSnapshots(options?: RunOptions): Promise<BrowserInte
156
161
  * })();
157
162
  * ```
158
163
  */
159
- export declare function findLeaks(runResult: BrowserInteractionResultReader): Promise<ISerializedInfo[]>;
164
+ export declare function findLeaks(runResult: BaseResultReader): Promise<ISerializedInfo[]>;
165
+ /**
166
+ * This API finds memory leaks by analyzing specified heap snapshots.
167
+ * This is equivalent to `memlab find-leaks` with
168
+ * the `--baseline`, `--target`, and `--final` flags in CLI.
169
+ *
170
+ * @param baselineSnapshot the file path of the baseline heap snapshot
171
+ * @param targetSnapshot the file path of the target heap snapshot
172
+ * @param finalSnapshot the file path of the final heap snapshot
173
+ * @param options optionally, you can specify a working
174
+ * directory (other than the default one) for heap analysis
175
+ * @returns leak traces detected and clustered from the browser interaction
176
+ */
177
+ export declare function findLeaksBySnapshotFilePaths(baselineSnapshot: string, targetSnapshot: string, finalSnapshot: string, options?: {
178
+ workDir?: string;
179
+ }): Promise<ISerializedInfo[]>;
160
180
  /**
161
181
  * This API analyzes heap snapshot(s) with a specified heap analysis.
162
182
  * This is equivalent to `memlab analyze` in CLI.
@@ -181,7 +201,7 @@ export declare function findLeaks(runResult: BrowserInteractionResultReader): Pr
181
201
  * })();
182
202
  * ```
183
203
  */
184
- export declare function analyze(runResult: BrowserInteractionResultReader, heapAnalyzer: BaseAnalysis, args?: ParsedArgs): Promise<void>;
204
+ export declare function analyze(runResult: BaseResultReader, heapAnalyzer: BaseAnalysis, args?: ParsedArgs): Promise<void>;
185
205
  /**
186
206
  * This warms up web server by sending web requests to the web sever.
187
207
  * This is equivalent to running `memlab warmup` in CLI.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"API.d.ts","sourceRoot":"","sources":["../src/API.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,UAAU,CAAC;AACzC,OAAO,KAAK,EACV,WAAW,EACX,eAAe,EACf,SAAS,EAGT,QAAQ,EACT,MAAM,cAAc,CAAC;AAEtB,OAAO,EAML,YAAY,EAEb,MAAM,cAAc,CAAC;AACtB,OAAO,EAEL,WAAW,EAGZ,MAAM,aAAa,CAAC;AACrB,OAAO,EAAC,YAAY,EAAC,MAAM,uBAAuB,CAAC;AAEnD,OAAO,8BAA8B,MAAM,gDAAgD,CAAC;AAC5F,OAAO,gBAAgB,MAAM,kCAAkC,CAAC;AAEhE;;GAEG;AACH,MAAM,MAAM,UAAU,GAAG;IACvB;;;OAGG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,wCAAwC;IACxC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,0BAA0B,CAAC,EAAE,WAAW,CAAC;IACzC;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B;;;;;;;;OAQG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC7B;;OAEG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,SAAS,GAAG;IACtB;;OAEG;IACH,KAAK,EAAE,eAAe,EAAE,CAAC;IACzB;;OAEG;IACH,SAAS,EAAE,8BAA8B,CAAC;CAC3C,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,UAAU,GAAG;IAGvB,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,0BAA0B,CAAC,EAAE,WAAW,CAAC;CAC1C,CAAC;AAEF;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,sBAAsB,CAC1C,OAAO,GAAE,UAAe,GACvB,OAAO,CAAC,8BAA8B,CAAC,CAWzC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,GAAG,CAAC,UAAU,GAAE,UAAe,GAAG,OAAO,CAAC,SAAS,CAAC,CAazE;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,aAAa,CACjC,OAAO,GAAE,UAAe,GACvB,OAAO,CAAC,8BAA8B,CAAC,CAQzC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,SAAS,CAC7B,SAAS,EAAE,gBAAgB,GAC1B,OAAO,CAAC,eAAe,EAAE,CAAC,CAK5B;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,4BAA4B,CAChD,gBAAgB,EAAE,MAAM,EACxB,cAAc,EAAE,MAAM,EACtB,aAAa,EAAE,MAAM,EACrB,OAAO,GAAE;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAM,GAC/B,OAAO,CAAC,eAAe,EAAE,CAAC,CAU5B;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAsB,OAAO,CAC3B,SAAS,EAAE,gBAAgB,EAC3B,YAAY,EAAE,YAAY,EAC1B,IAAI,GAAE,UAAoB,GACzB,OAAO,CAAC,IAAI,CAAC,CAIf;AAED;;;;;;GAMG;AACH,wBAAsB,MAAM,CAAC,OAAO,GAAE,UAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAuCpE;AAmED;;;GAGG;AACH,wBAAsB,aAAa,CAAC,OAAO,GAAE,UAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAsD3E"}
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"));
@@ -52,7 +52,9 @@ function warmupAndTakeSnapshots(options = {}) {
52
52
  config.scenario = options.scenario;
53
53
  const testPlanner = new e2e_1.TestPlanner({ config });
54
54
  const { evalInBrowserAfterInitLoad } = options;
55
- yield warmup({ testPlanner, config, evalInBrowserAfterInitLoad });
55
+ if (!options.skipWarmup) {
56
+ yield warmup({ testPlanner, config, evalInBrowserAfterInitLoad });
57
+ }
56
58
  yield testInBrowser({ testPlanner, config, evalInBrowserAfterInitLoad });
57
59
  return BrowserInteractionResultReader_1.default.from(config.workDir);
58
60
  });
@@ -86,7 +88,9 @@ function run(runOptions = {}) {
86
88
  config.scenario = runOptions.scenario;
87
89
  const testPlanner = new e2e_1.TestPlanner({ config });
88
90
  const { evalInBrowserAfterInitLoad } = runOptions;
89
- yield warmup({ testPlanner, config, evalInBrowserAfterInitLoad });
91
+ if (!runOptions.skipWarmup) {
92
+ yield warmup({ testPlanner, config, evalInBrowserAfterInitLoad });
93
+ }
90
94
  yield testInBrowser({ testPlanner, config, evalInBrowserAfterInitLoad });
91
95
  const runResult = BrowserInteractionResultReader_1.default.from(config.workDir);
92
96
  const leaks = yield findLeaks(runResult);
@@ -125,7 +129,7 @@ function takeSnapshots(options = {}) {
125
129
  }
126
130
  exports.takeSnapshots = takeSnapshots;
127
131
  /**
128
- * This API finds memory leaks by analyzing heap snapshot(s)
132
+ * This API finds memory leaks by analyzing heap snapshot(s).
129
133
  * This is equivalent to `memlab find-leaks` in CLI.
130
134
  *
131
135
  * @param runResult return value of a browser interaction run
@@ -152,6 +156,32 @@ function findLeaks(runResult) {
152
156
  });
153
157
  }
154
158
  exports.findLeaks = findLeaks;
159
+ /**
160
+ * This API finds memory leaks by analyzing specified heap snapshots.
161
+ * This is equivalent to `memlab find-leaks` with
162
+ * the `--baseline`, `--target`, and `--final` flags in CLI.
163
+ *
164
+ * @param baselineSnapshot the file path of the baseline heap snapshot
165
+ * @param targetSnapshot the file path of the target heap snapshot
166
+ * @param finalSnapshot the file path of the final heap snapshot
167
+ * @param options optionally, you can specify a working
168
+ * directory (other than the default one) for heap analysis
169
+ * @returns leak traces detected and clustered from the browser interaction
170
+ */
171
+ function findLeaksBySnapshotFilePaths(baselineSnapshot, targetSnapshot, finalSnapshot, options = {}) {
172
+ return __awaiter(this, void 0, void 0, function* () {
173
+ core_1.config.useExternalSnapshot = true;
174
+ core_1.config.externalSnapshotFilePaths = [
175
+ baselineSnapshot,
176
+ targetSnapshot,
177
+ finalSnapshot,
178
+ ];
179
+ core_1.fileManager.initDirs(core_1.config, { workDir: options.workDir });
180
+ core_1.config.chaseWeakMapEdge = false;
181
+ return yield core_1.analysis.checkLeak();
182
+ });
183
+ }
184
+ exports.findLeaksBySnapshotFilePaths = findLeaksBySnapshotFilePaths;
155
185
  /**
156
186
  * This API analyzes heap snapshot(s) with a specified heap analysis.
157
187
  * This is equivalent to `memlab analyze` in CLI.
@@ -309,30 +339,37 @@ function testInBrowser(options = {}) {
309
339
  const testPlanner = (_b = options.testPlanner) !== null && _b !== void 0 ? _b : e2e_1.defaultTestPlanner;
310
340
  let interactionManager = null;
311
341
  let xvfb = null;
342
+ let browser = null;
343
+ let page = null;
344
+ let maybeError = null;
312
345
  try {
313
346
  xvfb = e2e_1.Xvfb.startIfEnabled();
314
- const browser = yield APIUtils_1.default.getBrowser();
347
+ browser = yield APIUtils_1.default.getBrowser();
315
348
  const pages = yield browser.pages();
316
- const page = pages.length > 0 ? pages[0] : yield browser.newPage();
349
+ page = pages.length > 0 ? pages[0] : yield browser.newPage();
350
+ // create and configure web page interaction manager
317
351
  interactionManager = new e2e_1.E2EInteractionManager(page, browser);
318
352
  if (options.evalInBrowserAfterInitLoad) {
319
353
  interactionManager.setEvalFuncAfterInitLoad(options.evalInBrowserAfterInitLoad);
320
354
  }
321
355
  const visitPlan = testPlanner.getVisitPlan();
356
+ // setup page configuration
322
357
  config.setDevice(visitPlan.device);
323
358
  autoDismissDialog(page);
324
359
  yield initBrowserInfoInConfig(browser);
325
360
  core_1.browserInfo.monitorWebConsole(page);
326
361
  yield setupPage(page, options);
362
+ // interact with the web page and take heap snapshots
327
363
  yield interactionManager.visitAndGetSnapshots(options);
328
- yield core_1.utils.closePuppeteer(browser, [page]);
329
364
  }
330
365
  catch (ex) {
331
- const error = core_1.utils.getError(ex);
332
- core_1.utils.checkUninstalledLibrary(error);
333
- core_1.info.error(error.message);
366
+ maybeError = core_1.utils.getError(ex);
367
+ core_1.utils.checkUninstalledLibrary(maybeError);
334
368
  }
335
369
  finally {
370
+ if (browser && page) {
371
+ yield core_1.utils.closePuppeteer(browser, [page]);
372
+ }
336
373
  if (interactionManager) {
337
374
  interactionManager.clearCDPSession();
338
375
  }
@@ -343,6 +380,9 @@ function testInBrowser(options = {}) {
343
380
  }
344
381
  });
345
382
  }
383
+ if (maybeError != null) {
384
+ core_1.utils.haltOrThrow(maybeError);
385
+ }
346
386
  }
347
387
  });
348
388
  }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"E2EBasicAnalysis.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/API/E2EBasicAnalysis.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"E2EDetachedDOMAnalysis.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/API/E2EDetachedDOMAnalysis.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"E2EDuplicateObjectAnalysis.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/API/E2EDuplicateObjectAnalysis.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"E2EFindLeaks.example.d.ts","sourceRoot":"","sources":["../../../src/__tests__/API/E2EFindLeaks.example.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"E2EFindMemoryLeaks.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/API/E2EFindMemoryLeaks.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG"}
@@ -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 @@
1
+ {"version":3,"file":"E2EFindMemoryLeaksWithSnapshotFiles.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/API/E2EFindMemoryLeaksWithSnapshotFiles.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG"}
@@ -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);
@@ -0,0 +1 @@
1
+ {"version":3,"file":"E2EInjectLeaksWithSetup.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/API/E2EInjectLeaksWithSetup.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"E2EReloadInSetup.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/API/E2EReloadInSetup.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"E2EResultReader.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/API/E2EResultReader.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG"}
@@ -26,6 +26,7 @@ const fs_1 = __importDefault(require("fs"));
26
26
  const BrowserInteractionResultReader_1 = __importDefault(require("../../result-reader/BrowserInteractionResultReader"));
27
27
  const index_1 = require("../../index");
28
28
  const E2ETestSettings_1 = require("./lib/E2ETestSettings");
29
+ const SnapshotResultReader_1 = __importDefault(require("../../result-reader/SnapshotResultReader"));
29
30
  beforeEach(E2ETestSettings_1.testSetup);
30
31
  function inject() {
31
32
  // @ts-ignore
@@ -70,3 +71,38 @@ test('ResultReader.from is working as expected', () => __awaiter(void 0, void 0,
70
71
  });
71
72
  checkResultReader(BrowserInteractionResultReader_1.default.from(result.getRootDirectory()));
72
73
  }), E2ETestSettings_1.testTimeout);
74
+ function injectDetachedDOMElements() {
75
+ // @ts-ignore
76
+ window.injectHookForLink4 = () => {
77
+ class TestObject {
78
+ }
79
+ const arr = [];
80
+ for (let i = 0; i < 23; ++i) {
81
+ arr.push(document.createElement('div'));
82
+ }
83
+ // @ts-ignore
84
+ window.__injectedValue = arr;
85
+ // @ts-ignore
86
+ window._path_1 = { x: { y: document.createElement('div') } };
87
+ // @ts-ignore
88
+ window._path_2 = new Set([document.createElement('div')]);
89
+ // @ts-ignore
90
+ window._randomObject = [new TestObject()];
91
+ };
92
+ }
93
+ test('SnapshotResultReader is working as expected', () => __awaiter(void 0, void 0, void 0, function* () {
94
+ const result = yield (0, index_1.warmupAndTakeSnapshots)({
95
+ scenario: E2ETestSettings_1.scenario,
96
+ evalInBrowserAfterInitLoad: injectDetachedDOMElements,
97
+ });
98
+ const snapshotFiles = result.getSnapshotFiles();
99
+ expect(snapshotFiles.length).toBe(3);
100
+ const reader = SnapshotResultReader_1.default.fromSnapshots(snapshotFiles[0], snapshotFiles[1], snapshotFiles[2]);
101
+ const leaks = yield (0, index_1.findLeaks)(reader);
102
+ // detected all different leak trace cluster
103
+ expect(leaks.length >= 1).toBe(true);
104
+ // expect all traces are found
105
+ expect(leaks.some(leak => JSON.stringify(leak).includes('__injectedValue')));
106
+ expect(leaks.some(leak => JSON.stringify(leak).includes('_path_1')));
107
+ expect(leaks.some(leak => JSON.stringify(leak).includes('_path_2')));
108
+ }), E2ETestSettings_1.testTimeout);
@@ -0,0 +1 @@
1
+ {"version":3,"file":"E2ERunMultipleSnapshots.example.d.ts","sourceRoot":"","sources":["../../../src/__tests__/API/E2ERunMultipleSnapshots.example.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"E2ERunSingleSnapshot.example.d.ts","sourceRoot":"","sources":["../../../src/__tests__/API/E2ERunSingleSnapshot.example.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"E2EShapeUnboundGrowthAnalysis.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/API/E2EShapeUnboundGrowthAnalysis.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"E2EStringAnalysis.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/API/E2EStringAnalysis.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG"}
@@ -8,4 +8,4 @@
8
8
  * @oncall web_perf_infra
9
9
  */
10
10
  export {};
11
- //# sourceMappingURL=E2EFindWebWorkerLeaks.test.d.ts.map
11
+ //# sourceMappingURL=E2ETestScenarioCallback.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"E2ETestScenarioCallback.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/API/E2ETestScenarioCallback.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG"}
@@ -0,0 +1,135 @@
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 index_1 = require("../../index");
22
+ const E2ETestSettings_1 = require("./lib/E2ETestSettings");
23
+ beforeEach(E2ETestSettings_1.testSetup);
24
+ function injectDetachedDOMElements() {
25
+ // @ts-ignore
26
+ window.injectHookForLink4 = () => {
27
+ class TestObject {
28
+ }
29
+ const arr = [];
30
+ for (let i = 0; i < 23; ++i) {
31
+ arr.push(document.createElement('div'));
32
+ }
33
+ // @ts-ignore
34
+ window.__injectedValue = arr;
35
+ // @ts-ignore
36
+ window._path_1 = { x: { y: document.createElement('div') } };
37
+ // @ts-ignore
38
+ window._path_2 = new Set([document.createElement('div')]);
39
+ // @ts-ignore
40
+ window._randomObject = [new TestObject()];
41
+ };
42
+ }
43
+ test('callbacks in test scenarios are called in the right order', () => __awaiter(void 0, void 0, void 0, function* () {
44
+ const actualCalls = [];
45
+ // define a test scenario with all callbacks offered by memlab
46
+ const selfDefinedScenario = {
47
+ app: () => {
48
+ actualCalls.push('app');
49
+ return 'test-spa';
50
+ },
51
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
52
+ beforeInitialPageLoad: (_page) => __awaiter(void 0, void 0, void 0, function* () {
53
+ actualCalls.push('beforeInitialPageLoad');
54
+ }),
55
+ cookies: () => {
56
+ actualCalls.push('cookies');
57
+ return [];
58
+ },
59
+ repeat: () => {
60
+ actualCalls.push('repeat');
61
+ return 0;
62
+ },
63
+ isPageLoaded: (page) => __awaiter(void 0, void 0, void 0, function* () {
64
+ actualCalls.push('isPageLoaded');
65
+ yield page.waitForNavigation({
66
+ // consider navigation to be finished when there are
67
+ // no more than 2 network connections for at least 500 ms.
68
+ waitUntil: 'networkidle2',
69
+ // Maximum navigation time in milliseconds
70
+ timeout: 5000,
71
+ });
72
+ return true;
73
+ }),
74
+ url: () => {
75
+ actualCalls.push('url');
76
+ return '';
77
+ },
78
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
79
+ setup: (_page) => __awaiter(void 0, void 0, void 0, function* () {
80
+ actualCalls.push('setup');
81
+ }),
82
+ action: (page) => __awaiter(void 0, void 0, void 0, function* () {
83
+ actualCalls.push('action');
84
+ yield page.click('[data-testid="link-4"]');
85
+ }),
86
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
87
+ back: (_page) => __awaiter(void 0, void 0, void 0, function* () {
88
+ actualCalls.push('back');
89
+ }),
90
+ beforeLeakFilter: (
91
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
92
+ _snapshot,
93
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
94
+ _leakedNodeIds) => {
95
+ actualCalls.push('beforeLeakFilter');
96
+ },
97
+ leakFilter: (node) => {
98
+ actualCalls.push('leakFilter');
99
+ return node.name === 'TestObject' && node.type === 'object';
100
+ },
101
+ };
102
+ // run test scenario
103
+ yield (0, index_1.run)({
104
+ scenario: selfDefinedScenario,
105
+ evalInBrowserAfterInitLoad: injectDetachedDOMElements,
106
+ skipWarmup: true,
107
+ });
108
+ // squash all leakFilter call into a single identifier
109
+ let normalizedCalls = squash(actualCalls, ['leakFilter', 'isPageLoaded']);
110
+ normalizedCalls = normalizedCalls.slice(normalizedCalls.lastIndexOf('url'));
111
+ // expect all callbacks are called in the right order
112
+ expect(normalizedCalls).toEqual([
113
+ // the first 4 calls (app, cookies, repeat, url) can be in any order
114
+ 'url',
115
+ 'repeat',
116
+ 'app',
117
+ 'cookies',
118
+ // the following calls must be in the exact order listed
119
+ 'beforeInitialPageLoad',
120
+ 'isPageLoaded',
121
+ 'setup',
122
+ 'action',
123
+ 'isPageLoaded',
124
+ 'back',
125
+ 'isPageLoaded',
126
+ 'beforeLeakFilter',
127
+ 'leakFilter',
128
+ ]);
129
+ }), E2ETestSettings_1.testTimeout);
130
+ // Squashes consecutive occurrences of a specified element in
131
+ // an array into a single occurrence of that element
132
+ function squash(arr, elementsToSquash) {
133
+ const squashSet = new Set(elementsToSquash);
134
+ return arr.filter((value, index) => !squashSet.has(value) || value !== arr[index - 1]);
135
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"E2ETestSettings.d.ts","sourceRoot":"","sources":["../../../../src/__tests__/API/lib/E2ETestSettings.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,WAAW,CAAC;AAGpC,eAAO,MAAM,WAAW,QAAgB,CAAC;AAEzC,eAAO,MAAM,mBAAmB;;;;CAAkB,CAAC;AAEnD,eAAO,MAAM,QAAQ;eACV,MAAM;eACN,MAAM;mBACM,IAAI,KAAG,QAAQ,IAAI,CAAC;CAE1C,CAAC;AAEF,eAAO,MAAM,SAAS,QAAO,IAK5B,CAAC;AAGF,wBAAgB,WAAW,IAAI,MAAM,CAEpC"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"E2EHeapParser.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/heap/E2EHeapParser.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAC,QAAQ,EAA2B,MAAM,cAAc,CAAC;AAKrE,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,MAAM;QACd,QAAQ,EAAE,QAAQ,CAAC;KACpB;CACF"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"example-1.d.ts","sourceRoot":"","sources":["../../../../src/__tests__/heap/examples/example-1.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"example-2.d.ts","sourceRoot":"","sources":["../../../../src/__tests__/heap/examples/example-2.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"example-3.d.ts","sourceRoot":"","sources":["../../../../src/__tests__/heap/examples/example-3.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"example-4.d.ts","sourceRoot":"","sources":["../../../../src/__tests__/heap/examples/example-4.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"example-5.test.d.ts","sourceRoot":"","sources":["../../../../src/__tests__/heap/examples/example-5.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"example-6.d.ts","sourceRoot":"","sources":["../../../../src/__tests__/heap/examples/example-6.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"example-7.test.d.ts","sourceRoot":"","sources":["../../../../src/__tests__/heap/examples/example-7.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HeapParserTestUtils.d.ts","sourceRoot":"","sources":["../../../../src/__tests__/heap/lib/HeapParserTestUtils.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,cAAc,CAAC;AAUhD,wBAAsB,kBAAkB,CACtC,YAAY,EAAE,MAAM,IAAI,EACxB,eAAe,EAAE,CAAC,QAAQ,EAAE,aAAa,KAAK,OAAO,GACpD,OAAO,CAAC,IAAI,CAAC,CAGf"}
@@ -69,6 +69,7 @@ function saveSnapshotToFile(page, file) {
69
69
  const TEST_URL = 'about:blank';
70
70
  function dumpHeap(snapshotFile, leakInjector) {
71
71
  return __awaiter(this, void 0, void 0, function* () {
72
+ core_1.utils.tryToMutePuppeteerWarning();
72
73
  const browser = yield puppeteer.launch(core_1.config.puppeteerConfig);
73
74
  const page = yield browser.newPage();
74
75
  // set page size
@@ -0,0 +1 @@
1
+ {"version":3,"file":"heap-analysis.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/packages/heap-analysis.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG"}
package/dist/index.d.ts CHANGED
@@ -12,6 +12,7 @@ export declare function registerPackage(): Promise<void>;
12
12
  export * from './API';
13
13
  export * from '@memlab/heap-analysis';
14
14
  export { default as BrowserInteractionResultReader } from './result-reader/BrowserInteractionResultReader';
15
+ export { default as SnapshotResultReader } from './result-reader/SnapshotResultReader';
15
16
  export { dumpNodeHeapSnapshot, getNodeInnocentHeap, takeNodeMinimalHeap, } from '@memlab/core';
16
17
  /** @internal */
17
18
  export { config } from '@memlab/core';
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,gBAAgB;AAChB,wBAAsB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAErD;AAED,cAAc,OAAO,CAAC;AACtB,cAAc,uBAAuB,CAAC;AACtC,OAAO,EAAC,OAAO,IAAI,8BAA8B,EAAC,MAAM,gDAAgD,CAAC;AACzG,OAAO,EAAC,OAAO,IAAI,oBAAoB,EAAC,MAAM,sCAAsC,CAAC;AACrF,OAAO,EACL,oBAAoB,EACpB,mBAAmB,EACnB,mBAAmB,GACpB,MAAM,cAAc,CAAC;AACtB,gBAAgB;AAChB,OAAO,EAAC,MAAM,EAAC,MAAM,cAAc,CAAC"}
package/dist/index.js CHANGED
@@ -35,7 +35,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
35
35
  return (mod && mod.__esModule) ? mod : { "default": mod };
36
36
  };
37
37
  Object.defineProperty(exports, "__esModule", { value: true });
38
- exports.config = exports.takeNodeMinimalHeap = exports.getNodeInnocentHeap = exports.dumpNodeHeapSnapshot = exports.BrowserInteractionResultReader = exports.registerPackage = void 0;
38
+ exports.config = exports.takeNodeMinimalHeap = exports.getNodeInnocentHeap = exports.dumpNodeHeapSnapshot = exports.SnapshotResultReader = exports.BrowserInteractionResultReader = exports.registerPackage = void 0;
39
39
  const path_1 = __importDefault(require("path"));
40
40
  const core_1 = require("@memlab/core");
41
41
  /** @internal */
@@ -49,6 +49,8 @@ __exportStar(require("./API"), exports);
49
49
  __exportStar(require("@memlab/heap-analysis"), exports);
50
50
  var BrowserInteractionResultReader_1 = require("./result-reader/BrowserInteractionResultReader");
51
51
  Object.defineProperty(exports, "BrowserInteractionResultReader", { enumerable: true, get: function () { return __importDefault(BrowserInteractionResultReader_1).default; } });
52
+ var SnapshotResultReader_1 = require("./result-reader/SnapshotResultReader");
53
+ Object.defineProperty(exports, "SnapshotResultReader", { enumerable: true, get: function () { return __importDefault(SnapshotResultReader_1).default; } });
52
54
  var core_2 = require("@memlab/core");
53
55
  Object.defineProperty(exports, "dumpNodeHeapSnapshot", { enumerable: true, get: function () { return core_2.dumpNodeHeapSnapshot; } });
54
56
  Object.defineProperty(exports, "getNodeInnocentHeap", { enumerable: true, get: function () { return core_2.getNodeInnocentHeap; } });
@@ -0,0 +1 @@
1
+ {"version":3,"file":"APIUtils.d.ts","sourceRoot":"","sources":["../../src/lib/APIUtils.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,WAAW,CAAC;AACvC,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,cAAc,CAAC;AAU/C,iBAAe,UAAU,CACvB,OAAO,GAAE;IAAC,MAAM,CAAC,EAAE,YAAY,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAM,GACtD,OAAO,CAAC,OAAO,CAAC,CAoBlB;;;;AAED,wBAEE"}
@@ -29,6 +29,7 @@ function getBrowser(options = {}) {
29
29
  return __awaiter(this, void 0, void 0, function* () {
30
30
  const runConfig = (_a = options.config) !== null && _a !== void 0 ? _a : core_1.config;
31
31
  let browser;
32
+ core_1.utils.tryToMutePuppeteerWarning();
32
33
  if (runConfig.isLocalPuppeteer && !options.warmup) {
33
34
  try {
34
35
  browser = yield puppeteer.connect(Object.assign({ browserURL: `http://localhost:${runConfig.localBrowserPort}` }, runConfig.puppeteerConfig));
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BaseResultReader.d.ts","sourceRoot":"","sources":["../../src/result-reader/BaseResultReader.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAQ,WAAW,EAAC,MAAM,cAAc,CAAC;AAGhD;;;;GAIG;AACH,MAAM,CAAC,OAAO,OAAO,gBAAgB;IACnC,SAAS,CAAC,OAAO,EAAE,MAAM,CAAC;IAC1B,SAAS,CAAC,WAAW,EAAE,WAAW,CAAC;IACnC,OAAO,CAAC,OAAO,CAAU;IAEzB;;;;OAIG;IACH,SAAS,aAAa,OAAO,SAAK;IASlC,SAAS,CAAC,KAAK,IAAI,IAAI;IAOvB;;;;OAIG;IACH,MAAM,CAAC,IAAI,CAAC,OAAO,SAAK,GAAG,gBAAgB;IAI3C;;;;;;;;;;;;;;;;;OAiBG;IACI,gBAAgB,IAAI,MAAM;IAKjC;;;;;;;;;;;;;;;OAeG;IACI,OAAO,IAAI,IAAI;CAOvB"}
@@ -12,7 +12,7 @@ import BaseResultReader from './BaseResultReader';
12
12
  /**
13
13
  * A utility entity to read all generated files from
14
14
  * the directory holding the data and results from the
15
- * last browser interaction run
15
+ * last MemLab browser interaction run
16
16
  */
17
17
  export default class BrowserInteractionResultReader extends BaseResultReader {
18
18
  /**
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BrowserInteractionResultReader.d.ts","sourceRoot":"","sources":["../../src/result-reader/BrowserInteractionResultReader.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAC,WAAW,EAAE,WAAW,EAAC,MAAM,cAAc,CAAC;AAK3D,OAAO,gBAAgB,MAAM,oBAAoB,CAAC;AAElD;;;;GAIG;AACH,MAAM,CAAC,OAAO,OAAO,8BAA+B,SAAQ,gBAAgB;IAC1E;;;;;;;;;;;;;;OAcG;IACH,MAAM,CAAC,IAAI,CAAC,OAAO,SAAK,GAAG,8BAA8B;IAIzD;;;;;;;;;;;;;;;OAeG;IACI,gBAAgB,IAAI,MAAM,EAAE;IASnC;;;;;;;;;;;;;;;OAeG;IACI,kBAAkB,IAAI,MAAM;IAKnC;;;;;;;;;;;;;;;;OAgBG;IACI,mBAAmB,IAAI,WAAW,EAAE;IAQ3C;;;;;;;;;;;;;;;;OAgBG;IACI,cAAc,IAAI,WAAW;CAMrC"}
@@ -19,7 +19,7 @@ const BaseResultReader_1 = __importDefault(require("./BaseResultReader"));
19
19
  /**
20
20
  * A utility entity to read all generated files from
21
21
  * the directory holding the data and results from the
22
- * last browser interaction run
22
+ * last MemLab browser interaction run
23
23
  */
24
24
  class BrowserInteractionResultReader extends BaseResultReader_1.default {
25
25
  /**
@@ -0,0 +1,115 @@
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 { E2EStepInfo, RunMetaInfo } from '@memlab/core';
11
+ import BaseResultReader from './BaseResultReader';
12
+ /**
13
+ * A utility entity to read all MemLab files generated from
14
+ * baseline, target and final heap snapshots.
15
+ *
16
+ * The most useful feature of this class is when you have
17
+ * three separate snapshots (baseline, target, and final)
18
+ * that are not taken from MemLab, but you still would
19
+ * like to use the `findLeaks` to detect memory leaks:
20
+ *
21
+ * ```javascript
22
+ * const {SnapshotResultReader, findLeaks} = require('@memlab/api');
23
+ *
24
+ * // baseline, target, and final are file paths of heap snapshot files
25
+ * const reader = SnapshotResultReader.fromSnapshots(baseline, target, final);
26
+ * const leaks = await findLeaks(reader);
27
+ * ```
28
+ */
29
+ export default class SnapshotResultReader extends BaseResultReader {
30
+ private baselineSnapshot;
31
+ private targetSnapshot;
32
+ private finalSnapshot;
33
+ /**
34
+ * build a result reader
35
+ * @param workDir absolute path of the directory where the data
36
+ * and generated files of the memlab run were stored
37
+ */
38
+ protected constructor(baselineSnapshot: string, targetSnapshot: string, finalSnapshot: string);
39
+ private createMetaFilesOnDisk;
40
+ private checkSnapshotFiles;
41
+ /**
42
+ * Build a result reader from baseline, target, and final heap snapshot files.
43
+ * The three snapshot files do not have to be in the same directory.
44
+ * @param baselineSnapshot file path of the baseline heap snapshot
45
+ * @param targetSnapshot file path of the target heap snapshot
46
+ * @param finalSnapshot file path of the final heap snapshot
47
+ * @returns the ResultReader instance
48
+ *
49
+ * * **Examples**:
50
+ * ```javascript
51
+ * const {SnapshotResultReader, findLeaks} = require('@memlab/api');
52
+ *
53
+ * // baseline, target, and final are file paths of heap snapshot files
54
+ * const reader = SnapshotResultReader.fromSnapshots(baseline, target, final);
55
+ * const leaks = await findLeaks(reader);
56
+ * ```
57
+ */
58
+ static fromSnapshots(baselineSnapshot: string, targetSnapshot: string, finalSnapshot: string): SnapshotResultReader;
59
+ /**
60
+ * @internal
61
+ */
62
+ static from(workDir?: string): BaseResultReader;
63
+ /**
64
+ * get all snapshot files related to this SnapshotResultReader
65
+ * @returns an array of snapshot file's absolute path
66
+ *
67
+ * * **Examples**:
68
+ * ```javascript
69
+ * const {SnapshotResultReader} = require('@memlab/api');
70
+ *
71
+ * // baseline, target, and final are file paths of heap snapshot files
72
+ * const reader = SnapshotResultReader.fromSnapshots(baseline, target, final);
73
+ * const paths = reader.getSnapshotFiles();
74
+ * ```
75
+ */
76
+ getSnapshotFiles(): string[];
77
+ /**
78
+ * @internal
79
+ */
80
+ getSnapshotFileDir(): string;
81
+ /**
82
+ * browser interaction step sequence
83
+ * @returns an array of browser interaction step information
84
+ *
85
+ * * **Examples**:
86
+ * ```javascript
87
+ * const {SnapshotResultReader} = require('@memlab/api');
88
+ *
89
+ * // baseline, target, and final are file paths of heap snapshot files
90
+ * const reader = SnapshotResultReader.fromSnapshots(baseline, target, final);
91
+ * const paths = reader.getInteractionSteps();
92
+ * ```
93
+ */
94
+ getInteractionSteps(): E2EStepInfo[];
95
+ /**
96
+ * @internal
97
+ * general meta data of the browser interaction run
98
+ * @returns meta data about the entire browser interaction
99
+ *
100
+ * * **Examples**:
101
+ * ```javascript
102
+ * const {SnapshotResultReader} = require('@memlab/api');
103
+ *
104
+ * // baseline, target, and final are file paths of heap snapshot files
105
+ * const reader = SnapshotResultReader.fromSnapshots(baseline, target, final);
106
+ * const metaInfo = reader.getRunMetaInfo();
107
+ * ```
108
+ */
109
+ getRunMetaInfo(): RunMetaInfo;
110
+ /**
111
+ * @internal
112
+ */
113
+ cleanup(): void;
114
+ }
115
+ //# sourceMappingURL=SnapshotResultReader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SnapshotResultReader.d.ts","sourceRoot":"","sources":["../../src/result-reader/SnapshotResultReader.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAS,WAAW,EAAE,WAAW,EAAC,MAAM,cAAc,CAAC;AAI9D,OAAO,gBAAgB,MAAM,oBAAoB,CAAC;AAElD;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,OAAO,OAAO,oBAAqB,SAAQ,gBAAgB;IAChE,OAAO,CAAC,gBAAgB,CAAS;IACjC,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,aAAa,CAAS;IAE9B;;;;OAIG;IACH,SAAS,aACP,gBAAgB,EAAE,MAAM,EACxB,cAAc,EAAE,MAAM,EACtB,aAAa,EAAE,MAAM;IAavB,OAAO,CAAC,qBAAqB;IAU7B,OAAO,CAAC,kBAAkB;IAY1B;;;;;;;;;;;;;;;;OAgBG;IACH,MAAM,CAAC,aAAa,CAClB,gBAAgB,EAAE,MAAM,EACxB,cAAc,EAAE,MAAM,EACtB,aAAa,EAAE,MAAM,GACpB,oBAAoB;IAQvB;;OAEG;WACW,IAAI,CAAC,OAAO,SAAK,GAAG,gBAAgB;IAKlD;;;;;;;;;;;;OAYG;IACI,gBAAgB,IAAI,MAAM,EAAE;IAInC;;OAEG;IACI,kBAAkB,IAAI,MAAM;IAOnC;;;;;;;;;;;;OAYG;IACI,mBAAmB,IAAI,WAAW,EAAE;IAQ3C;;;;;;;;;;;;;OAaG;IACI,cAAc,IAAI,WAAW;IAIpC;;OAEG;IACI,OAAO,IAAI,IAAI;CAGvB"}
@@ -0,0 +1,156 @@
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 core_1 = require("@memlab/core");
16
+ const fs_extra_1 = __importDefault(require("fs-extra"));
17
+ const core_2 = require("@memlab/core");
18
+ const BaseResultReader_1 = __importDefault(require("./BaseResultReader"));
19
+ /**
20
+ * A utility entity to read all MemLab files generated from
21
+ * baseline, target and final heap snapshots.
22
+ *
23
+ * The most useful feature of this class is when you have
24
+ * three separate snapshots (baseline, target, and final)
25
+ * that are not taken from MemLab, but you still would
26
+ * like to use the `findLeaks` to detect memory leaks:
27
+ *
28
+ * ```javascript
29
+ * const {SnapshotResultReader, findLeaks} = require('@memlab/api');
30
+ *
31
+ * // baseline, target, and final are file paths of heap snapshot files
32
+ * const reader = SnapshotResultReader.fromSnapshots(baseline, target, final);
33
+ * const leaks = await findLeaks(reader);
34
+ * ```
35
+ */
36
+ class SnapshotResultReader extends BaseResultReader_1.default {
37
+ /**
38
+ * build a result reader
39
+ * @param workDir absolute path of the directory where the data
40
+ * and generated files of the memlab run were stored
41
+ */
42
+ constructor(baselineSnapshot, targetSnapshot, finalSnapshot) {
43
+ const fileManager = new core_2.FileManager();
44
+ const workDir = fileManager.generateTmpHeapDir();
45
+ fs_extra_1.default.ensureDirSync(workDir);
46
+ super(workDir);
47
+ this.baselineSnapshot = baselineSnapshot;
48
+ this.targetSnapshot = targetSnapshot;
49
+ this.finalSnapshot = finalSnapshot;
50
+ this.checkSnapshotFiles();
51
+ this.createMetaFilesOnDisk(fileManager, workDir);
52
+ }
53
+ createMetaFilesOnDisk(fileManager, workDir) {
54
+ fileManager.initDirs(core_1.config, { workDir });
55
+ const visitOrder = this.getInteractionSteps();
56
+ const snapSeqFile = fileManager.getSnapshotSequenceMetaFile({ workDir });
57
+ fs_extra_1.default.writeFileSync(snapSeqFile, JSON.stringify(visitOrder, null, 2), 'UTF-8');
58
+ }
59
+ checkSnapshotFiles() {
60
+ if (!fs_extra_1.default.existsSync(this.baselineSnapshot) ||
61
+ !fs_extra_1.default.existsSync(this.targetSnapshot) ||
62
+ !fs_extra_1.default.existsSync(this.finalSnapshot)) {
63
+ throw core_2.utils.haltOrThrow('invalid file path of baseline, target, or final heap snapshots');
64
+ }
65
+ }
66
+ /**
67
+ * Build a result reader from baseline, target, and final heap snapshot files.
68
+ * The three snapshot files do not have to be in the same directory.
69
+ * @param baselineSnapshot file path of the baseline heap snapshot
70
+ * @param targetSnapshot file path of the target heap snapshot
71
+ * @param finalSnapshot file path of the final heap snapshot
72
+ * @returns the ResultReader instance
73
+ *
74
+ * * **Examples**:
75
+ * ```javascript
76
+ * const {SnapshotResultReader, findLeaks} = require('@memlab/api');
77
+ *
78
+ * // baseline, target, and final are file paths of heap snapshot files
79
+ * const reader = SnapshotResultReader.fromSnapshots(baseline, target, final);
80
+ * const leaks = await findLeaks(reader);
81
+ * ```
82
+ */
83
+ static fromSnapshots(baselineSnapshot, targetSnapshot, finalSnapshot) {
84
+ return new SnapshotResultReader(baselineSnapshot, targetSnapshot, finalSnapshot);
85
+ }
86
+ /**
87
+ * @internal
88
+ */
89
+ static from(workDir = '') {
90
+ throw core_2.utils.haltOrThrow('SnapshotResultReader.from is not supported');
91
+ return new BaseResultReader_1.default(workDir);
92
+ }
93
+ /**
94
+ * get all snapshot files related to this SnapshotResultReader
95
+ * @returns an array of snapshot file's absolute path
96
+ *
97
+ * * **Examples**:
98
+ * ```javascript
99
+ * const {SnapshotResultReader} = require('@memlab/api');
100
+ *
101
+ * // baseline, target, and final are file paths of heap snapshot files
102
+ * const reader = SnapshotResultReader.fromSnapshots(baseline, target, final);
103
+ * const paths = reader.getSnapshotFiles();
104
+ * ```
105
+ */
106
+ getSnapshotFiles() {
107
+ return [this.baselineSnapshot, this.targetSnapshot, this.finalSnapshot];
108
+ }
109
+ /**
110
+ * @internal
111
+ */
112
+ getSnapshotFileDir() {
113
+ throw core_2.utils.haltOrThrow('SnapshotResultReader getSnapshotFileDir() method is not supported');
114
+ return '';
115
+ }
116
+ /**
117
+ * browser interaction step sequence
118
+ * @returns an array of browser interaction step information
119
+ *
120
+ * * **Examples**:
121
+ * ```javascript
122
+ * const {SnapshotResultReader} = require('@memlab/api');
123
+ *
124
+ * // baseline, target, and final are file paths of heap snapshot files
125
+ * const reader = SnapshotResultReader.fromSnapshots(baseline, target, final);
126
+ * const paths = reader.getInteractionSteps();
127
+ * ```
128
+ */
129
+ getInteractionSteps() {
130
+ return this.fileManager.createVisitOrderWithSnapshots(this.baselineSnapshot, this.targetSnapshot, this.finalSnapshot);
131
+ }
132
+ /**
133
+ * @internal
134
+ * general meta data of the browser interaction run
135
+ * @returns meta data about the entire browser interaction
136
+ *
137
+ * * **Examples**:
138
+ * ```javascript
139
+ * const {SnapshotResultReader} = require('@memlab/api');
140
+ *
141
+ * // baseline, target, and final are file paths of heap snapshot files
142
+ * const reader = SnapshotResultReader.fromSnapshots(baseline, target, final);
143
+ * const metaInfo = reader.getRunMetaInfo();
144
+ * ```
145
+ */
146
+ getRunMetaInfo() {
147
+ return new core_2.RunMetaInfoManager().loadRunMetaExternalTemplate();
148
+ }
149
+ /**
150
+ * @internal
151
+ */
152
+ cleanup() {
153
+ // do nothing
154
+ }
155
+ }
156
+ exports.default = SnapshotResultReader;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memlab/api",
3
- "version": "1.0.22",
3
+ "version": "1.0.24",
4
4
  "license": "MIT",
5
5
  "description": "memlab API",
6
6
  "author": "Liang Gong <lgong@fb.com>",
@@ -35,7 +35,8 @@
35
35
  "chalk": "^4.0.0",
36
36
  "fs-extra": "^4.0.2",
37
37
  "minimist": "^1.2.0",
38
- "puppeteer": "^13.5.1",
38
+ "puppeteer": "^21.0.3",
39
+ "puppeteer-core": "^21.0.3",
39
40
  "string-width": "^4.2.0",
40
41
  "util.promisify": "^1.1.1",
41
42
  "xvfb": "^0.4.0"
@@ -46,8 +47,8 @@
46
47
  "@types/minimist": "^1.2.2",
47
48
  "@types/node": "^12.16.3",
48
49
  "@types/puppeteer": "^5.4.4",
49
- "jest": "^27.5.1",
50
- "ts-jest": "^27.1.4",
50
+ "jest": "^29.6.2",
51
+ "ts-jest": "^29.1.1",
51
52
  "typescript": "^4.6.3"
52
53
  },
53
54
  "repository": {
@@ -1,69 +0,0 @@
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 index_1 = require("../../index");
22
- const E2ETestSettings_1 = require("./lib/E2ETestSettings");
23
- beforeEach(E2ETestSettings_1.testSetup);
24
- test('self-defined leak detector can find Web Worker TestObject (in initial load)', () => __awaiter(void 0, void 0, void 0, function* () {
25
- const selfDefinedScenario = {
26
- app: () => 'test-spa',
27
- url: () => '',
28
- leakFilter: (node) => {
29
- return node.name === 'WorkerTestObject' && node.type === 'object';
30
- },
31
- };
32
- const result = yield (0, index_1.run)({
33
- scenario: selfDefinedScenario,
34
- webWorker: null,
35
- });
36
- // detected all different leak trace cluster
37
- expect(result.leaks.length).toBe(1);
38
- }), E2ETestSettings_1.testTimeout);
39
- test('self-defined leak detector can find Web Worker TestObject with specified worker name (in initial load)', () => __awaiter(void 0, void 0, void 0, function* () {
40
- const selfDefinedScenario = {
41
- app: () => 'test-spa',
42
- url: () => '',
43
- leakFilter: (node) => {
44
- return node.name === 'WorkerTestObject' && node.type === 'object';
45
- },
46
- };
47
- const result = yield (0, index_1.run)({
48
- scenario: selfDefinedScenario,
49
- webWorker: 'test-worker',
50
- });
51
- // detected all different leak trace cluster
52
- expect(result.leaks.length).toBe(1);
53
- }), E2ETestSettings_1.testTimeout);
54
- test('self-defined leak detector can find Web Worker TestObject (during interaction)', () => __awaiter(void 0, void 0, void 0, function* () {
55
- const selfDefinedScenario = {
56
- app: () => 'test-spa',
57
- url: () => '',
58
- action: (page) => __awaiter(void 0, void 0, void 0, function* () { return yield page.click('[data-testid="link-4"]'); }),
59
- leakFilter: (node) => {
60
- return node.name === 'WorkerTestObject' && node.type === 'object';
61
- },
62
- };
63
- const result = yield (0, index_1.run)({
64
- scenario: selfDefinedScenario,
65
- webWorker: null,
66
- });
67
- // detected all different leak trace cluster
68
- expect(result.leaks.length).toBe(1);
69
- }), E2ETestSettings_1.testTimeout);