Using Playwright and Junit to write end-to-end tests for your Vaadin application

Erik Lumme
9 min readMar 26, 2021
A part of the source code and a screenshot of the application being tested.

Microsoft recently released a Java API for Playwright, their cross-browser web-automation library. Playwright automatically downloads and updates web drivers, making set-up a breeze and keeping the drivers ever green. In this post, we take a look at how it can be utilized for testing a Vaadin application.

Introduction

To test our application from end to end, we use Playwright to control a browser. The browser interacts with our running application as a user would. We organize the code into test cases and run them with Junit.

The base for the Vaadin 19 application I am testing was generated through start.vaadin.com. It has a TypeScript view for logging in, and a Java view for viewing and editing books. The complete code can be found on GitHub.

The book view with a form for editing a book or adding a new book.
The book view with a form for editing a book or adding a new book.

I added Maven dependencies for the latest version of Playwright and Junit. In my case these were com.microsoft.playwright:playwright:0.180.0 and org.junit.jupiter:junit-jupiter:5.7.0, both with the test scope.

Creating a reusable base class for testing

A good starting point is a base class that houses the common setup for all tests. I made an AbstractPlaywrightIT class that is set up to run tests in Chromium. Create the Playwright instance and launch the browser once per class using Junit’s @BeforeAll annotation.

By passing withHeadless(false)in the launch options, we can see the browser while running the tests, making them easier to debug. For a CI environment, leave it at its default value of true.

Close Playwright after executing all the tests in a class.

We want each test method to start from a clean slate, yet we use the same browser instance for all tests in a class. This is possible by running each test in its own context. This, much like an incognito window, does not share cache or cookies with other contexts.

A CI environment might read the URL from a configuration file, or get it from Java’s InetAddress. Note the use of @BeforeEach to create a new context for every test method.

Analogous to the @AfterAll method, we use @AfterEach to close the context.

Finally, add a getter to get the Playwright page.

Logging in

To promote reusability and maintainability, it’s a good idea to create one class for each view that we are accessing in our application. These classes, called page objects (PO), have methods for interacting with the view in question.

The user is first greeted by the login view, so create a class LoginPO. Pass it a reference to the Playwright Page in the constructor.

Next, we need methods for interacting with the login view. In fact we need just one: logging in. It is sensible to pass the user credentials as parameters.

This method will interact with the running application using Playwright’s Page class. This class has methods for selecting elements, typing into elements, clicking elements, and more. Most of these methods accept a selector, which can be a CSS selector, an XPath selector, or a selector based on the text that an element contains.

There are also id=… and data-test-id=… selectors. By default, all selectors also search inside shadow roots, except XPath.

Using the browser developer tools of choice, you can inspect the elements in the login view and find suitable selectors.

The login view being inspected in the browser developer tools.
The login view being inspected in the browser developer tools.

Use these to type in the credentials and click the login button.

Specifying the name attribute is a good way to future proof the selector. This way, it will not break if another text field is added to the view.

As the login view is always the first view you see when opening the application, it makes sense to have a method in our base class for getting an instance of it.

Testing the login

To test the login, create a JUnit test file LoginIT. The IT suffix stands for integration test, as we are testing how the parts of our code integrate with each other.

Write a test method that logs in using the LoginPO. The book view that is displayed after logging in has the id book-view, so we know that the login was successful if an element with that id is visible. The assertions are part of JUnit 5.

Try putting a breakpoint on the Assertions.assertTrue line, and run the test in debug mode. You are able to see that the Chromium browser has opened the page and logged in to the book view. This type of debugging is helpful when writing tests.

You can see the browser when running the test because we configured it to run in headful mode. The launch options also have a withSlowMo method that slows down the execution of the test, making it easier to follow along.

Preparing to test the book view

To test the book view, start by creating a BookViewPO the same way as the LoginPO. Also modify the LoginPO#logIn method to return a new BookViewPO, this makes for a nice flow in our code.

Because some changes on the website are asynchronous, elements may not yet be in the state we expect them to when executing tests. This leads to flaky tests that sometimes pass, sometimes not.

When such situations arise, it’s a good idea to wait for an element to be in the desired state, instead of assuming it already is. For the BookViewPO, add a line to the constructor for waiting until it’s visible.

The waitForSelector method takes an optional WaitForSelectorOptions argument, allowing you to wait for an element to become attached, detached, visible or hidden. You can also change the timeout.

Add a method for filling in the five fields in the book: name, author, date, pages, and isbn. The image will be handled separately.

For finding the fields, we combine several Playwright selector features. We use the >> operator to combine different types of selectors, first a CSS selector and then a text selector. A selector is automatically treated as a text selector if it’s surrounded in quotes, like ’Name’, but it can also be explicitly defined with text=Name.

