When creating a JHipster blueprint you usually start with an application and modify it to your needs. Afterwards you can start to modify existing templates or creating new ones if needed. As JHipster offers a lot of different options you might need to modify multiple applications in order to get the templates right.
In this post we will integrate Playwright into an existing JHipster application. You can find the example code on github.
Generate JHipster Application
After installing JHipster you can create an application with default options and Gradle as build system by using the jhipster cli:
jhipster jdl default-gradle
By default no end-2-end testing framework is configured.
As the Playwright blueprint is supposed to replace Cypress as end-2-end testing framework it is a good idea to add it.
To add Cypress to you application you need to modify .yo-rc.json
and run the generator again.
{
"generator-jhipster": {
...
"skipClient": false,
"testFrameworks": ["cypress"],
"websocket": false,
...
}
}
You can start the application via ./gradlew
and run Cypress via npm run e2e
.
Adding Playwright
Playwright supports different test runners. We choose Jest as JHipster already uses Jest for running unit and component tests.
npm install -D jest-playwright-preset playwright ts-jest eslint-plugin-jest-playwright
The configuration is stored in jest-playwright.config.js
:
module.exports = {
preset: 'jest-playwright-preset',
testMatch: ['<rootDir>/src/test/javascript/playwright/**/@(*.)@(spec.ts)'], 1
transform: {
'^.+\\.ts$': 'ts-jest',
},
testEnvironmentOptions: {
'jest-playwright': {
browsers: ['firefox', 'chromium'], 2
launchOptions: {
headless: false, 3
devtools: false,
},
},
},
};
- Playwright specs are stored in
src/test/javascript/playwrigth
- Playwright browser options
- Run not in headless mode
To have no typescript errors add following to types to compilerOptions
in tsconfig.json
:
"types": ["jest", "jest-playwright-preset", "expect-playwright"]
To start playwright add an additional command to package.json
:
"jest:e2e": "jest --config jest-playwright.config.js"
First Playwright Test
The existing Cypress tests are located under src/test/javascript/cypress
.
Respectively all Playwright files will be located under src/test/javascript/playwright
.
As a starting point we look at the existing login-page.spec
and try to create the same test with Playwright.
To structure the test the Page Object Model Pattern is used.
All page objects will be stored under src/test/javasript/playwright/models
.
The specification files will be located e.g. under src/test/playwright/account
.
export class LoginPage {
page: any;
constructor(page: any) {
this.page = page;
}
async navigate() {
await this.page.goto('http://localhost:8080/login');
}
async loginErrorVisible() { 1
await page.waitForSelector('[data-cy="loginError"]');
}
async login(username: string, password: string, awaitNavigation: boolean = true) { 2
await this.page.fill('[data-cy="username"]', username); 3
await this.page.fill('[data-cy="password"]', password);
await this.page.click('[data-cy="submit"]');
if (awaitNavigation) {
await this.page.waitForNavigation();
}
}
}
- Function to check if login failed
- Function to login
- Reuse
data-cy
attributes to select elements
import { LoginPage } from '../models/login';
let loginPage: LoginPage;
beforeAll(async () => { 1
loginPage = new LoginPage(page);
await loginPage.navigate();
});
test('requires username', async () => {
await loginPage.login('', 'a-password', false);
await loginPage.loginErrorVisible();
});
test('requires password', async () => {
await loginPage.login('a-login', '', false);
await loginPage.loginErrorVisible();
});
test('errors when password is incorrect', async () => {
await loginPage.login('admin', 'bad-password', false);
await loginPage.loginErrorVisible();
});
test('go to login page when successfully logs in', async () => {
await loginPage.login('admin', 'admin'); 2
await page.waitForSelector('[data-cy="adminMenu"]');
});
- Open login page before all tests
- Login and wait for admin menu to be visible
Mocking Responses
To avoid really e.g. really changing the password within a test some requests are mocked and replaced with a fixed responses. This also helps to test failure handling.
test('should be able to update password', async () => {
// eslint-disable-next-line @typescript-eslint/no-misused-promises
await page.route('**/api/account/change-password', route => 1
route.fulfill({
status: 200,
})
);
await changePasswordPage.updatePassword('correct-current-password', 'jhipster', 'jhipster'); 2
await page.waitForSelector('.alert-success');
await page.unroute('**/api/account/change-password'); 3
});
- Intercept requests to
/api/account/change-password
and return a sucessfull response if intercepted - Execute password change. See change-password.ts page object for details.
- Remote the route interception otherwise it will stay enabled
With this approach and techniques each Cypress test can be replaced with a corresponding Playwright test. In the next part we will have a look at how to write Playwright tests for generated entities.