Using Playwright and Junit to write end-to-end tests for your Vaadin application
Posted 15 Apr 2021 by Erik Lumme
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.
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.
I added Maven dependencies for the latest version of Playwright and Junit. In my case these were
org.junit.jupiter:junit-jupiter:5.7.0, both with the
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
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
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. A context, 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.
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
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.
Use these to type in the credentials and click the login button.
<vaadin-text-field name="username" id="vaadinLoginUsername" ...>
<vaadin-password-field name="password" id="vaadinLoginPassword" ...>
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
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
withSlowMomethod 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.
waitForSelectormethod takes an optional
WaitForSelectorOptionsargument, 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
vaadin-text-field >> 'Name' will find the element inside a
vaadin-text-field that contains the text “Name”, in this case the
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.
elem is the element that
elementHandle refers to, and
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
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
Running the test in debug mode with a breakpoint on the last line, we can see that the form is 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
Create a method in GridElement for finding a cell by content.
We could use
waitForSelector, causing this method to return
nullimmediately 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.
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
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!