generator-jhipster-playwright 1.0.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/LICENSE +176 -0
- package/README.md +137 -0
- package/cli/cli.cjs +37 -0
- package/generators/cypress/command.js +5 -0
- package/generators/cypress/files.js +75 -0
- package/generators/cypress/generator.js +111 -0
- package/generators/cypress/index.js +2 -0
- package/generators/cypress/templates/playwright.config.ts.ejs +30 -0
- package/generators/cypress/templates/src/test/javascript/cypress/e2e/account/login-page.spec.ts.ejs +83 -0
- package/generators/cypress/templates/src/test/javascript/cypress/e2e/account/logout.spec.ts.ejs +39 -0
- package/generators/cypress/templates/src/test/javascript/cypress/e2e/account/password-page.spec.ts.ejs +88 -0
- package/generators/cypress/templates/src/test/javascript/cypress/e2e/account/register-page.spec.ts.ejs +124 -0
- package/generators/cypress/templates/src/test/javascript/cypress/e2e/account/reset-password-page.spec.ts.ejs +57 -0
- package/generators/cypress/templates/src/test/javascript/cypress/e2e/account/settings-page.spec.ts.ejs +85 -0
- package/generators/cypress/templates/src/test/javascript/cypress/e2e/administration/administration.spec.ts.ejs +73 -0
- package/generators/cypress/templates/src/test/javascript/cypress/e2e/entity/_entity_.spec.ts.ejs +378 -0
- package/generators/cypress/templates/src/test/javascript/cypress/fixtures/integration-test.png +0 -0
- package/generators/cypress/templates/src/test/javascript/cypress/support/account.ts.ejs +26 -0
- package/generators/cypress/templates/src/test/javascript/cypress/support/commands.ts.ejs +200 -0
- package/generators/cypress/templates/src/test/javascript/cypress/support/entity.ts.ejs +73 -0
- package/generators/cypress/templates/src/test/javascript/cypress/support/index.ts.ejs +3 -0
- package/generators/cypress/templates/src/test/javascript/cypress/support/management.ts.ejs +12 -0
- package/generators/cypress/templates/src/test/javascript/cypress/support/navbar.ts.ejs +54 -0
- package/generators/cypress/templates/src/test/javascript/cypress/support/oauth2.ts.ejs +64 -0
- package/generators/cypress/templates/src/test/javascript/cypress/tsconfig.json.ejs +14 -0
- package/package.json +45 -0
package/generators/cypress/templates/src/test/javascript/cypress/e2e/entity/_entity_.spec.ts.ejs
ADDED
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
<%_
|
|
2
|
+
/**
|
|
3
|
+
* Entity spec template
|
|
4
|
+
* Translated from Cypress _entity_.cy.ts (v9)
|
|
5
|
+
*
|
|
6
|
+
* Source EJS variables (all from entity context merged with application):
|
|
7
|
+
* - readOnly, updatableEntity, entityApi, entityApiUrl, entityPage, entityInstance,
|
|
8
|
+
* entityNameCapitalized, primaryKey.name, adminEntity, fields, relationships,
|
|
9
|
+
* differentRelationships, builtInUserManagement, userManagement,
|
|
10
|
+
* graalvmSupport, cypressBootstrapEntities, paginationNo,
|
|
11
|
+
* clientFrameworkReact, clientFrameworkVue, jpaMetamodelFiltering,
|
|
12
|
+
* anyFieldHasFileBasedContentType, workaroundEntityCannotBeEmpty,
|
|
13
|
+
* workaroundInstantReactiveMariaDB, applicationTypeMicroservice
|
|
14
|
+
* - this.generateTestEntity() — generator method
|
|
15
|
+
* - this._ — lodash
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const skipUserManagementCreatePage = builtInUserManagement && !userManagement.skipClient;
|
|
19
|
+
const baseApi = entityApi + 'api/';
|
|
20
|
+
|
|
21
|
+
const entityFakeData = this.generateTestEntity(fields.filter(f => !f.autoGenerate && !f.nullable));
|
|
22
|
+
const requiredRelationships = relationships.filter(rel => rel.relationshipRequired || rel.id);
|
|
23
|
+
const requiredOtherEntities = this._.uniq(requiredRelationships.map(rel => rel.otherEntity));
|
|
24
|
+
const otherEntities = this._.uniq(Object.values(differentRelationships).filter(rels => rels.length > 0).map(rels => rels[0].otherEntity));
|
|
25
|
+
const skipCreateTest =
|
|
26
|
+
(
|
|
27
|
+
graalvmSupport ||
|
|
28
|
+
!cypressBootstrapEntities ||
|
|
29
|
+
requiredRelationships.some(rel => rel.otherEntity.primaryKey && rel.otherEntity.primaryKey.derived) ||
|
|
30
|
+
requiredRelationships.some(rel => rel.otherEntity.builtInUser || rel.otherEntity === this.entity) ||
|
|
31
|
+
requiredRelationships.map(rel => rel.otherEntity.relationships).flat().some(rel => rel.relationshipRequired) ||
|
|
32
|
+
!entityFakeData
|
|
33
|
+
) ? '.skip' : '';
|
|
34
|
+
|
|
35
|
+
const reason = graalvmSupport ? 'graalvm is failing.' : 'cannot create a required entity with relationship with required relationships.';
|
|
36
|
+
|
|
37
|
+
const sampleFields = fields.filter(f => !f.autoGenerate && !f.nullable);
|
|
38
|
+
|
|
39
|
+
if (workaroundEntityCannotBeEmpty && sampleFields.length === 0) {
|
|
40
|
+
const sample = fields.find(f => !f.autoGenerate);
|
|
41
|
+
if (sample) {
|
|
42
|
+
sampleFields.push(sample);
|
|
43
|
+
}
|
|
44
|
+
} else if (workaroundInstantReactiveMariaDB) {
|
|
45
|
+
const samples = fields.filter(f => !f.autoGenerate && f.nullable && f.fieldType === 'Instant');
|
|
46
|
+
if (samples.length > 0) {
|
|
47
|
+
sampleFields.push(...samples);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
_%>
|
|
51
|
+
import { test, expect } from '@playwright/test';
|
|
52
|
+
import {
|
|
53
|
+
entityTableSelector,
|
|
54
|
+
entityDetailsButtonSelector,
|
|
55
|
+
entityDetailsBackButtonSelector,
|
|
56
|
+
<%_ if (!readOnly) { _%>
|
|
57
|
+
entityCreateButtonSelector,
|
|
58
|
+
entityCreateSaveButtonSelector,
|
|
59
|
+
entityCreateCancelButtonSelector,
|
|
60
|
+
<%_ if (updatableEntity) { _%>
|
|
61
|
+
entityEditButtonSelector,
|
|
62
|
+
<%_ } _%>
|
|
63
|
+
entityDeleteButtonSelector,
|
|
64
|
+
entityConfirmDeleteButtonSelector,
|
|
65
|
+
<%_ } _%>
|
|
66
|
+
getEntityHeading,
|
|
67
|
+
getEntityCreateUpdateHeading,
|
|
68
|
+
getEntityDetailsHeading,
|
|
69
|
+
getEntityDeleteDialogHeading,
|
|
70
|
+
} from '../../support/entity';
|
|
71
|
+
import { credentials, login, authenticatedRequest } from '../../support/commands';
|
|
72
|
+
import { clickOn<%= adminEntity && !applicationTypeMicroservice ? 'Admin' : 'Entity' %>MenuItem } from '../../support/navbar';
|
|
73
|
+
|
|
74
|
+
test.describe('<%- entityNameCapitalized %> e2e test', () => {
|
|
75
|
+
|
|
76
|
+
const <%= entityInstance %>PageUrl = '/<%= entityPage %>';
|
|
77
|
+
const <%= entityInstance %>PageUrlPattern = new RegExp('/<%= entityPage %>(\\\\?.*)?$');
|
|
78
|
+
const <%= entityInstance %>ApiUrlPattern = new RegExp('/<%= baseApi + entityApiUrl %>(\\\\?.*)?$');
|
|
79
|
+
let username: string;
|
|
80
|
+
let password: string;
|
|
81
|
+
<%_ if (!readOnly) { _%>
|
|
82
|
+
<% if (skipCreateTest) { %>// <% } %>const <%= entityInstance %>Sample = <%- JSON.stringify(this.generateTestEntity(sampleFields)) %>;
|
|
83
|
+
<%_ } _%>
|
|
84
|
+
|
|
85
|
+
let <%= entityInstance %>: any;
|
|
86
|
+
<%_ for (const otherEntity of requiredOtherEntities) { _%>
|
|
87
|
+
<% if (skipCreateTest) { %>// <% } %>let <%= otherEntity.entityInstance %>: any;
|
|
88
|
+
<%_ } _%>
|
|
89
|
+
|
|
90
|
+
test.beforeAll(() => {
|
|
91
|
+
const creds = credentials();
|
|
92
|
+
({ <%- adminEntity ? 'adminUsername : ' : '' %>username, <%- adminEntity ? 'adminPassword : ' : '' %>password } = creds);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test.beforeEach(async ({ page, request }) => {
|
|
96
|
+
await login(page, request, username, password);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
<%_ if (requiredOtherEntities.length > 0) { _%>
|
|
100
|
+
<%_ if (skipCreateTest) { _%>
|
|
101
|
+
/* Disabled due to incompatibility
|
|
102
|
+
<%_ } _%>
|
|
103
|
+
test.beforeEach(async ({ request }) => {
|
|
104
|
+
<%_ for (const otherEntity of requiredOtherEntities) { _%>
|
|
105
|
+
const response<%= otherEntity.entityClass %> = await authenticatedRequest(request, {
|
|
106
|
+
method: 'POST',
|
|
107
|
+
url: '/<%= baseApi + otherEntity.entityApiUrl %>',
|
|
108
|
+
data: <%- JSON.stringify(this.generateTestEntity(otherEntity.fields.filter(f => !f.autoGenerate))) %>,
|
|
109
|
+
});
|
|
110
|
+
<%= otherEntity.entityInstance %> = await response<%= otherEntity.entityClass %>.json();
|
|
111
|
+
<%_ } _%>
|
|
112
|
+
});
|
|
113
|
+
<%_ if (skipCreateTest) { _%>
|
|
114
|
+
*/
|
|
115
|
+
<%_ } _%>
|
|
116
|
+
|
|
117
|
+
<%_ } _%>
|
|
118
|
+
test.afterEach(async ({ request }) => {
|
|
119
|
+
if (<%= entityInstance %>) {
|
|
120
|
+
await authenticatedRequest(request, { method: 'DELETE', url: `/<%= baseApi + entityApiUrl %>/${<%= entityInstance %>.<%= primaryKey.name %>}` });
|
|
121
|
+
<%= entityInstance %> = undefined;
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
<%_ if (requiredOtherEntities.length > 0) { _%>
|
|
126
|
+
<%_ if (skipCreateTest) { _%>
|
|
127
|
+
/* Disabled due to incompatibility
|
|
128
|
+
<%_ } _%>
|
|
129
|
+
test.afterEach(async ({ request }) => {
|
|
130
|
+
<%_ for (const otherEntity of requiredOtherEntities) { _%>
|
|
131
|
+
if (<%= otherEntity.entityInstance %>) {
|
|
132
|
+
await authenticatedRequest(request, { method: 'DELETE', url: `/<%= baseApi + otherEntity.entityApiUrl %>/${<%= otherEntity.entityInstance %>.<%= otherEntity.primaryKey.name %>}` });
|
|
133
|
+
<%= otherEntity.entityInstance %> = undefined;
|
|
134
|
+
}
|
|
135
|
+
<%_ } _%>
|
|
136
|
+
});
|
|
137
|
+
<%_ if (skipCreateTest) { _%>
|
|
138
|
+
*/
|
|
139
|
+
<%_ } _%>
|
|
140
|
+
|
|
141
|
+
<%_ } _%>
|
|
142
|
+
|
|
143
|
+
test('<%- entityNameCapitalized %> menu should load <%- entityNameCapitalized %> page', async ({ page }) => {
|
|
144
|
+
await page.goto('/');
|
|
145
|
+
await clickOn<%= adminEntity && !applicationTypeMicroservice ? 'Admin' : 'Entity' %>MenuItem(page, '<%= entityPage %>');
|
|
146
|
+
await expect(page).toHaveURL(<%= entityInstance %>PageUrlPattern);
|
|
147
|
+
await expect(getEntityHeading(page, '<%- entityNameCapitalized %>')).toBeVisible();
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test.describe('<%- entityNameCapitalized %> page', () => {
|
|
151
|
+
<%_ if (!readOnly) { _%>
|
|
152
|
+
test.describe('create button click', () => {
|
|
153
|
+
test.beforeEach(async ({ page }) => {
|
|
154
|
+
await page.goto(<%= entityInstance %>PageUrl);
|
|
155
|
+
await page.waitForResponse('**/<%= baseApi + entityApiUrl %>*');
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test('should load create <%- entityNameCapitalized %> page', async ({ page }) => {
|
|
159
|
+
await page.locator(entityCreateButtonSelector).click();
|
|
160
|
+
await expect(page).toHaveURL(new RegExp('/<%= entityPage %>/new$'));
|
|
161
|
+
await expect(getEntityCreateUpdateHeading(page, '<%- entityNameCapitalized %>')).toBeVisible();
|
|
162
|
+
await expect(page.locator(entityCreateSaveButtonSelector)).toBeVisible();
|
|
163
|
+
await page.locator(entityCreateCancelButtonSelector).click();
|
|
164
|
+
const response = await page.waitForResponse('**/<%= baseApi + entityApiUrl %>*');
|
|
165
|
+
expect(response.status()).toBe(200);
|
|
166
|
+
await expect(page).toHaveURL(<%= entityInstance %>PageUrlPattern);
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
<%_ } _%>
|
|
171
|
+
test.describe('with existing value', () => {
|
|
172
|
+
<%_ if (!readOnly) { _%>
|
|
173
|
+
<%_ if (skipCreateTest) { _%>
|
|
174
|
+
/* Disabled due to incompatibility
|
|
175
|
+
<%_ } _%>
|
|
176
|
+
test.beforeEach(async ({ page, request }) => {
|
|
177
|
+
const createResponse = await authenticatedRequest(request, {
|
|
178
|
+
method: 'POST',
|
|
179
|
+
url: '/<%= baseApi + entityApiUrl %>',
|
|
180
|
+
<%_ if (requiredRelationships.length > 0) { _%>
|
|
181
|
+
data: {
|
|
182
|
+
...<%= entityInstance %>Sample,
|
|
183
|
+
<%_ for (const relationship of requiredRelationships) { _%>
|
|
184
|
+
<%= relationship.propertyName %>: <%= relationship.collection ? '[' : '' %><%= relationship.otherEntity.entityInstance %><%= relationship.collection ? ']' : '' %>,
|
|
185
|
+
<%_ } _%>
|
|
186
|
+
},
|
|
187
|
+
<%_ } else { _%>
|
|
188
|
+
data: <%= entityInstance %>Sample,
|
|
189
|
+
<%_ } _%>
|
|
190
|
+
});
|
|
191
|
+
const created<%= entityNameCapitalized %> = await createResponse.json();
|
|
192
|
+
<%= entityInstance %> = {
|
|
193
|
+
...<%= entityInstance %>Sample,
|
|
194
|
+
...created<%= entityNameCapitalized %>,
|
|
195
|
+
};
|
|
196
|
+
<%_ if (builtInUserManagement) { _%>
|
|
197
|
+
if (<%= entityInstance %>.login) {
|
|
198
|
+
<%= entityInstance %>.login = String(<%= entityInstance %>.login).toLowerCase();
|
|
199
|
+
}
|
|
200
|
+
<%_ } _%>
|
|
201
|
+
|
|
202
|
+
// Mock the list endpoint
|
|
203
|
+
await page.route(<%= entityInstance %>ApiUrlPattern, async (route, req) => {
|
|
204
|
+
if (req.method() === 'GET') {
|
|
205
|
+
await route.fulfill({
|
|
206
|
+
status: 200,
|
|
207
|
+
<%_ if (!paginationNo) { _%>
|
|
208
|
+
headers: {
|
|
209
|
+
link: '<http://localhost/<%= baseApi + entityApiUrl %>?page=0&size=20>; rel="last",<http://localhost/<%= baseApi + entityApiUrl %>?page=0&size=20>; rel="first"',
|
|
210
|
+
},
|
|
211
|
+
<%_ } _%>
|
|
212
|
+
contentType: 'application/json',
|
|
213
|
+
body: JSON.stringify([<%= entityInstance %>]),
|
|
214
|
+
});
|
|
215
|
+
} else {
|
|
216
|
+
await route.continue();
|
|
217
|
+
}
|
|
218
|
+
}, { times: 1 });
|
|
219
|
+
|
|
220
|
+
const entitiesRequestPromise = page.waitForResponse(
|
|
221
|
+
response => response.url().match(<%= entityInstance %>ApiUrlPattern) !== null && response.request().method() === 'GET',
|
|
222
|
+
);
|
|
223
|
+
await page.goto(<%= entityInstance %>PageUrl);
|
|
224
|
+
const entitiesResponse = await entitiesRequestPromise;
|
|
225
|
+
expect(entitiesResponse.status()).toBe(200);
|
|
226
|
+
});
|
|
227
|
+
<%_ if (skipCreateTest) { _%>
|
|
228
|
+
*/
|
|
229
|
+
|
|
230
|
+
<%_ } _%>
|
|
231
|
+
<%_ } _%>
|
|
232
|
+
<%_ if (readOnly || skipCreateTest) { _%>
|
|
233
|
+
test.beforeEach(async ({ page }) => {
|
|
234
|
+
await page.goto(<%= entityInstance %>PageUrl);
|
|
235
|
+
const response = await page.waitForResponse('**/<%= baseApi + entityApiUrl %>*');
|
|
236
|
+
const body = await response.json();
|
|
237
|
+
if (body.length === 0) {
|
|
238
|
+
test.skip();
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
<%_ } _%>
|
|
243
|
+
|
|
244
|
+
test('detail button click should load details <%- entityNameCapitalized %> page', async ({ page }) => {
|
|
245
|
+
const entityRow = page.locator('tbody tr').filter({ hasText: String(<%= entityInstance %>.<%= primaryKey.name %>) }).first();
|
|
246
|
+
await entityRow.locator(entityDetailsButtonSelector).click();
|
|
247
|
+
await expect(getEntityDetailsHeading(page, '<%= entityInstance %>')).toBeVisible();
|
|
248
|
+
const responsePromise = page.waitForResponse('**/<%= baseApi + entityApiUrl %>*');
|
|
249
|
+
await page.locator(entityDetailsBackButtonSelector).click();
|
|
250
|
+
const response = await responsePromise;
|
|
251
|
+
expect(response.status()).toBe(200);
|
|
252
|
+
await expect(page).toHaveURL(<%= entityInstance %>PageUrlPattern);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
<%_ if (!readOnly && updatableEntity) { _%>
|
|
256
|
+
test('edit button click should load edit <%- entityNameCapitalized %> page and go back', async ({ page }) => {
|
|
257
|
+
const entityRow = page.locator('tbody tr').filter({ hasText: String(<%= entityInstance %>.<%= primaryKey.name %>) }).first();
|
|
258
|
+
await entityRow.locator(entityEditButtonSelector).click();
|
|
259
|
+
await expect(getEntityCreateUpdateHeading(page, '<%- entityNameCapitalized %>')).toBeVisible();
|
|
260
|
+
await expect(page.locator(entityCreateSaveButtonSelector)).toBeVisible();
|
|
261
|
+
const responsePromise = page.waitForResponse('**/<%= baseApi + entityApiUrl %>*');
|
|
262
|
+
await page.locator(entityCreateCancelButtonSelector).click();
|
|
263
|
+
const response = await responsePromise;
|
|
264
|
+
expect(response.status()).toBe(200);
|
|
265
|
+
await expect(page).toHaveURL(<%= entityInstance %>PageUrlPattern);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
test<% if (jpaMetamodelFiltering && clientFrameworkVue && requiredRelationships.length) { %>.skip<% } %>('edit button click should load edit <%- entityNameCapitalized %> page and save', async ({ page }) => {
|
|
269
|
+
const entityRow = page.locator('tbody tr').filter({ hasText: String(<%= entityInstance %>.<%= primaryKey.name %>) }).first();
|
|
270
|
+
await entityRow.locator(entityEditButtonSelector).click();
|
|
271
|
+
await expect(getEntityCreateUpdateHeading(page, '<%- entityNameCapitalized %>')).toBeVisible();
|
|
272
|
+
await page.locator(entityCreateSaveButtonSelector).click();
|
|
273
|
+
await expect(page).toHaveURL(<%= entityInstance %>PageUrlPattern);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
<%_ } _%>
|
|
277
|
+
<%_ if (!readOnly) { _%>
|
|
278
|
+
<% if (skipCreateTest) { %>// Reason: <%- reason %><% } %>
|
|
279
|
+
test<%= skipCreateTest %>('last delete button click should delete instance of <%- entityNameCapitalized %>', async ({ page }) => {
|
|
280
|
+
<%_ if (clientFrameworkReact) { _%>
|
|
281
|
+
const dialogPromise = page.waitForResponse('**/<%= baseApi + entityApiUrl %>/*');
|
|
282
|
+
<%_ } _%>
|
|
283
|
+
const entityRow = page.locator('tbody tr').filter({ hasText: String(<%= entityInstance %>.<%= primaryKey.name %>) }).first();
|
|
284
|
+
await entityRow.locator(entityDeleteButtonSelector).click();
|
|
285
|
+
<%_ if (clientFrameworkReact) { _%>
|
|
286
|
+
await dialogPromise;
|
|
287
|
+
<%_ } _%>
|
|
288
|
+
await expect(getEntityDeleteDialogHeading(page, '<%= entityInstance %>')).toBeVisible();
|
|
289
|
+
await page.locator(entityConfirmDeleteButtonSelector).click();
|
|
290
|
+
const deleteResponse = await page.waitForResponse(resp =>
|
|
291
|
+
resp.url().includes('/<%= baseApi + entityApiUrl %>/') && resp.request().method() === 'DELETE'
|
|
292
|
+
);
|
|
293
|
+
expect(deleteResponse.status()).toBe(204);
|
|
294
|
+
const listResponse = await page.waitForResponse('**/<%= baseApi + entityApiUrl %>*');
|
|
295
|
+
expect(listResponse.status()).toBe(200);
|
|
296
|
+
await expect(page).toHaveURL(<%= entityInstance %>PageUrlPattern);
|
|
297
|
+
<% if (cypressBootstrapEntities) { %>
|
|
298
|
+
<%= entityInstance %> = undefined;
|
|
299
|
+
<%_ } _%>
|
|
300
|
+
});
|
|
301
|
+
<%_ } _%>
|
|
302
|
+
});
|
|
303
|
+
});
|
|
304
|
+
<%_ if (!readOnly) { _%>
|
|
305
|
+
|
|
306
|
+
test.describe<% if (skipUserManagementCreatePage) { %>.skip<% } %>('new <%- entityNameCapitalized %> page', () => {
|
|
307
|
+
test.beforeEach(async ({ page }) => {
|
|
308
|
+
await page.goto(<%= entityInstance %>PageUrl);
|
|
309
|
+
await page.waitForResponse('**/<%= baseApi + entityApiUrl %>*');
|
|
310
|
+
await page.locator(entityCreateButtonSelector).click();
|
|
311
|
+
await expect(getEntityCreateUpdateHeading(page, '<%- entityNameCapitalized %>')).toBeVisible();
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
<% if (skipCreateTest) { %>// Reason: <%- reason %><% } %>
|
|
315
|
+
test<%= skipCreateTest %>('should create an instance of <%- entityNameCapitalized %>', async ({ page }) => {
|
|
316
|
+
<%_ fields.filter(field => (!field.id || !field.autoGenerate) && !field.hidden && !field.readonly).forEach((field) => {
|
|
317
|
+
const fieldName = field.fieldName;
|
|
318
|
+
const fieldIsEnum = field.fieldIsEnum;
|
|
319
|
+
let fieldValue = !entityFakeData ? field.generateFakeData('cypress') : entityFakeData[field.fieldName];
|
|
320
|
+
if (fieldValue === undefined) {
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
fieldValue = typeof fieldValue === 'string' ? fieldValue.replaceAll("\\", "\\\\").replaceAll("'", "\\'") : fieldValue;
|
|
324
|
+
_%>
|
|
325
|
+
|
|
326
|
+
<%_ if (field.fieldTypeBoolean) { _%>
|
|
327
|
+
const <%= fieldName %>Checkbox = page.locator(`[data-cy="<%= fieldName %>"]`);
|
|
328
|
+
await expect(<%= fieldName %>Checkbox).<%= field.fieldTsDefaultValue === 'true' ? '' : 'not.' %>toBeChecked();
|
|
329
|
+
await <%= fieldName %>Checkbox.click();
|
|
330
|
+
await expect(<%= fieldName %>Checkbox).<%= field.fieldTsDefaultValue === 'true' ? 'not.' : '' %>toBeChecked();
|
|
331
|
+
|
|
332
|
+
<%_ } else if (field.fieldTypeBinary && !field.blobContentTypeText) { _%>
|
|
333
|
+
// Playwright uses native setInputFiles instead of DataTransfer hack
|
|
334
|
+
const fixturePath = require('path').join(__dirname, '..', '..', 'fixtures', 'integration-test.png');
|
|
335
|
+
await page.locator(`[data-cy="<%= fieldName %>"]`).setInputFiles(fixturePath);
|
|
336
|
+
|
|
337
|
+
<%_ } else if (fieldIsEnum) { _%>
|
|
338
|
+
await page.locator(`[data-cy="<%= fieldName %>"]`).selectOption('<%- fieldValue %>');
|
|
339
|
+
|
|
340
|
+
<%_ } else if (field.fieldTypeString || field.fieldTypeNumeric || field.fieldTypeLocalDate || field.fieldTypeTimed || field.fieldTypeDuration) { _%>
|
|
341
|
+
await page.locator(`[data-cy="<%= fieldName %>"]`).fill('<%- fieldValue %>');
|
|
342
|
+
<% if (field.fieldTypeLocalDate || field.fieldTypeTimed || field.fieldTypeDuration) { %>await page.locator(`[data-cy="<%= fieldName %>"]`).evaluate(e => e.blur());<% } %> await expect(page.locator(`[data-cy="<%= fieldName %>"]`)).toHaveValue('<%- fieldValue %>');
|
|
343
|
+
|
|
344
|
+
<%_ } else { _%>
|
|
345
|
+
await page.locator(`[data-cy="<%= fieldName %>"]`).fill('<%- fieldValue %>');
|
|
346
|
+
await expect(page.locator(`[data-cy="<%= fieldName %>"]`)).toHaveValue(new RegExp('<%- fieldValue %>'));
|
|
347
|
+
|
|
348
|
+
<%_ } _%>
|
|
349
|
+
<%_ }); _%>
|
|
350
|
+
<%_ for (const relationship of requiredRelationships) {
|
|
351
|
+
_%>
|
|
352
|
+
await page.locator(`[data-cy="<%= relationship.relationshipFieldName %>"]`).selectOption(<%= relationship.collection ? '[0]' : '1' %>);
|
|
353
|
+
<%_ } _%>
|
|
354
|
+
|
|
355
|
+
<%_ if (builtInUserManagement) { _%>
|
|
356
|
+
await page.locator(`[data-cy="langKey"]`).selectOption({ index: 0 });
|
|
357
|
+
<%_ } _%>
|
|
358
|
+
|
|
359
|
+
<%_ if (anyFieldHasFileBasedContentType) { _%>
|
|
360
|
+
// Wait for blob fields to be validated
|
|
361
|
+
await page.waitForTimeout(200);
|
|
362
|
+
<%_ } _%>
|
|
363
|
+
const createResponsePromise = page.waitForResponse('**/<%= baseApi + entityApiUrl %>');
|
|
364
|
+
const listResponsePromise = page.waitForResponse(
|
|
365
|
+
resp => resp.url().includes('/<%= baseApi + entityApiUrl %>') && resp.request().method() === 'GET'
|
|
366
|
+
);
|
|
367
|
+
await page.locator(entityCreateSaveButtonSelector).click();
|
|
368
|
+
const createResponse = await createResponsePromise;
|
|
369
|
+
expect(createResponse.status()).toBe(201);
|
|
370
|
+
<%= entityInstance %> = await createResponse.json();
|
|
371
|
+
|
|
372
|
+
const listResponse = await listResponsePromise;
|
|
373
|
+
expect(listResponse.status()).toBe(200);
|
|
374
|
+
await expect(page).toHaveURL(<%= entityInstance %>PageUrlPattern);
|
|
375
|
+
});
|
|
376
|
+
});
|
|
377
|
+
<%_ } _%>
|
|
378
|
+
});
|
package/generators/cypress/templates/src/test/javascript/cypress/fixtures/integration-test.png
ADDED
|
Binary file
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Playwright account helpers
|
|
3
|
+
* Translated from Cypress account.ts (v9)
|
|
4
|
+
*
|
|
5
|
+
* Source: ZERO EJS variables — pure static translation.
|
|
6
|
+
*/
|
|
7
|
+
import type { APIRequestContext, APIResponse } from '@playwright/test';
|
|
8
|
+
import { authenticatedRequest } from './commands';
|
|
9
|
+
|
|
10
|
+
export type Account = Record<string, string | boolean | number>;
|
|
11
|
+
|
|
12
|
+
export async function getAccount(request: APIRequestContext): Promise<Account> {
|
|
13
|
+
const response = await authenticatedRequest(request, {
|
|
14
|
+
method: 'GET',
|
|
15
|
+
url: '/api/account',
|
|
16
|
+
});
|
|
17
|
+
return response.json() as Promise<Account>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function saveAccount(request: APIRequestContext, account: Account): Promise<APIResponse> {
|
|
21
|
+
return authenticatedRequest(request, {
|
|
22
|
+
method: 'POST',
|
|
23
|
+
url: '/api/account',
|
|
24
|
+
data: account,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Playwright support commands
|
|
3
|
+
* Translated from Cypress commands.ts (v9)
|
|
4
|
+
*
|
|
5
|
+
* Source EJS variables used:
|
|
6
|
+
* - authenticationTypeOauth2, authenticationTypeJwt, authenticationTypeSession
|
|
7
|
+
* - withAdminUi
|
|
8
|
+
* - clientFrameworkAngular, clientFrameworkReact, clientFrameworkVue
|
|
9
|
+
*/
|
|
10
|
+
import type { Page, APIRequestContext, APIResponse } from '@playwright/test';
|
|
11
|
+
import { expect, request as pwRequest } from '@playwright/test';
|
|
12
|
+
|
|
13
|
+
// ***********************************************
|
|
14
|
+
// Begin Specific Selector Attributes
|
|
15
|
+
// ***********************************************
|
|
16
|
+
|
|
17
|
+
// Navbar
|
|
18
|
+
export const navbarSelector = '[data-cy="navbar"]';
|
|
19
|
+
export const adminMenuSelector = '[data-cy="adminMenu"]';
|
|
20
|
+
export const accountMenuSelector = '[data-cy="accountMenu"]';
|
|
21
|
+
export const registerItemSelector = '[data-cy="register"]';
|
|
22
|
+
export const settingsItemSelector = '[data-cy="settings"]';
|
|
23
|
+
export const passwordItemSelector = '[data-cy="passwordItem"]';
|
|
24
|
+
export const loginItemSelector = '[data-cy="login"]';
|
|
25
|
+
export const logoutItemSelector = '[data-cy="logout"]';
|
|
26
|
+
export const entityItemSelector = '[data-cy="entity"]';
|
|
27
|
+
|
|
28
|
+
<%_ if (!authenticationTypeOauth2) { _%>
|
|
29
|
+
// Login
|
|
30
|
+
export const titleLoginSelector = '[data-cy="loginTitle"]';
|
|
31
|
+
export const errorLoginSelector = '[data-cy="loginError"]';
|
|
32
|
+
export const usernameLoginSelector = '[data-cy="username"]';
|
|
33
|
+
export const passwordLoginSelector = '[data-cy="password"]';
|
|
34
|
+
export const forgetYourPasswordSelector = '[data-cy="forgetYourPasswordSelector"]';
|
|
35
|
+
export const submitLoginSelector = '[data-cy="submit"]';
|
|
36
|
+
|
|
37
|
+
// Register
|
|
38
|
+
export const usernameRegisterSelector = '[data-cy="username"]';
|
|
39
|
+
export const emailRegisterSelector = '[data-cy="email"]';
|
|
40
|
+
export const firstPasswordRegisterSelector = '[data-cy="firstPassword"]';
|
|
41
|
+
export const secondPasswordRegisterSelector = '[data-cy="secondPassword"]';
|
|
42
|
+
export const submitRegisterSelector = '[data-cy="submit"]';
|
|
43
|
+
|
|
44
|
+
// Settings
|
|
45
|
+
export const firstNameSettingsSelector = '[data-cy="firstname"]';
|
|
46
|
+
export const lastNameSettingsSelector = '[data-cy="lastname"]';
|
|
47
|
+
export const emailSettingsSelector = '[data-cy="email"]';
|
|
48
|
+
export const submitSettingsSelector = '[data-cy="submit"]';
|
|
49
|
+
|
|
50
|
+
// Password
|
|
51
|
+
export const currentPasswordSelector = '[data-cy="currentPassword"]';
|
|
52
|
+
export const newPasswordSelector = '[data-cy="newPassword"]';
|
|
53
|
+
export const confirmPasswordSelector = '[data-cy="confirmPassword"]';
|
|
54
|
+
export const submitPasswordSelector = '[data-cy="submit"]';
|
|
55
|
+
|
|
56
|
+
// Reset Password
|
|
57
|
+
export const emailResetPasswordSelector = '[data-cy="emailResetPassword"]';
|
|
58
|
+
export const submitInitResetPasswordSelector = '[data-cy="submit"]';
|
|
59
|
+
<%_ } _%>
|
|
60
|
+
|
|
61
|
+
// Administration
|
|
62
|
+
export const swaggerFrameSelector = 'iframe[data-cy="swagger-frame"]';
|
|
63
|
+
export const swaggerPageSelector = '[id="swagger-ui"]';
|
|
64
|
+
<%_ if (withAdminUi) { _%>
|
|
65
|
+
export const metricsPageHeadingSelector = '[data-cy="metricsPageHeading"]';
|
|
66
|
+
export const healthPageHeadingSelector = '[data-cy="healthPageHeading"]';
|
|
67
|
+
export const logsPageHeadingSelector = '[data-cy="logsPageHeading"]';
|
|
68
|
+
export const configurationPageHeadingSelector = '[data-cy="configurationPageHeading"]';
|
|
69
|
+
<%_ } _%>
|
|
70
|
+
|
|
71
|
+
// ***********************************************
|
|
72
|
+
// End Specific Selector Attributes
|
|
73
|
+
// ***********************************************
|
|
74
|
+
|
|
75
|
+
export const classInvalid = <% if (clientFrameworkAngular) { %>'ng-invalid'<% } else { %>'is-invalid'<% } %>;
|
|
76
|
+
|
|
77
|
+
export const classValid = <% if (clientFrameworkAngular) { %>'ng-valid'<% } else { %>'is-valid'<% } %>;
|
|
78
|
+
|
|
79
|
+
export interface Credentials {
|
|
80
|
+
adminUsername: string;
|
|
81
|
+
adminPassword: string;
|
|
82
|
+
username: string;
|
|
83
|
+
password: string;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function waitForAppShell(page: Page): Promise<void> {
|
|
87
|
+
await expect(page.locator(navbarSelector)).toBeVisible();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Get test credentials from environment or defaults.
|
|
92
|
+
*/
|
|
93
|
+
export function credentials(): Credentials {
|
|
94
|
+
return {
|
|
95
|
+
adminUsername: process.env.E2E_USERNAME ?? 'admin',
|
|
96
|
+
adminPassword: process.env.E2E_PASSWORD ?? 'admin',
|
|
97
|
+
username: process.env.E2E_USERNAME ?? 'user',
|
|
98
|
+
password: process.env.E2E_PASSWORD ?? 'user',
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
<%_ if (authenticationTypeJwt) { _%>
|
|
103
|
+
// JWT token storage for authenticated API requests
|
|
104
|
+
let _jwtToken: string | undefined;
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Get a JWT token for API authentication.
|
|
108
|
+
*/
|
|
109
|
+
export async function getAuthToken(request: APIRequestContext, username: string, password: string): Promise<string> {
|
|
110
|
+
const response = await request.post('/api/authenticate', {
|
|
111
|
+
data: { username, password },
|
|
112
|
+
});
|
|
113
|
+
const { id_token } = await response.json();
|
|
114
|
+
return id_token;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Perform authenticated API request with JWT.
|
|
119
|
+
*/
|
|
120
|
+
export async function authenticatedRequest(
|
|
121
|
+
request: APIRequestContext,
|
|
122
|
+
data: { method?: string; url: string; data?: unknown },
|
|
123
|
+
): Promise<APIResponse> {
|
|
124
|
+
if (!_jwtToken) {
|
|
125
|
+
const creds = credentials();
|
|
126
|
+
_jwtToken = await getAuthToken(request, creds.adminUsername, creds.adminPassword);
|
|
127
|
+
}
|
|
128
|
+
return request.fetch(data.url, {
|
|
129
|
+
method: data.method ?? 'GET',
|
|
130
|
+
data: data.data,
|
|
131
|
+
headers: {
|
|
132
|
+
Authorization: `Bearer ${_jwtToken}`,
|
|
133
|
+
'Content-Type': 'application/json',
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Login and set up session.
|
|
140
|
+
*/
|
|
141
|
+
export async function login(page: Page, request: APIRequestContext, username: string, password: string): Promise<void> {
|
|
142
|
+
const token = await getAuthToken(request, username, password);
|
|
143
|
+
_jwtToken = token;
|
|
144
|
+
await page.goto('/');
|
|
145
|
+
await page.evaluate((t) => {
|
|
146
|
+
<%_ if (clientFrameworkVue) { _%>
|
|
147
|
+
sessionStorage.setItem('jhi-authenticationToken', t);
|
|
148
|
+
<%_ } else { _%>
|
|
149
|
+
sessionStorage.setItem('jhi-authenticationToken', JSON.stringify(t));
|
|
150
|
+
<%_ } _%>
|
|
151
|
+
}, token);
|
|
152
|
+
await page.goto('/');
|
|
153
|
+
await waitForAppShell(page);
|
|
154
|
+
}
|
|
155
|
+
<%_ } else if (authenticationTypeOauth2) { _%>
|
|
156
|
+
/**
|
|
157
|
+
* Perform authenticated API request (OAuth2).
|
|
158
|
+
*/
|
|
159
|
+
export async function authenticatedRequest(
|
|
160
|
+
request: APIRequestContext,
|
|
161
|
+
data: { method?: string; url: string; data?: unknown },
|
|
162
|
+
): Promise<APIResponse> {
|
|
163
|
+
return request.fetch(data.url, { method: data.method ?? 'GET', data: data.data });
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Login via OAuth2 (Keycloak).
|
|
168
|
+
*/
|
|
169
|
+
export async function login(page: Page, request: APIRequestContext, username: string, password: string): Promise<void> {
|
|
170
|
+
await page.goto('/oauth2/authorization/oidc');
|
|
171
|
+
await page.waitForSelector('#kc-form-login', { timeout: 15000 });
|
|
172
|
+
await page.locator('#username').fill(username);
|
|
173
|
+
await page.locator('#password').fill(password);
|
|
174
|
+
await page.locator('#kc-login').click();
|
|
175
|
+
await page.waitForURL('**/', { timeout: 15000 });
|
|
176
|
+
await waitForAppShell(page);
|
|
177
|
+
}
|
|
178
|
+
<%_ } else { _%>
|
|
179
|
+
/**
|
|
180
|
+
* Perform authenticated API request (Session).
|
|
181
|
+
*/
|
|
182
|
+
export async function authenticatedRequest(
|
|
183
|
+
request: APIRequestContext,
|
|
184
|
+
data: { method?: string; url: string; data?: unknown },
|
|
185
|
+
): Promise<APIResponse> {
|
|
186
|
+
return request.fetch(data.url, { method: data.method ?? 'GET', data: data.data });
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Login via Session auth.
|
|
191
|
+
*/
|
|
192
|
+
export async function login(page: Page, request: APIRequestContext, username: string, password: string): Promise<void> {
|
|
193
|
+
await request.post('/api/authentication', {
|
|
194
|
+
data: { username, password },
|
|
195
|
+
form: true,
|
|
196
|
+
});
|
|
197
|
+
await page.goto('/');
|
|
198
|
+
await waitForAppShell(page);
|
|
199
|
+
}
|
|
200
|
+
<%_ } _%>
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Playwright entity helpers
|
|
3
|
+
* Translated from Cypress entity.ts (v9)
|
|
4
|
+
*
|
|
5
|
+
* Source: ZERO EJS variables — pure static translation.
|
|
6
|
+
*/
|
|
7
|
+
import type { Page, Locator } from '@playwright/test';
|
|
8
|
+
import * as path from 'path';
|
|
9
|
+
|
|
10
|
+
// ***********************************************
|
|
11
|
+
// Begin Specific Selector Attributes
|
|
12
|
+
// ***********************************************
|
|
13
|
+
|
|
14
|
+
// Entity
|
|
15
|
+
export const entityTableSelector = '[data-cy="entityTable"]';
|
|
16
|
+
export const entityCreateButtonSelector = '[data-cy="entityCreateButton"]';
|
|
17
|
+
export const entityCreateSaveButtonSelector = '[data-cy="entityCreateSaveButton"]';
|
|
18
|
+
export const entityCreateCancelButtonSelector = '[data-cy="entityCreateCancelButton"]';
|
|
19
|
+
export const entityDetailsButtonSelector = '[data-cy="entityDetailsButton"]'; // can return multiple elements
|
|
20
|
+
export const entityDetailsBackButtonSelector = '[data-cy="entityDetailsBackButton"]';
|
|
21
|
+
export const entityEditButtonSelector = '[data-cy="entityEditButton"]';
|
|
22
|
+
export const entityDeleteButtonSelector = '[data-cy="entityDeleteButton"]';
|
|
23
|
+
export const entityConfirmDeleteButtonSelector = '[data-cy="entityConfirmDeleteButton"]';
|
|
24
|
+
|
|
25
|
+
// ***********************************************
|
|
26
|
+
// End Specific Selector Attributes
|
|
27
|
+
// ***********************************************
|
|
28
|
+
|
|
29
|
+
export function getEntityHeading(page: Page, entityName: string): Locator {
|
|
30
|
+
return page.locator(`[data-cy="${entityName}Heading"]`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function getEntityCreateUpdateHeading(page: Page, entityName: string): Locator {
|
|
34
|
+
return page.locator(`[data-cy="${entityName}CreateUpdateHeading"]`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function getEntityDetailsHeading(page: Page, entityInstanceName: string): Locator {
|
|
38
|
+
return page.locator(`[data-cy="${entityInstanceName}DetailsHeading"]`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function getEntityDeleteDialogHeading(page: Page, entityInstanceName: string): Locator {
|
|
42
|
+
return page.locator(`[data-cy="${entityInstanceName}DeleteDialogHeading"]`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Upload a binary file to an image field.
|
|
47
|
+
* Translates Cypress's DataTransfer hack → Playwright's native setInputFiles.
|
|
48
|
+
*/
|
|
49
|
+
export async function setFieldImageAsBytesOfEntity(
|
|
50
|
+
page: Page,
|
|
51
|
+
fieldName: string,
|
|
52
|
+
fileName: string,
|
|
53
|
+
_mimeType: string,
|
|
54
|
+
): Promise<void> {
|
|
55
|
+
const fixturePath = path.join(__dirname, '..', 'fixtures', fileName);
|
|
56
|
+
await page.locator(`[data-cy="${fieldName}"]`).setInputFiles(fixturePath);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Select the last option in a select field.
|
|
61
|
+
* Translates Cypress setFieldSelectToLastOfEntity.
|
|
62
|
+
*/
|
|
63
|
+
export async function setFieldSelectToLastOfEntity(page: Page, fieldName: string): Promise<void> {
|
|
64
|
+
const select = page.locator(`[data-cy="${fieldName}"]`);
|
|
65
|
+
const options = select.locator('option');
|
|
66
|
+
const count = await options.count();
|
|
67
|
+
if (count > 0) {
|
|
68
|
+
const lastOption = await options.nth(count - 1).getAttribute('value');
|
|
69
|
+
if (lastOption) {
|
|
70
|
+
await select.selectOption(lastOption);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|