@memlab/e2e 1.0.2 → 1.0.5
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/README.md +3 -2
- package/dist/lib/operations/TestPlanner.d.ts +1 -0
- package/dist/lib/operations/TestPlanner.js +16 -1
- package/dist/plugins/DefaultScenarioSynthesizer.js +4 -1
- package/dist/plugins/scenarios/test-databricks.js +1 -1
- package/package.json +2 -1
- package/static/example/pages/_app.js +1 -0
- package/static/example/pages/_document.js +1 -0
- package/static/example/pages/examples/{unbound-object.jsx → oversized-object.jsx} +6 -3
- package/static/example/pages/index.js +2 -1
- package/static/example/scenarios/detached-dom.js +9 -7
- package/static/example/scenarios/oversized-leak-filter.js +9 -0
- package/static/example/scenarios/oversized-object-with-filter.js +23 -0
- package/static/example/scenarios/oversized-object.js +17 -0
- package/static/links/src/App.js +1 -1
- package/static/example/scenarios/unbound-object.js +0 -22
package/README.md
CHANGED
|
@@ -3,5 +3,6 @@
|
|
|
3
3
|
This is the memlab E2E testing framework for headless/headful Chromium. It is mainly responsible
|
|
4
4
|
for controling/interacting with browser, recording heap snapshots, and logging other execution meta data.
|
|
5
5
|
|
|
6
|
-
##
|
|
7
|
-
https://facebookincubator.github.io/memlab
|
|
6
|
+
## Online Resources
|
|
7
|
+
* [Official Website and Demo](https://facebookincubator.github.io/memlab)
|
|
8
|
+
* [Documentation](https://facebookincubator.github.io/memlab/docs/intro)
|
|
@@ -31,6 +31,7 @@ export declare class TestPlanner {
|
|
|
31
31
|
private prepareSynthesizer;
|
|
32
32
|
private generateVisitPlan;
|
|
33
33
|
loadCookies(visitPlan: IE2EScenarioVisitPlan, synthesizer: IE2EScenarioSynthesizer): Cookies;
|
|
34
|
+
private populateDomainInCookiesFromScenario;
|
|
34
35
|
private populateDomainInCookies;
|
|
35
36
|
private setSnapshotMode;
|
|
36
37
|
private assignSnapshotIds;
|
|
@@ -107,7 +107,8 @@ class TestPlanner {
|
|
|
107
107
|
loadCookies(visitPlan, synthesizer) {
|
|
108
108
|
var _a;
|
|
109
109
|
if (this.scenario && this.scenario.cookies) {
|
|
110
|
-
|
|
110
|
+
const cookies = this.scenario.cookies();
|
|
111
|
+
return this.populateDomainInCookiesFromScenario(cookies, this.scenario);
|
|
111
112
|
}
|
|
112
113
|
let cookieFile = synthesizer.getCookieFile(visitPlan);
|
|
113
114
|
// if no cookie file is specified
|
|
@@ -122,6 +123,20 @@ class TestPlanner {
|
|
|
122
123
|
const cookies = JSON.parse(fs_1.default.readFileSync(cookieFile, 'UTF-8'));
|
|
123
124
|
return this.populateDomainInCookies(cookies, synthesizer.getDomain(), synthesizer.getDomainPrefixes());
|
|
124
125
|
}
|
|
126
|
+
populateDomainInCookiesFromScenario(cookies, scenario) {
|
|
127
|
+
const ret = [];
|
|
128
|
+
const url = new URL(scenario.url());
|
|
129
|
+
const domain = '.' + url.hostname.split('.').slice(1).join('.');
|
|
130
|
+
for (const cookie of cookies) {
|
|
131
|
+
if (cookie.domain) {
|
|
132
|
+
ret.push(Object.assign({}, cookie));
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
ret.push(Object.assign(Object.assign({}, cookie), { domain }));
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return ret;
|
|
139
|
+
}
|
|
125
140
|
populateDomainInCookies(cookies, domain, domainPrefixes) {
|
|
126
141
|
const cookiesTemplate = cookies;
|
|
127
142
|
const ret = [];
|
|
@@ -53,7 +53,10 @@ class DefaultScenarioSynthesizer extends BaseSynthesizer_1.default {
|
|
|
53
53
|
getPageLoadChecker() {
|
|
54
54
|
return (page) => __awaiter(this, void 0, void 0, function* () {
|
|
55
55
|
yield InteractionUtils_1.default.waitFor(500);
|
|
56
|
-
yield page.waitForNavigation({
|
|
56
|
+
yield page.waitForNavigation({
|
|
57
|
+
waitUntil: 'networkidle0',
|
|
58
|
+
timeout: this.config.waitForNetworkInDefaultScenario,
|
|
59
|
+
});
|
|
57
60
|
return true;
|
|
58
61
|
});
|
|
59
62
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@memlab/e2e",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "memlab browser E2E interaction libraries",
|
|
6
6
|
"author": "Liang Gong <lgong@fb.com>",
|
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
},
|
|
49
49
|
"scripts": {
|
|
50
50
|
"build-pkg": "tsc",
|
|
51
|
+
"publish-patch": "npm version patch --force && npm publish",
|
|
51
52
|
"clean-pkg": "rm -rf ./dist && rm -rf ./node_modules && rm -f ./tsconfig.tsbuildinfo"
|
|
52
53
|
},
|
|
53
54
|
"bugs": {
|
|
@@ -2,14 +2,17 @@
|
|
|
2
2
|
import Link from 'next/link';
|
|
3
3
|
import React, {useEffect} from 'react';
|
|
4
4
|
|
|
5
|
-
export default function
|
|
5
|
+
export default function OversizedObject() {
|
|
6
6
|
const bigArray = Array(1024 * 1024 * 2).fill(0);
|
|
7
7
|
|
|
8
8
|
const eventHandler = () => {
|
|
9
|
-
|
|
9
|
+
// the eventHandler closure keeps a reference
|
|
10
|
+
// to the bigArray in the outter scope
|
|
11
|
+
console.log('Using hugeObject', bigArray);
|
|
10
12
|
};
|
|
11
13
|
|
|
12
14
|
useEffect(() => {
|
|
15
|
+
// eventHandler is never unregistered
|
|
13
16
|
window.addEventListener('custom-click', eventHandler);
|
|
14
17
|
}, []);
|
|
15
18
|
|
|
@@ -18,7 +21,7 @@ export default function UnboundObject() {
|
|
|
18
21
|
<div className="row">
|
|
19
22
|
<Link href="/">Go back</Link>
|
|
20
23
|
</div>
|
|
21
|
-
<br
|
|
24
|
+
<br/>
|
|
22
25
|
<div className="row">
|
|
23
26
|
Object<code>bigArray</code>is leaked. Please check <code>Memory</code>{' '}
|
|
24
27
|
tab in devtools
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @emails oncall+ws_labs
|
|
8
8
|
* @format
|
|
9
|
+
* @oncall ws_labs
|
|
9
10
|
*/
|
|
10
11
|
|
|
11
12
|
import Link from 'next/link';
|
|
@@ -24,7 +25,7 @@ export default function Home() {
|
|
|
24
25
|
<p>Detached DOM element</p>
|
|
25
26
|
</div>
|
|
26
27
|
<div className="col-md-3">
|
|
27
|
-
<Link href="/examples/
|
|
28
|
+
<Link href="/examples/oversized-object">
|
|
28
29
|
<a>
|
|
29
30
|
<h2>Example 2 →</h2>
|
|
30
31
|
</a>
|
|
@@ -11,28 +11,30 @@ function url() {
|
|
|
11
11
|
* Specify how memlab should perform action that you want
|
|
12
12
|
* to test whether the action is causing memory leak.
|
|
13
13
|
*
|
|
14
|
-
* @param page - Puppeteer's
|
|
14
|
+
* @param page - Puppeteer's page object:
|
|
15
|
+
* https://pptr.dev/api/puppeteer.page/
|
|
15
16
|
*/
|
|
16
17
|
async function action(page) {
|
|
17
|
-
const
|
|
18
|
+
const elements = await page.$x(
|
|
18
19
|
"//button[contains(., 'Create detached DOMs')]"
|
|
19
20
|
);
|
|
21
|
+
const [button] = elements;
|
|
20
22
|
if (button) {
|
|
21
23
|
await button.click();
|
|
22
24
|
}
|
|
25
|
+
// clean up external references from memlab
|
|
26
|
+
await Promise.all(elements.map(e => e.dispose()));
|
|
23
27
|
}
|
|
24
28
|
|
|
25
29
|
/**
|
|
26
30
|
* Specify how memlab should perform action that would
|
|
27
31
|
* reset the action you performed above.
|
|
28
32
|
*
|
|
29
|
-
* @param page - Puppeteer's
|
|
33
|
+
* @param page - Puppeteer's page object:
|
|
34
|
+
* https://pptr.dev/api/puppeteer.page/
|
|
30
35
|
*/
|
|
31
36
|
async function back(page) {
|
|
32
|
-
|
|
33
|
-
if (button) {
|
|
34
|
-
await button.click();
|
|
35
|
-
}
|
|
37
|
+
await page.click('a[href="/"]');
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
module.exports = { action, back, url };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/* @nolint */
|
|
2
|
+
|
|
3
|
+
// leakFilter is called with each object (node) in browser
|
|
4
|
+
// allocated by `action` but not released after the `back` call
|
|
5
|
+
function leakFilter(node, _snapshot, _leakedNodeIds) {
|
|
6
|
+
return node.retainedSize > 1000 * 1000;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
module.exports = { leakFilter };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/* @nolint */
|
|
2
|
+
|
|
3
|
+
function url() {
|
|
4
|
+
return 'http://localhost:3000/';
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
// action where you suspect the memory leak might be happening
|
|
8
|
+
async function action(page) {
|
|
9
|
+
await page.click('a[href="/examples/oversized-object"]');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// how to go back to the state before actionw
|
|
13
|
+
async function back(page) {
|
|
14
|
+
await page.click('a[href="/"]');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// leakFilter is called with each object (node) in browser
|
|
18
|
+
// allocated by `action` but not released after the `back` call
|
|
19
|
+
function leakFilter(node, _snapshot, _leakedNodeIds) {
|
|
20
|
+
return node.retainedSize > 1000 * 1000;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
module.exports = { action, back, leakFilter, url };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/* @nolint */
|
|
2
|
+
|
|
3
|
+
function url() {
|
|
4
|
+
return 'http://localhost:3000/';
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
// action where you suspect the memory leak might be happening
|
|
8
|
+
async function action(page) {
|
|
9
|
+
await page.click('a[href="/examples/oversized-object"]');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// how to go back to the state before actionw
|
|
13
|
+
async function back(page) {
|
|
14
|
+
await page.click('a[href="/"]');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
module.exports = { action, back, url };
|
package/static/links/src/App.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
import React from 'react';
|
|
13
13
|
|
|
14
|
-
// Use the
|
|
14
|
+
// Use the window.injectHookForLink{n} hook to inject objects
|
|
15
15
|
// to memory when link-{n} is triggered, for example:
|
|
16
16
|
//
|
|
17
17
|
// window.injectHookForLink4 = () => {
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
// @nolint
|
|
2
|
-
|
|
3
|
-
// memlab/packages/e2e/static/example/scenario/unbound-object.js
|
|
4
|
-
function url() {
|
|
5
|
-
return "http://localhost:3000/";
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
// action where you suspect the memory leak might be happening
|
|
9
|
-
async function action(page) {
|
|
10
|
-
await page.click('a[href="/examples/unbound-object"]');
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
// how to go back to the state before actionw
|
|
14
|
-
async function back(page) {
|
|
15
|
-
await page.click('a[href="/"]');
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
// function leakFilter(node, _snapshot, _leakedNodeIds) {
|
|
19
|
-
// return node.retainedSize > 1000 * 1000;
|
|
20
|
-
// };
|
|
21
|
-
|
|
22
|
-
module.exports = { action, back, url };
|