The selector vaadin-text-field >> ‘Name’ will find the element inside a vaadin-text-field that contains the text “Name”, in this case the label.

Part of the HTML that a Vaadin text field consists of, here the label element is pictured.
The contents of a Vaadin text field.

While it is useful to find an input field based on the label, we need a reference to the text field itself for typing into. We can mark the element to be returned by using an asterisk *. When using an asterisk, we must also explicitly define the type of the selector, in this case CSS.

To save the form, we simply use Playwright to click the save button. The single quotes around ’Save’ denote to Playwright that this is a text selector.

Setting the value of a date picker

Typing into the date picker requires a little more work. Create a class DatePickerElement that encapsulates the code for interacting with a date picker. The constructor has two parameters: the Page, and an ElementHandle representing the date picker element.

By default, the Vaadin date picker expects entered dates to be formatted according to the browser’s locale. We can do this by passing the date parts to the browser for formatting.

Here we pass the JavaScript code to be evaluated. The argument elem is the element that elementHandle refers to, and date is the integer array. Using an array, we can pass multiple values. As JavaScript months are zero-indexed, we decrease the month value by one. The code will be evaluated in the browser, and the result passed back to our test.

Now we can type in this date. Pressing the Enter key afterwards closes the date picker dialog that would otherwise be open and block other fields from being filled.

To ensure that the date picker dialog has been closed before proceeding, wait for it to be hidden.

Combining this code into a setValue(LocaleDate date) method, we can reuse the same code for all date pickers that our application might have. It can now be used in the BookViewPO, to initialize it we simply pass the page and an element reference as queried through Playwright.

Uploading a file

The form has an upload field for uploading a cover image. The upload element also deserves its own UploadElement class, again passing an element handle to the constructor.

Playwright has a method for setting the files of an input, accepting a series of Paths. We can use the following code to get the paths to our image resources, with images in src/test/resources.

We can now use the upload in the BookViewPO, creating a method that sets the image. Note that the element handle needs to refer to the input element inside the Vaadin upload.

Testing the book view

The code we have is enough to fill in the book form. Create a BookViewIT, and set it up to log in before every test. As we add a new @BeforeEach method, we must manually call the super method to make sure Playwright is set up.

Next add a new test case, in which we use the BookViewPO to fill in the book form. The image test-image.png is located in src/test/resources.

Running the test in debug mode with a breakpoint on the last line, we can see that the form is filled in.

The book form has been filled in.
The book form has been filled in.

What we still need to do is to verify that the book was actually saved. To do this, we look for it in the grid.

Finding the book in the grid

Start by creating a GridElement class for wrapping interactions with a Vaadin grid.

When saving a new book, it is added to the bottom of the grid. With enough items in the grid, it will not be visible until the user scrolls all the way down. Luckily, the Vaadin grid web component has a method for scrolling the grid to a certain index, as listed in the API. Use it to add a method to GridElement for scrolling to an index.

Another approach to writing end-to-end tests is using Vaadin TestBench. It uses Selenium for browser automation, and comes with ready-made elements for all Vaadin components, allowing developers to write tests with minimal effort.

We will confirm the presence of a book in the grid by finding cells based on their text content. The text will be inside of an vaadin-grid-cell-content element.

The text “my-isbn” inside a Vaadin grid cell.
The text “my-isbn” inside a Vaadin grid cell.

Create a method in GridElement for finding a cell by content.

We could use querySelector instead of waitForSelector, causing this method to return null immediately instead of timing out if the cell does not exist. By using waitForSelector, we give the grid time to finish scrolling or updating.

Add a method to the BookViewPO class for getting the grid, after initializing the grid in the constructor.

Now we can expand our test case in BookViewIT to check that the book was added to the grid. Scroll the grid to the bottom by passing a high index.

We use date.toString() here because that’s how the column is configured to display it. There is no need for assertions, as the waitForSelector method we used will throw an error if the cell can not be found. If we used querySelector, the presence could be asserted with Assertions.assertNotNull(…).

The image is a bit more tricky, both when it comes to finding the cell and confirming the content. Locate the element by looking for the last img element that has a src attribute. The source attribute is needed to differentiate from dummy rows that the grid might insert at the end.

Image comparisons are a chapter of their own, and the method depends on if images are stored as Base64, if the server compresses them and so on. For our purposes, it is enough to ensure that the true width of the image is the same as for the one we uploaded.

That’s it, you can now sleep well knowing that your users will always be able to save new books!

--

--

Erik Lumme

Software engineer working with Java and related technologies