Cypress testing CMS configured layouts.
CMS driven layouts offer new challenges and new opportunities
End-to-end tests are dependent upon an applications' UI. With this dependency, the tests have to stay up to date with a shifting UI. What if we generated tests based on the UI? What would this look like? In this example, I explore test generation based on a CMS configured layout provided to the application via an API.
Data Gathering
We can use the Cypress Request API to retrieve the page layout. In this step, we gather the page layout from the CMS via the API. Now we know which components we can expect to be on the page.
// Example request:
cy.request({
url: "https://myfakeapi.com/graphql/layout",
method: "POST",
headers: {
// if you need an auth header
// or other header, put that here
},
body: {
// this query shape is just an example
// of writing a generic query to retrieve
// the components for a page
query: `
query layout(page) {
components: {
type
}
}
`,
},
});
// Example response:
{
"data": {
"page": {
"components": [
{
"type": "carousel"
},
{
"type": "gridSection"
},
{
"type": "alert"
}
]
}
}
}
Data Actioning
Success! Now we know which components we can expect to be on the page. Lets' start thinking about how we can retrieve the appropriate Cypress tests for these components. Lets' write our Cypress tests as functions. Cypress will queue up the tests appropriately. Here's a simple example of a test for the Alert component. Since we can never be 100% sure of how many times this component appears on a page, we can use Cypress to handle any quantity of this component.
// ./cypress/partials/alert.js
export const alertText = () => {
// Find all of the elements with a matching test id
// loop over results
cy.findAllByTestId("alertContainer").each(($alertItem) => {
// in each individual item do the following
cy.wrap($alertItem)
.srollIntoView()
.within(() => {
// Get the h3 inside of the alert item
// make sure its not empty
// and make sure its visible
//
// by asserting that its not empty
// we validate the CMS has had content loaded
// and the app is displaying it correctly
cy.findByRole("heading", { level: 3 })
.should("not.be.empty")
.should("be.visible");
});
});
};
Putting it together
Great! We wrote a test function. The next step is to allow our test file to consume this function when needed. In our test spec file, we need to import our test function. After we import our test function file into our spec file, we will have the ability to execute our test function on command.
// ./cypress/integration/test.spec.js
import { alert } from "cypress/partials/alert";
const componentMap = {
// myKey : testFunction
alert: alert,
};
it("Generates a layout based test from CMS data", () => {
cy.request({
url: "https://myfakeapi.com/graphql/layout",
method: "POST",
body: {
query: `
query layout(page) {
components: {
type
}
}
`,
},
}).then((result) => {
cy.visit("/").then(() => {
// get the component names from the response
const componentNames = result?.body?.data?.page?.components.map(
(comp) => comp.type
);
// strip out the duplicates.
// Our tests look for all components of a type.
const components = [...new Set(componentNames)];
// for each component name, run the test!
components.forEach((component) => {
cy.log(`running test for ${component}`);
componentMap[component]();
});
});
});
});
Wrapping up
Now our test builds itself! Based on the layout information returned from the CMS, we retrieve the appropriate tests and execute them. Our tests take care of scenarios where any quantity of an individual component is on the page. Since our page can change due to the CMS changing layout or content, our tests can respond to these changes without being updated to remain in sync. The only setup required here is to write tests for each component in the application.
Thanks
If you learned anything from this, or want to share it yourself, go ahead! I ask that you include a link to my website when you share. If you wish to contact me, my information is in the footer!