@memlab/core 1.0.9 → 1.1.0

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.
@@ -139,6 +139,7 @@ export declare class MemLabConfig {
139
139
  waitAfterOperation: number;
140
140
  waitAfterScrolling: number;
141
141
  waitAfterTyping: number;
142
+ waitForNetworkInDefaultScenario: number;
142
143
  stressTestRepeat: number;
143
144
  avoidLeakWithoutDetachedElements: boolean;
144
145
  hideBrowserLeak: boolean;
@@ -192,6 +192,8 @@ class MemLabConfig {
192
192
  this.waitAfterScrolling = 5000;
193
193
  // extra delay after typing
194
194
  this.waitAfterTyping = 1000;
195
+ // page load checker: default wait for network idle in scenario test
196
+ this.waitForNetworkInDefaultScenario = 10000;
195
197
  // default repeat for stress testing
196
198
  this.stressTestRepeat = 5;
197
199
  // only show leaks with detached HTML elements
@@ -10,6 +10,7 @@
10
10
  import { ParsedArgs } from 'minimist';
11
11
  import type { LaunchOptions, Page } from 'puppeteer';
12
12
  import type { ErrorHandling, MemLabConfig } from './Config';
13
+ /** @internal */
13
14
  export declare type AnyValue = any;
14
15
  /** @internal */
15
16
  export declare type RecordValue = string | number | boolean | null | RecordValue[] | {
@@ -141,23 +142,319 @@ export declare type QuickExperiment = {
141
142
  experiment: string;
142
143
  group: string;
143
144
  };
145
+ /**
146
+ * The type for defining custom leak-filtering logic.
147
+ * * **Examples**:
148
+ * ```typescript
149
+ * const scenario = {
150
+ *
151
+ * };
152
+ *
153
+ * let map = Object.create(null);
154
+ * const beforeLeakFilter = (snapshot: IHeapSnapshot, _leakedNodeIds: HeapNodeIdSet): void => {
155
+ * map = initializeMapUsingSnapshot(snapshot);
156
+ * };
157
+ *
158
+ * // duplicated string with size > 1KB as memory leak
159
+ * const leakFilter = (node: IHeapNode): boolean => {
160
+ * if (node.type !== 'string' || node.retainedSize < 1000) {
161
+ * return false;
162
+ * }
163
+ * const str = utils.getStringNodeValue(node);
164
+ * return map[str] > 1;
165
+ * };
166
+ *
167
+ * export default {beforeLeakFilter, leakFilter};
168
+ * ```
169
+ */
144
170
  export interface ILeakFilter {
145
171
  beforeLeakFilter?: InitLeakFilterCallback;
146
172
  leakFilter: LeakFilterCallback;
147
173
  }
174
+ /**
175
+ * Lifecycle function callback that is invoked initially once before calling any
176
+ * leak filter function.
177
+ *
178
+ * @param snaphost - heap snapshot see {@link IHeapSnapshot}
179
+ * @param leakedNodeIds - the set of leaked object (node) ids.
180
+ */
148
181
  export declare type InitLeakFilterCallback = (snapshot: IHeapSnapshot, leakedNodeIds: HeapNodeIdSet) => void;
182
+ /**
183
+ * Callback that can be used to define a logic to filter the
184
+ * leaked objects. The callback is only called for every node
185
+ * allocated but not released from the target interaction
186
+ * in the heap snapshot.
187
+ *
188
+ * @param node - the node that is kept alive in the memory in the heap snapshot
189
+ * @param snapshot - the snapshot of target interaction
190
+ * @param leakedNodeIds - the set of leaked node ids
191
+ *
192
+ * @returns the value indicating whether the given node in the snapshot
193
+ * should be considered as leaked.
194
+ * * **Examples**:
195
+ * ```javascript
196
+ * // any node in the heap snapshot that is greater than 1MB
197
+ * function leakFilter(node, _snapshot, _leakedNodeIds) {
198
+ * return node.retainedSize > 1000000;
199
+ * };
200
+ * ```
201
+ */
149
202
  export declare type LeakFilterCallback = (node: IHeapNode, snapshot: IHeapSnapshot, leakedNodeIds: HeapNodeIdSet) => boolean;
203
+ /**
204
+ * The callback defines browser interactions which are
205
+ * used by memlab to interact with the web app under test.
206
+ */
150
207
  export declare type InteractionsCallback = (page: Page, args?: OperationArgs) => Promise<void>;
208
+ /**
209
+ * Test scenario specifies how you want a E2E test to interact with a web browser.
210
+ * The test scenario can be saved as a `.js` file and passed to the `memlab
211
+ * run --scenario` command:
212
+ * ```javascript
213
+ * // save as test.js and use in terminal:
214
+ * // $ memlab run --scenario test.js
215
+ *
216
+ * module.exports = {
217
+ * url: () => 'https://www.npmjs.com/',
218
+ * action: async () => ... ,
219
+ * back: async () => ... ,
220
+ * };
221
+ * ```
222
+ *
223
+ * The test scenario instance can also be passed to the
224
+ * [`run` API](../modules/api_src#run) exported by `@memlab/api`.
225
+ * ```typescript
226
+ * const {run} = require('@memlab/api');
227
+ *
228
+ * (async function () {
229
+ * const scenario = {
230
+ * url: () => 'https://www.facebook.com',
231
+ * action: async () => ... ,
232
+ * back: async () => ... ,
233
+ * };
234
+ * const leaks = await run({scenario});
235
+ * })();
236
+ * ```
237
+ */
151
238
  export interface IScenario {
239
+ /** @internal */
152
240
  name?: () => string;
241
+ /** @internal */
153
242
  app?: () => string;
243
+ /**
244
+ * If the page you are running memlab against requires authentication or
245
+ * specific cookie(s) to be set, you can pass them as
246
+ * a list of <name, value> pairs.
247
+ * @returns cookie list
248
+ * * **Examples**:
249
+ * ```typescript
250
+ * const scenario = {
251
+ * url: () => 'https://www.facebook.com/',
252
+ * cookies: () => [
253
+ * {"name":"cm_j","value":"none"},
254
+ * {"name":"datr","value":"yJvIY..."},
255
+ * {"name":"c_user","value":"8917..."},
256
+ * {"name":"xs","value":"95:9WQ..."},
257
+ * // ...
258
+ * ],
259
+ * };
260
+ * ```
261
+ */
154
262
  cookies?: () => Cookies;
263
+ /**
264
+ * String value of the initial url of the page.
265
+ *
266
+ * @returns the string value of the initial url
267
+ * * **Examples**:
268
+ * ```typescript
269
+ * const scenario = {
270
+ * url: () => 'https://www.npmjs.com/',
271
+ * };
272
+ * ```
273
+ * If a test scenario only specifies the `url` callback (without the `action`
274
+ * callback), memlab will try to detect memory leaks from the initial page
275
+ * load. All objects allocated by the initial page load will be candidates
276
+ * for memory leak filtering.
277
+ */
155
278
  url: () => string;
279
+ /**
280
+ * `action` is the callback function that defines the interaction
281
+ * where you want to trigger memory leaks after the initial page load.
282
+ * All JS objects in browser allocated by the browser interactions triggered
283
+ * from the `action` callback will be candidates for memory leak filtering.
284
+ *
285
+ * * **Parameters**:
286
+ * * page: `Page` | the puppeteer [`Page`](https://pptr.dev/api/puppeteer.page)
287
+ * object, which provides APIs to interact with the web browser
288
+ *
289
+ * * **Examples**:
290
+ * ```typescript
291
+ * const scenario = {
292
+ * url: () => 'https://www.npmjs.com/',
293
+ * action: async (page) => {
294
+ * await page.click('a[href="/link"]');
295
+ * },
296
+ * back: async (page) => {
297
+ * await page.click('a[href="/back"]');
298
+ * },
299
+ * }
300
+ * ```
301
+ * Note: always clean up external puppeteer references to JS objects
302
+ * in the browser context.
303
+ * ```typescript
304
+ * const scenario = {
305
+ * url: () => 'https://www.npmjs.com/',
306
+ * action: async (page) => {
307
+ * const elements = await page.$x("//button[contains(., 'Text in Button')]");
308
+ * const [button] = elements;
309
+ * if (button) {
310
+ * await button.click();
311
+ * }
312
+ * // dispose external references to JS objects in browser context
313
+ * await promise.all(elements.map(e => e.dispose()));
314
+ * },
315
+ * back: async (page) => ... ,
316
+ * }
317
+ ```
318
+ */
156
319
  action?: InteractionsCallback;
320
+ /**
321
+ * `back` is the callback function that specifies how memlab should
322
+ * back/revert the `action` callback. Think of it as an undo action.
323
+ *
324
+ * * **Examples**:
325
+ * ```typescript
326
+ * const scenario = {
327
+ * url: () => 'https://www.npmjs.com/',
328
+ * action: async (page) => {
329
+ * await page.click('a[href="/link"]');
330
+ * },
331
+ * back: async (page) => {
332
+ * await page.click('a[href="/back"]');
333
+ * },
334
+ * }
335
+ * ```
336
+ * Check out [this page](/docs/how-memlab-works) on why
337
+ * memlab needs to undo/revert the `action` callback.
338
+ */
157
339
  back?: InteractionsCallback;
340
+ /**
341
+ * Specifies how many **extra** `action` and `back` actions performed by memlab.
342
+ * * **Examples**:
343
+ * ```typescript
344
+ * module.exports = {
345
+ * url: () => ... ,
346
+ * action: async (page) => ... ,
347
+ * back: async (page) => ... ,
348
+ * // browser interaction: two additional [ action -> back ]
349
+ * // init-load -> action -> back -> action -> back -> action -> back
350
+ * repeat: () => 2,
351
+ * };
352
+ * ```
353
+ */
158
354
  repeat?: () => number;
355
+ /**
356
+ * Optional callback function that checks if the web page is loaded
357
+ * after for initial page loading and subsequent browser interactions.
358
+ *
359
+ * If this callback is not provided, memlab by default
360
+ * considers a navigation to be finished when there are no network
361
+ * connections for at least 500ms.
362
+ *
363
+ * * **Parameters**:
364
+ * * page: `Page` | the puppeteer [`Page`](https://pptr.dev/api/puppeteer.page)
365
+ * object, which provides APIs to interact with the web browser
366
+ * * **Returns**: a boolean value, if it returns `true`, memlab will consider
367
+ * the navigation completes, if it returns `false`, memlab will keep calling
368
+ * this callback until it returns `true`. This is an async callback, you can
369
+ * also `await` and returns `true` until some async logic is resolved.
370
+ * * **Examples**:
371
+ * ```typescript
372
+ * module.exports = {
373
+ * url: () => ... ,
374
+ * action: async (page) => ... ,
375
+ * back: async (page) => ... ,
376
+ * isPageLoaded: async (page) => {
377
+ * await page.waitForNavigation({
378
+ * // consider navigation to be finished when there are
379
+ * // no more than 2 network connections for at least 500 ms.
380
+ * waitUntil: 'networkidle2',
381
+ * // Maximum navigation time in milliseconds
382
+ * timeout: 5000,
383
+ * });
384
+ * return true;
385
+ * },
386
+ * };
387
+ * ```
388
+ */
159
389
  isPageLoaded?: CheckPageLoadCallback;
390
+ /**
391
+ * Lifecycle function callback that is invoked initially once before
392
+ * the subsequent `leakFilter` function calls. This callback could
393
+ * be used to initialize some data stores or to do some one-off
394
+ * preprocessings.
395
+ *
396
+ * * **Parameters**:
397
+ * * snapshot: `IHeapSnapshot` | the final heap snapshot taken after
398
+ * all browser interactions are done.
399
+ * Check out {@link IHeapSnapshot} for more APIs that queries the heap snapshot.
400
+ * * leakedNodeIds: `Set<number>` | the set of ids of all JS heap objects
401
+ * allocated by the `action` call but not released after the `back` call
402
+ * in browser.
403
+ *
404
+ * * **Examples**:
405
+ * ```typescript
406
+ * module.exports = {
407
+ * url: () => ... ,
408
+ * action: async (page) => ... ,
409
+ * back: async (page) => ... ,
410
+ * beforeLeakFilter: (snapshot, leakedNodeIds) {
411
+ * // initialize some data stores
412
+ * },
413
+ * };
414
+ * ```
415
+ */
160
416
  beforeLeakFilter?: InitLeakFilterCallback;
417
+ /**
418
+ * This callback that defines how you want to filter out the
419
+ * leaked objects. The callback is called for every node (JS heap
420
+ * object in browser) allocated by the `action` callback, but not
421
+ * released after the `back` callback. Those objects could be caches
422
+ * that are retained in memory on purpose, or they are memory leaks.
423
+ *
424
+ * This optional callback allows you to define your own algorithm
425
+ * to cherry pick memory leaks for specific JS program under test.
426
+ *
427
+ * If this optional callback is not defined, memlab will use its
428
+ * built-in leak filter, which considers detached DOM elements
429
+ * and unmounted Fiber nodes (detached from React Fiber tree) as
430
+ * memory leaks.
431
+ *
432
+ * * **Parameters**:
433
+ * * node: `IHeapNode` | one of the heap object allocated but not released.
434
+ * * snapshot: `IHeapSnapshot` | the final heap snapshot taken after
435
+ * all browser interactions are done.
436
+ * Check out {@link IHeapSnapshot} for more APIs that queries the heap snapshot.
437
+ * * leakedNodeIds: `Set<number>` | the set of ids of all JS heap objects
438
+ * allocated by the `action` call but not released after the `back` call
439
+ * in browser.
440
+ *
441
+ * * **Returns**: the boolean value indicating whether the given node in
442
+ * the snapshot should be considered as leaked.
443
+ *
444
+ * * **Examples**:
445
+ * ```typescript
446
+ * module.exports = {
447
+ * url: () => ... ,
448
+ * action: async (page) => ... ,
449
+ * back: async (page) => ... ,
450
+ * leakFilter(node, snapshot, leakedNodeIds) {
451
+ * // any unreleased node (JS heap object) with 1MB+
452
+ * // retained size is considered a memory leak
453
+ * return node.retainedSize > 1000000;
454
+ * },
455
+ * };
456
+ * ```
457
+ */
161
458
  leakFilter?: LeakFilterCallback;
162
459
  }
163
460
  /** @internal */
@@ -245,7 +542,10 @@ export interface IDataBuilder {
245
542
  className: string;
246
543
  state: Record<string, AnyValue>;
247
544
  }
248
- /** @internal */
545
+ /**
546
+ * Callback function to provide if the page is loaded.
547
+ * @param page - puppeteer's [Page](https://pptr.dev/api/puppeteer.page/) object.
548
+ */
249
549
  export declare type CheckPageLoadCallback = (page: Page) => Promise<boolean>;
250
550
  /** @internal */
251
551
  export interface IE2EScenarioVisitPlan {
package/dist/lib/Types.js CHANGED
@@ -8,4 +8,5 @@
8
8
  * @emails oncall+ws_labs
9
9
  * @format
10
10
  */
11
+ /* eslint-disable @typescript-eslint/no-explicit-any */
11
12
  Object.defineProperty(exports, "__esModule", { value: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memlab/core",
3
- "version": "1.0.9",
3
+ "version": "1.1.0",
4
4
  "license": "MIT",
5
5
  "description": "memlab core libraries",
6
6
  "author": "Liang Gong <lgong@fb.com>",
@@ -56,7 +56,6 @@
56
56
  "directory": "packages/core"
57
57
  },
58
58
  "scripts": {
59
- "preinstall": "export PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true",
60
59
  "build-pkg": "tsc",
61
60
  "test-pkg": "jest .",
62
61
  "clean-pkg": "rm -rf ./dist && rm -rf ./node_modules && rm -f ./tsconfig.tsbuildinfo"