@applitools/eyes-cypress 3.23.9 → 3.24.0-beta.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.
Files changed (56) hide show
  1. package/CHANGELOG.md +2187 -2185
  2. package/LICENSE +25 -25
  3. package/README.md +778 -769
  4. package/commands.js +2 -2
  5. package/dist/browser/spec-driver.js +104 -0
  6. package/dist/plugin/handler.js +55 -0
  7. package/eyes-index.d.ts +34 -34
  8. package/index.js +2 -2
  9. package/package.json +97 -81
  10. package/src/browser/commands.js +167 -147
  11. package/src/browser/eyesCheckMapping.js +71 -0
  12. package/src/browser/eyesOpenMapping.js +34 -0
  13. package/src/browser/makeSend.js +18 -22
  14. package/src/browser/refer.js +57 -0
  15. package/src/browser/sendRequest.js +16 -16
  16. package/src/browser/socket.js +143 -0
  17. package/src/browser/socketCommands.js +81 -0
  18. package/src/browser/spec-driver.ts +109 -0
  19. package/src/pem/server.cert +22 -22
  20. package/src/pem/server.key +27 -27
  21. package/src/plugin/concurrencyMsg.js +8 -8
  22. package/src/plugin/config.js +54 -53
  23. package/src/plugin/defaultPort.js +1 -1
  24. package/src/plugin/errorDigest.js +96 -96
  25. package/src/plugin/getErrorsAndDiffs.js +34 -34
  26. package/src/plugin/handleTestResults.js +39 -0
  27. package/src/plugin/handler.ts +58 -0
  28. package/src/plugin/hooks.js +49 -42
  29. package/src/plugin/isGlobalHooksSupported.js +13 -13
  30. package/src/plugin/pluginExport.js +60 -57
  31. package/src/plugin/server.js +98 -46
  32. package/src/plugin/startPlugin.js +13 -34
  33. package/src/plugin/webSocket.js +130 -0
  34. package/src/setup/addEyesCommands.js +24 -24
  35. package/src/setup/addEyesCypressPlugin.js +15 -15
  36. package/src/setup/getCypressConfig.js +16 -16
  37. package/src/setup/getFilePath.js +22 -22
  38. package/src/setup/handleCommands.js +23 -23
  39. package/src/setup/handlePlugin.js +23 -23
  40. package/src/setup/handleTypeScript.js +21 -21
  41. package/src/setup/isCommandsDefined.js +7 -7
  42. package/src/setup/isPluginDefined.js +7 -7
  43. package/test/fixtures/testAppCopies/.gitignore +1 -1
  44. package/bin/eyes-setup.js +0 -21
  45. package/src/browser/eyesCheckWindow.js +0 -132
  46. package/src/browser/getAllBlobs.js +0 -14
  47. package/src/browser/getBrowserInfo.js +0 -39
  48. package/src/browser/makeHandleCypressViewport.js +0 -22
  49. package/src/browser/poll.js +0 -25
  50. package/src/plugin/app.js +0 -42
  51. package/src/plugin/handlers.js +0 -205
  52. package/src/plugin/makeHandleBatchResultsFile.js +0 -17
  53. package/src/plugin/pollingHandler.js +0 -46
  54. package/src/plugin/processCloseAndAbort.js +0 -33
  55. package/src/plugin/runningTests.js +0 -27
  56. package/src/plugin/waitForBatch.js +0 -33
