Delaying Intercept responses in Cypress - reactjs

I am writing cypress tests, and I want to test a feature of our program which will display data as it begins to appear. This could be a list with several hundred elements, and in order to speed it up, we break the request up into multiple requests.
The first request will fetch the first 50 items, the second request the next 100, etc.
I want to ensure that after the first request returns, the list contains 50 items, and that after the second request returns, the list contains 150 items.
What I don't know how to do, is reliably delay the intercept responses so that I can return the first 50, check the list length, then allow more data to come in.
This is what I have so far:
const baseUrl = 'https://example.io/listData*'
const document = 'public_document_10101'
cy.intercept(
{
url: baseUrl,
query: {
limit: '50',
'document': document
},
},
{ fixture: 'first-response.js' }
).as('firstResponse')
cy.intercept(
{
url: baseUrl,
query: {
limit: '100',
skip: '50',
'document': document
},
},
{ fixture: 'second-response.js' }
).as('secondResponse')
I don't believe that something like cy.wait('#firstResponse') will do what I want, as this doesn't prevent the second one from return at all. I'm open to using a delay of some kind, however it would be really nice if there was a wait to completely block it until I say so.

You can also use setDelay. Cypress Docs
cy.intercept({ url: 'http://localhost:3001/**', middleware: true }, (req) => {
req.on('response', (res) => {
// Wait for delay in milliseconds before sending the response to the client.
res.setDelay(1000)
})
})

This looks like the functionality you want Throttle or delay response all incoming responses
// Code from Real World App (RWA)
// cypress/support/index.ts
import { isMobile } from './utils'
import './commands'
// Throttle API responses for mobile testing to simulate real world conditions
if (isMobile()) {
cy.intercept({ url: 'http://localhost:3001/**', middleware: true }, (req) => {
req.on('response', (res) => {
// Throttle the response to 1 Mbps to simulate a mobile 3G connection
res.setThrottle(1000)
})
})
}
Or maybe Static Response object with fixture property and delay property
{
/**
* Serve a fixture as the response body.
*/
fixture?: string
/**
* Serve a static string/JSON object as the response body.
*/
body?: string | object | object[]
/**
* HTTP headers to accompany the response.
* #default {}
*/
headers?: { [key: string]: string }
/**
* The HTTP status code to send.
* #default 200
*/
statusCode?: number
/**
* If 'forceNetworkError' is truthy, Cypress will destroy the browser connection
* and send no response. Useful for simulating a server that is not reachable.
* Must not be set in combination with other options.
*/
forceNetworkError?: boolean
/**
* Milliseconds to delay before the response is sent.
*/
delay?: number
/**
* Kilobits per second to send 'body'.
*/
throttleKbps?: number
}

Related

how intercept and stub the response of a rpc call in react with cypress

I want to intercept a rpc call that I made to the api in my react app. I'm using a custom hook that receives the buffer and the rpc method that I want to call and returns the data(something like react-query useQuery hook).
The thing is because of being a rpc call, the request urls of my requests are all the same and the response is binary, I can't distinguish the requests and intercept the one to stub.
One example of making a rpc call:
const {response, loading, error} = useRpc({
Buffer: GetUser,
Request: GetUserRequest
});
Edit 1:
I'm using
cy.fixture('fixutre-file').then((data) => {
const response = new TextDecoder().decode(res.body);
cy.intercept('https://example.com/', { method: 'POST' },
(req) => {
req.continue((res) => {
if ("some condition for distinguishing the request I want to intercept, here") {
res.send({ fixture: 'fixutre-file' });
}
});
});
}):
to get the response and decide whether or not intercept this req and instead send back my fixture data. But the response constant is still some unreadable string. What's wrong with my approach?
Edit 2:
Another approach that I used, was to use the cypress-protobuf package and encode my fixture.json file with the related protobuffer file:
cy.fixture('fixutre-file').then((data) => {
cy.task('protobufEncode', {
fixtureBody: data,
message: 'the_message',
protoFilePath: './protobuf/protofile.proto'
}).then((encodedData) => {
cy.intercept('https://example.com/', { method: 'POST' },
(req) => {
/////// approach 1(didn't work): ///////
// can't use this approach(because there is no identifier on
// req object to distinguish the requests I want to
// intercept)
// if ("some condition based on `req` here") {
// req.reply(encodedData);
// } else {
// req.continue();
// }
/////// approach 2: ///////
// using encodedData to compare it with res.body
req.continue(res => {
// can't compare res.body with encodedData, because
// encodedData is an empty string!
});
}).as('needToWait');
cy.wait('#needToWait').get('some selector').should('exist')
});
}):
Now the problem is:
encodedData is just an empty string, meaning it didn't work, so I can't compare the response with my fixture data to intercept the related request
You can simply check for some value from the request that distinguishes it from the other requests. Request bodies and headers are often good places to start. Additionally, you can use req.alias to conditionally assign an alias if you need to wait for that specific call.
cy.intercept('/foo', (req) => {
if (req.body.bar === true) { // or whatever logic indicates the call you want to intercept
req.alias = 'baz'; // conditionally assign alias
req.reply({foo: 'bar'}); // mock response
} else {
req.continue(); // do not mock response
}
});
cy.get('something')
.click()
.wait('#baz'); // waits for your specific 'baz' call to happen.

