@tramvai/test-pw 2.70.1 → 2.72.3
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/lib/constants.es.js +13 -0
- package/lib/constants.js +17 -0
- package/lib/index.es.js +4 -167
- package/lib/index.js +12 -174
- package/lib/launch.es.js +47 -0
- package/lib/launch.js +51 -0
- package/lib/router.es.js +29 -0
- package/lib/router.js +33 -0
- package/lib/utils.es.js +14 -0
- package/lib/utils.js +18 -0
- package/lib/wrapper.es.js +74 -0
- package/lib/wrapper.js +82 -0
- package/package.json +5 -6
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const PLAYWRIGHT_DEFAULT_LAUNCH_OPTIONS = {
|
|
2
|
+
args: [
|
|
3
|
+
'--no-sandbox',
|
|
4
|
+
'--disable-setuid-sandbox',
|
|
5
|
+
'--disable-dev-shm-usage',
|
|
6
|
+
process.env.HTTPS_PROXY ? `--proxy-server=${process.env.HTTPS_PROXY}` : '',
|
|
7
|
+
].filter(Boolean),
|
|
8
|
+
executablePath: process.env.PLAYWRIGHT_EXECUTABLE_PATH,
|
|
9
|
+
headless: process.env.HEADLESS !== 'false',
|
|
10
|
+
timeout: 60 * 3 * 1000,
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export { PLAYWRIGHT_DEFAULT_LAUNCH_OPTIONS };
|
package/lib/constants.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
const PLAYWRIGHT_DEFAULT_LAUNCH_OPTIONS = {
|
|
6
|
+
args: [
|
|
7
|
+
'--no-sandbox',
|
|
8
|
+
'--disable-setuid-sandbox',
|
|
9
|
+
'--disable-dev-shm-usage',
|
|
10
|
+
process.env.HTTPS_PROXY ? `--proxy-server=${process.env.HTTPS_PROXY}` : '',
|
|
11
|
+
].filter(Boolean),
|
|
12
|
+
executablePath: process.env.PLAYWRIGHT_EXECUTABLE_PATH,
|
|
13
|
+
headless: process.env.HEADLESS !== 'false',
|
|
14
|
+
timeout: 60 * 3 * 1000,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
exports.PLAYWRIGHT_DEFAULT_LAUNCH_OPTIONS = PLAYWRIGHT_DEFAULT_LAUNCH_OPTIONS;
|
package/lib/index.es.js
CHANGED
|
@@ -1,168 +1,5 @@
|
|
|
1
|
-
import { chromium } from 'playwright-core';
|
|
2
1
|
export * from 'playwright-core';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const PLAYWRIGHT_DEFAULT_LAUNCH_OPTIONS = {
|
|
8
|
-
args: [
|
|
9
|
-
'--no-sandbox',
|
|
10
|
-
'--disable-setuid-sandbox',
|
|
11
|
-
'--disable-dev-shm-usage',
|
|
12
|
-
process.env.HTTPS_PROXY ? `--proxy-server=${process.env.HTTPS_PROXY}` : '',
|
|
13
|
-
].filter(Boolean),
|
|
14
|
-
executablePath: process.env.PLAYWRIGHT_EXECUTABLE_PATH,
|
|
15
|
-
headless: process.env.HEADLESS !== 'false',
|
|
16
|
-
timeout: 60 * 3 * 1000,
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
const wrapRouter = (page) => {
|
|
20
|
-
const navigate = async (options) => {
|
|
21
|
-
return page.evaluate((navigateOptions) => {
|
|
22
|
-
return window.contextExternal.di.get('router pageService').navigate(navigateOptions);
|
|
23
|
-
}, options);
|
|
24
|
-
};
|
|
25
|
-
const navigateThenWaitForReload = async (options) => {
|
|
26
|
-
return Promise.all([
|
|
27
|
-
page.evaluate((navigateOptions) => {
|
|
28
|
-
window.contextExternal.di.get('router pageService').navigate(navigateOptions);
|
|
29
|
-
}, options),
|
|
30
|
-
page.waitForNavigation(),
|
|
31
|
-
]);
|
|
32
|
-
};
|
|
33
|
-
const updateCurrentRoute = async (options) => {
|
|
34
|
-
return page.evaluate((navigateOptions) => {
|
|
35
|
-
return window.contextExternal.di
|
|
36
|
-
.get('router pageService')
|
|
37
|
-
.updateCurrentRoute(navigateOptions);
|
|
38
|
-
}, options);
|
|
39
|
-
};
|
|
40
|
-
return {
|
|
41
|
-
navigate,
|
|
42
|
-
navigateThenWaitForReload,
|
|
43
|
-
updateCurrentRoute,
|
|
44
|
-
};
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
// reference: https://github.com/vercel/next.js/blob/canary/packages/next/client/app-index.tsx#L162
|
|
48
|
-
const waitHydrated = (page) => {
|
|
49
|
-
return Promise.race([
|
|
50
|
-
page.waitForFunction(() => {
|
|
51
|
-
return (window.contextExternal &&
|
|
52
|
-
window.contextExternal.di.get({ token: '__TRAMVAI_HYDRATED', optional: true }));
|
|
53
|
-
}, { polling: 100 }),
|
|
54
|
-
sleep(10000),
|
|
55
|
-
]);
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
const checkSsrErrors = (text) => {
|
|
59
|
-
if (
|
|
60
|
-
// react@<18
|
|
61
|
-
text.indexOf('Server: "%s" Client: "%s"%s') !== -1 ||
|
|
62
|
-
// react@18
|
|
63
|
-
text.indexOf('An error occurred during hydration. The server HTML was replaced with client content') !== -1) {
|
|
64
|
-
throw new Error(`SSR breaking error: ${text}`);
|
|
65
|
-
}
|
|
66
|
-
};
|
|
67
|
-
const format = consoleWithStyle((stderr && stderr.level) || 0);
|
|
68
|
-
const { nativeConsole } = global;
|
|
69
|
-
const wrapPlaywrightPage = (page) => {
|
|
70
|
-
if (page.url() && page.url() !== 'about:blank') {
|
|
71
|
-
throw new Error(`You should wrap blank page before navigation, but page already has url "${page.url()}"`);
|
|
72
|
-
}
|
|
73
|
-
const originalGoto = page.goto;
|
|
74
|
-
// wait for page loading and hydration, because selective hydration ends later
|
|
75
|
-
// eslint-disable-next-line no-param-reassign
|
|
76
|
-
page.goto = async function goto(...args) {
|
|
77
|
-
const [response] = await Promise.all([
|
|
78
|
-
originalGoto.apply(page, args),
|
|
79
|
-
page.waitForNavigation({ waitUntil: 'domcontentloaded' }),
|
|
80
|
-
]);
|
|
81
|
-
await waitHydrated(page);
|
|
82
|
-
return response;
|
|
83
|
-
};
|
|
84
|
-
page.on('requestfailed', (request) => nativeConsole.error('[PAGE REQUEST FAILED]', {
|
|
85
|
-
error: request.failure(),
|
|
86
|
-
url: request.url(),
|
|
87
|
-
headers: request.headers(),
|
|
88
|
-
}));
|
|
89
|
-
page.on('crash', (error) => {
|
|
90
|
-
nativeConsole.error(`[PAGE CRASHED]`, error);
|
|
91
|
-
});
|
|
92
|
-
page.on('pageerror', (error) => {
|
|
93
|
-
nativeConsole.error(`[PAGE ERROR]`, error.message);
|
|
94
|
-
});
|
|
95
|
-
page.on('console', async (consoleObj) => {
|
|
96
|
-
const args = consoleObj.args();
|
|
97
|
-
const text = consoleObj.text();
|
|
98
|
-
const messages = [];
|
|
99
|
-
checkSsrErrors(text);
|
|
100
|
-
for (let i = 0; i < args.length; i++) {
|
|
101
|
-
const arg = args[i];
|
|
102
|
-
const json = await arg.jsonValue().catch(() => null);
|
|
103
|
-
messages.push(json);
|
|
104
|
-
}
|
|
105
|
-
const logLevel = consoleObj.type() === 'error' ? 'error' : 'log';
|
|
106
|
-
const consoleArgs = messages.length ? messages : [text];
|
|
107
|
-
nativeConsole[logLevel](`[PAGE ${consoleObj.type().toUpperCase()}]`, format(...consoleArgs));
|
|
108
|
-
});
|
|
109
|
-
return {
|
|
110
|
-
page,
|
|
111
|
-
reset: (url = 'about:blank') => {
|
|
112
|
-
return page.goto(url);
|
|
113
|
-
},
|
|
114
|
-
waitForUrl: (url) => {
|
|
115
|
-
return page.waitForFunction((expectedUrl) => {
|
|
116
|
-
return window.location.href === expectedUrl;
|
|
117
|
-
}, url);
|
|
118
|
-
},
|
|
119
|
-
router: wrapRouter(page),
|
|
120
|
-
close: () => {
|
|
121
|
-
return page.close();
|
|
122
|
-
},
|
|
123
|
-
};
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
// проставляет данные в localStorage на домене, по факту не заходя ни на какую страницу
|
|
127
|
-
// идея взята из https://github.com/puppeteer/puppeteer/issues/3692#issuecomment-453186180
|
|
128
|
-
const enableBrowserLogger = async ({ browser, serverUrl, }) => {
|
|
129
|
-
const page = await browser.newPage();
|
|
130
|
-
page.route('**/*', (route) => {
|
|
131
|
-
route.fulfill({
|
|
132
|
-
status: 200,
|
|
133
|
-
contentType: 'text/plain',
|
|
134
|
-
body: 'tweak me.',
|
|
135
|
-
});
|
|
136
|
-
});
|
|
137
|
-
await page.goto(serverUrl, { timeout: 0 });
|
|
138
|
-
await page.evaluate(() => {
|
|
139
|
-
localStorage.setItem('_t_logger', '{"level":10,"enabledName":["command:*"],"enabledLevel":[50]}');
|
|
140
|
-
});
|
|
141
|
-
await page.close();
|
|
142
|
-
};
|
|
143
|
-
const initPlaywright = async (serverUrl, { enableLogging = true, ...options } = {}) => {
|
|
144
|
-
const browser = await chromium.launch({
|
|
145
|
-
...PLAYWRIGHT_DEFAULT_LAUNCH_OPTIONS,
|
|
146
|
-
...options,
|
|
147
|
-
});
|
|
148
|
-
if (enableLogging) {
|
|
149
|
-
await enableBrowserLogger({ browser, serverUrl });
|
|
150
|
-
}
|
|
151
|
-
return {
|
|
152
|
-
browser,
|
|
153
|
-
getPageWrapper: async (url) => {
|
|
154
|
-
const page = await browser.newPage();
|
|
155
|
-
await page.setDefaultNavigationTimeout(60000);
|
|
156
|
-
const wrapper = wrapPlaywrightPage(page);
|
|
157
|
-
if (url) {
|
|
158
|
-
await page.goto(url);
|
|
159
|
-
}
|
|
160
|
-
return wrapper;
|
|
161
|
-
},
|
|
162
|
-
close: () => {
|
|
163
|
-
return browser.close();
|
|
164
|
-
},
|
|
165
|
-
};
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
export { PLAYWRIGHT_DEFAULT_LAUNCH_OPTIONS, initPlaywright, waitHydrated, wrapPlaywrightPage };
|
|
2
|
+
export { PLAYWRIGHT_DEFAULT_LAUNCH_OPTIONS } from './constants.es.js';
|
|
3
|
+
export { initPlaywright } from './launch.es.js';
|
|
4
|
+
export { wrapPlaywrightPage } from './wrapper.es.js';
|
|
5
|
+
export { waitHydrated } from './utils.es.js';
|
package/lib/index.js
CHANGED
|
@@ -3,182 +3,20 @@
|
|
|
3
3
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
4
|
|
|
5
5
|
var playwrightCore = require('playwright-core');
|
|
6
|
-
var
|
|
7
|
-
var
|
|
8
|
-
var
|
|
6
|
+
var constants = require('./constants.js');
|
|
7
|
+
var launch = require('./launch.js');
|
|
8
|
+
var wrapper = require('./wrapper.js');
|
|
9
|
+
var utils = require('./utils.js');
|
|
9
10
|
|
|
10
|
-
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
11
11
|
|
|
12
|
-
var consoleWithStyle__default = /*#__PURE__*/_interopDefaultLegacy(consoleWithStyle);
|
|
13
12
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
'--disable-dev-shm-usage',
|
|
19
|
-
process.env.HTTPS_PROXY ? `--proxy-server=${process.env.HTTPS_PROXY}` : '',
|
|
20
|
-
].filter(Boolean),
|
|
21
|
-
executablePath: process.env.PLAYWRIGHT_EXECUTABLE_PATH,
|
|
22
|
-
headless: process.env.HEADLESS !== 'false',
|
|
23
|
-
timeout: 60 * 3 * 1000,
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
const wrapRouter = (page) => {
|
|
27
|
-
const navigate = async (options) => {
|
|
28
|
-
return page.evaluate((navigateOptions) => {
|
|
29
|
-
return window.contextExternal.di.get('router pageService').navigate(navigateOptions);
|
|
30
|
-
}, options);
|
|
31
|
-
};
|
|
32
|
-
const navigateThenWaitForReload = async (options) => {
|
|
33
|
-
return Promise.all([
|
|
34
|
-
page.evaluate((navigateOptions) => {
|
|
35
|
-
window.contextExternal.di.get('router pageService').navigate(navigateOptions);
|
|
36
|
-
}, options),
|
|
37
|
-
page.waitForNavigation(),
|
|
38
|
-
]);
|
|
39
|
-
};
|
|
40
|
-
const updateCurrentRoute = async (options) => {
|
|
41
|
-
return page.evaluate((navigateOptions) => {
|
|
42
|
-
return window.contextExternal.di
|
|
43
|
-
.get('router pageService')
|
|
44
|
-
.updateCurrentRoute(navigateOptions);
|
|
45
|
-
}, options);
|
|
46
|
-
};
|
|
47
|
-
return {
|
|
48
|
-
navigate,
|
|
49
|
-
navigateThenWaitForReload,
|
|
50
|
-
updateCurrentRoute,
|
|
51
|
-
};
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
// reference: https://github.com/vercel/next.js/blob/canary/packages/next/client/app-index.tsx#L162
|
|
55
|
-
const waitHydrated = (page) => {
|
|
56
|
-
return Promise.race([
|
|
57
|
-
page.waitForFunction(() => {
|
|
58
|
-
return (window.contextExternal &&
|
|
59
|
-
window.contextExternal.di.get({ token: '__TRAMVAI_HYDRATED', optional: true }));
|
|
60
|
-
}, { polling: 100 }),
|
|
61
|
-
testIntegration.sleep(10000),
|
|
62
|
-
]);
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
const checkSsrErrors = (text) => {
|
|
66
|
-
if (
|
|
67
|
-
// react@<18
|
|
68
|
-
text.indexOf('Server: "%s" Client: "%s"%s') !== -1 ||
|
|
69
|
-
// react@18
|
|
70
|
-
text.indexOf('An error occurred during hydration. The server HTML was replaced with client content') !== -1) {
|
|
71
|
-
throw new Error(`SSR breaking error: ${text}`);
|
|
72
|
-
}
|
|
73
|
-
};
|
|
74
|
-
const format = consoleWithStyle__default["default"]((supportsColor.stderr && supportsColor.stderr.level) || 0);
|
|
75
|
-
const { nativeConsole } = global;
|
|
76
|
-
const wrapPlaywrightPage = (page) => {
|
|
77
|
-
if (page.url() && page.url() !== 'about:blank') {
|
|
78
|
-
throw new Error(`You should wrap blank page before navigation, but page already has url "${page.url()}"`);
|
|
79
|
-
}
|
|
80
|
-
const originalGoto = page.goto;
|
|
81
|
-
// wait for page loading and hydration, because selective hydration ends later
|
|
82
|
-
// eslint-disable-next-line no-param-reassign
|
|
83
|
-
page.goto = async function goto(...args) {
|
|
84
|
-
const [response] = await Promise.all([
|
|
85
|
-
originalGoto.apply(page, args),
|
|
86
|
-
page.waitForNavigation({ waitUntil: 'domcontentloaded' }),
|
|
87
|
-
]);
|
|
88
|
-
await waitHydrated(page);
|
|
89
|
-
return response;
|
|
90
|
-
};
|
|
91
|
-
page.on('requestfailed', (request) => nativeConsole.error('[PAGE REQUEST FAILED]', {
|
|
92
|
-
error: request.failure(),
|
|
93
|
-
url: request.url(),
|
|
94
|
-
headers: request.headers(),
|
|
95
|
-
}));
|
|
96
|
-
page.on('crash', (error) => {
|
|
97
|
-
nativeConsole.error(`[PAGE CRASHED]`, error);
|
|
98
|
-
});
|
|
99
|
-
page.on('pageerror', (error) => {
|
|
100
|
-
nativeConsole.error(`[PAGE ERROR]`, error.message);
|
|
101
|
-
});
|
|
102
|
-
page.on('console', async (consoleObj) => {
|
|
103
|
-
const args = consoleObj.args();
|
|
104
|
-
const text = consoleObj.text();
|
|
105
|
-
const messages = [];
|
|
106
|
-
checkSsrErrors(text);
|
|
107
|
-
for (let i = 0; i < args.length; i++) {
|
|
108
|
-
const arg = args[i];
|
|
109
|
-
const json = await arg.jsonValue().catch(() => null);
|
|
110
|
-
messages.push(json);
|
|
111
|
-
}
|
|
112
|
-
const logLevel = consoleObj.type() === 'error' ? 'error' : 'log';
|
|
113
|
-
const consoleArgs = messages.length ? messages : [text];
|
|
114
|
-
nativeConsole[logLevel](`[PAGE ${consoleObj.type().toUpperCase()}]`, format(...consoleArgs));
|
|
115
|
-
});
|
|
116
|
-
return {
|
|
117
|
-
page,
|
|
118
|
-
reset: (url = 'about:blank') => {
|
|
119
|
-
return page.goto(url);
|
|
120
|
-
},
|
|
121
|
-
waitForUrl: (url) => {
|
|
122
|
-
return page.waitForFunction((expectedUrl) => {
|
|
123
|
-
return window.location.href === expectedUrl;
|
|
124
|
-
}, url);
|
|
125
|
-
},
|
|
126
|
-
router: wrapRouter(page),
|
|
127
|
-
close: () => {
|
|
128
|
-
return page.close();
|
|
129
|
-
},
|
|
130
|
-
};
|
|
131
|
-
};
|
|
132
|
-
|
|
133
|
-
// проставляет данные в localStorage на домене, по факту не заходя ни на какую страницу
|
|
134
|
-
// идея взята из https://github.com/puppeteer/puppeteer/issues/3692#issuecomment-453186180
|
|
135
|
-
const enableBrowserLogger = async ({ browser, serverUrl, }) => {
|
|
136
|
-
const page = await browser.newPage();
|
|
137
|
-
page.route('**/*', (route) => {
|
|
138
|
-
route.fulfill({
|
|
139
|
-
status: 200,
|
|
140
|
-
contentType: 'text/plain',
|
|
141
|
-
body: 'tweak me.',
|
|
142
|
-
});
|
|
143
|
-
});
|
|
144
|
-
await page.goto(serverUrl, { timeout: 0 });
|
|
145
|
-
await page.evaluate(() => {
|
|
146
|
-
localStorage.setItem('_t_logger', '{"level":10,"enabledName":["command:*"],"enabledLevel":[50]}');
|
|
147
|
-
});
|
|
148
|
-
await page.close();
|
|
149
|
-
};
|
|
150
|
-
const initPlaywright = async (serverUrl, { enableLogging = true, ...options } = {}) => {
|
|
151
|
-
const browser = await playwrightCore.chromium.launch({
|
|
152
|
-
...PLAYWRIGHT_DEFAULT_LAUNCH_OPTIONS,
|
|
153
|
-
...options,
|
|
154
|
-
});
|
|
155
|
-
if (enableLogging) {
|
|
156
|
-
await enableBrowserLogger({ browser, serverUrl });
|
|
157
|
-
}
|
|
158
|
-
return {
|
|
159
|
-
browser,
|
|
160
|
-
getPageWrapper: async (url) => {
|
|
161
|
-
const page = await browser.newPage();
|
|
162
|
-
await page.setDefaultNavigationTimeout(60000);
|
|
163
|
-
const wrapper = wrapPlaywrightPage(page);
|
|
164
|
-
if (url) {
|
|
165
|
-
await page.goto(url);
|
|
166
|
-
}
|
|
167
|
-
return wrapper;
|
|
168
|
-
},
|
|
169
|
-
close: () => {
|
|
170
|
-
return browser.close();
|
|
171
|
-
},
|
|
172
|
-
};
|
|
173
|
-
};
|
|
174
|
-
|
|
175
|
-
exports.PLAYWRIGHT_DEFAULT_LAUNCH_OPTIONS = PLAYWRIGHT_DEFAULT_LAUNCH_OPTIONS;
|
|
176
|
-
exports.initPlaywright = initPlaywright;
|
|
177
|
-
exports.waitHydrated = waitHydrated;
|
|
178
|
-
exports.wrapPlaywrightPage = wrapPlaywrightPage;
|
|
13
|
+
exports.PLAYWRIGHT_DEFAULT_LAUNCH_OPTIONS = constants.PLAYWRIGHT_DEFAULT_LAUNCH_OPTIONS;
|
|
14
|
+
exports.initPlaywright = launch.initPlaywright;
|
|
15
|
+
exports.wrapPlaywrightPage = wrapper.wrapPlaywrightPage;
|
|
16
|
+
exports.waitHydrated = utils.waitHydrated;
|
|
179
17
|
Object.keys(playwrightCore).forEach(function (k) {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
18
|
+
if (k !== 'default' && !exports.hasOwnProperty(k)) Object.defineProperty(exports, k, {
|
|
19
|
+
enumerable: true,
|
|
20
|
+
get: function () { return playwrightCore[k]; }
|
|
21
|
+
});
|
|
184
22
|
});
|
package/lib/launch.es.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { chromium } from 'playwright-core';
|
|
2
|
+
import { PLAYWRIGHT_DEFAULT_LAUNCH_OPTIONS } from './constants.es.js';
|
|
3
|
+
import { wrapPlaywrightPage } from './wrapper.es.js';
|
|
4
|
+
|
|
5
|
+
// проставляет данные в localStorage на домене, по факту не заходя ни на какую страницу
|
|
6
|
+
// идея взята из https://github.com/puppeteer/puppeteer/issues/3692#issuecomment-453186180
|
|
7
|
+
const enableBrowserLogger = async ({ browser, serverUrl, }) => {
|
|
8
|
+
const page = await browser.newPage();
|
|
9
|
+
page.route('**/*', (route) => {
|
|
10
|
+
route.fulfill({
|
|
11
|
+
status: 200,
|
|
12
|
+
contentType: 'text/plain',
|
|
13
|
+
body: 'tweak me.',
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
await page.goto(serverUrl, { timeout: 0 });
|
|
17
|
+
await page.evaluate(() => {
|
|
18
|
+
localStorage.setItem('_t_logger', '{"level":10,"enabledName":["command:*"],"enabledLevel":[50]}');
|
|
19
|
+
});
|
|
20
|
+
await page.close();
|
|
21
|
+
};
|
|
22
|
+
const initPlaywright = async (serverUrl, { enableLogging = true, ...options } = {}) => {
|
|
23
|
+
const browser = await chromium.launch({
|
|
24
|
+
...PLAYWRIGHT_DEFAULT_LAUNCH_OPTIONS,
|
|
25
|
+
...options,
|
|
26
|
+
});
|
|
27
|
+
if (enableLogging) {
|
|
28
|
+
await enableBrowserLogger({ browser, serverUrl });
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
browser,
|
|
32
|
+
getPageWrapper: async (url) => {
|
|
33
|
+
const page = await browser.newPage();
|
|
34
|
+
await page.setDefaultNavigationTimeout(60000);
|
|
35
|
+
const wrapper = wrapPlaywrightPage(page);
|
|
36
|
+
if (url) {
|
|
37
|
+
await page.goto(url);
|
|
38
|
+
}
|
|
39
|
+
return wrapper;
|
|
40
|
+
},
|
|
41
|
+
close: () => {
|
|
42
|
+
return browser.close();
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export { initPlaywright };
|
package/lib/launch.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var playwrightCore = require('playwright-core');
|
|
6
|
+
var constants = require('./constants.js');
|
|
7
|
+
var wrapper = require('./wrapper.js');
|
|
8
|
+
|
|
9
|
+
// проставляет данные в localStorage на домене, по факту не заходя ни на какую страницу
|
|
10
|
+
// идея взята из https://github.com/puppeteer/puppeteer/issues/3692#issuecomment-453186180
|
|
11
|
+
const enableBrowserLogger = async ({ browser, serverUrl, }) => {
|
|
12
|
+
const page = await browser.newPage();
|
|
13
|
+
page.route('**/*', (route) => {
|
|
14
|
+
route.fulfill({
|
|
15
|
+
status: 200,
|
|
16
|
+
contentType: 'text/plain',
|
|
17
|
+
body: 'tweak me.',
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
await page.goto(serverUrl, { timeout: 0 });
|
|
21
|
+
await page.evaluate(() => {
|
|
22
|
+
localStorage.setItem('_t_logger', '{"level":10,"enabledName":["command:*"],"enabledLevel":[50]}');
|
|
23
|
+
});
|
|
24
|
+
await page.close();
|
|
25
|
+
};
|
|
26
|
+
const initPlaywright = async (serverUrl, { enableLogging = true, ...options } = {}) => {
|
|
27
|
+
const browser = await playwrightCore.chromium.launch({
|
|
28
|
+
...constants.PLAYWRIGHT_DEFAULT_LAUNCH_OPTIONS,
|
|
29
|
+
...options,
|
|
30
|
+
});
|
|
31
|
+
if (enableLogging) {
|
|
32
|
+
await enableBrowserLogger({ browser, serverUrl });
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
browser,
|
|
36
|
+
getPageWrapper: async (url) => {
|
|
37
|
+
const page = await browser.newPage();
|
|
38
|
+
await page.setDefaultNavigationTimeout(60000);
|
|
39
|
+
const wrapper$1 = wrapper.wrapPlaywrightPage(page);
|
|
40
|
+
if (url) {
|
|
41
|
+
await page.goto(url);
|
|
42
|
+
}
|
|
43
|
+
return wrapper$1;
|
|
44
|
+
},
|
|
45
|
+
close: () => {
|
|
46
|
+
return browser.close();
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
exports.initPlaywright = initPlaywright;
|
package/lib/router.es.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const wrapRouter = (page) => {
|
|
2
|
+
const navigate = async (options) => {
|
|
3
|
+
return page.evaluate((navigateOptions) => {
|
|
4
|
+
return window.contextExternal.di.get('router pageService').navigate(navigateOptions);
|
|
5
|
+
}, options);
|
|
6
|
+
};
|
|
7
|
+
const navigateThenWaitForReload = async (options) => {
|
|
8
|
+
return Promise.all([
|
|
9
|
+
page.evaluate((navigateOptions) => {
|
|
10
|
+
window.contextExternal.di.get('router pageService').navigate(navigateOptions);
|
|
11
|
+
}, options),
|
|
12
|
+
page.waitForNavigation(),
|
|
13
|
+
]);
|
|
14
|
+
};
|
|
15
|
+
const updateCurrentRoute = async (options) => {
|
|
16
|
+
return page.evaluate((navigateOptions) => {
|
|
17
|
+
return window.contextExternal.di
|
|
18
|
+
.get('router pageService')
|
|
19
|
+
.updateCurrentRoute(navigateOptions);
|
|
20
|
+
}, options);
|
|
21
|
+
};
|
|
22
|
+
return {
|
|
23
|
+
navigate,
|
|
24
|
+
navigateThenWaitForReload,
|
|
25
|
+
updateCurrentRoute,
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export { wrapRouter };
|
package/lib/router.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
const wrapRouter = (page) => {
|
|
6
|
+
const navigate = async (options) => {
|
|
7
|
+
return page.evaluate((navigateOptions) => {
|
|
8
|
+
return window.contextExternal.di.get('router pageService').navigate(navigateOptions);
|
|
9
|
+
}, options);
|
|
10
|
+
};
|
|
11
|
+
const navigateThenWaitForReload = async (options) => {
|
|
12
|
+
return Promise.all([
|
|
13
|
+
page.evaluate((navigateOptions) => {
|
|
14
|
+
window.contextExternal.di.get('router pageService').navigate(navigateOptions);
|
|
15
|
+
}, options),
|
|
16
|
+
page.waitForNavigation(),
|
|
17
|
+
]);
|
|
18
|
+
};
|
|
19
|
+
const updateCurrentRoute = async (options) => {
|
|
20
|
+
return page.evaluate((navigateOptions) => {
|
|
21
|
+
return window.contextExternal.di
|
|
22
|
+
.get('router pageService')
|
|
23
|
+
.updateCurrentRoute(navigateOptions);
|
|
24
|
+
}, options);
|
|
25
|
+
};
|
|
26
|
+
return {
|
|
27
|
+
navigate,
|
|
28
|
+
navigateThenWaitForReload,
|
|
29
|
+
updateCurrentRoute,
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
exports.wrapRouter = wrapRouter;
|
package/lib/utils.es.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { sleep } from '@tramvai/test-integration';
|
|
2
|
+
|
|
3
|
+
// reference: https://github.com/vercel/next.js/blob/canary/packages/next/client/app-index.tsx#L162
|
|
4
|
+
const waitHydrated = (page) => {
|
|
5
|
+
return Promise.race([
|
|
6
|
+
page.waitForFunction(() => {
|
|
7
|
+
return (window.contextExternal &&
|
|
8
|
+
window.contextExternal.di.get({ token: '__TRAMVAI_HYDRATED', optional: true }));
|
|
9
|
+
}, { polling: 100 }),
|
|
10
|
+
sleep(10000),
|
|
11
|
+
]);
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export { waitHydrated };
|
package/lib/utils.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var testIntegration = require('@tramvai/test-integration');
|
|
6
|
+
|
|
7
|
+
// reference: https://github.com/vercel/next.js/blob/canary/packages/next/client/app-index.tsx#L162
|
|
8
|
+
const waitHydrated = (page) => {
|
|
9
|
+
return Promise.race([
|
|
10
|
+
page.waitForFunction(() => {
|
|
11
|
+
return (window.contextExternal &&
|
|
12
|
+
window.contextExternal.di.get({ token: '__TRAMVAI_HYDRATED', optional: true }));
|
|
13
|
+
}, { polling: 100 }),
|
|
14
|
+
testIntegration.sleep(10000),
|
|
15
|
+
]);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
exports.waitHydrated = waitHydrated;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { stderr } from 'supports-color';
|
|
2
|
+
import consoleWithStyle from 'console-with-style';
|
|
3
|
+
import { wrapRouter } from './router.es.js';
|
|
4
|
+
import { waitHydrated } from './utils.es.js';
|
|
5
|
+
|
|
6
|
+
const checkSsrErrors = (text) => {
|
|
7
|
+
if (
|
|
8
|
+
// react@<18
|
|
9
|
+
text.indexOf('Server: "%s" Client: "%s"%s') !== -1 ||
|
|
10
|
+
// react@18
|
|
11
|
+
text.indexOf('An error occurred during hydration. The server HTML was replaced with client content') !== -1) {
|
|
12
|
+
throw new Error(`SSR breaking error: ${text}`);
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
const format = consoleWithStyle((stderr && stderr.level) || 0);
|
|
16
|
+
const { nativeConsole } = global;
|
|
17
|
+
const wrapPlaywrightPage = (page) => {
|
|
18
|
+
if (page.url() && page.url() !== 'about:blank') {
|
|
19
|
+
throw new Error(`You should wrap blank page before navigation, but page already has url "${page.url()}"`);
|
|
20
|
+
}
|
|
21
|
+
const originalGoto = page.goto;
|
|
22
|
+
// wait for page loading and hydration, because selective hydration ends later
|
|
23
|
+
// eslint-disable-next-line no-param-reassign
|
|
24
|
+
page.goto = async function goto(...args) {
|
|
25
|
+
const [response] = await Promise.all([
|
|
26
|
+
originalGoto.apply(page, args),
|
|
27
|
+
page.waitForNavigation({ waitUntil: 'domcontentloaded' }),
|
|
28
|
+
]);
|
|
29
|
+
await waitHydrated(page);
|
|
30
|
+
return response;
|
|
31
|
+
};
|
|
32
|
+
page.on('requestfailed', (request) => nativeConsole.error('[PAGE REQUEST FAILED]', {
|
|
33
|
+
error: request.failure(),
|
|
34
|
+
url: request.url(),
|
|
35
|
+
headers: request.headers(),
|
|
36
|
+
}));
|
|
37
|
+
page.on('crash', (error) => {
|
|
38
|
+
nativeConsole.error(`[PAGE CRASHED]`, error);
|
|
39
|
+
});
|
|
40
|
+
page.on('pageerror', (error) => {
|
|
41
|
+
nativeConsole.error(`[PAGE ERROR]`, error.message);
|
|
42
|
+
});
|
|
43
|
+
page.on('console', async (consoleObj) => {
|
|
44
|
+
const args = consoleObj.args();
|
|
45
|
+
const text = consoleObj.text();
|
|
46
|
+
const messages = [];
|
|
47
|
+
checkSsrErrors(text);
|
|
48
|
+
for (let i = 0; i < args.length; i++) {
|
|
49
|
+
const arg = args[i];
|
|
50
|
+
const json = await arg.jsonValue().catch(() => null);
|
|
51
|
+
messages.push(json);
|
|
52
|
+
}
|
|
53
|
+
const logLevel = consoleObj.type() === 'error' ? 'error' : 'log';
|
|
54
|
+
const consoleArgs = messages.length ? messages : [text];
|
|
55
|
+
nativeConsole[logLevel](`[PAGE ${consoleObj.type().toUpperCase()}]`, format(...consoleArgs));
|
|
56
|
+
});
|
|
57
|
+
return {
|
|
58
|
+
page,
|
|
59
|
+
reset: (url = 'about:blank') => {
|
|
60
|
+
return page.goto(url);
|
|
61
|
+
},
|
|
62
|
+
waitForUrl: (url) => {
|
|
63
|
+
return page.waitForFunction((expectedUrl) => {
|
|
64
|
+
return window.location.href === expectedUrl;
|
|
65
|
+
}, url);
|
|
66
|
+
},
|
|
67
|
+
router: wrapRouter(page),
|
|
68
|
+
close: () => {
|
|
69
|
+
return page.close();
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export { wrapPlaywrightPage };
|
package/lib/wrapper.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var supportsColor = require('supports-color');
|
|
6
|
+
var consoleWithStyle = require('console-with-style');
|
|
7
|
+
var router = require('./router.js');
|
|
8
|
+
var utils = require('./utils.js');
|
|
9
|
+
|
|
10
|
+
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
11
|
+
|
|
12
|
+
var consoleWithStyle__default = /*#__PURE__*/_interopDefaultLegacy(consoleWithStyle);
|
|
13
|
+
|
|
14
|
+
const checkSsrErrors = (text) => {
|
|
15
|
+
if (
|
|
16
|
+
// react@<18
|
|
17
|
+
text.indexOf('Server: "%s" Client: "%s"%s') !== -1 ||
|
|
18
|
+
// react@18
|
|
19
|
+
text.indexOf('An error occurred during hydration. The server HTML was replaced with client content') !== -1) {
|
|
20
|
+
throw new Error(`SSR breaking error: ${text}`);
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
const format = consoleWithStyle__default["default"]((supportsColor.stderr && supportsColor.stderr.level) || 0);
|
|
24
|
+
const { nativeConsole } = global;
|
|
25
|
+
const wrapPlaywrightPage = (page) => {
|
|
26
|
+
if (page.url() && page.url() !== 'about:blank') {
|
|
27
|
+
throw new Error(`You should wrap blank page before navigation, but page already has url "${page.url()}"`);
|
|
28
|
+
}
|
|
29
|
+
const originalGoto = page.goto;
|
|
30
|
+
// wait for page loading and hydration, because selective hydration ends later
|
|
31
|
+
// eslint-disable-next-line no-param-reassign
|
|
32
|
+
page.goto = async function goto(...args) {
|
|
33
|
+
const [response] = await Promise.all([
|
|
34
|
+
originalGoto.apply(page, args),
|
|
35
|
+
page.waitForNavigation({ waitUntil: 'domcontentloaded' }),
|
|
36
|
+
]);
|
|
37
|
+
await utils.waitHydrated(page);
|
|
38
|
+
return response;
|
|
39
|
+
};
|
|
40
|
+
page.on('requestfailed', (request) => nativeConsole.error('[PAGE REQUEST FAILED]', {
|
|
41
|
+
error: request.failure(),
|
|
42
|
+
url: request.url(),
|
|
43
|
+
headers: request.headers(),
|
|
44
|
+
}));
|
|
45
|
+
page.on('crash', (error) => {
|
|
46
|
+
nativeConsole.error(`[PAGE CRASHED]`, error);
|
|
47
|
+
});
|
|
48
|
+
page.on('pageerror', (error) => {
|
|
49
|
+
nativeConsole.error(`[PAGE ERROR]`, error.message);
|
|
50
|
+
});
|
|
51
|
+
page.on('console', async (consoleObj) => {
|
|
52
|
+
const args = consoleObj.args();
|
|
53
|
+
const text = consoleObj.text();
|
|
54
|
+
const messages = [];
|
|
55
|
+
checkSsrErrors(text);
|
|
56
|
+
for (let i = 0; i < args.length; i++) {
|
|
57
|
+
const arg = args[i];
|
|
58
|
+
const json = await arg.jsonValue().catch(() => null);
|
|
59
|
+
messages.push(json);
|
|
60
|
+
}
|
|
61
|
+
const logLevel = consoleObj.type() === 'error' ? 'error' : 'log';
|
|
62
|
+
const consoleArgs = messages.length ? messages : [text];
|
|
63
|
+
nativeConsole[logLevel](`[PAGE ${consoleObj.type().toUpperCase()}]`, format(...consoleArgs));
|
|
64
|
+
});
|
|
65
|
+
return {
|
|
66
|
+
page,
|
|
67
|
+
reset: (url = 'about:blank') => {
|
|
68
|
+
return page.goto(url);
|
|
69
|
+
},
|
|
70
|
+
waitForUrl: (url) => {
|
|
71
|
+
return page.waitForFunction((expectedUrl) => {
|
|
72
|
+
return window.location.href === expectedUrl;
|
|
73
|
+
}, url);
|
|
74
|
+
},
|
|
75
|
+
router: router.wrapRouter(page),
|
|
76
|
+
close: () => {
|
|
77
|
+
return page.close();
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
exports.wrapPlaywrightPage = wrapPlaywrightPage;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tramvai/test-pw",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.72.3",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"typings": "lib/index.d.ts",
|
|
@@ -13,14 +13,13 @@
|
|
|
13
13
|
"url": "git@github.com:Tinkoff/tramvai.git"
|
|
14
14
|
},
|
|
15
15
|
"scripts": {
|
|
16
|
-
"build": "tramvai-build --
|
|
17
|
-
"watch": "tsc -w"
|
|
18
|
-
"build-for-publish": "true"
|
|
16
|
+
"build": "tramvai-build --forPublish --preserveModules",
|
|
17
|
+
"watch": "tsc -w"
|
|
19
18
|
},
|
|
20
19
|
"dependencies": {
|
|
21
20
|
"@tinkoff/utils": "^2.1.2",
|
|
22
|
-
"@tramvai/test-integration": "2.
|
|
23
|
-
"@tramvai/tokens-router": "2.
|
|
21
|
+
"@tramvai/test-integration": "2.72.3",
|
|
22
|
+
"@tramvai/tokens-router": "2.72.3",
|
|
24
23
|
"console-with-style": "^1.1.0",
|
|
25
24
|
"supports-color": "8.1.1",
|
|
26
25
|
"tslib": "^2.4.0",
|