How to Test Pimcore Admin Using Cypress
Cypress is the next-generation front-end testing tool built for the modern web. It is most often compared to Selenium; however, Cypress is fundamentally and architecturally different. It is not constrained by the same restrictions as Selenium, enabling you to write faster, easier, and more reliable tests.
In this blog, we will show you how to use Cypress to test the Pimcore admin interface and check whether some admin functionalities, as well as some custom ones, are working as expected.
How to Set Up Cypress Testing with Pimcore Admin
First, we have to ensure that Pimcore will work in Cypress. I ran into a minor issue while trying to set up Cypress to test Pimcore admin. Specifically, Pimcore admin wouldn't load in Cypress after logging in.
After some research, I discovered the problem when I encountered it on Git Hub. The fix was simple, so I created a merge request.
Now, if you are using version 10.6., you probably won't have this problem, but in case you do, here is how to fix it. You have two approaches to this:
- Find a file in your vendor folder and change it manually there the same way it's changed here (not recommended).
- Install the patch.
To install the patch to your Pimcore installation, add these lines to composer.json:
After that, run composer install and check if it's installed by going to vendor/pimcore/pimcore/bundles/AdminBundle/Resources/public/js/pimcore/functions.js on line 75.
If everything is in order, you can start testing your Pimcore administration.
Tips on How to Select Elements in Pimcore Admin
The main problem in Pimcore admin is that a lot of elements have the same classes. So, to select an element, we should consider other ways of selecting, not just by class. The best way would be with ids, but some elements don't have them.
In cases where classes are not unique enough and/or there is no id on an element, the best way to select that element is by using attributes. For example, the selector could look like this:
.x-menu[role="menu"][aria-hidden="false"][aria-expanded="true"]
This example is the best way to select the pop-up menu in Pimcore admin that opens up when creating a new object or document.
For instance, selecting inputs in admin. The best way I found to select them is by selecting input that is located inside of them and targeting it with id or by name. By name, you would target it, for example, like this: input[name= "brand"].
If you can't select it in any of those ways, you will have to find another way, either by selecting some class that is unique enough or some ID that encapsulates input.
After getting that input element, we need to get a parent element with class x-form-trigger-wrap, then find an element with class x-form-trigger and click it. Clicking on that element will open the select dropdown from which we can select the desired value. Now, we have two ways to select the dropdown we just opened. One way is by getting an element with class x-boundlist and making sure it's correct; the other is a bit complex.
For that, we need to grab the data-componentid value from input, and after opening the dropdown, we need to grab the element with class and id like this: ".x-boundlist#{componentid}-picker". That way, we are sure that we got the right dropdown.
Here is the example code:
For multi-selects and other types of inputs, for instance, I recommend getting a label first, then getting an element that encapsulates both label and input, and then finding input in that element. Unless you can target it with some id or by name attribute like we discussed previously.
Selecting Elements When Having Multiple Nested Menus
When we want to create a new object, we can use the click() function when selecting "Add Object," but when selecting any object from the second menu, click() might not work properly. So I recommend using trigger('click') because sometimes click will be triggered on the body element for some reason, and trigger('click') solves that.
Here is an example:
If you want to do a test in which you add an areabrick to new or existing documents, you will first need to get the iframe of that document since Pimcore uses iframes to allow you to edit and preview documents.
To get an iframe, I recommend creating a getIframeBody function (you can name it whatever you want). Here is an example of that function:
All the function does is it finds an iframe whose id starts with "document_iframe_", checks that its document body is not empty, and if it isn't empty, it wraps the iframe body element and returns it so that we can chain other Cypress functions after that. Functions such as find, contains, and so on.
After calling the function in the test, save it in a variable. In this case, as the "createdPage". After that, whatever you want to do or add in that document, you need to do it inside of the iframe we just saved in the variable. You can see how it is done in the example above. So, basically, every line of code that does something in the iframe needs to start with cy.get('@createdPage').
Custom Commands for Pimcore Admin
Many custom commands can be written for Pimcore administration, but I will show you two very useful and similar commands. Both of them are for logging in to Pimcore admin; the difference is that one is for logging in as admin, and the other is for logging in by user role.
The first one that we will cover is login as admin:
This command, called adminLogin, expects an object containing keys, username, and password. Then, it gets the Pimcore admin login form, saves it as loginForm, and proceeds to find the input for the username and type in the provided username. The same goes for the password input.
After that, it finds the submit button and clicks it. The reason why commands return undefined is that we don't need to return anything, and if we don't want to return anything, we have to return undefined. Otherwise, it will throw an error.
The second command is a command called actAs:
It's basically the same as the first one, but the main difference is that we save an object with different roles to a global state, with each having a username and password, and then pass the role to the command. The command first checks if the role exists in the credentials object; if it does, it proceeds and checks if that role has a username and password. After that, it's the same command as the one before.
Clearing Database Before Tests
Sometimes, you will have to clear the database before each test. For example, if your test requires creating some object or document with a specific name. I recommend using names for objects, documents, or users that are created with tests beginning with something like "Cypress-".
That way, deleting them later with an SQL query is easier. To be able to delete data from a database, first, we need to be able to connect to it and then write some SQL to delete the data we need to delete.
We will create a db.js file in cypress/support/. In it, we will require a mysql2/promise package and then create a function that connects to the database by providing necessary data such as host, user, password, database, and port. Afterward, we will import that function to our cypress.config.js file and create a new event in the setupNodeEvents function.
Now, we will create a function that will delete specific users from the database. In the event, we will create a new function called clearDBUsers, and in it, we will connect to the database, delete users, and then end the connection:
To trigger the event, we just need to write in one of our tests in either before or beforeEach function this: cy.task('clearDBUsers'), where the task is the name of the event, and clearDBUser is the name of the function in that event.
Conclusion
In short, the most difficult thing about testing in Pimcore administration is finding the best and most consistent way of selecting elements with which we want to interact. Best ways are by id, specific attributes or occasionally with class. Sometimes, you will find some element with an id, but that id will be something like "select-1354"; in that case, I don't recommend using an id to select that element because it could change at any point. Unless you find where the "1354" part originates from, then you can use that id selection without worrying about it changing later on.
We hope this guide will help you with more efficient testing process. Feel free to reach out if you have any questions.