generator-jhipster-playwright 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -9,7 +9,15 @@
|
|
|
9
9
|
|
|
10
10
|
This is a JHipster blueprint. It overrides the `cypress` sub-generator and writes Playwright files instead of Cypress files for generated applications.
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
### Supported Matrix
|
|
13
|
+
|
|
14
|
+
| Framework | JWT | Session | OAuth2 |
|
|
15
|
+
|-----------|-----|---------|--------|
|
|
16
|
+
| React | Yes | Yes | Yes |
|
|
17
|
+
| Angular | Yes | Yes | Yes |
|
|
18
|
+
| Vue | Yes | Yes | Yes |
|
|
19
|
+
|
|
20
|
+
All 9 combinations are verified in CI against freshly generated JHipster applications. OAuth2 tests run against a Keycloak instance.
|
|
13
21
|
|
|
14
22
|
## Installation
|
|
15
23
|
|
|
@@ -59,6 +67,7 @@ application {
|
|
|
59
67
|
config {
|
|
60
68
|
baseName myApp
|
|
61
69
|
clientFramework react
|
|
70
|
+
authenticationType jwt
|
|
62
71
|
testFrameworks [cypress]
|
|
63
72
|
}
|
|
64
73
|
}
|
|
@@ -86,6 +95,12 @@ npx playwright test
|
|
|
86
95
|
|
|
87
96
|
The generated `playwright.config.ts` starts the frontend dev server automatically. The Spring Boot backend still needs to be running before the tests execute.
|
|
88
97
|
|
|
98
|
+
For OAuth2 applications, a Keycloak instance must be running before the backend starts:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
docker compose -f src/main/docker/keycloak.yml up -d
|
|
102
|
+
```
|
|
103
|
+
|
|
89
104
|
### Generated Output
|
|
90
105
|
|
|
91
106
|
The blueprint writes:
|
|
@@ -97,6 +112,8 @@ The blueprint writes:
|
|
|
97
112
|
|
|
98
113
|
For Angular applications, the blueprint also adds `@popperjs/core` to the generated app dependencies so the generated frontend has the required Popper peer dependency available.
|
|
99
114
|
|
|
115
|
+
For Angular session-auth applications, a custom `proxy.config.playwright.mjs` is generated to avoid proxying lazy-loaded route chunks.
|
|
116
|
+
|
|
100
117
|
## Local Development
|
|
101
118
|
|
|
102
119
|
To work on the blueprint locally:
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* - clientFrameworkAngular, clientFrameworkReact, clientFrameworkVue
|
|
9
9
|
*/
|
|
10
10
|
import type { Page, APIRequestContext, APIResponse } from '@playwright/test';
|
|
11
|
-
import { expect
|
|
11
|
+
import { expect } from '@playwright/test';
|
|
12
12
|
|
|
13
13
|
// ***********************************************
|
|
14
14
|
// Begin Specific Selector Attributes
|
|
@@ -153,14 +153,30 @@ export async function login(page: Page, request: APIRequestContext, username: st
|
|
|
153
153
|
await waitForAppShell(page);
|
|
154
154
|
}
|
|
155
155
|
<%_ } else if (authenticationTypeOauth2) { _%>
|
|
156
|
+
// Session cookies captured after browser OAuth2 login for API requests.
|
|
157
|
+
let _sessionCookie: string | undefined;
|
|
158
|
+
let _xsrfToken: string | undefined;
|
|
159
|
+
|
|
156
160
|
/**
|
|
157
161
|
* Perform authenticated API request (OAuth2).
|
|
162
|
+
* Uses session cookies captured from the browser after login.
|
|
158
163
|
*/
|
|
159
164
|
export async function authenticatedRequest(
|
|
160
165
|
request: APIRequestContext,
|
|
161
166
|
data: { method?: string; url: string; data?: unknown },
|
|
162
167
|
): Promise<APIResponse> {
|
|
163
|
-
|
|
168
|
+
const headers: Record<string, string> = { 'Content-Type': 'application/json' };
|
|
169
|
+
if (_sessionCookie) {
|
|
170
|
+
headers['Cookie'] = _sessionCookie;
|
|
171
|
+
}
|
|
172
|
+
if (_xsrfToken) {
|
|
173
|
+
headers['X-XSRF-TOKEN'] = _xsrfToken;
|
|
174
|
+
}
|
|
175
|
+
return request.fetch(data.url, {
|
|
176
|
+
method: data.method ?? 'GET',
|
|
177
|
+
data: data.data,
|
|
178
|
+
headers,
|
|
179
|
+
});
|
|
164
180
|
}
|
|
165
181
|
|
|
166
182
|
/**
|
|
@@ -188,6 +204,10 @@ export async function login(page: Page, request: APIRequestContext, username: st
|
|
|
188
204
|
}
|
|
189
205
|
await page.waitForURL('**/', { timeout: 15000 });
|
|
190
206
|
await waitForAppShell(page);
|
|
207
|
+
// Capture session cookies so authenticatedRequest() can make API calls.
|
|
208
|
+
const cookies = await page.context().cookies();
|
|
209
|
+
_sessionCookie = cookies.map(c => `${c.name}=${c.value}`).join('; ');
|
|
210
|
+
_xsrfToken = cookies.find(c => c.name === 'XSRF-TOKEN')?.value;
|
|
191
211
|
}
|
|
192
212
|
<%_ } else { _%>
|
|
193
213
|
/**
|
|
@@ -19,9 +19,14 @@ import {
|
|
|
19
19
|
|
|
20
20
|
async function clickDropdownItem(menu: Locator, item: string): Promise<void> {
|
|
21
21
|
await expect(menu).toBeVisible();
|
|
22
|
-
await menu.click();
|
|
23
22
|
const itemLocator = menu.locator(item);
|
|
24
|
-
|
|
23
|
+
// Retry open-then-check: the dropdown can close due to animation or re-render.
|
|
24
|
+
await expect(async () => {
|
|
25
|
+
if (!(await itemLocator.isVisible())) {
|
|
26
|
+
await menu.click();
|
|
27
|
+
}
|
|
28
|
+
await expect(itemLocator).toBeVisible();
|
|
29
|
+
}).toPass({ timeout: 10_000 });
|
|
25
30
|
await itemLocator.click();
|
|
26
31
|
}
|
|
27
32
|
|