@deot/dev-test 1.1.1 → 2.0.1
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/index.cjs.js +407 -104
- package/dist/index.d.ts +67 -0
- package/dist/index.es.js +385 -88
- package/package.json +3 -2
package/dist/index.cjs.js
CHANGED
|
@@ -1,124 +1,427 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
4
|
+
|
|
5
|
+
const devShared = require('@deot/dev-shared');
|
|
6
|
+
const childProcess = require('child_process');
|
|
7
|
+
const puppeteer = require('puppeteer');
|
|
5
8
|
|
|
6
9
|
function _interopNamespaceDefault(e) {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
10
|
+
const n = Object.create(null, { [Symbol.toStringTag]: { value: 'Module' } });
|
|
11
|
+
if (e) {
|
|
12
|
+
for (const k in e) {
|
|
13
|
+
if (k !== 'default') {
|
|
14
|
+
const d = Object.getOwnPropertyDescriptor(e, k);
|
|
15
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
16
|
+
enumerable: true,
|
|
17
|
+
get: () => e[k]
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
n.default = e;
|
|
23
|
+
return Object.freeze(n);
|
|
21
24
|
}
|
|
22
25
|
|
|
23
|
-
|
|
26
|
+
const childProcess__namespace = /*#__PURE__*/_interopNamespaceDefault(childProcess);
|
|
24
27
|
|
|
25
28
|
const { LOCAL_COMMAND_MAP } = devShared.Shell;
|
|
26
29
|
const KEY_MAP = {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
30
|
+
DOWN: "\x1B[B",
|
|
31
|
+
UP: "\x1B[A",
|
|
32
|
+
ENTER: "\r",
|
|
33
|
+
// return;
|
|
34
|
+
SPACE: ""
|
|
31
35
|
};
|
|
32
36
|
class Command {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
37
|
+
target;
|
|
38
|
+
code;
|
|
39
|
+
error;
|
|
40
|
+
resolve;
|
|
41
|
+
reject;
|
|
42
|
+
stdout;
|
|
43
|
+
stderr;
|
|
44
|
+
emitter;
|
|
45
|
+
isClose;
|
|
46
|
+
schedule;
|
|
47
|
+
constructor(command, args) {
|
|
48
|
+
this.target = new Promise((resolve, reject) => {
|
|
49
|
+
this.resolve = this._handleEnd(resolve);
|
|
50
|
+
this.reject = this._handleEnd(reject);
|
|
51
|
+
});
|
|
52
|
+
this.code = null;
|
|
53
|
+
this.error = null;
|
|
54
|
+
this.stdout = "";
|
|
55
|
+
this.stderr = "";
|
|
56
|
+
this.emitter = this.start(command, args);
|
|
57
|
+
this.isClose = false;
|
|
58
|
+
this.schedule = {
|
|
59
|
+
target: Promise.resolve(),
|
|
60
|
+
complete: (
|
|
61
|
+
/* istanbul ignore next */
|
|
62
|
+
() => {
|
|
63
|
+
}
|
|
64
|
+
)
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
_handleEnd(fn) {
|
|
68
|
+
return (e) => {
|
|
69
|
+
this.isClose = true;
|
|
70
|
+
const { code, error } = e;
|
|
71
|
+
this.code = code;
|
|
72
|
+
this.error = error;
|
|
73
|
+
fn({
|
|
74
|
+
...e,
|
|
75
|
+
stdout: this.stdout,
|
|
76
|
+
stderr: this.stderr
|
|
77
|
+
});
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
start(command, args) {
|
|
81
|
+
const SPACE = " ";
|
|
82
|
+
const [command$, ...args$] = (command + SPACE + args.join(SPACE)).replace(/\s+/g, SPACE).split(SPACE).filter((i) => !!i).map((i) => LOCAL_COMMAND_MAP[i] || i);
|
|
83
|
+
const emitter = childProcess__namespace.spawn(command$, args$, {
|
|
84
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
85
|
+
});
|
|
86
|
+
emitter.on("close", (code) => {
|
|
87
|
+
if (code !== 0) {
|
|
88
|
+
this.reject({ code });
|
|
89
|
+
} else {
|
|
90
|
+
this.resolve({ code });
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
emitter.on(
|
|
94
|
+
"error",
|
|
95
|
+
/* istanbul ignore next */
|
|
96
|
+
(error) => {
|
|
97
|
+
!process.exitCode && (process.exitCode = 1);
|
|
98
|
+
this.reject({ code: process.exitCode, error });
|
|
99
|
+
}
|
|
100
|
+
);
|
|
101
|
+
emitter.stdout.on("data", (e) => {
|
|
102
|
+
this.stdout += e.toString();
|
|
103
|
+
this.schedule.complete();
|
|
104
|
+
});
|
|
105
|
+
emitter.stderr.on(
|
|
106
|
+
"data",
|
|
107
|
+
/* istanbul ignore next */
|
|
108
|
+
(e) => this.stderr += e.toString()
|
|
109
|
+
);
|
|
110
|
+
return emitter;
|
|
111
|
+
}
|
|
112
|
+
async stop() {
|
|
113
|
+
await this.schedule.target;
|
|
114
|
+
if (!this.isClose) {
|
|
115
|
+
this.emitter.stdin.end();
|
|
116
|
+
this.isClose = true;
|
|
71
117
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
118
|
+
const response = await this.target;
|
|
119
|
+
return response;
|
|
120
|
+
}
|
|
121
|
+
async press(key, timeout = 200) {
|
|
122
|
+
if (!key || this.isClose)
|
|
123
|
+
return;
|
|
124
|
+
await this.schedule.target;
|
|
125
|
+
this.schedule.target = new Promise((resolve) => {
|
|
126
|
+
this.schedule.complete = resolve;
|
|
127
|
+
});
|
|
128
|
+
await new Promise((resolve) => {
|
|
129
|
+
this.emitter.stdin.write(
|
|
130
|
+
KEY_MAP[key.toUpperCase()] || key,
|
|
131
|
+
"utf8",
|
|
132
|
+
resolve
|
|
133
|
+
);
|
|
134
|
+
});
|
|
135
|
+
await new Promise((_) => setTimeout(_, timeout));
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
class Operater {
|
|
140
|
+
page;
|
|
141
|
+
constructor(v) {
|
|
142
|
+
this.page = v;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* @param {string} selector ~
|
|
146
|
+
* @param {ClickOptions} options ~
|
|
147
|
+
*/
|
|
148
|
+
async click(selector, options) {
|
|
149
|
+
await this.page.click(selector, options);
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* @param {string} selector ~
|
|
153
|
+
* @returns {number} ~
|
|
154
|
+
*/
|
|
155
|
+
async count(selector) {
|
|
156
|
+
return (await this.page.$$(selector)).length;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* @param {string} selector ~
|
|
160
|
+
* @returns {Promise<string>} ~
|
|
161
|
+
*/
|
|
162
|
+
async text(selector) {
|
|
163
|
+
return await this.page.$eval(selector, (node) => node.textContent);
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* @param {string} selector ~
|
|
167
|
+
* @returns {Promise<string>} ~
|
|
168
|
+
*/
|
|
169
|
+
async value(selector) {
|
|
170
|
+
return await this.page.$eval(selector, (node) => node.value);
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* @param {string} selector ~
|
|
174
|
+
* @returns {Promise<string>} ~
|
|
175
|
+
*/
|
|
176
|
+
async html(selector) {
|
|
177
|
+
return await this.page.$eval(selector, (node) => node.innerHTML);
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* @param {string} selector ~
|
|
181
|
+
* @returns {Promise<string[]>} ~
|
|
182
|
+
*/
|
|
183
|
+
async classList(selector) {
|
|
184
|
+
return await this.page.$eval(selector, (node) => [...node.classList]);
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* @param {string} selector ~
|
|
188
|
+
* @returns {Promise<HTMLElement[]>} ~
|
|
189
|
+
*/
|
|
190
|
+
async children(selector) {
|
|
191
|
+
return await this.page.$eval(selector, (node) => [...node.children]);
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* @param {string} selector ~
|
|
195
|
+
* @returns {Promise<boolean>} ~
|
|
196
|
+
*/
|
|
197
|
+
async isVisible(selector) {
|
|
198
|
+
const display = await this.page.$eval(selector, (node) => {
|
|
199
|
+
return window.getComputedStyle(node).display;
|
|
200
|
+
});
|
|
201
|
+
return display !== "none";
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
*
|
|
205
|
+
* @param {string} selector ~
|
|
206
|
+
* @returns {Promise<boolean>} ~
|
|
207
|
+
*/
|
|
208
|
+
async isChecked(selector) {
|
|
209
|
+
return await this.page.$eval(
|
|
210
|
+
selector,
|
|
211
|
+
(node) => node.checked
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
*
|
|
216
|
+
* @param {string} selector ~
|
|
217
|
+
* @returns {Promise<boolean>} ~
|
|
218
|
+
*/
|
|
219
|
+
async isFocused(selector) {
|
|
220
|
+
return await this.page.$eval(selector, (node) => node === document.activeElement);
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* @param {string} selector ~
|
|
224
|
+
* @param {string} value$ ~
|
|
225
|
+
* @returns {Promise<void>} ~
|
|
226
|
+
*/
|
|
227
|
+
async setValue(selector, value$) {
|
|
228
|
+
await this.page.$eval(
|
|
229
|
+
selector,
|
|
230
|
+
(node, value$$) => {
|
|
231
|
+
node.value = value$$;
|
|
232
|
+
node.dispatchEvent(new Event("input"));
|
|
233
|
+
},
|
|
234
|
+
value$
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* @param {string} selector ~
|
|
239
|
+
* @param {string} value$ ~
|
|
240
|
+
* @returns {Promise<void>} ~
|
|
241
|
+
*/
|
|
242
|
+
async typeValue(selector, value$) {
|
|
243
|
+
const el = await this.page.$(selector);
|
|
244
|
+
await el.evaluate((node) => node.value = "");
|
|
245
|
+
await el.type(value$);
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* @param {string} selector ~
|
|
249
|
+
* @param {string} value$ ~
|
|
250
|
+
* @returns {Promise<void>} ~
|
|
251
|
+
*/
|
|
252
|
+
async enterValue(selector, value$) {
|
|
253
|
+
const el = await this.page.$(selector);
|
|
254
|
+
await el.evaluate((node) => node.value = "");
|
|
255
|
+
await el.type(value$);
|
|
256
|
+
await el.press("Enter");
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* @param {string} selector ~
|
|
260
|
+
* @returns {Promise<void>} ~
|
|
261
|
+
*/
|
|
262
|
+
async clearValue(selector) {
|
|
263
|
+
return await this.page.$eval(
|
|
264
|
+
selector,
|
|
265
|
+
(node) => {
|
|
266
|
+
node.value = "";
|
|
267
|
+
}
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
*
|
|
272
|
+
* @param {number} time ~
|
|
273
|
+
* @returns {Promise<any>} ~
|
|
274
|
+
*/
|
|
275
|
+
sleep(time) {
|
|
276
|
+
return this.page.evaluate((time$) => {
|
|
277
|
+
return new Promise((r) => {
|
|
278
|
+
setTimeout(r, time$);
|
|
279
|
+
});
|
|
280
|
+
}, time);
|
|
281
|
+
}
|
|
282
|
+
nextFrame() {
|
|
283
|
+
return this.page.evaluate(() => {
|
|
284
|
+
return new Promise((resolve) => {
|
|
285
|
+
requestAnimationFrame(() => {
|
|
286
|
+
requestAnimationFrame(resolve);
|
|
93
287
|
});
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
288
|
+
});
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
class Launch {
|
|
294
|
+
browser;
|
|
295
|
+
page;
|
|
296
|
+
operater;
|
|
297
|
+
_browser;
|
|
298
|
+
_page;
|
|
299
|
+
options;
|
|
300
|
+
constructor() {
|
|
301
|
+
this.operater = new Proxy({}, {
|
|
302
|
+
get: () => {
|
|
303
|
+
throw new Error("operater is not defined. create* invote first");
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
this.browser = new Proxy({}, {
|
|
307
|
+
get: () => {
|
|
308
|
+
throw new Error("browser is not defined. createBrowser invote first");
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
this.page = new Proxy({}, {
|
|
312
|
+
get: () => {
|
|
313
|
+
throw new Error("page is not defined. createPage invote first");
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
this.options = process.env.CI ? (
|
|
317
|
+
/* istanbul ignore next */
|
|
318
|
+
{ args: ["--no-sandbox", "--disable-setuid-sandbox"] }
|
|
319
|
+
) : {};
|
|
320
|
+
}
|
|
321
|
+
createBrowser(force) {
|
|
322
|
+
if (force || !this._browser) {
|
|
323
|
+
if (force && this._browser) {
|
|
324
|
+
this._browser.then((browser) => {
|
|
325
|
+
browser.isConnected() && browser.close();
|
|
97
326
|
});
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
}
|
|
107
|
-
const response = await this.target;
|
|
108
|
-
return response;
|
|
327
|
+
}
|
|
328
|
+
this._browser = puppeteer.launch({
|
|
329
|
+
...this.options,
|
|
330
|
+
headless: "new"
|
|
331
|
+
});
|
|
332
|
+
this._browser.then((browser) => {
|
|
333
|
+
this.browser = browser;
|
|
334
|
+
});
|
|
109
335
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
336
|
+
return this._browser;
|
|
337
|
+
}
|
|
338
|
+
async createPage(force) {
|
|
339
|
+
await this.createBrowser();
|
|
340
|
+
if (force || !this._page) {
|
|
341
|
+
if (force && this._page) {
|
|
342
|
+
this._page.then((page) => {
|
|
343
|
+
!page.isClosed() && page.close();
|
|
116
344
|
});
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
345
|
+
}
|
|
346
|
+
this._page = this.browser.newPage();
|
|
347
|
+
this._page.then((page) => {
|
|
348
|
+
this.page = page;
|
|
349
|
+
this.operater = new Operater(page);
|
|
350
|
+
page.evaluateOnNewDocument(
|
|
351
|
+
/* istanbul ignore next */
|
|
352
|
+
() => {
|
|
353
|
+
localStorage.clear();
|
|
354
|
+
}
|
|
355
|
+
);
|
|
356
|
+
page.on(
|
|
357
|
+
"console",
|
|
358
|
+
/* istanbul ignore next */
|
|
359
|
+
(e) => {
|
|
360
|
+
if (e.type() === "error") {
|
|
361
|
+
const err = e.args()[0];
|
|
362
|
+
console.error(
|
|
363
|
+
`Error from Puppeteer-loaded page:
|
|
364
|
+
`,
|
|
365
|
+
err.remoteObject().description
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
);
|
|
370
|
+
});
|
|
121
371
|
}
|
|
372
|
+
return this._page;
|
|
373
|
+
}
|
|
122
374
|
}
|
|
123
375
|
|
|
376
|
+
const TIME_OUT = 30 * 1e3;
|
|
377
|
+
const impl = () => {
|
|
378
|
+
let launch = new Launch();
|
|
379
|
+
beforeAll(async () => {
|
|
380
|
+
await launch.createBrowser();
|
|
381
|
+
}, 2e4);
|
|
382
|
+
beforeEach(async () => {
|
|
383
|
+
await launch.createPage(true);
|
|
384
|
+
});
|
|
385
|
+
afterEach(async () => {
|
|
386
|
+
await launch.page.close();
|
|
387
|
+
});
|
|
388
|
+
afterAll(async () => {
|
|
389
|
+
await launch.browser.close();
|
|
390
|
+
});
|
|
391
|
+
return launch;
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
const e2e = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({
|
|
395
|
+
__proto__: null,
|
|
396
|
+
TIME_OUT,
|
|
397
|
+
impl
|
|
398
|
+
}, Symbol.toStringTag, { value: 'Module' }));
|
|
399
|
+
|
|
400
|
+
const sleep = (s) => new Promise((_) => {
|
|
401
|
+
setTimeout(_, s || 0);
|
|
402
|
+
});
|
|
403
|
+
const expectByPolling = async (poll, expected, options) => {
|
|
404
|
+
const { maxTries = 30, interval = 50, to } = options || {};
|
|
405
|
+
for (let tries = 0; tries < maxTries; tries++) {
|
|
406
|
+
const actual = await poll();
|
|
407
|
+
const allowMatch = (!to || to === "toMatch") && typeof expected === "string" && typeof actual === "string";
|
|
408
|
+
if (allowMatch && actual.indexOf(expected) > -1 || actual === expected || tries === maxTries - 1) {
|
|
409
|
+
allowMatch ? expect(actual).toMatch(expected) : expect(actual)[to || "toBe"](expected);
|
|
410
|
+
break;
|
|
411
|
+
} else {
|
|
412
|
+
await sleep(interval);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
const utils = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({
|
|
418
|
+
__proto__: null,
|
|
419
|
+
expectByPolling,
|
|
420
|
+
sleep
|
|
421
|
+
}, Symbol.toStringTag, { value: 'Module' }));
|
|
422
|
+
|
|
124
423
|
exports.Command = Command;
|
|
424
|
+
exports.E2E = e2e;
|
|
425
|
+
exports.Launch = Launch;
|
|
426
|
+
exports.Operater = Operater;
|
|
427
|
+
exports.Utils = utils;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
2
|
|
|
3
|
+
import type { Browser } from 'puppeteer';
|
|
3
4
|
import * as childProcess from 'child_process';
|
|
5
|
+
import type { ClickOptions } from 'puppeteer';
|
|
4
6
|
import type { Nullable } from '@deot/dev-shared';
|
|
7
|
+
import type { Page } from 'puppeteer';
|
|
8
|
+
import type { PuppeteerLaunchOptions } from 'puppeteer';
|
|
5
9
|
|
|
6
10
|
export declare class Command {
|
|
7
11
|
target: Promise<any>;
|
|
@@ -24,4 +28,67 @@ export declare class Command {
|
|
|
24
28
|
press(key: string, timeout?: number): Promise<void>;
|
|
25
29
|
}
|
|
26
30
|
|
|
31
|
+
declare namespace E2E {
|
|
32
|
+
export {
|
|
33
|
+
TIME_OUT,
|
|
34
|
+
impl
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export { E2E }
|
|
38
|
+
|
|
39
|
+
declare const expectByPolling: (poll: () => Promise<any> | any, expected: any, options?: ExpectByPollingOptions) => Promise<void>;
|
|
40
|
+
|
|
41
|
+
declare interface ExpectByPollingOptions {
|
|
42
|
+
interval?: number;
|
|
43
|
+
maxTries?: number;
|
|
44
|
+
to?: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
declare const impl: () => Launch;
|
|
48
|
+
|
|
49
|
+
export declare class Launch {
|
|
50
|
+
browser: Browser;
|
|
51
|
+
page: Page;
|
|
52
|
+
operater: Operater;
|
|
53
|
+
private _browser;
|
|
54
|
+
private _page;
|
|
55
|
+
options: PuppeteerLaunchOptions;
|
|
56
|
+
constructor();
|
|
57
|
+
createBrowser(force?: boolean): Promise<Browser>;
|
|
58
|
+
createPage(force?: boolean): Promise<Page>;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export declare class Operater {
|
|
62
|
+
page: Page;
|
|
63
|
+
constructor(v: Page);
|
|
64
|
+
click(selector: string, options?: ClickOptions): Promise<void>;
|
|
65
|
+
count(selector: string): Promise<number>;
|
|
66
|
+
text(selector: string): Promise<string>;
|
|
67
|
+
value(selector: string): Promise<string>;
|
|
68
|
+
html(selector: string): Promise<string>;
|
|
69
|
+
classList(selector: string): Promise<string[]>;
|
|
70
|
+
children(selector: string): Promise<HTMLElement[]>;
|
|
71
|
+
isVisible(selector: string): Promise<boolean>;
|
|
72
|
+
isChecked(selector: string): Promise<boolean>;
|
|
73
|
+
isFocused(selector: string): Promise<boolean>;
|
|
74
|
+
setValue(selector: string, value$: string): Promise<void>;
|
|
75
|
+
typeValue(selector: string, value$: string): Promise<void>;
|
|
76
|
+
enterValue(selector: string, value$: string): Promise<void>;
|
|
77
|
+
clearValue(selector: string): Promise<void>;
|
|
78
|
+
sleep(time: number): Promise<any>;
|
|
79
|
+
nextFrame(): Promise<unknown>;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
declare const sleep: (s?: number) => Promise<unknown>;
|
|
83
|
+
|
|
84
|
+
declare const TIME_OUT: number;
|
|
85
|
+
|
|
86
|
+
declare namespace Utils {
|
|
87
|
+
export {
|
|
88
|
+
sleep,
|
|
89
|
+
expectByPolling
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
export { Utils }
|
|
93
|
+
|
|
27
94
|
export { }
|
package/dist/index.es.js
CHANGED
|
@@ -1,103 +1,400 @@
|
|
|
1
1
|
import { Shell } from '@deot/dev-shared';
|
|
2
2
|
import * as childProcess from 'child_process';
|
|
3
|
+
import puppeteer from 'puppeteer';
|
|
3
4
|
|
|
4
5
|
const { LOCAL_COMMAND_MAP } = Shell;
|
|
5
6
|
const KEY_MAP = {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
DOWN: "\x1B[B",
|
|
8
|
+
UP: "\x1B[A",
|
|
9
|
+
ENTER: "\r",
|
|
10
|
+
// return;
|
|
11
|
+
SPACE: ""
|
|
10
12
|
};
|
|
11
13
|
class Command {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
14
|
+
target;
|
|
15
|
+
code;
|
|
16
|
+
error;
|
|
17
|
+
resolve;
|
|
18
|
+
reject;
|
|
19
|
+
stdout;
|
|
20
|
+
stderr;
|
|
21
|
+
emitter;
|
|
22
|
+
isClose;
|
|
23
|
+
schedule;
|
|
24
|
+
constructor(command, args) {
|
|
25
|
+
this.target = new Promise((resolve, reject) => {
|
|
26
|
+
this.resolve = this._handleEnd(resolve);
|
|
27
|
+
this.reject = this._handleEnd(reject);
|
|
28
|
+
});
|
|
29
|
+
this.code = null;
|
|
30
|
+
this.error = null;
|
|
31
|
+
this.stdout = "";
|
|
32
|
+
this.stderr = "";
|
|
33
|
+
this.emitter = this.start(command, args);
|
|
34
|
+
this.isClose = false;
|
|
35
|
+
this.schedule = {
|
|
36
|
+
target: Promise.resolve(),
|
|
37
|
+
complete: (
|
|
38
|
+
/* istanbul ignore next */
|
|
39
|
+
() => {
|
|
40
|
+
}
|
|
41
|
+
)
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
_handleEnd(fn) {
|
|
45
|
+
return (e) => {
|
|
46
|
+
this.isClose = true;
|
|
47
|
+
const { code, error } = e;
|
|
48
|
+
this.code = code;
|
|
49
|
+
this.error = error;
|
|
50
|
+
fn({
|
|
51
|
+
...e,
|
|
52
|
+
stdout: this.stdout,
|
|
53
|
+
stderr: this.stderr
|
|
54
|
+
});
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
start(command, args) {
|
|
58
|
+
const SPACE = " ";
|
|
59
|
+
const [command$, ...args$] = (command + SPACE + args.join(SPACE)).replace(/\s+/g, SPACE).split(SPACE).filter((i) => !!i).map((i) => LOCAL_COMMAND_MAP[i] || i);
|
|
60
|
+
const emitter = childProcess.spawn(command$, args$, {
|
|
61
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
62
|
+
});
|
|
63
|
+
emitter.on("close", (code) => {
|
|
64
|
+
if (code !== 0) {
|
|
65
|
+
this.reject({ code });
|
|
66
|
+
} else {
|
|
67
|
+
this.resolve({ code });
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
emitter.on(
|
|
71
|
+
"error",
|
|
72
|
+
/* istanbul ignore next */
|
|
73
|
+
(error) => {
|
|
74
|
+
!process.exitCode && (process.exitCode = 1);
|
|
75
|
+
this.reject({ code: process.exitCode, error });
|
|
76
|
+
}
|
|
77
|
+
);
|
|
78
|
+
emitter.stdout.on("data", (e) => {
|
|
79
|
+
this.stdout += e.toString();
|
|
80
|
+
this.schedule.complete();
|
|
81
|
+
});
|
|
82
|
+
emitter.stderr.on(
|
|
83
|
+
"data",
|
|
84
|
+
/* istanbul ignore next */
|
|
85
|
+
(e) => this.stderr += e.toString()
|
|
86
|
+
);
|
|
87
|
+
return emitter;
|
|
88
|
+
}
|
|
89
|
+
async stop() {
|
|
90
|
+
await this.schedule.target;
|
|
91
|
+
if (!this.isClose) {
|
|
92
|
+
this.emitter.stdin.end();
|
|
93
|
+
this.isClose = true;
|
|
50
94
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
95
|
+
const response = await this.target;
|
|
96
|
+
return response;
|
|
97
|
+
}
|
|
98
|
+
async press(key, timeout = 200) {
|
|
99
|
+
if (!key || this.isClose)
|
|
100
|
+
return;
|
|
101
|
+
await this.schedule.target;
|
|
102
|
+
this.schedule.target = new Promise((resolve) => {
|
|
103
|
+
this.schedule.complete = resolve;
|
|
104
|
+
});
|
|
105
|
+
await new Promise((resolve) => {
|
|
106
|
+
this.emitter.stdin.write(
|
|
107
|
+
KEY_MAP[key.toUpperCase()] || key,
|
|
108
|
+
"utf8",
|
|
109
|
+
resolve
|
|
110
|
+
);
|
|
111
|
+
});
|
|
112
|
+
await new Promise((_) => setTimeout(_, timeout));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
class Operater {
|
|
117
|
+
page;
|
|
118
|
+
constructor(v) {
|
|
119
|
+
this.page = v;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* @param {string} selector ~
|
|
123
|
+
* @param {ClickOptions} options ~
|
|
124
|
+
*/
|
|
125
|
+
async click(selector, options) {
|
|
126
|
+
await this.page.click(selector, options);
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* @param {string} selector ~
|
|
130
|
+
* @returns {number} ~
|
|
131
|
+
*/
|
|
132
|
+
async count(selector) {
|
|
133
|
+
return (await this.page.$$(selector)).length;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* @param {string} selector ~
|
|
137
|
+
* @returns {Promise<string>} ~
|
|
138
|
+
*/
|
|
139
|
+
async text(selector) {
|
|
140
|
+
return await this.page.$eval(selector, (node) => node.textContent);
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* @param {string} selector ~
|
|
144
|
+
* @returns {Promise<string>} ~
|
|
145
|
+
*/
|
|
146
|
+
async value(selector) {
|
|
147
|
+
return await this.page.$eval(selector, (node) => node.value);
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* @param {string} selector ~
|
|
151
|
+
* @returns {Promise<string>} ~
|
|
152
|
+
*/
|
|
153
|
+
async html(selector) {
|
|
154
|
+
return await this.page.$eval(selector, (node) => node.innerHTML);
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* @param {string} selector ~
|
|
158
|
+
* @returns {Promise<string[]>} ~
|
|
159
|
+
*/
|
|
160
|
+
async classList(selector) {
|
|
161
|
+
return await this.page.$eval(selector, (node) => [...node.classList]);
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* @param {string} selector ~
|
|
165
|
+
* @returns {Promise<HTMLElement[]>} ~
|
|
166
|
+
*/
|
|
167
|
+
async children(selector) {
|
|
168
|
+
return await this.page.$eval(selector, (node) => [...node.children]);
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* @param {string} selector ~
|
|
172
|
+
* @returns {Promise<boolean>} ~
|
|
173
|
+
*/
|
|
174
|
+
async isVisible(selector) {
|
|
175
|
+
const display = await this.page.$eval(selector, (node) => {
|
|
176
|
+
return window.getComputedStyle(node).display;
|
|
177
|
+
});
|
|
178
|
+
return display !== "none";
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
*
|
|
182
|
+
* @param {string} selector ~
|
|
183
|
+
* @returns {Promise<boolean>} ~
|
|
184
|
+
*/
|
|
185
|
+
async isChecked(selector) {
|
|
186
|
+
return await this.page.$eval(
|
|
187
|
+
selector,
|
|
188
|
+
(node) => node.checked
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
*
|
|
193
|
+
* @param {string} selector ~
|
|
194
|
+
* @returns {Promise<boolean>} ~
|
|
195
|
+
*/
|
|
196
|
+
async isFocused(selector) {
|
|
197
|
+
return await this.page.$eval(selector, (node) => node === document.activeElement);
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* @param {string} selector ~
|
|
201
|
+
* @param {string} value$ ~
|
|
202
|
+
* @returns {Promise<void>} ~
|
|
203
|
+
*/
|
|
204
|
+
async setValue(selector, value$) {
|
|
205
|
+
await this.page.$eval(
|
|
206
|
+
selector,
|
|
207
|
+
(node, value$$) => {
|
|
208
|
+
node.value = value$$;
|
|
209
|
+
node.dispatchEvent(new Event("input"));
|
|
210
|
+
},
|
|
211
|
+
value$
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* @param {string} selector ~
|
|
216
|
+
* @param {string} value$ ~
|
|
217
|
+
* @returns {Promise<void>} ~
|
|
218
|
+
*/
|
|
219
|
+
async typeValue(selector, value$) {
|
|
220
|
+
const el = await this.page.$(selector);
|
|
221
|
+
await el.evaluate((node) => node.value = "");
|
|
222
|
+
await el.type(value$);
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* @param {string} selector ~
|
|
226
|
+
* @param {string} value$ ~
|
|
227
|
+
* @returns {Promise<void>} ~
|
|
228
|
+
*/
|
|
229
|
+
async enterValue(selector, value$) {
|
|
230
|
+
const el = await this.page.$(selector);
|
|
231
|
+
await el.evaluate((node) => node.value = "");
|
|
232
|
+
await el.type(value$);
|
|
233
|
+
await el.press("Enter");
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* @param {string} selector ~
|
|
237
|
+
* @returns {Promise<void>} ~
|
|
238
|
+
*/
|
|
239
|
+
async clearValue(selector) {
|
|
240
|
+
return await this.page.$eval(
|
|
241
|
+
selector,
|
|
242
|
+
(node) => {
|
|
243
|
+
node.value = "";
|
|
244
|
+
}
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
*
|
|
249
|
+
* @param {number} time ~
|
|
250
|
+
* @returns {Promise<any>} ~
|
|
251
|
+
*/
|
|
252
|
+
sleep(time) {
|
|
253
|
+
return this.page.evaluate((time$) => {
|
|
254
|
+
return new Promise((r) => {
|
|
255
|
+
setTimeout(r, time$);
|
|
256
|
+
});
|
|
257
|
+
}, time);
|
|
258
|
+
}
|
|
259
|
+
nextFrame() {
|
|
260
|
+
return this.page.evaluate(() => {
|
|
261
|
+
return new Promise((resolve) => {
|
|
262
|
+
requestAnimationFrame(() => {
|
|
263
|
+
requestAnimationFrame(resolve);
|
|
72
264
|
});
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
265
|
+
});
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
class Launch {
|
|
271
|
+
browser;
|
|
272
|
+
page;
|
|
273
|
+
operater;
|
|
274
|
+
_browser;
|
|
275
|
+
_page;
|
|
276
|
+
options;
|
|
277
|
+
constructor() {
|
|
278
|
+
this.operater = new Proxy({}, {
|
|
279
|
+
get: () => {
|
|
280
|
+
throw new Error("operater is not defined. create* invote first");
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
this.browser = new Proxy({}, {
|
|
284
|
+
get: () => {
|
|
285
|
+
throw new Error("browser is not defined. createBrowser invote first");
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
this.page = new Proxy({}, {
|
|
289
|
+
get: () => {
|
|
290
|
+
throw new Error("page is not defined. createPage invote first");
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
this.options = process.env.CI ? (
|
|
294
|
+
/* istanbul ignore next */
|
|
295
|
+
{ args: ["--no-sandbox", "--disable-setuid-sandbox"] }
|
|
296
|
+
) : {};
|
|
297
|
+
}
|
|
298
|
+
createBrowser(force) {
|
|
299
|
+
if (force || !this._browser) {
|
|
300
|
+
if (force && this._browser) {
|
|
301
|
+
this._browser.then((browser) => {
|
|
302
|
+
browser.isConnected() && browser.close();
|
|
76
303
|
});
|
|
77
|
-
|
|
78
|
-
|
|
304
|
+
}
|
|
305
|
+
this._browser = puppeteer.launch({
|
|
306
|
+
...this.options,
|
|
307
|
+
headless: "new"
|
|
308
|
+
});
|
|
309
|
+
this._browser.then((browser) => {
|
|
310
|
+
this.browser = browser;
|
|
311
|
+
});
|
|
79
312
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
}
|
|
89
|
-
async press(key, timeout = 200) {
|
|
90
|
-
if (!key || this.isClose)
|
|
91
|
-
return;
|
|
92
|
-
await this.schedule.target;
|
|
93
|
-
this.schedule.target = new Promise(resolve => {
|
|
94
|
-
this.schedule.complete = resolve;
|
|
95
|
-
});
|
|
96
|
-
await new Promise(resolve => {
|
|
97
|
-
this.emitter.stdin.write(KEY_MAP[key.toUpperCase()] || key, 'utf8', resolve);
|
|
313
|
+
return this._browser;
|
|
314
|
+
}
|
|
315
|
+
async createPage(force) {
|
|
316
|
+
await this.createBrowser();
|
|
317
|
+
if (force || !this._page) {
|
|
318
|
+
if (force && this._page) {
|
|
319
|
+
this._page.then((page) => {
|
|
320
|
+
!page.isClosed() && page.close();
|
|
98
321
|
});
|
|
99
|
-
|
|
322
|
+
}
|
|
323
|
+
this._page = this.browser.newPage();
|
|
324
|
+
this._page.then((page) => {
|
|
325
|
+
this.page = page;
|
|
326
|
+
this.operater = new Operater(page);
|
|
327
|
+
page.evaluateOnNewDocument(
|
|
328
|
+
/* istanbul ignore next */
|
|
329
|
+
() => {
|
|
330
|
+
localStorage.clear();
|
|
331
|
+
}
|
|
332
|
+
);
|
|
333
|
+
page.on(
|
|
334
|
+
"console",
|
|
335
|
+
/* istanbul ignore next */
|
|
336
|
+
(e) => {
|
|
337
|
+
if (e.type() === "error") {
|
|
338
|
+
const err = e.args()[0];
|
|
339
|
+
console.error(
|
|
340
|
+
`Error from Puppeteer-loaded page:
|
|
341
|
+
`,
|
|
342
|
+
err.remoteObject().description
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
);
|
|
347
|
+
});
|
|
100
348
|
}
|
|
349
|
+
return this._page;
|
|
350
|
+
}
|
|
101
351
|
}
|
|
102
352
|
|
|
103
|
-
|
|
353
|
+
const TIME_OUT = 30 * 1e3;
|
|
354
|
+
const impl = () => {
|
|
355
|
+
let launch = new Launch();
|
|
356
|
+
beforeAll(async () => {
|
|
357
|
+
await launch.createBrowser();
|
|
358
|
+
}, 2e4);
|
|
359
|
+
beforeEach(async () => {
|
|
360
|
+
await launch.createPage(true);
|
|
361
|
+
});
|
|
362
|
+
afterEach(async () => {
|
|
363
|
+
await launch.page.close();
|
|
364
|
+
});
|
|
365
|
+
afterAll(async () => {
|
|
366
|
+
await launch.browser.close();
|
|
367
|
+
});
|
|
368
|
+
return launch;
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
const e2e = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({
|
|
372
|
+
__proto__: null,
|
|
373
|
+
TIME_OUT,
|
|
374
|
+
impl
|
|
375
|
+
}, Symbol.toStringTag, { value: 'Module' }));
|
|
376
|
+
|
|
377
|
+
const sleep = (s) => new Promise((_) => {
|
|
378
|
+
setTimeout(_, s || 0);
|
|
379
|
+
});
|
|
380
|
+
const expectByPolling = async (poll, expected, options) => {
|
|
381
|
+
const { maxTries = 30, interval = 50, to } = options || {};
|
|
382
|
+
for (let tries = 0; tries < maxTries; tries++) {
|
|
383
|
+
const actual = await poll();
|
|
384
|
+
const allowMatch = (!to || to === "toMatch") && typeof expected === "string" && typeof actual === "string";
|
|
385
|
+
if (allowMatch && actual.indexOf(expected) > -1 || actual === expected || tries === maxTries - 1) {
|
|
386
|
+
allowMatch ? expect(actual).toMatch(expected) : expect(actual)[to || "toBe"](expected);
|
|
387
|
+
break;
|
|
388
|
+
} else {
|
|
389
|
+
await sleep(interval);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
const utils = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({
|
|
395
|
+
__proto__: null,
|
|
396
|
+
expectByPolling,
|
|
397
|
+
sleep
|
|
398
|
+
}, Symbol.toStringTag, { value: 'Module' }));
|
|
399
|
+
|
|
400
|
+
export { Command, e2e as E2E, Launch, Operater, utils as Utils };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deot/dev-test",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"main": "dist/index.es.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
"access": "public"
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@deot/dev-shared": "^
|
|
15
|
+
"@deot/dev-shared": "^2.0.0",
|
|
16
|
+
"puppeteer": "^20.7.3"
|
|
16
17
|
}
|
|
17
18
|
}
|