@percy/sdk-utils 1.7.2 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bundle.js CHANGED
@@ -107,26 +107,26 @@
107
107
  log: [ns, lvl, msg, meta]
108
108
  }));
109
109
  } else {
110
- // keep log history when not remote
111
- let [debug, level, message, timestamp] = [ns, lvl, msg, Date.now()];
110
+ // keep log history of full message when not remote
111
+ let message = err ? msg.stack : msg.toString();
112
+ let [debug, level, timestamp, error] = [ns, lvl, Date.now(), !!err];
112
113
  (log.history || (log.history = [])).push({
113
114
  debug,
114
115
  level,
115
116
  message,
116
117
  meta,
117
- timestamp
118
+ timestamp,
119
+ error
118
120
  });
119
121
  } // check if the specific level is within the local loglevel range
120
122
 
121
123
 
122
124
  if (LOG_LEVELS[lvl] != null && LOG_LEVELS[lvl] >= LOG_LEVELS[loglevel()]) {
123
- var _msg;
124
-
125
125
  let debug = loglevel() === 'debug';
126
126
  let label = debug ? `percy:${ns}` : 'percy'; // colorize the label when possible for consistency with the CLI logger
127
127
 
128
128
  if (!process.env.__PERCY_BROWSERIFIED__) label = `\u001b[95m${label}\u001b[39m`;
129
- msg = `[${label}] ${err && debug && ((_msg = msg) === null || _msg === void 0 ? void 0 : _msg.stack) || msg}`;
129
+ msg = `[${label}] ${err && debug && msg.stack || msg}`;
130
130
 
131
131
  if (process.env.__PERCY_BROWSERIFIED__) {
132
132
  // use console[warn|error|log] in browsers
@@ -172,15 +172,17 @@
172
172
 
173
173
  let address = new URL('/logger', info.address).href; // create and cache a websocket connection
174
174
 
175
- let ws = remote.socket = await createWebSocket(address, timeout); // accept loglevel updates
176
-
177
- /* istanbul ignore next: difficult to test currently */
178
-
179
- ws.onmessage = e => loglevel(JSON.parse(e.data).loglevel); // cleanup message handler on close
180
-
181
-
182
- ws.onclose = () => remote.socket = ws.onmessage = ws.onclose = null; // send any messages already logged in this environment
175
+ let ws = remote.socket = await createWebSocket(address, timeout);
176
+ await new Promise((resolve, reject) => {
177
+ // accept loglevel updates and resolve after first message
178
+ ws.onmessage = e => resolve(loglevel(JSON.parse(e.data).loglevel));
183
179
 
180
+ ws.onclose = () => {
181
+ // cleanup listeners and reject if not resolved
182
+ remote.socket = ws.onmessage = ws.onclose = null;
183
+ reject(new Error('Connection closed'));
184
+ };
185
+ }); // send any messages already logged in this environment
184
186
 
185
187
  if (log.history) ws.send(JSON.stringify({
186
188
  messages: log.history
@@ -205,7 +207,9 @@
205
207
 
206
208
  if (!(response.status >= 200 && response.status < 300)) {
207
209
  throw Object.assign(new Error(), {
208
- message: response.body.error || `${response.status} ${response.statusText}`,
210
+ message: response.body.error ||
211
+ /* istanbul ignore next: in tests, there's always an error message */
212
+ `${response.status} ${response.statusText}`,
209
213
  response
210
214
  });
211
215
  }
package/dist/logger.js CHANGED
@@ -52,26 +52,26 @@ const log = logger.log = (ns, lvl, msg, meta) => {
52
52
  log: [ns, lvl, msg, meta]
53
53
  }));
54
54
  } else {
55
- // keep log history when not remote
56
- let [debug, level, message, timestamp] = [ns, lvl, msg, Date.now()];
55
+ // keep log history of full message when not remote
56
+ let message = err ? msg.stack : msg.toString();
57
+ let [debug, level, timestamp, error] = [ns, lvl, Date.now(), !!err];
57
58
  (log.history || (log.history = [])).push({
58
59
  debug,
59
60
  level,
60
61
  message,
61
62
  meta,
62
- timestamp
63
+ timestamp,
64
+ error
63
65
  });
64
66
  } // check if the specific level is within the local loglevel range
65
67
 
66
68
 
67
69
  if (LOG_LEVELS[lvl] != null && LOG_LEVELS[lvl] >= LOG_LEVELS[loglevel()]) {
68
- var _msg;
69
-
70
70
  let debug = loglevel() === 'debug';
71
71
  let label = debug ? `percy:${ns}` : 'percy'; // colorize the label when possible for consistency with the CLI logger
72
72
 
73
73
  if (!process.env.__PERCY_BROWSERIFIED__) label = `\u001b[95m${label}\u001b[39m`;
74
- msg = `[${label}] ${err && debug && ((_msg = msg) === null || _msg === void 0 ? void 0 : _msg.stack) || msg}`;
74
+ msg = `[${label}] ${err && debug && msg.stack || msg}`;
75
75
 
76
76
  if (process.env.__PERCY_BROWSERIFIED__) {
77
77
  // use console[warn|error|log] in browsers
@@ -116,15 +116,17 @@ const remote = logger.remote = async timeout => {
116
116
 
117
117
  let address = new URL('/logger', _percyInfo.default.address).href; // create and cache a websocket connection
118
118
 
119
- let ws = remote.socket = await createWebSocket(address, timeout); // accept loglevel updates
120
-
121
- /* istanbul ignore next: difficult to test currently */
122
-
123
- ws.onmessage = e => loglevel(JSON.parse(e.data).loglevel); // cleanup message handler on close
124
-
125
-
126
- ws.onclose = () => remote.socket = ws.onmessage = ws.onclose = null; // send any messages already logged in this environment
127
-
119
+ let ws = remote.socket = await createWebSocket(address, timeout);
120
+ await new Promise((resolve, reject) => {
121
+ // accept loglevel updates and resolve after first message
122
+ ws.onmessage = e => resolve(loglevel(JSON.parse(e.data).loglevel));
123
+
124
+ ws.onclose = () => {
125
+ // cleanup listeners and reject if not resolved
126
+ remote.socket = ws.onmessage = ws.onclose = null;
127
+ reject(new Error('Connection closed'));
128
+ };
129
+ }); // send any messages already logged in this environment
128
130
 
129
131
  if (log.history) ws.send(JSON.stringify({
130
132
  messages: log.history
package/dist/request.js CHANGED
@@ -27,7 +27,9 @@ async function request(path, options = {}) {
27
27
 
28
28
  if (!(response.status >= 200 && response.status < 300)) {
29
29
  throw Object.assign(new Error(), {
30
- message: response.body.error || `${response.status} ${response.statusText}`,
30
+ message: response.body.error ||
31
+ /* istanbul ignore next: in tests, there's always an error message */
32
+ `${response.status} ${response.statusText}`,
31
33
  response
32
34
  });
33
35
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@percy/sdk-utils",
3
- "version": "1.7.2",
3
+ "version": "1.9.0",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -26,8 +26,6 @@
26
26
  "node": "./dist/index.js",
27
27
  "default": "./dist/bundle.js"
28
28
  },
29
- "./test/server": "./test/server.js",
30
- "./test/client": "./test/client.js",
31
29
  "./test/helpers": {
32
30
  "node": "./test/helpers.js",
33
31
  "default": "./test/client.js"
@@ -36,13 +34,9 @@
36
34
  "scripts": {
37
35
  "build": "node ../../scripts/build",
38
36
  "lint": "eslint --ignore-path ../../.gitignore .",
39
- "test": "node ../../scripts/test",
37
+ "test": "percy exec --testing -- node ../../scripts/test",
40
38
  "test:coverage": "yarn test --coverage"
41
39
  },
42
- "karma": {
43
- "run_start": "node test/server start",
44
- "run_complete": "node test/server stop"
45
- },
46
40
  "rollup": {
47
41
  "external": [
48
42
  "ws"
@@ -56,5 +50,5 @@
56
50
  ]
57
51
  }
58
52
  },
59
- "gitHead": "d9da4a4f9eafc3c3ac361a50f679f030172ad362"
53
+ "gitHead": "3332a2a63802c58848d0a5fbdd3c7aadc076212b"
60
54
  }
package/test/client.js CHANGED
@@ -14,206 +14,36 @@
14
14
  var require$$0__default = /*#__PURE__*/_interopDefaultLegacy(require$$0);
15
15
 
16
16
  const utils = require$$0__default["default"];
17
-
18
- function stub(object, method, func) {
19
- if (object[method].restore) object[method].restore();
20
- let stub = object[method] = Object.assign(function stub() {
21
- for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
22
- args[_key] = arguments[_key];
23
- }
24
-
25
- stub.calls.push(args);
26
- if (func) return func.apply(this, args);
27
- }, {
28
- restore: () => object[method] = stub.originalValue,
29
- reset: () => (stub.calls.length = 0) || stub,
30
- originalValue: object[method],
31
- calls: []
32
- });
33
- return stub;
34
- } // matches ansi escape sequences
35
-
36
-
37
- const ANSI_REG = new RegExp('[\\u001B\\u009B][[\\]()#;?]*((?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)' + '|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))', 'g'); // strips a log message of excessive newlines and asni escape sequences
38
-
39
- function sanitizeLog(str) {
40
- return str.replace(/\r\n/g, '\n').replace(ANSI_REG, '').replace(/\n$/, '');
41
- }
42
-
43
17
  const helpers = {
44
- async setup() {
18
+ async setupTest() {
45
19
  utils.percy.version = '';
46
20
  delete utils.percy.config;
47
21
  delete utils.percy.enabled;
48
22
  delete utils.percy.domScript;
23
+ delete utils.logger.log.history;
24
+ delete utils.logger.loglevel.lvl;
25
+ delete process.env.PERCY_LOGLEVEL;
49
26
  delete process.env.PERCY_SERVER_ADDRESS;
50
- await helpers.call('server.mock');
51
- await helpers.logger.mock();
27
+ await helpers.test('reset');
28
+ await utils.logger.remote();
52
29
  },
53
30
 
54
- async teardown() {
55
- var _utils$logger$log$res, _utils$logger$log;
56
-
57
- (_utils$logger$log$res = (_utils$logger$log = utils.logger.log).restore) === null || _utils$logger$log$res === void 0 ? void 0 : _utils$logger$log$res.call(_utils$logger$log);
58
-
59
- if (process.env.__PERCY_BROWSERIFIED__) {
60
- for (let m of ['warn', 'error', 'log']) {
61
- var _console$m$restore, _console$m;
62
-
63
- (_console$m$restore = (_console$m = console[m]).restore) === null || _console$m$restore === void 0 ? void 0 : _console$m$restore.call(_console$m);
64
- }
65
- } else {
66
- for (let io of ['stdout', 'stderr']) {
67
- var _process$io$write$res, _process$io$write;
68
-
69
- (_process$io$write$res = (_process$io$write = process[io].write).restore) === null || _process$io$write$res === void 0 ? void 0 : _process$io$write$res.call(_process$io$write);
70
- }
71
- }
72
-
73
- return helpers.call('server.close');
31
+ async test(cmd, arg) {
32
+ let res = await utils.request.post(`/test/api/${cmd}`, arg);
33
+ return res.body;
74
34
  },
75
35
 
76
- getRequests: () => helpers.call('server.requests'),
77
- testReply: (path, reply) => helpers.call('server.reply', path, reply),
78
- testFailure: function () {
79
- for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
80
- args[_key2] = arguments[_key2];
81
- }
82
-
83
- return helpers.call('server.test.failure', ...args);
36
+ async get(what, map) {
37
+ let res = await utils.request(`/test/${what}`);
38
+ if (!map) map = what === 'logs' ? l => l.message : i => i;
39
+ return Array.isArray(res.body[what]) ? res.body[what].map(map) : map(res.body);
84
40
  },
85
- testError: path => helpers.call('server.test.error', path),
86
- testSerialize: fn => !fn ? helpers.call('server.test.serialize') // get
87
- : helpers.call('server.test.serialize', fn),
88
- // set
89
- mockSite: () => helpers.call('site.mock'),
90
- closeSite: () => helpers.call('site.close'),
91
- logger: {
92
- stdout: [],
93
- stderr: [],
94
- loglevel: utils.logger.loglevel,
95
-
96
- async mock() {
97
- helpers.logger.reset();
98
- let shouldCaptureLogs = false;
99
- stub(utils.logger, 'log', function () {
100
- shouldCaptureLogs = true;
101
- utils.logger.log.originalValue(...arguments);
102
- shouldCaptureLogs = false;
103
- });
104
-
105
- let stubLogs = (ctx, method, err) => stub(ctx, method, msg => {
106
- if (!shouldCaptureLogs) return ctx[method].originalValue.call(ctx, msg);else helpers.logger[err ? 'stderr' : 'stdout'].push(sanitizeLog(msg));
107
- });
108
-
109
- if (process.env.__PERCY_BROWSERIFIED__) {
110
- for (let m of ['warn', 'error', 'log']) stubLogs(console, m, m !== 'log');
111
- } else {
112
- for (let io of ['stdout', 'stderr']) stubLogs(process[io], 'write', io === 'stderr');
113
- }
114
- },
115
-
116
- reset() {
117
- var _utils$logger$remote$, _utils$logger$log$res2, _utils$logger$log2;
118
-
119
- (_utils$logger$remote$ = utils.logger.remote.socket) === null || _utils$logger$remote$ === void 0 ? void 0 : _utils$logger$remote$.close();
120
- delete utils.logger.loglevel.lvl;
121
- delete utils.logger.log.history;
122
- helpers.logger.stdout.length = 0;
123
- helpers.logger.stderr.length = 0;
124
- (_utils$logger$log$res2 = (_utils$logger$log2 = utils.logger.log).reset) === null || _utils$logger$log$res2 === void 0 ? void 0 : _utils$logger$log$res2.call(_utils$logger$log2);
125
-
126
- if (process.env.__PERCY_BROWSERIFIED__) {
127
- for (let m of ['warn', 'error', 'log']) {
128
- var _console$m$reset, _console$m2;
129
-
130
- (_console$m$reset = (_console$m2 = console[m]).reset) === null || _console$m$reset === void 0 ? void 0 : _console$m$reset.call(_console$m2);
131
- }
132
- } else {
133
- for (let io of ['stdout', 'stderr']) {
134
- var _process$io$write$res2, _process$io$write2;
135
-
136
- (_process$io$write$res2 = (_process$io$write2 = process[io].write).reset) === null || _process$io$write$res2 === void 0 ? void 0 : _process$io$write$res2.call(_process$io$write2);
137
- }
138
- }
139
- }
140
41
 
42
+ get testSnapshotURL() {
43
+ return `${utils.percy.address}/test/snapshot`;
141
44
  }
142
- };
143
-
144
- if (process.env.__PERCY_BROWSERIFIED__) {
145
- helpers.call = async function call(event) {
146
- for (var _len3 = arguments.length, args = new Array(_len3 > 1 ? _len3 - 1 : 0), _key3 = 1; _key3 < _len3; _key3++) {
147
- args[_key3 - 1] = arguments[_key3];
148
- }
149
-
150
- let {
151
- socket,
152
- pending = {}
153
- } = helpers.call;
154
-
155
- if (!socket) {
156
- socket = new window.WebSocket('ws://localhost:5339');
157
- await new Promise((resolve, reject) => {
158
- let done = event => {
159
- clearTimeout(timeoutid);
160
- socket.onopen = socket.onerror = null;
161
-
162
- if (event && (event.error || event.type === 'error')) {
163
- reject(event.error || new Error('Test client connection failed'));
164
- } else resolve(socket);
165
- };
166
-
167
- let timeoutid = setTimeout(done, 1000, {
168
- error: new Error('Test client connection timed out')
169
- });
170
- socket.onopen = socket.onerror = done;
171
- });
172
-
173
- socket.onmessage = _ref => {
174
- let {
175
- data
176
- } = _ref;
177
- let {
178
- id,
179
- resolve,
180
- reject
181
- } = JSON.parse(data);
182
- if (!pending[id]) return;
183
- if (resolve) pending[id].resolve(resolve.result);
184
- if (reject) pending[id].reject(reject.error);
185
- };
186
-
187
- Object.assign(helpers.call, {
188
- socket,
189
- pending
190
- });
191
- }
192
-
193
- let id = helpers.call.uid = (helpers.call.uid || 0) + 1;
194
- args = args.map(a => typeof a === 'function' ? a.toString() : a);
195
- socket.send(JSON.stringify({
196
- id,
197
- event,
198
- args
199
- }));
200
- return (pending[id] = {}).promise = new Promise((resolve, reject) => {
201
- Object.assign(pending[id], {
202
- resolve,
203
- reject
204
- });
205
- });
206
- };
207
- } else {
208
- helpers.call = async function call() {
209
- let {
210
- context
211
- } = await ({});
212
- helpers.context = helpers.context || (await context());
213
- return helpers.context.call(...arguments);
214
- };
215
- }
216
45
 
46
+ };
217
47
  var helpers_1 = helpers;
218
48
 
219
49
  return helpers_1;
package/test/helpers.js CHANGED
@@ -1,162 +1,33 @@
1
1
  const utils = require('@percy/sdk-utils');
2
2
 
3
- function stub(object, method, func) {
4
- if (object[method].restore) object[method].restore();
5
-
6
- let stub = object[method] = Object.assign(function stub(...args) {
7
- stub.calls.push(args);
8
- if (func) return func.apply(this, args);
9
- }, {
10
- restore: () => (object[method] = stub.originalValue),
11
- reset: () => (stub.calls.length = 0) || stub,
12
- originalValue: object[method],
13
- calls: []
14
- });
15
-
16
- return stub;
17
- }
18
-
19
- // matches ansi escape sequences
20
- const ANSI_REG = new RegExp((
21
- '[\\u001B\\u009B][[\\]()#;?]*((?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)' +
22
- '|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))'
23
- ), 'g');
24
-
25
- // strips a log message of excessive newlines and asni escape sequences
26
- function sanitizeLog(str) {
27
- return str.replace(/\r\n/g, '\n')
28
- .replace(ANSI_REG, '')
29
- .replace(/\n$/, '');
30
- }
31
-
32
3
  const helpers = {
33
- async setup() {
4
+ async setupTest() {
34
5
  utils.percy.version = '';
35
6
  delete utils.percy.config;
36
7
  delete utils.percy.enabled;
37
8
  delete utils.percy.domScript;
9
+ delete utils.logger.log.history;
10
+ delete utils.logger.loglevel.lvl;
11
+ delete process.env.PERCY_LOGLEVEL;
38
12
  delete process.env.PERCY_SERVER_ADDRESS;
39
- await helpers.call('server.mock');
40
- await helpers.logger.mock();
13
+ await helpers.test('reset');
14
+ await utils.logger.remote();
41
15
  },
42
16
 
43
- async teardown() {
44
- utils.logger.log.restore?.();
45
-
46
- if (process.env.__PERCY_BROWSERIFIED__) {
47
- for (let m of ['warn', 'error', 'log']) console[m].restore?.();
48
- } else {
49
- for (let io of ['stdout', 'stderr']) process[io].write.restore?.();
50
- }
51
-
52
- return helpers.call('server.close');
17
+ async test(cmd, arg) {
18
+ let res = await utils.request.post(`/test/api/${cmd}`, arg);
19
+ return res.body;
53
20
  },
54
21
 
55
- getRequests: () => helpers.call('server.requests'),
56
- testReply: (path, reply) => helpers.call('server.reply', path, reply),
57
- testFailure: (...args) => helpers.call('server.test.failure', ...args),
58
- testError: path => helpers.call('server.test.error', path),
59
- testSerialize: fn => !fn
60
- ? helpers.call('server.test.serialize') // get
61
- : helpers.call('server.test.serialize', fn), // set
62
- mockSite: () => helpers.call('site.mock'),
63
- closeSite: () => helpers.call('site.close'),
64
-
65
- logger: {
66
- stdout: [],
67
- stderr: [],
68
- loglevel: utils.logger.loglevel,
69
-
70
- async mock() {
71
- helpers.logger.reset();
72
-
73
- let shouldCaptureLogs = false;
74
-
75
- stub(utils.logger, 'log', (...args) => {
76
- shouldCaptureLogs = true;
77
- utils.logger.log.originalValue(...args);
78
- shouldCaptureLogs = false;
79
- });
80
-
81
- let stubLogs = (ctx, method, err) => stub(ctx, method, msg => {
82
- if (!shouldCaptureLogs) return ctx[method].originalValue.call(ctx, msg);
83
- else helpers.logger[err ? 'stderr' : 'stdout'].push(sanitizeLog(msg));
84
- });
85
-
86
- if (process.env.__PERCY_BROWSERIFIED__) {
87
- for (let m of ['warn', 'error', 'log']) stubLogs(console, m, m !== 'log');
88
- } else {
89
- for (let io of ['stdout', 'stderr']) stubLogs(process[io], 'write', io === 'stderr');
90
- }
91
- },
92
-
93
- reset() {
94
- utils.logger.remote.socket?.close();
95
- delete utils.logger.loglevel.lvl;
96
- delete utils.logger.log.history;
97
-
98
- helpers.logger.stdout.length = 0;
99
- helpers.logger.stderr.length = 0;
100
- utils.logger.log.reset?.();
22
+ async get(what, map) {
23
+ let res = await utils.request(`/test/${what}`);
24
+ if (!map) map = what === 'logs' ? (l => l.message) : (i => i);
25
+ return Array.isArray(res.body[what]) ? res.body[what].map(map) : map(res.body);
26
+ },
101
27
 
102
- if (process.env.__PERCY_BROWSERIFIED__) {
103
- for (let m of ['warn', 'error', 'log']) console[m].reset?.();
104
- } else {
105
- for (let io of ['stdout', 'stderr']) process[io].write.reset?.();
106
- }
107
- }
28
+ get testSnapshotURL() {
29
+ return `${utils.percy.address}/test/snapshot`;
108
30
  }
109
31
  };
110
32
 
111
- if (process.env.__PERCY_BROWSERIFIED__) {
112
- helpers.call = async function call(event, ...args) {
113
- let { socket, pending = {} } = helpers.call;
114
-
115
- if (!socket) {
116
- socket = new window.WebSocket('ws://localhost:5339');
117
-
118
- await new Promise((resolve, reject) => {
119
- let done = event => {
120
- clearTimeout(timeoutid);
121
- socket.onopen = socket.onerror = null;
122
- if (event && (event.error || event.type === 'error')) {
123
- reject(event.error || new Error('Test client connection failed'));
124
- } else resolve(socket);
125
- };
126
-
127
- let timeoutid = setTimeout(done, 1000, {
128
- error: new Error('Test client connection timed out')
129
- });
130
-
131
- socket.onopen = socket.onerror = done;
132
- });
133
-
134
- socket.onmessage = ({ data }) => {
135
- let { id, resolve, reject } = JSON.parse(data);
136
- if (!pending[id]) return;
137
- if (resolve) pending[id].resolve(resolve.result);
138
- if (reject) pending[id].reject(reject.error);
139
- };
140
-
141
- Object.assign(helpers.call, { socket, pending });
142
- }
143
-
144
- let id = helpers.call.uid = (helpers.call.uid || 0) + 1;
145
- args = args.map(a => typeof a === 'function' ? a.toString() : a);
146
- socket.send(JSON.stringify({ id, event, args }));
147
-
148
- return ((pending[id] = {}).promise = (
149
- new Promise((resolve, reject) => {
150
- Object.assign(pending[id], { resolve, reject });
151
- })
152
- ));
153
- };
154
- } else {
155
- helpers.call = async function call() {
156
- let { context } = await import('./server.js');
157
- helpers.context = (helpers.context || await context());
158
- return helpers.context.call(...arguments);
159
- };
160
- }
161
-
162
33
  module.exports = helpers;
package/test/server.js DELETED
@@ -1,205 +0,0 @@
1
- const fs = require('fs');
2
- const path = require('path');
3
-
4
- // create a testing context for mocking the local percy server and a local testing site
5
- async function context() {
6
- let { createTestServer } = await import('@percy/core/test/helpers/server');
7
-
8
- let ctx = {
9
- async call(path, ...args) {
10
- let [key, ...paths] = path.split('.').reverse();
11
- let subject = paths.reduceRight((c, k) => c && c[k], ctx);
12
- if (!subject) return;
13
-
14
- let { value, get, set } = (
15
- Object.getOwnPropertyDescriptor(subject, key) ||
16
- Object.getOwnPropertyDescriptor(Object.getPrototypeOf(subject), key)
17
- ) || {};
18
-
19
- if (typeof value === 'function') {
20
- value = await value.apply(subject, args);
21
- } else if (!get && !set && args.length) {
22
- value = subject[key] = args[0];
23
- } else if (set && args.length) {
24
- value = set.apply(subject, args);
25
- } else if (get) {
26
- value = get.apply(subject);
27
- }
28
-
29
- if (value && typeof value[Symbol.iterator] === 'function') {
30
- value = Array.from(value);
31
- }
32
-
33
- return value;
34
- }
35
- };
36
-
37
- let mockServer = async () => {
38
- let allowSocketConnections = false;
39
-
40
- let serializeDOM = options => {
41
- let { dom = document, domTransformation } = options || {};
42
- let doc = (dom || document).cloneNode(true).documentElement;
43
- if (domTransformation) domTransformation(doc);
44
- return doc.outerHTML;
45
- };
46
-
47
- if (ctx.server.close) ctx.server.close();
48
-
49
- ctx.server = await createTestServer({
50
- '/percy/dom.js': () => [200, 'application/javascript', (
51
- `window.PercyDOM = { serialize: ${serializeDOM} }`)],
52
- '/percy/healthcheck': () => [200, 'application/json', (
53
- { success: true, config: { snapshot: { widths: [1280] } } })],
54
- '/percy/config': ({ body }) => [200, 'application/json', (
55
- { success: true, config: body })],
56
- '/percy/snapshot': () => [200, 'application/json', { success: true }],
57
- '/percy/idle': () => [200, 'application/json', { success: true }]
58
- }, 5338);
59
-
60
- ctx.server.route((req, res, next) => {
61
- if (req.body) try { req.body = JSON.parse(req.body); } catch {}
62
- res.setHeader('Access-Control-Expose-Headers', '*, X-Percy-Core-Version');
63
- res.setHeader('X-Percy-Core-Version', ctx.server.version || '1.0.0');
64
- return next();
65
- });
66
-
67
- ctx.server.websocket('/(logger)?', ws => {
68
- if (!allowSocketConnections) return ws.terminate();
69
- ws.onmessage = ({ data }) => ctx.server.messages.push(data);
70
- });
71
-
72
- Object.assign(ctx.server, {
73
- mock: mockServer,
74
- messages: [],
75
-
76
- test: {
77
- get serialize() { return serializeDOM; },
78
- set serialize(fn) { return (serializeDOM = fn); },
79
- failure: (path, error, o) => ctx.server.reply(path, () => (
80
- [500, 'application/json', { success: false, error, ...o }])),
81
- error: path => ctx.server.reply(path, r => r.connection.destroy()),
82
- remote: () => (allowSocketConnections = true)
83
- }
84
- });
85
- };
86
-
87
- let mockSite = async () => {
88
- if (ctx.site.close) ctx.site.close();
89
- ctx.site = Object.assign(await createTestServer({
90
- default: () => [200, 'text/html', 'Snapshot Me']
91
- }), { mock: mockSite });
92
- };
93
-
94
- ctx.server = { mock: mockServer };
95
- ctx.site = { mock: mockSite };
96
-
97
- ctx.mockAll = () => Promise.all([
98
- ctx.server.mock(),
99
- ctx.site.mock()
100
- ]);
101
-
102
- ctx.close = () => {
103
- if (ctx.server.close) ctx.server.close();
104
- if (ctx.site.close) ctx.site.close();
105
- };
106
-
107
- return ctx;
108
- }
109
-
110
- // start a testing server to control a context remotely
111
- async function start(args) {
112
- let { logger } = await import('@percy/logger');
113
- let { WebSocketServer } = await import('ws');
114
- let log = logger('utils:test/server');
115
-
116
- let startSocketServer = (tries = 0) => new Promise((resolve, reject) => {
117
- let server = new WebSocketServer({ port: 5339 });
118
- server.on('listening', () => resolve(server)).on('error', reject);
119
- }).catch(err => {
120
- if (err.code === 'EADDRINUSE' && tries < 10) {
121
- return stop().then(() => startSocketServer(++tries));
122
- } else throw err;
123
- });
124
-
125
- let wss = await startSocketServer();
126
- let ctx = await context();
127
-
128
- let close = () => {
129
- if (close.called) return;
130
- close.called = true;
131
-
132
- if (ctx) ctx.call('close');
133
- for (let ws of wss.clients) ws.terminate();
134
- wss.close(() => log.info('Closed SDK testing server'));
135
- };
136
-
137
- wss.on('connection', ws => {
138
- ws.on('message', data => {
139
- if (data.toString() === 'CLOSE') return close();
140
- let { id, event, args = [] } = JSON.parse(data);
141
-
142
- Promise.resolve().then(async () => {
143
- let result = await ctx.call(event, ...args);
144
- if (typeof result === 'function') result = result.toString();
145
- ws.send(JSON.stringify({ id, resolve: { result } }));
146
- log('debug', `${event}: ${JSON.stringify({ args, result })}`);
147
- }).catch(err => {
148
- let error = { message: err.message, stack: err.stack };
149
- ws.send(JSON.stringify({ id, reject: { error } }));
150
- log.debug(`${event}: ${error.stack}`);
151
- });
152
- });
153
- });
154
-
155
- await ctx.call('mockAll');
156
- log.info('Started SDK testing server');
157
- }
158
-
159
- // stop any existing testing server
160
- async function stop() {
161
- let { default: WS } = await import('ws');
162
-
163
- await new Promise(resolve => {
164
- let ws = new WS('ws://localhost:5339');
165
- ws.on('open', () => ws.send('CLOSE'));
166
- ws.on('close', () => resolve());
167
- });
168
- }
169
-
170
- // start & stop a testing server around a command
171
- async function exec(args) {
172
- let argsep = args.indexOf('--');
173
- if (argsep < 0) throw new Error('Must supply a command after `--`');
174
-
175
- let { spawn } = await import('child_process');
176
- let [cmd, ...cmdargs] = args.slice(argsep + 1);
177
- let startargs = args.slice(0, argsep);
178
- await start(startargs);
179
-
180
- spawn(cmd, cmdargs, { stdio: 'inherit' })
181
- .on('exit', process.exit)
182
- .on('error', error => {
183
- console.error(error);
184
- process.exit(1);
185
- });
186
- }
187
-
188
- // allow invoking start/stop/exec as CLI commands
189
- if (require.main === module) {
190
- const args = process.argv.slice(2);
191
- const run = { start, stop, exec }[args[0]];
192
-
193
- if (!run) {
194
- process.stderr.write('usage: node test/server <start|stop|exec>\n');
195
- } else if (!process.send && fs.existsSync(path.join(__filename, '../../src'))) {
196
- import('child_process').then(cp => cp.fork(__filename, args, {
197
- execArgv: ['--no-warnings', '--loader=../../scripts/loader.js']
198
- }));
199
- } else {
200
- run(args.slice(1)).catch(console.error);
201
- }
202
- }
203
-
204
- // export the namespace by default
205
- module.exports = { context, start, stop, exec };