Reputation: 2161
I'm trying to intercept the client side request that's done by React Server Components for my tests in Cypress.
I thought it should be as simple as looking at and copying the request response (including headers) in the network tab and copy that to Cypress' intercept
function, but I keep getting the error Connection closed
and the request result in my front end code logs undefined
.
Any ideas?
My current intercept
implementation in my Cypress test:
cy.intercept("POST", "/account/register", {
statusCode: 200,
headers: {
"x-action-revalidated": "[[],0,0]",
"content-type": "text/x-component",
},
body: `0:["$@1",["development",null]]\n1:{"result":"success","status":200,"data":{"id":"91ffe221-ace9-40a1-98d5-2851cb071cbd","name":"Test User","email":"[email protected]","createdAt":"$D2024-05-26T16:32:11.280Z","updatedAt":"$D2024-05-26T14:32:11.288Z"}}`,
}).as("registerRequest");
The error message in my console:
index-3fe21bb9.js:133640 Error: The following error originated from your application code, not from Cypress. It was caused by an unhandled promise rejection.
> Connection closed.
When Cypress detects uncaught errors originating from your application it will automatically fail the current test.
This behavior is configurable, and you can choose to turn this off by listening to the `uncaught:exception` event.
at close (webpack-internal:///app-pages-browser/./node_modules/next/dist/compiled/react-server-dom-webpack/cjs/react-server-dom-webpack-client.browser.development.js:2131:31)
at progress (webpack-internal:///app-pages-browser/./node_modules/next/dist/compiled/react-server-dom-webpack/cjs/react-server-dom-webpack-client.browser.development.js:2148:7)
From previous event:
at Promise.longStackTracesCaptureStackTrace [as _captureStackTrace] (http://localhost:3000/__cypress/runner/cypress_runner.js:3486:19)
at Promise._then (http://localhost:3000/__cypress/runner/cypress_runner.js:1239:17)
at Promise._passThrough (http://localhost:3000/__cypress/runner/cypress_runner.js:4110:17)
at Promise.lastly.Promise.finally (http://localhost:3000/__cypress/runner/cypress_runner.js:4119:17)
at Object.onRunnableRun (http://localhost:3000/__cypress/runner/cypress_runner.js:162793:53)
at $Cypress.action (http://localhost:3000/__cypress/runner/cypress_runner.js:41042:28)
at Runnable.run (http://localhost:3000/__cypress/runner/cypress_runner.js:145381:13)
at Runner.runTest (http://localhost:3000/__cypress/runner/cypress_runner.js:155323:10)
at http://localhost:3000/__cypress/runner/cypress_runner.js:155449:12
at next (http://localhost:3000/__cypress/runner/cypress_runner.js:155232:14)
at http://localhost:3000/__cypress/runner/cypress_runner.js:155242:7
at next (http://localhost:3000/__cypress/runner/cypress_runner.js:155144:14)
at http://localhost:3000/__cypress/runner/cypress_runner.js:155210:5
at timeslice (http://localhost:3000/__cypress/runner/cypress_runner.js:145721:27)
Upvotes: 3
Views: 1059
Reputation: 492
I handle server actions by updating parameters in my URL. Each server action should have a unique identifier. When an action is called, it should be added to the URL as a parameter and removed once the server action is completed (whether it succeeds or fails). This ensures that each server action is called with a unique identifier, making it easier to intercept. This approach will add extra logic to your server actions and application. Here is an example of the code:
React component:
"use client"
import React, { useState } from 'react'
import { getData } from '@/actions/getData.action';
const addParamToUrl = (param: string, value: string) => {
const url = new URL(window.location.href);
if (value) {
url.searchParams.set(param, value);
} else {
url.searchParams.delete(param);
}
window.history.pushState({}, '', url);
}
const Button = () => {
const [loading, setLoading] = useState(false);
const [data, setData] = useState<string | null>(null);
const [error, setError] = useState<string | null>(null);
const handleGetData = async (identifier: string, wrong?: boolean) => {
setLoading(true);
addParamToUrl("identifier", identifier);
const data = await getData(identifier, wrong);
addParamToUrl("identifier", "");
if(data?.error) {
setError("Error fetching data");
} else {
setData(data?.data);
}
setLoading(false);
}
return (
<>
<button
className='bg-green-500 cursor-pointer p-5 rounded-md'
data-cy="success"
onClick={() => handleGetData("successIdentifier")}
>
Success message
</button>
<button
className='bg-red-500 cursor-pointer p-5 rounded-md'
data-cy="error"
onClick={() => handleGetData("errorIdentifier", true)}
>
Error message
</button>
<div className='flex items-center justify-center flex-col'>
{JSON.stringify(data)}
{loading && <div>Loading...</div>}
{error && <div className='text-red-500'>{error}</div>}
</div>
</>
)
}
export default Button
Cypress test:
describe("template spec", () => {
it("Click Success and r", async() => {
cy.visit("http://localhost:3000");
cy.intercept("POST", "http://localhost:3000/?identifier=successIdentifier")
.as("successHandler");
cy.intercept("POST", "http://localhost:3000/?identifier=errorIdentifier")
.as("errorHandler");
cy.get('[data-cy="success"]').click();
cy.get('[data-cy="error"]').click();
cy.wait("@successHandler").then(() => {
cy.log("successHandler intercepted");
});
cy.wait("@errorHandler").then(() => {
cy.log("errorHandler intercepted");
});
});
});
Server action:
'use server'
const delay = (time: number): Promise<void> => {
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, time);
});
}
export async function getData(identifier: string, wrong?: boolean) {
try {
await delay(Math.random() * 10000);
if(wrong){
throw new Error("Error fetching data");
}
const res = await fetch(`https://jsonplaceholder.typicode.com/todos/1`)
const data = await res.json();
console.log(identifier);
return {
data
}
} catch {
return {
error: "Error fetching data",
}
}
}
Upvotes: 0
Reputation: 2161
I've figured it out. There are two things to note when intercepting and returning a custom response for a React Server Action request:
Content-Type: "text/x-component"
is present on the responseThe second one is what I was missing.
This example interception does work:
cy.intercept("POST", "/account/register", {
headers: {
"content-type": "text/x-component",
},
body: `0:["$@1",["development",null]]\n1:{"result":"success","status":200,"data":{"id":"982310j2","name":"Test User","email":"[email protected]","createdAt":"$D2024-05-26T16:32:11.280Z","updatedAt":"$D2024-05-26T14:32:11.288Z"}}\n`,
}).as("registerRequest");
I published a repository example cypress test using React Server Actions, so if anyone still has problems with mocking the response of a server action in cypress, they can look at my implementation here: https://github.com/terrabythia/cypress-server-action-test-example
Upvotes: 3