@percy/core 1.0.0-beta.76 → 1.0.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/snapshot.js DELETED
@@ -1,279 +0,0 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.discoverSnapshotResources = discoverSnapshotResources;
7
- exports.getSnapshotConfig = getSnapshotConfig;
8
-
9
- var _logger = _interopRequireDefault(require("@percy/logger"));
10
-
11
- var _config = _interopRequireDefault(require("@percy/config"));
12
-
13
- var _utils = require("@percy/config/dist/utils");
14
-
15
- var _utils2 = require("./utils");
16
-
17
- function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
18
-
19
- // Validates and returns snapshot options merged with percy config options.
20
- function getSnapshotConfig(percy, options) {
21
- var _ref, _snapshot$widths;
22
-
23
- if (!options.url) throw new Error('Missing required URL for snapshot');
24
- let {
25
- config
26
- } = percy;
27
- let uri = new URL(options.url);
28
- let name = options.name || `${uri.pathname}${uri.search}${uri.hash}`;
29
- let meta = {
30
- snapshot: {
31
- name
32
- },
33
- build: percy.build
34
- };
35
- let log = (0, _logger.default)('core:snapshot'); // migrate deprecated snapshot config options
36
-
37
- let {
38
- clientInfo,
39
- environmentInfo,
40
- ...snapshot
41
- } = _config.default.migrate(options, '/snapshot'); // throw an error when missing required widths
42
-
43
-
44
- if (!((_ref = (_snapshot$widths = snapshot.widths) !== null && _snapshot$widths !== void 0 ? _snapshot$widths : percy.config.snapshot.widths) !== null && _ref !== void 0 && _ref.length)) {
45
- throw new Error('Missing required widths for snapshot');
46
- } // validate and scrub according to dom snaphot presence
47
-
48
-
49
- let errors = _config.default.validate(snapshot, snapshot.domSnapshot ? '/snapshot/dom' : '/snapshot');
50
-
51
- if (errors) {
52
- log.warn('Invalid snapshot options:', meta);
53
-
54
- for (let e of errors) log.warn(`- ${e.path}: ${e.message}`, meta);
55
- } // inherit options from the percy config
56
-
57
-
58
- return (0, _utils.merge)([config.snapshot, {
59
- name,
60
- meta,
61
- clientInfo,
62
- environmentInfo,
63
- // only specific discovery options are used per-snapshot
64
- discovery: {
65
- allowedHostnames: [uri.hostname, ...config.discovery.allowedHostnames],
66
- networkIdleTimeout: config.discovery.networkIdleTimeout,
67
- requestHeaders: config.discovery.requestHeaders,
68
- authorization: config.discovery.authorization,
69
- disableCache: config.discovery.disableCache,
70
- userAgent: config.discovery.userAgent
71
- }
72
- }, snapshot], (path, prev, next) => {
73
- switch (path.map(k => k.toString()).join('.')) {
74
- case 'widths':
75
- // override and sort widths
76
- return [path, next.sort((a, b) => a - b)];
77
-
78
- case 'percyCSS':
79
- // concatenate percy css
80
- return [path, [prev, next].filter(Boolean).join('\n')];
81
-
82
- case 'execute':
83
- // shorthand for execute.beforeSnapshot
84
- return Array.isArray(next) || typeof next !== 'object' ? [path.concat('beforeSnapshot'), next] : [path];
85
- } // ensure additional snapshots have complete names
86
-
87
-
88
- if (path[0] === 'additionalSnapshots' && path.length === 2) {
89
- let {
90
- prefix = '',
91
- suffix = '',
92
- ...n
93
- } = next;
94
- next = {
95
- name: `${prefix}${name}${suffix}`,
96
- ...n
97
- };
98
- return [path, next];
99
- }
100
- });
101
- } // Returns a complete and valid snapshot config object and logs verbose debug logs detailing various
102
- // snapshot options. When `showInfo` is true, specific messages will be logged as info logs rather
103
- // than debug logs.
104
-
105
-
106
- function debugSnapshotConfig(snapshot, showInfo) {
107
- let log = (0, _logger.default)('core:snapshot'); // log snapshot info
108
-
109
- log.debug('---------', snapshot.meta);
110
- if (showInfo) log.info(`Snapshot found: ${snapshot.name}`, snapshot.meta);else log.debug(`Handling snapshot: ${snapshot.name}`, snapshot.meta); // will log debug info for an object property if its value is defined
111
-
112
- let debugProp = (obj, prop, format = String) => {
113
- let val = prop.split('.').reduce((o, k) => o === null || o === void 0 ? void 0 : o[k], obj);
114
-
115
- if (val != null) {
116
- // join formatted array values with a space
117
- val = [].concat(val).map(format).join(', ');
118
- log.debug(`- ${prop}: ${val}`, snapshot.meta);
119
- }
120
- };
121
-
122
- debugProp(snapshot, 'url');
123
- debugProp(snapshot, 'widths', v => `${v}px`);
124
- debugProp(snapshot, 'minHeight', v => `${v}px`);
125
- debugProp(snapshot, 'enableJavaScript');
126
- debugProp(snapshot, 'waitForTimeout');
127
- debugProp(snapshot, 'waitForSelector');
128
- debugProp(snapshot, 'execute.afterNavigation');
129
- debugProp(snapshot, 'execute.beforeResize');
130
- debugProp(snapshot, 'execute.afterResize');
131
- debugProp(snapshot, 'execute.beforeSnapshot');
132
- debugProp(snapshot, 'discovery.allowedHostnames');
133
- debugProp(snapshot, 'discovery.requestHeaders', JSON.stringify);
134
- debugProp(snapshot, 'discovery.authorization', JSON.stringify);
135
- debugProp(snapshot, 'discovery.disableCache');
136
- debugProp(snapshot, 'discovery.userAgent');
137
- debugProp(snapshot, 'clientInfo');
138
- debugProp(snapshot, 'environmentInfo');
139
- debugProp(snapshot, 'domSnapshot', Boolean);
140
-
141
- for (let added of snapshot.additionalSnapshots || []) {
142
- if (showInfo) log.info(`Snapshot found: ${added.name}`, snapshot.meta);else log.debug(`Additional snapshot: ${added.name}`, snapshot.meta);
143
- debugProp(added, 'waitForTimeout');
144
- debugProp(added, 'waitForSelector');
145
- debugProp(added, 'execute');
146
- }
147
- } // Calls the provided callback with additional resources
148
-
149
-
150
- function handleSnapshotResources(snapshot, map, callback) {
151
- let resources = [...map.values()]; // sort the root resource first
152
-
153
- let [root] = resources.splice(resources.findIndex(r => r.root), 1);
154
- resources.unshift(root); // inject Percy CSS
155
-
156
- if (snapshot.percyCSS) {
157
- let css = (0, _utils2.createPercyCSSResource)(root.url, snapshot.percyCSS);
158
- resources.push(css); // replace root contents and associated properties
159
-
160
- Object.assign(root, (0, _utils2.createRootResource)(root.url, root.content.replace(/(<\/body>)(?!.*\1)/is, `<link data-percy-specific-css rel="stylesheet" href="${css.pathname}"/>` + '$&')));
161
- } // include associated snapshot logs matched by meta information
162
-
163
-
164
- resources.push((0, _utils2.createLogResource)(_logger.default.query(log => {
165
- var _log$meta$snapshot;
166
-
167
- return ((_log$meta$snapshot = log.meta.snapshot) === null || _log$meta$snapshot === void 0 ? void 0 : _log$meta$snapshot.name) === snapshot.meta.snapshot.name;
168
- })));
169
- return callback(snapshot, resources);
170
- } // Wait for a page's asset discovery network to idle
171
-
172
-
173
- function waitForDiscoveryNetworkIdle(page, options) {
174
- let {
175
- allowedHostnames,
176
- networkIdleTimeout
177
- } = options;
178
-
179
- let filter = r => (0, _utils2.hostnameMatches)(allowedHostnames, r.url);
180
-
181
- return page.network.idle(filter, networkIdleTimeout);
182
- } // Used to cache resources across core instances
183
-
184
-
185
- const RESOURCE_CACHE_KEY = Symbol('resource-cache'); // Discovers resources for a snapshot using a browser page to intercept requests. The callback
186
- // function will be called with the snapshot name (for additional snapshots) and an array of
187
- // discovered resources. When additional snapshots are provided, the callback will be called once
188
- // for each snapshot.
189
-
190
- async function* discoverSnapshotResources(percy, snapshot, callback) {
191
- var _snapshot$enableJavaS;
192
-
193
- debugSnapshotConfig(snapshot, percy.dryRun); // when dry-running, invoke the callback for each snapshot and immediately return
194
-
195
- let allSnapshots = [snapshot, ...(snapshot.additionalSnapshots || [])];
196
- if (percy.dryRun) return allSnapshots.map(s => callback(s)); // keep a global resource cache across snapshots
197
-
198
- let cache = percy[RESOURCE_CACHE_KEY] || (percy[RESOURCE_CACHE_KEY] = new Map()); // copy widths to prevent mutation later
199
-
200
- let widths = snapshot.widths.slice(); // preload the root resource for existing dom snapshots
201
-
202
- let resources = new Map(snapshot.domSnapshot && [(0, _utils2.createRootResource)(snapshot.url, snapshot.domSnapshot)].map(resource => [resource.url, resource])); // open a new browser page
203
-
204
- let page = yield percy.browser.page({
205
- enableJavaScript: (_snapshot$enableJavaS = snapshot.enableJavaScript) !== null && _snapshot$enableJavaS !== void 0 ? _snapshot$enableJavaS : !snapshot.domSnapshot,
206
- networkIdleTimeout: snapshot.discovery.networkIdleTimeout,
207
- requestHeaders: snapshot.discovery.requestHeaders,
208
- authorization: snapshot.discovery.authorization,
209
- userAgent: snapshot.discovery.userAgent,
210
- meta: snapshot.meta,
211
- // enable network inteception
212
- intercept: {
213
- enableJavaScript: snapshot.enableJavaScript,
214
- disableCache: snapshot.discovery.disableCache,
215
- allowedHostnames: snapshot.discovery.allowedHostnames,
216
- getResource: u => resources.get(u) || cache.get(u),
217
- saveResource: r => resources.set(r.url, r) && cache.set(r.url, r)
218
- }
219
- });
220
-
221
- try {
222
- var _snapshot$execute;
223
-
224
- // set the initial page size
225
- yield page.resize({
226
- width: widths.shift(),
227
- height: snapshot.minHeight
228
- }); // navigate to the url
229
-
230
- yield page.goto(snapshot.url);
231
- yield page.evaluate((_snapshot$execute = snapshot.execute) === null || _snapshot$execute === void 0 ? void 0 : _snapshot$execute.afterNavigation); // trigger resize events for other widths
232
-
233
- for (let width of widths) {
234
- var _snapshot$execute2, _snapshot$execute3;
235
-
236
- yield page.evaluate((_snapshot$execute2 = snapshot.execute) === null || _snapshot$execute2 === void 0 ? void 0 : _snapshot$execute2.beforeResize);
237
- yield waitForDiscoveryNetworkIdle(page, snapshot.discovery);
238
- yield page.resize({
239
- width,
240
- height: snapshot.minHeight
241
- });
242
- yield page.evaluate((_snapshot$execute3 = snapshot.execute) === null || _snapshot$execute3 === void 0 ? void 0 : _snapshot$execute3.afterResize);
243
- }
244
-
245
- if (snapshot.domSnapshot) {
246
- // ensure discovery has finished and handle resources
247
- yield waitForDiscoveryNetworkIdle(page, snapshot.discovery);
248
- handleSnapshotResources(snapshot, resources, callback);
249
- } else {
250
- let {
251
- enableJavaScript
252
- } = snapshot; // capture snapshots sequentially
253
-
254
- for (let snap of allSnapshots) {
255
- // will wait for timeouts, selectors, and additional network activity
256
- let {
257
- url,
258
- dom
259
- } = yield page.snapshot({
260
- enableJavaScript,
261
- ...snap
262
- });
263
- resources.set(url, (0, _utils2.createRootResource)(url, dom)); // shallow merge with root snapshot options
264
-
265
- handleSnapshotResources({ ...snapshot,
266
- ...snap
267
- }, resources, callback); // remove the previously captured dom snapshot
268
-
269
- resources.delete(url);
270
- }
271
- } // page clean up
272
-
273
-
274
- await page.close();
275
- } catch (error) {
276
- await page.close();
277
- throw error;
278
- }
279
- }
package/dist/utils.js DELETED
@@ -1,170 +0,0 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.createLogResource = createLogResource;
7
- exports.createPercyCSSResource = createPercyCSSResource;
8
- exports.createResource = createResource;
9
- exports.createRootResource = createRootResource;
10
- exports.generatePromise = generatePromise;
11
- exports.hostname = hostname;
12
- Object.defineProperty(exports, "hostnameMatches", {
13
- enumerable: true,
14
- get: function () {
15
- return _utils.hostnameMatches;
16
- }
17
- });
18
- exports.normalizeURL = normalizeURL;
19
- Object.defineProperty(exports, "request", {
20
- enumerable: true,
21
- get: function () {
22
- return _request.request;
23
- }
24
- });
25
- exports.waitFor = waitFor;
26
-
27
- var _utils = require("@percy/client/dist/utils");
28
-
29
- var _request = require("@percy/client/dist/request");
30
-
31
- function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
32
-
33
- // Returns the hostname portion of a URL.
34
- function hostname(url) {
35
- return new URL(url).hostname;
36
- } // Normalizes a URL by stripping hashes to ensure unique resources.
37
-
38
-
39
- function normalizeURL(url) {
40
- let {
41
- protocol,
42
- host,
43
- pathname,
44
- search
45
- } = new URL(url);
46
- return `${protocol}//${host}${pathname}${search}`;
47
- } // Creates a local resource object containing the resource URL, mimetype, content, sha, and any
48
- // other additional resources attributes.
49
-
50
-
51
- function createResource(url, content, mimetype, attrs) {
52
- return { ...attrs,
53
- sha: (0, _utils.sha256hash)(content),
54
- mimetype,
55
- content,
56
- url
57
- };
58
- } // Creates a root resource object with an additional `root: true` property. The URL is normalized
59
- // here as a convenience since root resources are usually created outside of asset discovery.
60
-
61
-
62
- function createRootResource(url, content) {
63
- return createResource(normalizeURL(url), content, 'text/html', {
64
- root: true
65
- });
66
- } // Creates a Percy CSS resource object.
67
-
68
-
69
- function createPercyCSSResource(url, css) {
70
- let {
71
- href,
72
- pathname
73
- } = new URL(`/percy-specific.${Date.now()}.css`, url);
74
- return createResource(href, css, 'text/css', {
75
- pathname
76
- });
77
- } // Creates a log resource object.
78
-
79
-
80
- function createLogResource(logs) {
81
- return createResource(`/percy.${Date.now()}.log`, JSON.stringify(logs), 'text/plain');
82
- } // Creates a thennable, cancelable, generator instance
83
-
84
-
85
- function generatePromise(gen) {
86
- var _gen, _gen2;
87
-
88
- // ensure a generator is provided
89
- if (typeof gen === 'function') gen = gen();
90
- if (typeof ((_gen = gen) === null || _gen === void 0 ? void 0 : _gen.then) === 'function') return gen;
91
- if (typeof ((_gen2 = gen) === null || _gen2 === void 0 ? void 0 : _gen2.next) !== 'function' || !(typeof gen[Symbol.iterator] === 'function' || typeof gen[Symbol.asyncIterator] === 'function')) return Promise.resolve(gen); // used to trigger cancelation
92
-
93
- class Canceled extends Error {
94
- constructor(...args) {
95
- super(...args);
96
-
97
- _defineProperty(this, "name", 'Canceled');
98
-
99
- _defineProperty(this, "canceled", true);
100
- }
101
-
102
- } // recursively runs the generator, maybe throwing an error when canceled
103
-
104
-
105
- let handleNext = async (g, last) => {
106
- let canceled = g.cancel.triggered;
107
- let {
108
- done,
109
- value
110
- } = canceled ? await g.throw(canceled) : await g.next(last);
111
- if (canceled) delete g.cancel.triggered;
112
- return done ? value : handleNext(g, value);
113
- }; // handle cancelation errors by calling any cancel handlers
114
-
115
-
116
- let cancelable = async function* () {
117
- try {
118
- return yield* gen;
119
- } catch (error) {
120
- if (error.canceled) {
121
- let cancelers = cancelable.cancelers || [];
122
-
123
- for (let c of cancelers) await c(error);
124
- }
125
-
126
- throw error;
127
- }
128
- }(); // augment the cancelable generator with promise-like and cancel methods
129
-
130
-
131
- return Object.assign(cancelable, {
132
- run: () => cancelable.promise || (cancelable.promise = handleNext(cancelable)),
133
- then: (resolve, reject) => cancelable.run().then(resolve, reject),
134
- catch: reject => cancelable.run().catch(reject),
135
- cancel: message => {
136
- cancelable.cancel.triggered = new Canceled(message);
137
- return cancelable;
138
- },
139
- canceled: handler => {
140
- (cancelable.cancelers || (cancelable.cancelers = [])).push(handler);
141
- return cancelable;
142
- }
143
- });
144
- } // Resolves when the predicate function returns true within the timeout. If an idle option is
145
- // provided, the predicate will be checked again before resolving, after the idle period. The poll
146
- // option determines how often the predicate check will be run.
147
-
148
-
149
- function waitFor(predicate, options) {
150
- let {
151
- poll = 10,
152
- timeout,
153
- idle
154
- } = Number.isInteger(options) ? {
155
- timeout: options
156
- } : options || {};
157
- return generatePromise(async function* check(start, done) {
158
- while (true) {
159
- if (timeout && Date.now() - start >= timeout) {
160
- throw new Error(`Timeout of ${timeout}ms exceeded.`);
161
- } else if (!predicate()) {
162
- yield new Promise(r => setTimeout(r, poll, done = false));
163
- } else if (idle && !done) {
164
- yield new Promise(r => setTimeout(r, idle, done = true));
165
- } else {
166
- return;
167
- }
168
- }
169
- }(Date.now()));
170
- }
package/post-install.js DELETED
@@ -1,23 +0,0 @@
1
- // Automatically download and install Chromium if the PERCY_POSTINSTALL_BROWSER environment variable
2
- // is present and truthy, or if this module is required directly from within another module. Useful
3
- // when running in CI environments with heavy caching of node_modules.
4
- if (process.env.PERCY_POSTINSTALL_BROWSER || require.main !== module) {
5
- const fs = require('fs');
6
- const path = require('path');
7
-
8
- // the src directory indicates postinstall during development
9
- const isDev = fs.existsSync(path.join(__dirname, 'src'));
10
-
11
- // register babel transforms for development install
12
- if (isDev) require('../../scripts/babel-register');
13
-
14
- // require dev or production modules
15
- const install = require(isDev ? './src/install' : './dist/install');
16
- const log = require(isDev ? '../logger/src' : '@percy/logger')('core:post-install');
17
-
18
- // install chromium
19
- install.chromium().catch(error => {
20
- log.error('Encountered an error while installing Chromium');
21
- log.error(error);
22
- });
23
- }
@@ -1,35 +0,0 @@
1
- // aliased to src for coverage during tests without needing to compile this file
2
- const { default: Server } = require('@percy/core/dist/server');
3
-
4
- function createTestServer({ default: defaultReply, ...replies }, port = 8000) {
5
- let server = new Server();
6
-
7
- // alternate route handling
8
- let handleReply = reply => async (req, res) => {
9
- let [status, headers, body] = typeof reply === 'function' ? await reply(req) : reply;
10
- if (!Buffer.isBuffer(body) && typeof body !== 'string') body = JSON.stringify(body);
11
- return res.send(status, headers, body);
12
- };
13
-
14
- // map replies to alternate route handlers
15
- server.reply = (p, reply) => (replies[p] = handleReply(reply));
16
- for (let [p, reply] of Object.entries(replies)) server.reply(p, reply);
17
- if (defaultReply) defaultReply = handleReply(defaultReply);
18
-
19
- // track requests and route replies
20
- server.requests = [];
21
- server.route(async (req, res, next) => {
22
- let pathname = req.url.pathname;
23
- if (req.url.search) pathname += req.url.search;
24
- server.requests.push(req.body ? [pathname, req.body] : [pathname]);
25
- let reply = replies[req.url.pathname] || defaultReply;
26
- return reply ? await reply(req, res) : next();
27
- });
28
-
29
- // automatically listen
30
- return server.listen(port);
31
- };
32
-
33
- // support commonjs environments
34
- module.exports = createTestServer;
35
- module.exports.createTestServer = createTestServer;
package/types/index.d.ts DELETED
@@ -1,101 +0,0 @@
1
- // utility types
2
- type Without<T, U> = { [P in Exclude<keyof T, keyof U>]?: never };
3
- type XOR<T, U> = (T | U) extends object ? (Without<T, U> & U) | (Without<U, T> & T) : T | U;
4
-
5
- type LogLevel = 'error' | 'warn' | 'info' | 'debug' | 'silent';
6
-
7
- interface Pojo {
8
- [x: string]: any;
9
- }
10
-
11
- interface AuthCredentials {
12
- username: string;
13
- password: string;
14
- }
15
-
16
- interface DiscoveryOptions {
17
- requestHeaders?: Pojo;
18
- authorization?: AuthCredentials;
19
- allowedHostnames?: string[];
20
- disableCache?: boolean;
21
- }
22
-
23
- interface DiscoveryLaunchOptions {
24
- executable?: string;
25
- args?: string[];
26
- timeout?: number;
27
- headless?: boolean;
28
- }
29
-
30
- interface AllDiscoveryOptions extends DiscoveryOptions {
31
- networkIdleTimeout?: number;
32
- concurrency?: number;
33
- launchOptions?: DiscoveryLaunchOptions;
34
- }
35
-
36
- interface CommonSnapshotOptions {
37
- widths?: number[];
38
- minHeight?: number;
39
- percyCSS?: string;
40
- enableJavaScript?: boolean;
41
- }
42
-
43
- export interface SnapshotOptions extends CommonSnapshotOptions {
44
- discovery?: DiscoveryOptions;
45
- }
46
-
47
- type ClientEnvInfo = {
48
- clientInfo?: string,
49
- environmentInfo?: string
50
- }
51
-
52
- export type PercyConfigOptions<C = Pojo> = C & {
53
- snapshot?: CommonSnapshotOptions,
54
- discovery?: AllDiscoveryOptions
55
- }
56
-
57
- export type PercyOptions<C = Pojo> = {
58
- token?: string,
59
- server?: boolean,
60
- port?: number,
61
- concurrency?: number,
62
- loglevel?: LogLevel,
63
- config?: undefined | string | false
64
- } & ClientEnvInfo & PercyConfigOptions<C>;
65
-
66
- type SnapshotExec = () => void | Promise<void>;
67
-
68
- type AdditionalSnapshot = (XOR<XOR<
69
- { name: string },
70
- { prefix: string, suffix?: string }>,
71
- { suffix: string, prefix?: string }>
72
- ) & { execute: SnapshotExec };
73
-
74
- declare class Percy {
75
- static start(options?: PercyOptions): Promise<Percy>;
76
- constructor(options?: PercyOptions);
77
- loglevel(): LogLevel;
78
- loglevel(level: LogLevel): void;
79
- config: PercyConfigOptions;
80
- setConfig(config: ClientEnvInfo & PercyConfigOptions): PercyConfigOptions;
81
- start(): Promise<void>;
82
- stop(force?: boolean): Promise<void>;
83
- idle(): Promise<void>;
84
- close(): void;
85
-
86
- snapshot(options: {
87
- url: string,
88
- name?: string,
89
- clientInfo?: string,
90
- environmentInfo?: string
91
- } & XOR<{
92
- domSnapshot: string
93
- }, {
94
- waitForTimeout?: number,
95
- waitForSelector?: string,
96
- execute?: SnapshotExec,
97
- additionalSnapshots?: AdditionalSnapshot[]
98
- }> & SnapshotOptions): Promise<void>;
99
- }
100
-
101
- export default Percy;