@@ -1,147 +1,167 @@
1
- /* global Cypress,cy,window,before,after,navigator */
2
- 'use strict';
3
- const poll = require('./poll');
4
- const processPage = require('@applitools/dom-snapshot/dist/processPageCjs');
5
- const domSnapshotOptions = {dontFetchResources: Cypress.config('eyesDisableBrowserFetching')};
6
- const makeEyesCheckWindow = require('./eyesCheckWindow');
7
- const makeHandleCypressViewport = require('./makeHandleCypressViewport');
8
- const handleCypressViewport = makeHandleCypressViewport({cy});
9
- const makeSend = require('./makeSend');
10
- const send = makeSend(Cypress.config('eyesPort'), window.fetch);
11
- const makeSendRequest = require('./sendRequest');
12
- const sendRequest = makeSendRequest(send);
13
-
14
- const eyesCheckWindow = makeEyesCheckWindow({sendRequest, processPage, domSnapshotOptions});
15
-
16
- function getGlobalConfigProperty(prop) {
17
- const property = Cypress.config(prop);
18
- const shouldParse = ['eyesBrowser', 'eyesLayoutBreakpoints'];
19
- return property ? (shouldParse.includes(prop) ? JSON.parse(property) : property) : undefined;
20
- }
21
-
22
- const shouldUseBrowserHooks =
23
- !getGlobalConfigProperty('eyesIsDisabled') &&
24
- (getGlobalConfigProperty('isInteractive') ||
25
- !getGlobalConfigProperty('eyesIsGlobalHooksSupported'));
26
-
27
- if (shouldUseBrowserHooks) {
28
- const batchEnd = poll(() => {
29
- return sendRequest({command: 'batchEnd'});
30
- });
31
-
32
- before(() => {
33
- sendRequest({
34
- command: 'batchStart',
35
- data: {isInteractive: getGlobalConfigProperty('isInteractive')},
36
- });
37
- });
38
-
39
- after(() => {
40
- cy.then({timeout: 86400000}, () => {
41
- return batchEnd().catch(e => {
42
- if (!!getGlobalConfigProperty('eyesFailCypressOnDiff')) {
43
- throw e;
44
- }
45
- });
46
- });
47
- });
48
- }
49
-
50
- let isCurrentTestDisabled;
51
-
52
- Cypress.Commands.add('eyesOpen', function(args = {}) {
53
- Cypress.config('eyesOpenArgs', args);
54
- Cypress.log({name: 'Eyes: open'});
55
- const userAgent = navigator.userAgent;
56
- const {title: testName} = this.currentTest || this.test || Cypress.currentTest;
57
- const {browser: eyesOpenBrowser, isDisabled} = args;
58
- const globalBrowser = getGlobalConfigProperty('eyesBrowser');
59
- const defaultBrowser = {
60
- width: getGlobalConfigProperty('viewportWidth'),
61
- height: getGlobalConfigProperty('viewportHeight'),
62
- name: 'chrome',
63
- };
64
-
65
- const browser =
66
- validateBrowser(eyesOpenBrowser) || validateBrowser(globalBrowser) || defaultBrowser;
67
-
68
- if (Cypress.config('eyesIsDisabled') && isDisabled === false) {
69
- throw new Error(
70
- `Eyes-Cypress is disabled by an env variable or in the applitools.config.js file, but the "${testName}" test was passed isDisabled:false. A single test cannot be enabled when Eyes.Cypress is disabled through the global configuration. Please remove "isDisabled:false" from cy.eyesOpen() for this test, or enable Eyes.Cypress in the global configuration, either by unsetting the APPLITOOLS_IS_DISABLED env var, or by deleting 'isDisabled' from the applitools.config.js file.`,
71
- );
72
- }
73
- isCurrentTestDisabled = getGlobalConfigProperty('eyesIsDisabled') || isDisabled;
74
- if (isCurrentTestDisabled) return;
75
-
76
- if (browser) {
77
- if (Array.isArray(browser)) {
78
- browser.forEach(fillDefaultBrowserName);
79
- } else {
80
- fillDefaultBrowserName(browser);
81
- }
82
- }
83
-
84
- return handleCypressViewport(browser).then({timeout: 15000}, () =>
85
- sendRequest({
86
- command: 'open',
87
- data: Object.assign({testName}, args, {browser, userAgent}),
88
- }),
89
- );
90
- });
91
-
92
- Cypress.Commands.add('eyesCheckWindow', args => {
93
- Cypress.log({name: 'Eyes: check window'});
94
- if (isCurrentTestDisabled) return;
95
- const eyesOpenArgs = getGlobalConfigProperty('eyesOpenArgs');
96
- const defaultBrowser = {
97
- width: getGlobalConfigProperty('viewportWidth'),
98
- height: getGlobalConfigProperty('viewportHeight'),
99
- };
100
- const globalArgs = {
101
- browser: getGlobalConfigProperty('eyesBrowser'),
102
- layoutBreakpoints: getGlobalConfigProperty('eyesLayoutBreakpoints'),
103
- waitBeforeCapture: getGlobalConfigProperty('eyesWaitBeforeCapture'),
104
- };
105
-
106
- const browser = eyesOpenArgs.browser || globalArgs.browser || defaultBrowser;
107
- const layoutBreakpoints =
108
- (args && args.layoutBreakpoints) ||
109
- (eyesOpenArgs && eyesOpenArgs.layoutBreakpoints) ||
110
- globalArgs.layoutBreakpoints;
111
-
112
- const waitBeforeCapture =
113
- (args && args.waitBeforeCapture) ||
114
- (eyesOpenArgs && eyesOpenArgs.waitBeforeCapture) ||
115
- globalArgs.waitBeforeCapture;
116
-
117
- const checkArgs = {layoutBreakpoints, browser, waitBeforeCapture};
118
- if (typeof args === 'object') {
119
- Object.assign(checkArgs, args);
120
- } else {
121
- Object.assign(checkArgs, {tag: args});
122
- }
123
-
124
- return cy.document({log: false}).then({timeout: 60000}, doc => eyesCheckWindow(doc, checkArgs));
125
- });
126
-
127
- Cypress.Commands.add('eyesClose', () => {
128
- Cypress.log({name: 'Eyes: close'});
129
- if (isCurrentTestDisabled) {
130
- isCurrentTestDisabled = false;
131
- return;
132
- }
133
- return sendRequest({command: 'close'});
134
- });
135
-
136
- function fillDefaultBrowserName(browser) {
137
- if (!browser.name && !browser.iosDeviceInfo && !browser.chromeEmulationInfo) {
138
- browser.name = 'chrome';
139
- }
140
- }
141
-
142
- function validateBrowser(browser) {
143
- if (!browser) return false;
144
- if (Array.isArray(browser)) return browser.length ? browser : false;
145
- if (Object.keys(browser).length === 0) return false;
146
- return browser;
147
- }
1
+ /* global Cypress,cy,after,navigator */
2
+ 'use strict';
3
+ const spec = require('../../dist/browser/spec-driver');
4
+ const Refer = require('./refer');
5
+ const Socket = require('./socket');
6
+ const {socketCommands} = require('./socketCommands');
7
+ const {eyesOpenMapValues} = require('./eyesOpenMapping');
8
+ const {eyesCheckMapValues} = require('./eyesCheckMapping');
9
+
10
+ const refer = new Refer(value => {
11
+ if (!value || !value.constructor || !value.constructor.name) return false;
12
+ const name = value.constructor.name;
13
+ return name === 'HTMLDocument' || name === 'Window' || value.ownerDocument;
14
+ });
15
+ const socket = new Socket();
16
+ const throwErr = Cypress.config('failCypressOnDiff');
17
+ socketCommands(socket, refer);
18
+ let connectedToUniversal = false;
19
+
20
+ let manager,
21
+ eyes,
22
+ closePromiseArr = [];
23
+
24
+ function getGlobalConfigProperty(prop) {
25
+ const property = Cypress.config(prop);
26
+ const shouldParse = ['eyesBrowser', 'eyesLayoutBreakpoints'];
27
+ return property ? (shouldParse.includes(prop) ? JSON.parse(property) : property) : undefined;
28
+ }
29
+
30
+ const shouldUseBrowserHooks =
31
+ !getGlobalConfigProperty('eyesIsDisabled') &&
32
+ (getGlobalConfigProperty('isInteractive') ||
33
+ !getGlobalConfigProperty('eyesIsGlobalHooksSupported'));
34
+
35
+ Cypress.Commands.add('eyesGetAllTestResults', async () => {
36
+ if (isCurrentTestDisabled) {
37
+ isCurrentTestDisabled = false;
38
+ return;
39
+ }
40
+ await Promise.all(closePromiseArr);
41
+ return socket.request('EyesManager.closeAllEyes', {manager, throwErr});
42
+ });
43
+
44
+ if (shouldUseBrowserHooks) {
45
+ after(() => {
46
+ if (!manager) return;
47
+ return cy.then({timeout: 86400000}, async () => {
48
+ if (isCurrentTestDisabled) {
49
+ isCurrentTestDisabled = false;
50
+ return;
51
+ }
52
+ const resultConfig = {
53
+ showLogs: Cypress.config('appliConfFile').showLogs,
54
+ eyesFailCypressOnDiff: Cypress.config('eyesFailCypressOnDiff'),
55
+ isTextTerminal: Cypress.config('isTextTerminal'),
56
+ tapDirPath: Cypress.config('appliConfFile').tapDirPath,
57
+ tapFileName: Cypress.config('appliConfFile').tapFileName,
58
+ };
59
+ await Promise.all(closePromiseArr);
60
+ const testResults = await socket.request('EyesManager.closeAllEyes', {manager, throwErr});
61
+ const message = await socket.request('Test.printTestResults', {testResults, resultConfig});
62
+ if (
63
+ !!getGlobalConfigProperty('eyesFailCypressOnDiff') &&
64
+ message &&
65
+ message.includes('Eyes-Cypress detected diffs or errors')
66
+ ) {
67
+ throw new Error(message);
68
+ }
69
+ });
70
+ });
71
+ }
72
+
73
+ let isCurrentTestDisabled;
74
+
75
+ Cypress.Commands.add('eyesOpen', function(args = {}) {
76
+ Cypress.log({name: 'Eyes: open'});
77
+ Cypress.config('eyesOpenArgs', args);
78
+ const {title: testName} = this.currentTest || this.test || Cypress.currentTest;
79
+
80
+ if (Cypress.config('eyesIsDisabled') && args.isDisabled === false) {
81
+ throw new Error(
82
+ `Eyes-Cypress is disabled by an env variable or in the applitools.config.js file, but the "${testName}" test was passed isDisabled:false. A single test cannot be enabled when Eyes.Cypress is disabled through the global configuration. Please remove "isDisabled:false" from cy.eyesOpen() for this test, or enable Eyes.Cypress in the global configuration, either by unsetting the APPLITOOLS_IS_DISABLED env var, or by deleting 'isDisabled' from the applitools.config.js file.`,
83
+ );
84
+ }
85
+ isCurrentTestDisabled = getGlobalConfigProperty('eyesIsDisabled') || args.isDisabled;
86
+ if (isCurrentTestDisabled) return;
87
+
88
+ return cy.then({timeout: 86400000}, async () => {
89
+ setRootContext();
90
+ const driver = refer.ref(cy.state('window').document);
91
+
92
+ if (!connectedToUniversal) {
93
+ socket.connect(`ws://localhost:${Cypress.config('eyesPort')}/eyes`);
94
+ connectedToUniversal = true;
95
+ socket.emit('Core.makeSDK', {
96
+ name: 'eyes.cypress',
97
+ version: require('../../package.json').version,
98
+ commands: Object.keys(spec).concat(['isSelector', 'isDriver', 'isElement']), // TODO fix spec.isSelector and spec.isDriver and spec.isElement in driver utils
99
+ cwd: process.cwd(),
100
+ });
101
+
102
+ manager =
103
+ manager ||
104
+ (await socket.request(
105
+ 'Core.makeManager',
106
+ Object.assign(
107
+ {},
108
+ {concurrency: Cypress.config('eyesTestConcurrency')},
109
+ {legacy: false, type: 'vg'},
110
+ ),
111
+ ));
112
+ }
113
+
114
+ const appliConfFile = Cypress.config('appliConfFile');
115
+ const config = eyesOpenMapValues({
116
+ args,
117
+ appliConfFile,
118
+ testName,
119
+ shouldUseBrowserHooks,
120
+ defaultBrowser: {
121
+ width: Cypress.config('viewportWidth'),
122
+ height: Cypress.config('viewportHeight'),
123
+ name: 'chrome',
124
+ },
125
+ });
126
+ eyes = await socket.request('EyesManager.openEyes', {manager, driver, config});
127
+ });
128
+ });
129
+
130
+ Cypress.Commands.add('eyesCheckWindow', args =>
131
+ cy.then({timeout: 86400000}, () => {
132
+ if (isCurrentTestDisabled) return;
133
+
134
+ setRootContext();
135
+
136
+ Cypress.log({name: 'Eyes: check window'});
137
+
138
+ const checkSettings = eyesCheckMapValues({args});
139
+
140
+ return socket.request('Eyes.check', {
141
+ eyes,
142
+ settings: checkSettings,
143
+ });
144
+ }),
145
+ );
146
+
147
+ Cypress.Commands.add('eyesClose', () => {
148
+ return cy.then({timeout: 86400000}, () => {
149
+ if (isCurrentTestDisabled) return;
150
+
151
+ Cypress.log({name: 'Eyes: close'});
152
+ if (isCurrentTestDisabled) {
153
+ isCurrentTestDisabled = false;
154
+ return;
155
+ }
156
+
157
+ // intentionally not returning the result in order to not wait on the close promise
158
+ const p = socket.request('Eyes.close', {eyes, throwErr: false}).catch(err => {
159
+ console.log('Error in cy.eyesClose', err);
160
+ });
161
+ closePromiseArr.push(p);
162
+ });
163
+ });
164
+
165
+ function setRootContext() {
166
+ cy.state('window').document['applitools-marker'] = 'root-context';
167
+ }
@@ -0,0 +1,71 @@
1
+ function eyesCheckMapValues({args}) {
2
+ return toCheckWindowConfiguration(args);
3
+ }
4
+
5
+ function toCheckWindowConfiguration(config = {}) {
6
+ const mappedValues = [
7
+ 'tag',
8
+ 'hooks',
9
+ 'ignore',
10
+ 'floating',
11
+ 'strict',
12
+ 'layout',
13
+ 'content',
14
+ 'accessibility',
15
+ 'region',
16
+ 'selector',
17
+ ];
18
+
19
+ let regionSettings = {};
20
+ let shadowDomSettings = {};
21
+ const checkSettings = {
22
+ name: config.tag,
23
+ scriptHooks: config.hooks,
24
+ ignoreRegions: config.ignore,
25
+ floatingRegions: config.floating,
26
+ strictRegions: config.strict,
27
+ layoutRegions: config.layout,
28
+ contentRegions: config.content,
29
+ accessibilityRegions: config.accessibility,
30
+ };
31
+
32
+ if (config.target === 'region') {
33
+ if (!Array.isArray(config.selector)) {
34
+ if (!config.hasOwnProperty('selector')) {
35
+ regionSettings = {
36
+ region: config.region,
37
+ };
38
+ } else {
39
+ regionSettings = {
40
+ region: config.selector,
41
+ };
42
+ }
43
+ } else {
44
+ const selectors = config.selector;
45
+ for (let i = selectors.length - 1; i > -1; i--) {
46
+ if (i === selectors.length - 1) {
47
+ shadowDomSettings['shadow'] = selectors[i].selector;
48
+ } else {
49
+ const prevSettings = Object.assign({}, shadowDomSettings);
50
+ shadowDomSettings['selector'] = selectors[i].selector;
51
+ if (!prevSettings.hasOwnProperty('selector')) {
52
+ shadowDomSettings['shadow'] = prevSettings.shadow;
53
+ } else {
54
+ shadowDomSettings['shadow'] = prevSettings;
55
+ }
56
+ }
57
+ }
58
+ regionSettings = {region: shadowDomSettings};
59
+ }
60
+ }
61
+
62
+ for (const val of mappedValues) {
63
+ if (config.hasOwnProperty(val)) {
64
+ delete config[val];
65
+ }
66
+ }
67
+
68
+ return Object.assign({}, checkSettings, regionSettings, config);
69
+ }
70
+
71
+ module.exports = {eyesCheckMapValues};
@@ -0,0 +1,34 @@
1
+ function eyesOpenMapValues({args, appliConfFile, testName, shouldUseBrowserHooks}) {
2
+ let browsersInfo = args.browser || appliConfFile.browser;
3
+
4
+ if (browsersInfo) {
5
+ if (Array.isArray(browsersInfo)) {
6
+ browsersInfo.forEach(fillDefaultBrowserName);
7
+ } else {
8
+ fillDefaultBrowserName(browsersInfo);
9
+ browsersInfo = [browsersInfo];
10
+ }
11
+ }
12
+
13
+ const mappedArgs = {
14
+ ...args,
15
+ browsersInfo,
16
+ };
17
+
18
+ delete mappedArgs.browser;
19
+ delete appliConfFile.browser;
20
+
21
+ return Object.assign(
22
+ {testName, dontCloseBatches: !shouldUseBrowserHooks},
23
+ appliConfFile,
24
+ mappedArgs,
25
+ );
26
+ }
27
+
28
+ function fillDefaultBrowserName(browser) {
29
+ if (!browser.name && !browser.iosDeviceInfo && !browser.chromeEmulationInfo) {
30
+ browser.name = 'chrome';
31
+ }
32
+ }
33
+
34
+ module.exports = {eyesOpenMapValues};
@@ -1,22 +1,18 @@
1
- 'use strict';
2
- const throat = require('throat');
3
- const CONCURRENCY_LIMITATION = 100;
4
-
5
- function makeSend(port, fetch) {
6
- const send = function send({
7
- command,
8
- data,
9
- method = 'POST',
10
- headers = {'Content-Type': 'application/json'},
11
- }) {
12
- return fetch(`https://localhost:${port}/eyes/${command}`, {
13
- method,
14
- body: headers['Content-Type'] === 'application/json' ? JSON.stringify(data) : data,
15
- headers,
16
- });
17
- };
18
-
19
- return throat(CONCURRENCY_LIMITATION, send);
20
- }
21
-
22
- module.exports = makeSend;
1
+ 'use strict';
2
+
3
+ function makeSend(port, fetch) {
4
+ return function send({
5
+ command,
6
+ data,
7
+ method = 'POST',
8
+ headers = {'Content-Type': 'application/json'},
9
+ }) {
10
+ return fetch(`https://localhost:${port}/eyes/${command}`, {
11
+ method,
12
+ body: headers['Content-Type'] === 'application/json' ? JSON.stringify(data) : data,
13
+ headers,
14
+ });
15
+ };
16
+ }
17
+
18
+ module.exports = makeSend;
@@ -0,0 +1,57 @@
1
+ const uuid = require('uuid');
2
+
3
+ const REF_ID = 'applitools-ref-id';
4
+ class Refer {
5
+ constructor(check) {
6
+ this.store = new Map();
7
+ this.relation = new Map();
8
+ this.check = check;
9
+ }
10
+
11
+ isRef(ref) {
12
+ return Boolean(ref && ref[REF_ID]);
13
+ }
14
+
15
+ ref(value, parentRef) {
16
+ if (this.check(value)) {
17
+ const ref = uuid.v4();
18
+ this.store.set(ref, value);
19
+ if (parentRef) {
20
+ let childRefs = this.relation.get(parentRef[REF_ID]);
21
+ if (!childRefs) {
22
+ childRefs = new Set();
23
+ this.relation.set(parentRef[REF_ID], childRefs);
24
+ }
25
+ childRefs.add({[REF_ID]: ref});
26
+ }
27
+ return {[REF_ID]: ref};
28
+ } else if (Array.isArray(value)) {
29
+ return value.map(value => this.ref(value, parentRef));
30
+ } else if (typeof value === 'object' && value !== null) {
31
+ return Object.entries(value).reduce((obj, [key, value]) => {
32
+ return Object.assign(obj, {[key]: this.ref(value, parentRef)});
33
+ }, {});
34
+ } else {
35
+ return value;
36
+ }
37
+ }
38
+
39
+ deref(ref) {
40
+ if (this.isRef(ref)) {
41
+ return this.store.get(ref[REF_ID]);
42
+ } else {
43
+ return ref;
44
+ }
45
+ }
46
+
47
+ destroy(ref) {
48
+ if (!this.isRef(ref)) return;
49
+ const childRefs = this.relation.get(ref[REF_ID]);
50
+ if (childRefs) {
51
+ childRefs.forEach(childRef => this.destroy(childRef));
52
+ }
53
+ this.store.delete(ref[REF_ID]);
54
+ }
55
+ }
56
+
57
+ module.exports = Refer;
@@ -1,16 +1,16 @@
1
- 'use strict';
2
-
3
- function makeSendRequest(send) {
4
- return function sendRequest(args) {
5
- return send(args)
6
- .then(resp => resp.json())
7
- .then(body => {
8
- if (!body.success) {
9
- throw new Error(body.error);
10
- }
11
- return body.result;
12
- });
13
- };
14
- }
15
-
16
- module.exports = makeSendRequest;
1
+ 'use strict';
2
+
3
+ function makeSendRequest(send) {
4
+ return function sendRequest(args) {
5
+ return send(args)
6
+ .then(resp => resp.json())
7
+ .then(body => {
8
+ if (!body.success) {
9
+ throw new Error(body.error);
10
+ }
11
+ return body.result;
12
+ });
13
+ };
14
+ }
15
+
16
+ module.exports = makeSendRequest;