@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 CHANGED
@@ -1,124 +1,427 @@
1
1
  'use strict';
2
2
 
3
- var devShared = require('@deot/dev-shared');
4
- var childProcess = require('child_process');
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
- var n = Object.create(null);
8
- if (e) {
9
- Object.keys(e).forEach(function (k) {
10
- if (k !== 'default') {
11
- var d = Object.getOwnPropertyDescriptor(e, k);
12
- Object.defineProperty(n, k, d.get ? d : {
13
- enumerable: true,
14
- get: function () { return e[k]; }
15
- });
16
- }
17
- });
18
- }
19
- n.default = e;
20
- return Object.freeze(n);
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
- var childProcess__namespace = /*#__PURE__*/_interopNamespaceDefault(childProcess);
26
+ const childProcess__namespace = /*#__PURE__*/_interopNamespaceDefault(childProcess);
24
27
 
25
28
  const { LOCAL_COMMAND_MAP } = devShared.Shell;
26
29
  const KEY_MAP = {
27
- DOWN: '\x1b\x5b\x42',
28
- UP: '\x1b\x5b\x41',
29
- ENTER: '\x0d',
30
- SPACE: '\x7f'
30
+ DOWN: "\x1B[B",
31
+ UP: "\x1B[A",
32
+ ENTER: "\r",
33
+ // return;
34
+ SPACE: ""
31
35
  };
32
36
  class Command {
33
- target;
34
- code;
35
- error;
36
- resolve;
37
- reject;
38
- stdout;
39
- stderr;
40
- emitter;
41
- isClose;
42
- schedule;
43
- constructor(command, args) {
44
- this.target = new Promise((resolve, reject) => {
45
- this.resolve = this._handleEnd(resolve);
46
- this.reject = this._handleEnd(reject);
47
- });
48
- this.code = null;
49
- this.error = null;
50
- this.stdout = '';
51
- this.stderr = '';
52
- this.emitter = this.start(command, args);
53
- this.isClose = false;
54
- this.schedule = {
55
- target: Promise.resolve(),
56
- complete: () => { }
57
- };
58
- }
59
- _handleEnd(fn) {
60
- return (e) => {
61
- this.isClose = true;
62
- const { code, error } = e;
63
- this.code = code;
64
- this.error = error;
65
- fn({
66
- ...e,
67
- stdout: this.stdout,
68
- stderr: this.stderr
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
- start(command, args) {
73
- const SPACE = ' ';
74
- const [command$, ...args$] = (command + SPACE + args.join(SPACE))
75
- .replace(/\s+/g, SPACE)
76
- .split(SPACE)
77
- .filter(i => !!i)
78
- .map(i => LOCAL_COMMAND_MAP[i] || i);
79
- const emitter = childProcess__namespace.spawn(command$, args$, {
80
- stdio: ['pipe', 'pipe', 'pipe']
81
- });
82
- emitter.on('close', (code) => {
83
- if (code !== 0) {
84
- this.reject({ code });
85
- }
86
- else {
87
- this.resolve({ code });
88
- }
89
- });
90
- emitter.on('error', (error) => {
91
- !process.exitCode && (process.exitCode = 1);
92
- this.reject({ code: process.exitCode, error });
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
- emitter.stdout.on('data', e => {
95
- this.stdout += e.toString();
96
- this.schedule.complete();
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
- emitter.stderr.on('data', e => this.stderr += e.toString());
99
- return emitter;
100
- }
101
- async stop() {
102
- await this.schedule.target;
103
- if (!this.isClose) {
104
- this.emitter.stdin.end();
105
- this.isClose = true;
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
- async press(key, timeout = 200) {
111
- if (!key || this.isClose)
112
- return;
113
- await this.schedule.target;
114
- this.schedule.target = new Promise(resolve => {
115
- this.schedule.complete = resolve;
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
- await new Promise(resolve => {
118
- this.emitter.stdin.write(KEY_MAP[key.toUpperCase()] || key, 'utf8', resolve);
119
- });
120
- await new Promise(_ => setTimeout(_, timeout));
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
- DOWN: '\x1b\x5b\x42',
7
- UP: '\x1b\x5b\x41',
8
- ENTER: '\x0d',
9
- SPACE: '\x7f'
7
+ DOWN: "\x1B[B",
8
+ UP: "\x1B[A",
9
+ ENTER: "\r",
10
+ // return;
11
+ SPACE: ""
10
12
  };
11
13
  class Command {
12
- target;
13
- code;
14
- error;
15
- resolve;
16
- reject;
17
- stdout;
18
- stderr;
19
- emitter;
20
- isClose;
21
- schedule;
22
- constructor(command, args) {
23
- this.target = new Promise((resolve, reject) => {
24
- this.resolve = this._handleEnd(resolve);
25
- this.reject = this._handleEnd(reject);
26
- });
27
- this.code = null;
28
- this.error = null;
29
- this.stdout = '';
30
- this.stderr = '';
31
- this.emitter = this.start(command, args);
32
- this.isClose = false;
33
- this.schedule = {
34
- target: Promise.resolve(),
35
- complete: () => { }
36
- };
37
- }
38
- _handleEnd(fn) {
39
- return (e) => {
40
- this.isClose = true;
41
- const { code, error } = e;
42
- this.code = code;
43
- this.error = error;
44
- fn({
45
- ...e,
46
- stdout: this.stdout,
47
- stderr: this.stderr
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
- start(command, args) {
52
- const SPACE = ' ';
53
- const [command$, ...args$] = (command + SPACE + args.join(SPACE))
54
- .replace(/\s+/g, SPACE)
55
- .split(SPACE)
56
- .filter(i => !!i)
57
- .map(i => LOCAL_COMMAND_MAP[i] || i);
58
- const emitter = childProcess.spawn(command$, args$, {
59
- stdio: ['pipe', 'pipe', 'pipe']
60
- });
61
- emitter.on('close', (code) => {
62
- if (code !== 0) {
63
- this.reject({ code });
64
- }
65
- else {
66
- this.resolve({ code });
67
- }
68
- });
69
- emitter.on('error', (error) => {
70
- !process.exitCode && (process.exitCode = 1);
71
- this.reject({ code: process.exitCode, error });
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
- emitter.stdout.on('data', e => {
74
- this.stdout += e.toString();
75
- this.schedule.complete();
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
- emitter.stderr.on('data', e => this.stderr += e.toString());
78
- return emitter;
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
- async stop() {
81
- await this.schedule.target;
82
- if (!this.isClose) {
83
- this.emitter.stdin.end();
84
- this.isClose = true;
85
- }
86
- const response = await this.target;
87
- return response;
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
- await new Promise(_ => setTimeout(_, timeout));
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
- export { Command };
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": "1.1.1",
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": "^1.1.1"
15
+ "@deot/dev-shared": "^2.0.0",
16
+ "puppeteer": "^20.7.3"
16
17
  }
17
18
  }