@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.
- package/dist/lib/Config.d.ts +1 -0
- package/dist/lib/Config.js +2 -0
- package/dist/lib/Types.d.ts +301 -1
- package/dist/lib/Types.js +1 -0
- package/package.json +1 -2
package/dist/lib/Config.d.ts
CHANGED
|
@@ -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;
|
package/dist/lib/Config.js
CHANGED
|
@@ -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
|
package/dist/lib/Types.d.ts
CHANGED
|
@@ -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
|
-
/**
|
|
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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@memlab/core",
|
|
3
|
-
"version": "1.0
|
|
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"
|