Check outgoing browser network calls using Cypress.io - browser

On our site, we have Omniture calls that are fired when someone clicks on a link or takes some action. In Chrome DevTools in the network tab, you can see the network request being fired.
Is there a way for Cypress.io to capture outgoing network requests, so we can inspect/parse the URLs? The equivalent to this would be something like Browsermob proxy for webdriver set ups. I want to use Cypress.io to tell it to click the link, but then I want to check the outgoing network request by the browser.

You should be able to use cy.route to wait on and make assertions against network requests:
cy.route({
url:'*omniture.com*',
method: 'POST',
onRequest: (xhr) => {
expect(xhr.request.body).to.eql('somebody')
}
})
If the above doesn't work, it may be because the module is using fetch, which doesn't have native support yet. However, you can just have omniture fallback to XHR by adding this to your cy.visit():
cy.visit('example.com', {
onBeforeLoad: (win) => {
win.fetch = null
}
})
..
or (as you mentioned) you can spy on the omniture global directly
You can use cy.spy() to spy on a global object on your site, here's an example:
cy.visit('example.com')
cy.window().should('have.property', 'omnitureRequest').then(win=>{
cy.spy(win, 'omnitureRequest')
})
(the should() will wait for the object to be present before attempting to spy on it, since the omniture <script> tag could load asynchronously

We found a workaround to our Omniture problem. The request URL is stored in an Omniture property on an object attached to the browser window object. Using cy.window() we can get the window object and access this property and use a node module (querystring) to parse the query string.
We could not find any native way in Cypress.io to inspect network requests.

Related

Page not changing onClick using useNavigate in React?

I have a very basic UI for a login page:
Upon clicking the LOGIN button, the following methods gets called:
async function loginPatient(){
let item ={username:userName, password};
let result = await fetch("http://localhost:8000/users/login",{
method:'POST',
headers:{
"Content-Type":"application/json",
"Accept":"application/json"
},
body: JSON.stringify(item)
});
alert(result);
alert("breakpoint")
result = await result.json();
localStorage.setItem("user-info",JSON.stringify(result));
nav('/patient')
}
At this point I simply want it to change the page when the button is clicked. My API returns the following information from the database:
To test I did console.log("hello world") in the first line of the function and it works
However, If I run console.log("hello world") after the let result = await fetch(...) part it does not work. How can I test this to see why it's not working ?
Here are the errors from the console:
I did not write the API and do not know how Node works yet, I am just doing the front end for this
The issue is code is never reaching after fetch line, basically request is failing, the error on console is saying the due to CORS issue, the request failed, and in your loginPatient function, you have not handled the failed case, if you just wrap your fetch call inside try/catch block, you will see your code will fall into fail block, as api failed.
You need to enable CORS on your server or backend, Cross-Origin Resource Sharing (CORS) is an HTTP-header based mechanism that allows a server to indicate any origins (domain, scheme, or port) other than its own from which a browser should permit loading resources.
You can read more about cors at:
https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
Looks like your client is on some other domain or port(if your are developing locally) than your server. You need to enable CORS permission for your client url.
And if you are using express for your backend, you can check the following url to enable cors.
https://expressjs.com/en/resources/middleware/cors.html
And last thing why Postman is getting success response, because it is by passing this cors check, as Postman is making request from it's server to your direct server instead of browser.
First initialize you navigation variable as follows
const navigate =useNavigate()
then navigate to you specific route by returning you navigation variable as follows.
return navigation("/");
Happy Coding!

How can I intercept only one endpoint of a domain for my browser API calls?

Suppose I enter a (public) website that makes 3 XHR/fetch calls on 3 different endpoints:
https://api.example.com/path1
https://api.example.com/path2
https://api.example.com/path3
What I want to achieve is intercept the call to https://api.example.com/path2 only, redirect it to a local service (localhost:8000) and let path1 and path3 through to the original domain.
What kind of options do I have here? I have studied a lot of approaches to this issue:
DNS rewriting - this solution is not suitable as I still have to intercept path1 and path3, only redirect them to the original IPs and try to mimic the headers as much as possible - which means I would have to do a specific proxy configuration for each intercepted domain - this is unfeasible
Chrome extensions - found none to deal specifically with single endpoint intercepting
Overwriting both fetch and XmlHttpRequest after page load - still doesn't cover all scenarios, maybe some websites cache the values of fetch and XmlHttpRequest before page load (?)
Combining the chrome extension and fetch overwrite will work.
download an webextension that let you load javascript code before a given page loads, e.g. User JavaScript and CSS
Add the following script to run before your page loads, base on: Intercepting JavaScript Fetch API requests and responses
const { fetch: originalFetch } = window;
window.fetch = async (...args) => {
let [resource, config ] = args;
// request interceptor starts
resource = resource === "https://api.example.com/path2" ? "http://localhost:8000/path2" : resource
// request interceptor ends
const response = await originalFetch(resource, config);
// response interceptor here
return response;
};

Puppeteer: Storing and accessing contextual or meta data per page visit

I'm experimenting with auto-generating outcomes using #xstate/test and combining it with puppeteer and jest. Using jest's beforeAll and afterAll hooks I'm calling page.setRequestInterception and registering a callback to intercept network requests and mock their response.
XState generates the pathways that I want to test and from each path I can determine whether the test wants to test what should occur when the API fails or succeeds. I'm stuck on how to properly communicate that back to puppeteer and access that information in my request handler.
One option I discovered is using query parameters. The Request object that is passed to the event handler has access to the frame that made the request.
In my test:
it(..., async () => {
await page.goto('http://localhost:3000?failApi=true')
..
and in my request handler:
page.on('request', req => {
// check req.frame.url() here and make choices
...

running function after res.send

I'm trying to run this code
module.exports = async (req, res, next) => {
res.set('Content-Type', 'text/javascript');
const response = {};
res.status(200).render('/default.js', { response });
await fn(response);
};
fn is a function that calls an api to a service that will output to the client something. but its dependent on the default.js file to be loaded first. How can do something like
res.render('/default.js', { response }).then(async() => {
await fn(response);
};
tried it, but doesn't seem to like the then()
also, fn doesn't return data to the client, it calls an api service that is connected with the web sockets opened by the code from default.js that is rendered.
do i have to make an ajax request for the fn call and not call it internally?
any ideas?
Once you call res.render(), you can send no more data to the client, the http response has been sent and the http connection is done - you can't send any more to it. So, it does you no good to try to add something more to the response after you call res.render().
It sounds like you're trying to put some data INTO the script that you send to the browser. Your choices for that are to either:
Get the data you need to with let data = await fn() before you call res.render() and then pass that to res.render() so your template engine can put that data into the script file that you send the server (before you send it).
You will need to change the script file template to be able to do this so it has appropriate directives to insert data into the script file and you will have to be very careful to format the data as Javascript data structures.
Have a script in the page make an ajax call to get the desired data and then do your task in client-side Javascript after the page is already up and running.
It looks like it might be helpful for you to understand the exact sequence of things between browser and server.
Browser is displaying some web page.
User clicks on a link to a new web page.
Browser requests new web page from the server for a particular URL.
Server delivers HTML page for that URL.
Browser parses that HTML page and discovers some other resources required to render the page (script files, CSS files, images, fonts, etc...)
Browser requests each of those other resources from the server
Server gets a request for each separate resource and returns each one of them to the browser.
Browser incorporates those resources into the HTML page it previously downloaded and parsed.
Any client side scripts it retrieved for that page are then run.
So, the code you show appears to be a route for one of script files (in step 5 above). This is where it fits into the overall scheme of loading a page. Once you've returned the script file to the client with res.render(), it has been sent and that request is done. The browser isn't connected to your server anymore for that resource so you can't send anything else on that same request.

Is it possible to send `postMessage` to an app with Cypress? How to pass data that would be received via `postMessage`?

I'm creating Cypress e2e tests, however our app is opened as a modal (iframe) on top of a parent page. As Cypress does not support iframes I decided to try and run app "standalone". However on start app is getting data from parent page via window.postMessage (info about flow and session). Is it possible to pass this data to app via Cypress?
I have tried to get data that we receive from backend via cy.request and it solves problem with session data, however I still can't figure out how to pass information about flow.
You can send a window.postMessage() in Cypress. Because Cypress runs in the browser next to your application, it has access to all web APIs.
In order to get a reference to the window object of your application, you can use cy.window()
Putting it all together, this is how you send a postMessage to your application under test:
cy.window() // get a reference to application's `window`
.then($window => {
const message = 'some data here'
$window.postMessage(message, '*')
})
Sending POST forms when visiting a page is indeed possible. As of Cypress 3.2.0, you can send POST requests using cy.visit.
You can check the cy.visit() docs for more details, but here is a quick code example of what you're trying to do:
cy.visit({
url: 'http://my-app.com/standalone-iframe-modal.html',
method: 'POST',
body: {
formKey: 'some-value',
anotherKey: 'some-other-value'
}
})

Resources