Snippets
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 | import { ECHO_BACK_URL } from "./env/shared";
const visitConf = {
onBeforeLoad: (cw: any) => {
cw.XMLHttpRequest.prototype.open = getNewXhrOpen(cw.XMLHttpRequest.prototype.open)
cw.XMLHttpRequest.prototype.send = getNewSender(cw.XMLHttpRequest.prototype.send);
}
}
const getNewSender = (oldSend: any) => function(this: any, body: any) {
// get the body mapper or an identity fn
const newBody = (this.bodyMappers as any[])
.map(m => m(body))
.filter(doesBodyMatch)
.map(stringify)
.pop();
return oldSend.bind(this)(
newBody || body
);
}
const getNewXhrOpen = (oldOpen: any) => function(this: any, method: any, url: any) {
this.bodyMappers = (cy.routeBodyMatchers || [])
.map(matcher => matcher(method, url))
.filter(Boolean);
return oldOpen.bind(this)(
method,
this.bodyMappers.length ? ECHO_BACK_URL : url,
true
);
}
const doesBodyMatch = (res: any) => res !== '__NO_MATCH__';
const stringify = (v: any) => JSON.stringify(v);
const parseOrValue = (v: any) => {
try {
return JSON.parse(v);
} catch (e) {
return v;
}
}
const safeFn = (fn: ((...args: any[]) => any)) => (...args: any[]): any => {
try { return fn(...args); } catch (e) { console.log('caught and ignore')}
}
const routeBodyCommand = (method: string, url: string, tester: (body:any) => boolean, response: any, ...responses: any[]) => {
let allResponses = [response, ...responses];
tester = safeFn(tester);
// make a mapper to determine if we should replace the body
let routeToBodyMapper = (method2: string, fullUrl: string) => {
if (method2.toUpperCase() !== method.toUpperCase()) {
return false;
}
if (!fullUrl.match(new RegExp(url.split('**').join('.+?'), 'i'))) {
return false;
}
// this __NO_MATCH__ is so that the user can force false or undefined or whatever they want though
return (body: any) => console.log('compare', allResponses, url, body) as any || (!tester(parseOrValue(body)) ? '__NO_MATCH__' : allResponses.length ? allResponses.shift() : response);
}
// registerUrlBodyMatch(method, url, tester, response);
cy.routeBodyMatchers = (cy.routeBodyMatchers || []).concat(routeToBodyMapper);
}
Cypress.Commands.overwrite('visit', (orig, ...args) => {
const [one, two] = args;
let config;
let url;
if (typeof two === 'object') {
url = one;
config = {
...two,
...visitConf
}
} else if(typeof one === 'object') {
url = one.url;
config = {
...one,
...visitConf
}
} else {
url = one;
config = visitConf;
}
return orig(url, config);
});
Cypress.Commands.overwrite('route', (orig, ...args: any[]) => {
if (args.length < 4) return orig(...args);
const [method, url, maybeFnMaybeResponse, ...responses] = args;
const fn = typeof maybeFnMaybeResponse === 'function'
? maybeFnMaybeResponse
: () => true; // we are in subsequent call mode so all bodies match
const allResponses = typeof maybeFnMaybeResponse === 'function'
? responses
: [maybeFnMaybeResponse, ...responses]
return (routeBodyCommand as any)(method, url, fn, ...allResponses);
});
|
What and Why
This adds support for having multiple url routes based on body. Cypress doesn't currently support so a single URL can have only one return value.
Usage 1 - body matching
this will set a watcher on the url and return 'here' but only if the body validate fn returns true when passed the request body
cy.routeBody('POST', '**/v1/lookup/**', body => body.some(propEq('value', 'serviceType')), 'here');
cy.visitPage('**/some/url/path');
Usage 1 - sequential results
this will watch the url and will reply with here1 on the first ajax, here2 on the second, here3 on the third and is variadic so it will support as many responses as you need
cy.routeBody('POST', '**/v1/lookup/**', 'here1', 'here2', 'here3');
cy.visitPage('**/some/url/path');
Instalation
you must have a url that will echo back whatever body is sent to it and export that as ECHO_BACK_URL which is imported by bodyRouter.ts
copy the included files to cypress/utils
in cypress/support/command.ts
import the bodyBodyRouter.ts file
import '../utils/bodyRouter';
Comments (0)
You can clone a snippet to your computer for local editing. Learn more.