What do the keys in the '_performanceLogger' object of an XMLHttpRequest response mean?

I am trying to measure download (and later upload) speed using Axios in React Native. My question's relate to the most appropriate way to do so, and understanding if the _performanceLogger in the response from XMLHttpRequest is accurate for this purpose. There surprisingly doesn't seem to be much out there about _performanceLogger ... Appreciate the help!
A MWE is:
import React from "react";
import axios from 'axios';
// Dummy API endpoint
let requestURL = 'https://jsonplaceholder.typicode.com/users/2'
// Getting timestamp immediately before request is started
var startTimestamp = (new Date()).getTime()
// Make request through Axios
// Make a request for a user with a given ID
axios.get(requestURL)
.then(function (response) {
// Handle success
console.log("And I am burdened with glorious purpose.")
// Getting timestamp immediately after request is completed
let endTimestamp = (new Date()).getTime()
console.log("Request start time stamp: " + startTimestamp)
console.log("Request completed time stamp: " + endTimestamp)
// Logging the whole _performanceLogger object
let responsePerformanceLogger = response.request._performanceLogger
console.log('Perf Logger:')
console.log(responsePerformanceLogger)
// Logging the key/value specific to the actual request within the _performanceLogger object
let requestPerfString = 'network_XMLHttpRequest_' + requestURL
let responseTimespans = responsePerformanceLogger._timespans[requestPerfString]
console.log('Specific Request Timings:')
console.log(responseTimespans)
})
.catch(function (error) {
// Handle error
console.log("This is a mistake! I shouldn't be here!")
console.log(error);
})
.then(function () {
// always executed
console.log('For all time! Always!')
});
I use Expo to run this code on my device, and I get the following output to the console:
And I am burdened with glorious purpose.
Request start time stamp: 1640229022039
Request completed time stamp: 1640229022156
Perf Logger:
PerformanceLogger {
"_closed": false,
"_extras": Object {},
"_pointExtras": Object {},
"_points": Object {
"initializeCore_end": 194691080.25150004,
"initializeCore_start": 194691027.19495836,
},
"_timespans": Object {
"network_XMLHttpRequest_http://192.168.1.40:19000/logs": Object {
"endExtras": undefined,
"endTime": 194692352.89145836,
"startExtras": undefined,
"startTime": 194691383.22187504,
"totalTime": 969.6695833206177,
},
"network_XMLHttpRequest_http://192.168.1.40:19001/symbolicate": Object {
"endExtras": undefined,
"endTime": 194694267.96945837,
"startExtras": undefined,
"startTime": 194694100.6875417,
"totalTime": 167.2819166779518,
},
"network_XMLHttpRequest_https://jsonplaceholder.typicode.com/users/2": Object {
"endExtras": undefined,
"endTime": 194699846.2774167,
"startExtras": undefined,
"startTime": 194699738.0606667,
"totalTime": 108.21674999594688,
},
},
}
Specific Request Timings:
Object {
"endExtras": undefined,
"endTime": 194699846.2774167,
"startExtras": undefined,
"startTime": 194699738.0606667,
"totalTime": 108.21674999594688,
}
For all time! Always!
My specific questions are:
What are the different objects under the _timespans key? There are two (log, symbolicate) to the local IP for Expo ... what's happening here? Presumably these won't there when I deploy outside of Expo.
Is the third, the one that contains the actual URL made in the request, the actual timing of the download? (I've extracted this one as Specific Request Timings.) This is total download time (latency + file download), correct?
Is relying on the XMLHttpRequest timing less/more accurate than just using the simple getTime() timestamps that I have called before the request and after successful completion? I would expect that they should be close, but the getTime()'s require some finite execution time that could be variable by device/load.

How can I make 1 million+ requests to an API in React appication?

I'm trying to make a large number of post requests to an API and create "service tickets".
I have 3 types of services and I want to create packages of 50, 100, 500, 1k, 10k, and 1Million depending of what the user selects. (I am already storing that in a React State)
When I click I button I would like to send all the requests for the tickets at once, so we can have at the end:
service 1 = 1,000,000 tickets
service 2 = 500 tickets
service 3 = 1,000,000 tickets
total of Tickets = 2,000,500
I was wondering what is the best way to handle that amount of post requests in a single click? Any help will be very useful. Thanks!
This is the function I'm passing to my onClick event that creates a single ticket:
const addTicket = async () => {
try {
const token = await getTokenSilently(); // from auth0 authentication
let url = '.../endpoint'
const post = {
createdFor: hostId, // here I put the id of the user I want to add the tickets
service: typeOfService // Here I select one of the 3 types of services
}
const response = await fetch(url, {
method: 'POST',
body: JSON.stringify(post),
headers: {
"Authorization": "Bearer "+ token
}
});
const responseData = await response.json();
} catch (error) {
console.error(error);
}
};
Im using:
-"react": "^16.13.1"
-the backend is running AWS Lambda
A million requests are not required, you can make an API endpoint where you can make 1 request with information of how many tickets you want and then handle the number of tickes on the server-side.
If you will make 1 million requests you will need a much better server and resources and you will be wasting a lot of unnecessary money

Cancel query on refetch in apollo

I'm trying to build a small widget with some inputs and cards that contain data which is recalculated on the backend using react and apollo. Cards contain a loader to indicate that request is in progress.
Whenever I change an input, I want to trigger a request to backend, which takes a long time (due to computation) - around 30 seconds.
When you input data - mutation is triggered via one apollo client, but right afterwards it triggers a query to another apollo client, which takes 30 seconds or so.
In that time it is possible to update input again - and in that case, I would like to cancel the previous request and trigger a new one.
This is the query
const {
data,
loading,
refetch,
networkStatus,
} = useCalculationQuery({
widget_id: widgetId,
skip: !isCalcTriggered,
});
Code for useCalculationQuery
function useCalculationQuery({ widget_id, skip }: Options = {}) {
const { teamId } = ... ;
const query = useQuery(QUERY_CALCULATION, {
skip,
fetchPolicy: 'network-only',
variables: {
team_id: teamId,
widget_id,
},
client: apolloCalculations,
});
return query;
}
And this is the fetch function
async function handleCalcFetch() {
if (isCalcTriggered) {
setIsFetchInProgress(true);
await refetch();
setIsFetchInProgress(false);
}
if (!isCalcTriggered) {
setIsCalcTriggered(true);
}
}
As you can see - with isCalcTriggered flag I skip the query execution until handleCalcFetch is called. Then I set the flag, and on every other call except the first I do refetch.
These other flag (isFetchInProgress which is set with setIsFetchInProgress) is to indicate that request is in place, because after calling refetch, networkStatus is always 7 rather than 4. That's issue number one.
Issue number two is that I want to cancel previous requests whenever I hit refetch(), so that I am sure I always get the newest data, which now is not the case.

Relay Modern: Connecting websocket to network layer

I’m having issues figuring out how to connect the Relay Modern network layer with my websocket instance.
I’m currently instantiating a websocket instance as:
const subscriptionWebSocket = new ReconnectingWebSocket('ws://url.url/ws/subscriptions/', null, options);
I'm specifying the subscription and creating a new instance of requestSubscription:
const subscription = graphql`
subscription mainSubscription {
testData {
anotherNode {
data
}
}
}
`;
requestSubscription(
environment,
{
subscription,
variables: {},
onComplete: () => {...},
onError: (error) => {...},
onNext: (response) => {...},
updater: (updaterStoreConfig) => {...},
},
);
Which then allows me to send any subscription requests:
function subscriptionHandler(subscriptionConfig, variables, cacheConfig, observer) {
subscriptionWebSocket.send(JSON.stringify(subscriptionConfig.text));
return {
dispose: () => {
console.log('subscriptionHandler: Disposing subscription');
},
};
}
const network = Network.create(fetchQuery, subscriptionHandler);
through to my server (currently using Graphene-python), and I’m able to interpret the received message on the server.
However, what I’m having issues figuring out is how to respond to a subscription; for example, when something changes in my DB, I want to generate a response and return to any potential subscribers.
The question being, how do I connect the onMessage event from my websocket instance into my Relay Modern Network Layer? I've browsed through the source for relay but can't seem to figure out what callback, or what method should be implementing an onreceive.
Any tips are appreciated.
I've managed to make subscriptions with Relay Modern work as well and wanted to share my minimal setup, maybe it's helpful for someone!
Note that I'm not using WebSocket but the SubscriptionClient that can be found in subscriptions-transport-ws to manage the connection to the server.
Here's my minimal setup code:
Environment.js
import { SubscriptionClient } from 'subscriptions-transport-ws'
const {
Environment,
Network,
RecordSource,
Store,
} = require('relay-runtime')
const store = new Store(new RecordSource())
const fetchQuery = (operation, variables) => {
return fetch('https://api.graph.cool/relay/v1/__GRAPHCOOL_PROJECT_ID__', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
query: operation.text,
variables,
}),
}).then(response => {
return response.json()
})
}
const websocketURL = 'wss://subscriptions.graph.cool/v1/__GRAPHCOOL_PROJECT_ID__'
function setupSubscription(
config,
variables,
cacheConfig,
observer,
) {
const query = config.text
const subscriptionClient = new SubscriptionClient(websocketURL, {reconnect: true})
const id = subscriptionClient.subscribe({query, variables}, (error, result) => {
observer.onNext({data: result})
})
}
const network = Network.create(fetchQuery, setupSubscription)
const environment = new Environment({
network,
store,
})
export default environment
NewLinkSubscription.js
import {
graphql,
requestSubscription
} from 'react-relay'
import environment from '../Environment'
const newLinkSubscription = graphql`
subscription NewLinkSubscription {
Link {
mutation
node {
id
description
url
createdAt
postedBy {
id
name
}
}
}
}
`
export default (onNext, onError, onCompleted, updater) => {
const subscriptionConfig = {
subscription: newLinkSubscription,
variables: {},
onError,
onNext,
onCompleted,
updater
}
requestSubscription(
environment,
subscriptionConfig
)
}
Now you can simply use the exported function to subscribe. For example, in one of my React components in componentDidMount I can now do the following:
componentDidMount() {
NewLinkSubscription(
response => console.log(`Received data: `, response),
error => console.log(`An error occurred:`, error),
() => console.log(`Completed`)
)
}
Note that the SubscriptionClient can only be used if your server implements this protocol!
If you want to learn more, check out the fullstack How to GraphQL tutorial that describes in detail how to make subscriptions work with Relay Modern.
I’ll just write down how I’ve approached this issue after the assistance found in this thread. It might be usable for someone else. This is very dependent on the server-side solution that you've chosen.
My approach:
Firstly I built a SubscriptionHandler that will handle the requestStream#subscribeFunction through SubscriptionHandler#setupSubscription.
The SubscriptionHandler instantiates a WebSocket (using a custom version of ReconnectingWebSockets) and attaches the onmessage event to an internal method (SubscriptionHandler#receiveSubscriptionPayload) which will add the payload to the corresponding request.
We create new subscriptions through SubscriptionHandler#newSubscription which will use the internal attribute SubscriptionHandler.subscriptions to add a keyed entry of this subscription (we use an MD5-hash util over the query and variables); meaning the object will come out as:
SubscriptionHandler.subscriptions = {
[md5hash]: {
query: QueryObject,
variables: SubscriptionVariables,
observer: Observer (contains OnNext method)
}
Whenever the server sends a subscription response the SubscriptionHandler#receiveSubscriptionPayload method will be called and it will identify what subscription the payload belongs to by using the query/variables md5 hash, then use the SubscriptionHandler.subscriptions observer onNext method.
This approach requires the server to return a message such as:
export type ServerResponseMessageParsed = {
payload: QueryPayload,
request: {
query: string,
variables: Object,
}
}
I do not know if this is a great way of handling subscriptions, but it works for now with my current setup.
SubscriptionHandler.js
class SubscriptionHandler {
subscriptions: Object;
subscriptionEnvironment: RelayModernEnvironment;
websocket: Object;
/**
* The SubscriptionHandler constructor. Will setup a new websocket and bind
* it to internal method to handle receving messages from the ws server.
*
* #param {string} websocketUrl - The WebSocket URL to listen to.
* #param {Object} webSocketSettings - The options object.
* See ReconnectingWebSocket.
*/
constructor(websocketUrl: string, webSocketSettings: WebSocketSettings) {
// All subscription hashes and objects will be stored in the
// this.subscriptions attribute on the subscription handler.
this.subscriptions = {};
// Store the given environment internally to be reused when registering new
// subscriptions. This is required as per the requestRelaySubscription spec
// (method requestSubscription).
this.subscriptionEnvironment = null;
// Create a new WebSocket instance to be able to receive messages on the
// given URL. Always opt for default protocol for the RWS, second arg.
this.websocket = new ReconnectingWebSocket(
websocketUrl,
null, // Protocol.
webSocketSettings,
);
// Bind an internal method to handle incoming messages from the websocket.
this.websocket.onmessage = this.receiveSubscriptionPayload;
}
/**
* Method to attach the Relay Environment to the subscription handler.
* This is required as the Network needs to be instantiated with the
* SubscriptionHandler's methods, and the Environment needs the Network Layer.
*
* #param {Object} environment - The apps environment.
*/
attachEnvironment = (environment: RelayModernEnvironment) => {
this.subscriptionEnvironment = environment;
}
/**
* Generates a hash from a given query and variable pair. The method
* used is a recreatable MD5 hash, which is used as a 'key' for the given
* subscription. Using the MD5 hash we can identify what subscription is valid
* based on the query/variable given from the server.
*
* #param {string} query - A string representation of the subscription.
* #param {Object} variables - An object containing all variables used
* in the query.
* #return {string} The MD5 hash of the query and variables.
*/
getHash = (query: string, variables: HashVariables) => {
const queryString = query.replace(/\s+/gm, '');
const variablesString = JSON.stringify(variables);
const hash = md5(queryString + variablesString).toString();
return hash;
}
/**
* Method to be bound to the class websocket instance. The method will be
* called each time the WebSocket receives a message on the subscribed URL
* (see this.websocket options).
*
* #param {string} message - The message received from the websocket.
*/
receiveSubscriptionPayload = (message: ServerResponseMessage) => {
const response: ServerResponseMessageParsed = JSON.parse(message.data);
const { query, variables } = response.request;
const hash = this.getHash(query, variables);
// Fetch the subscription instance from the subscription handlers stored
// subscriptions.
const subscription = this.subscriptions[hash];
if (subscription) {
// Execute the onNext method with the received payload after validating
// that the received hash is currently stored. If a diff occurs, meaning
// no hash is stored for the received response, ignore the execution.
subscription.observer.onNext(response.payload);
} else {
console.warn(Received payload for unregistered hash: ${hash});
}
}
/**
* Method to generate new subscriptions that will be bound to the
* SubscriptionHandler's environment and will be stored internally in the
* instantiated handler object.
*
* #param {string} subscriptionQuery - The query to subscribe to. Needs to
* be a validated subscription type.
* #param {Object} variables - The variables for the passed query.
* #param {Object} configs - A subscription configuration. If
* override is required.
*/
newSubscription = (
subscriptionQuery: GraphQLTaggedNode,
variables: Variables,
configs: GraphQLSubscriptionConfig,
) => {
const config = configs || DEFAULT_CONFIG;
requestSubscription(
this.subscriptionEnvironment,
{
subscription: subscriptionQuery,
variables: {},
...config,
},
);
}
setupSubscription = (
config: ConcreteBatch,
variables: Variables,
cacheConfig: ?CacheConfig,
observer: Observer,
) => {
const query = config.text;
// Get the hash from the given subscriptionQuery and variables. Used to
// identify this specific subscription.
const hash = this.getHash(query, variables);
// Store the newly created subscription request internally to be re-used
// upon message receival or local data updates.
this.subscriptions[hash] = { query, variables };
const subscription = this.subscriptions[hash];
subscription.observer = observer;
// Temp fix to avoid WS Connection state.
setTimeout(() => {
this.websocket.send(JSON.stringify({ query, variables }));
}, 100);
}
}
const subscriptionHandler = new SubscriptionHandler(WS_URL, WS_OPTIONS);
export default subscriptionHandler;
For anyone stumbling across this recently, I did not have success with either the solutions above because of recent updates in the libraries involved. Yet they were a great source to start and I put up together a small example based on the official relay modern todo example, it is very minimalistic and uses helpers libraries from Apollo but works well with relay modern:
https://github.com/jeremy-colin/relay-examples-subscription
It includes both server and client
Hope it can help
I think this repo would fit your needs.
Helps you creating your subscriptions server-side

Resources