@percy/sdk-utils 1.8.1 → 1.10.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 +19 -15
- package/dist/logger.js +17 -15
- package/dist/request.js +3 -1
- package/package.json +3 -9
- package/test/client.js +16 -186
- package/test/helpers.js +16 -145
- package/test/server.js +0 -205
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
|
|
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 &&
|
|
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);
|
|
176
|
-
|
|
177
|
-
|
|
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 ||
|
|
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
|
|
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 &&
|
|
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);
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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 ||
|
|
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.
|
|
3
|
+
"version": "1.10.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": "
|
|
53
|
+
"gitHead": "a6934eda4fc3b84845ae606d7f5a901f25e0a56f"
|
|
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
|
|
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.
|
|
51
|
-
await
|
|
27
|
+
await helpers.test('reset');
|
|
28
|
+
await utils.logger.remote();
|
|
52
29
|
},
|
|
53
30
|
|
|
54
|
-
async
|
|
55
|
-
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
|
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.
|
|
40
|
-
await
|
|
13
|
+
await helpers.test('reset');
|
|
14
|
+
await utils.logger.remote();
|
|
41
15
|
},
|
|
42
16
|
|
|
43
|
-
async
|
|
44
|
-
utils.
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
-
|
|
103
|
-
|
|
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 };
|