@progress/kendo-e2e 4.11.3 → 4.12.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 +33 -111
- package/dist/selenium/browser.d.ts +245 -4
- package/dist/selenium/browser.js +245 -4
- package/dist/selenium/browser.js.map +1 -1
- package/dist/selenium/conditions.d.ts +255 -0
- package/dist/selenium/conditions.js +251 -0
- package/dist/selenium/conditions.js.map +1 -1
- package/dist/selenium/driver-manager.d.ts +123 -0
- package/dist/selenium/driver-manager.js +118 -0
- package/dist/selenium/driver-manager.js.map +1 -1
- package/dist/selenium/electron-app.d.ts +32 -2
- package/dist/selenium/electron-app.js +32 -2
- package/dist/selenium/electron-app.js.map +1 -1
- package/dist/selenium/expect.d.ts +227 -0
- package/dist/selenium/expect.js +22 -0
- package/dist/selenium/expect.js.map +1 -1
- package/dist/selenium/web-app.d.ts +779 -9
- package/dist/selenium/web-app.js +778 -9
- package/dist/selenium/web-app.js.map +1 -1
- package/docs/API_REFERENCE.md +1309 -0
- package/docs/GETTING_STARTED.md +337 -0
- package/docs/PATTERNS.md +629 -0
- package/package.json +3 -2
package/docs/PATTERNS.md
ADDED
|
@@ -0,0 +1,629 @@
|
|
|
1
|
+
# Common Testing Patterns
|
|
2
|
+
|
|
3
|
+
Real-world patterns and best practices for writing reliable e2e tests with kendo-e2e.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Form Testing](#form-testing)
|
|
8
|
+
- [Navigation](#navigation)
|
|
9
|
+
- [Modal Dialogs](#modal-dialogs)
|
|
10
|
+
- [Dynamic Content](#dynamic-content)
|
|
11
|
+
- [Tables and Lists](#tables-and-lists)
|
|
12
|
+
- [File Uploads](#file-uploads)
|
|
13
|
+
- [Drag and Drop](#drag-and-drop)
|
|
14
|
+
- [Keyboard Navigation](#keyboard-navigation)
|
|
15
|
+
- [Visual Testing](#visual-testing)
|
|
16
|
+
- [Error Handling](#error-handling)
|
|
17
|
+
- [Mobile Testing](#mobile-testing)
|
|
18
|
+
|
|
19
|
+
## Form Testing
|
|
20
|
+
|
|
21
|
+
### Basic Form Submission
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
it('should submit registration form', async () => {
|
|
25
|
+
await browser.navigateTo('https://example.com/register');
|
|
26
|
+
|
|
27
|
+
// Fill form fields
|
|
28
|
+
await browser.type('#firstName', 'John');
|
|
29
|
+
await browser.type('#lastName', 'Doe');
|
|
30
|
+
await browser.type('#email', 'john@example.com');
|
|
31
|
+
await browser.type('#password', 'SecurePass123!');
|
|
32
|
+
await browser.type('#confirmPassword', 'SecurePass123!');
|
|
33
|
+
|
|
34
|
+
// Check checkbox
|
|
35
|
+
await browser.click('#agreeToTerms');
|
|
36
|
+
|
|
37
|
+
// Submit
|
|
38
|
+
await browser.click('#submitButton');
|
|
39
|
+
|
|
40
|
+
// Verify success
|
|
41
|
+
await browser.expect('.success-message').toBeVisible();
|
|
42
|
+
await browser.expect('.success-message').toHaveText('Registration successful!');
|
|
43
|
+
});
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Form Validation
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
it('should show validation errors', async () => {
|
|
50
|
+
await browser.navigateTo('https://example.com/form');
|
|
51
|
+
|
|
52
|
+
// Try to submit empty form
|
|
53
|
+
await browser.click('#submit');
|
|
54
|
+
|
|
55
|
+
// Check for validation messages
|
|
56
|
+
await browser.expect('#email-error').toBeVisible();
|
|
57
|
+
await browser.expect('#email-error').toHaveText('Email is required');
|
|
58
|
+
|
|
59
|
+
// Fill invalid email
|
|
60
|
+
await browser.type('#email', 'invalid-email');
|
|
61
|
+
await browser.click('#submit');
|
|
62
|
+
|
|
63
|
+
// Check validation for format
|
|
64
|
+
await browser.expect('#email-error').toHaveText('Invalid email format');
|
|
65
|
+
|
|
66
|
+
// Fill valid email
|
|
67
|
+
await browser.type('#email', 'valid@example.com');
|
|
68
|
+
await browser.click('#submit');
|
|
69
|
+
|
|
70
|
+
// Validation error should disappear
|
|
71
|
+
await browser.expect('#email-error').not.toBeVisible();
|
|
72
|
+
});
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Multi-Step Form
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
it('should complete multi-step wizard', async () => {
|
|
79
|
+
await browser.navigateTo('https://example.com/wizard');
|
|
80
|
+
|
|
81
|
+
// Step 1: Personal Info
|
|
82
|
+
await browser.expect('.step-indicator').toHaveText('Step 1 of 3');
|
|
83
|
+
await browser.type('#name', 'John Doe');
|
|
84
|
+
await browser.type('#email', 'john@example.com');
|
|
85
|
+
await browser.click('#next-button');
|
|
86
|
+
|
|
87
|
+
// Step 2: Address
|
|
88
|
+
await browser.expect('.step-indicator').toHaveText('Step 2 of 3');
|
|
89
|
+
await browser.type('#street', '123 Main St');
|
|
90
|
+
await browser.type('#city', 'New York');
|
|
91
|
+
await browser.click('#next-button');
|
|
92
|
+
|
|
93
|
+
// Step 3: Confirmation
|
|
94
|
+
await browser.expect('.step-indicator').toHaveText('Step 3 of 3');
|
|
95
|
+
await browser.expect('.review-name').toHaveText('John Doe');
|
|
96
|
+
await browser.expect('.review-email').toHaveText('john@example.com');
|
|
97
|
+
await browser.click('#submit-button');
|
|
98
|
+
|
|
99
|
+
// Success
|
|
100
|
+
await browser.expect('.completion-message').toBeVisible();
|
|
101
|
+
});
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Navigation
|
|
105
|
+
|
|
106
|
+
### Page Navigation with Verification
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
it('should navigate through site', async () => {
|
|
110
|
+
await browser.navigateTo('https://example.com');
|
|
111
|
+
|
|
112
|
+
// Navigate to products
|
|
113
|
+
await browser.click('a[href="/products"]');
|
|
114
|
+
await browser.expect('h1').toHaveText('Products');
|
|
115
|
+
|
|
116
|
+
// Verify URL changed
|
|
117
|
+
const url = await browser.getCurrentUrl();
|
|
118
|
+
expect(url).toContain('/products');
|
|
119
|
+
|
|
120
|
+
// Navigate back
|
|
121
|
+
await browser.driver.navigate().back();
|
|
122
|
+
await browser.expect('h1').toHaveText('Home');
|
|
123
|
+
});
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Menu Navigation
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
it('should navigate through dropdown menu', async () => {
|
|
130
|
+
await browser.navigateTo('https://example.com');
|
|
131
|
+
|
|
132
|
+
// Hover to show dropdown
|
|
133
|
+
await browser.hover('.nav-item-products');
|
|
134
|
+
await browser.expect('.dropdown-menu').toBeVisible();
|
|
135
|
+
|
|
136
|
+
// Click submenu item
|
|
137
|
+
await browser.click('.dropdown-menu a[href="/products/widgets"]');
|
|
138
|
+
await browser.expect('h1').toHaveText('Widgets');
|
|
139
|
+
});
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Modal Dialogs
|
|
143
|
+
|
|
144
|
+
### Opening and Closing Modals
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
it('should open and close modal', async () => {
|
|
148
|
+
await browser.navigateTo('https://example.com');
|
|
149
|
+
|
|
150
|
+
// Open modal
|
|
151
|
+
await browser.click('#open-modal-button');
|
|
152
|
+
await browser.expect('.modal').toBeVisible();
|
|
153
|
+
await browser.expect('.modal-title').toHaveText('Confirm Action');
|
|
154
|
+
|
|
155
|
+
// Close modal
|
|
156
|
+
await browser.click('.modal .close-button');
|
|
157
|
+
await browser.expect('.modal').not.toBeVisible();
|
|
158
|
+
});
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Modal with Form Submission
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
it('should submit form in modal', async () => {
|
|
165
|
+
await browser.navigateTo('https://example.com/dashboard');
|
|
166
|
+
|
|
167
|
+
// Open edit modal
|
|
168
|
+
await browser.click('.edit-button');
|
|
169
|
+
await browser.expect('#edit-modal').toBeVisible();
|
|
170
|
+
|
|
171
|
+
// Edit in modal
|
|
172
|
+
await browser.type('#edit-modal #name', 'Updated Name');
|
|
173
|
+
await browser.type('#edit-modal #description', 'New description');
|
|
174
|
+
await browser.click('#edit-modal #save-button');
|
|
175
|
+
|
|
176
|
+
// Modal should close
|
|
177
|
+
await browser.expect('#edit-modal').not.toBeVisible();
|
|
178
|
+
|
|
179
|
+
// Verify update
|
|
180
|
+
await browser.expect('.item-name').toHaveText('Updated Name');
|
|
181
|
+
});
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Waiting for Modal Animation
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
it('should wait for animated modal', async () => {
|
|
188
|
+
await browser.navigateTo('https://example.com');
|
|
189
|
+
|
|
190
|
+
await browser.click('#show-modal');
|
|
191
|
+
|
|
192
|
+
// Wait for animation to complete before interacting
|
|
193
|
+
await browser.waitForAnimation('.modal');
|
|
194
|
+
await browser.click('.modal #confirm-button');
|
|
195
|
+
});
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## Dynamic Content
|
|
199
|
+
|
|
200
|
+
### Waiting for AJAX Content
|
|
201
|
+
|
|
202
|
+
```typescript
|
|
203
|
+
it('should load dynamic content', async () => {
|
|
204
|
+
await browser.navigateTo('https://example.com/dashboard');
|
|
205
|
+
|
|
206
|
+
// Click to load data
|
|
207
|
+
await browser.click('#load-data-button');
|
|
208
|
+
|
|
209
|
+
// Wait for loading spinner to appear and disappear
|
|
210
|
+
await browser.expect('.spinner').toBeVisible();
|
|
211
|
+
await browser.expect('.spinner').not.toBeVisible({ timeout: 10000 });
|
|
212
|
+
|
|
213
|
+
// Verify content loaded
|
|
214
|
+
const items = await browser.findAllWithTimeout('.data-item');
|
|
215
|
+
expect(items.length).toBeGreaterThan(0);
|
|
216
|
+
});
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### Infinite Scroll
|
|
220
|
+
|
|
221
|
+
```typescript
|
|
222
|
+
it('should handle infinite scroll', async () => {
|
|
223
|
+
await browser.navigateTo('https://example.com/feed');
|
|
224
|
+
|
|
225
|
+
// Count initial items
|
|
226
|
+
let items = await browser.findAll('.feed-item');
|
|
227
|
+
const initialCount = items.length;
|
|
228
|
+
|
|
229
|
+
// Scroll to bottom
|
|
230
|
+
await browser.driver.executeScript('window.scrollTo(0, document.body.scrollHeight)');
|
|
231
|
+
|
|
232
|
+
// Wait for new items to load
|
|
233
|
+
await browser.sleep(1000); // Give time for scroll trigger
|
|
234
|
+
|
|
235
|
+
// Verify more items loaded
|
|
236
|
+
items = await browser.findAll('.feed-item');
|
|
237
|
+
expect(items.length).toBeGreaterThan(initialCount);
|
|
238
|
+
});
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### Polling for Updates
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
it('should wait for status update', async () => {
|
|
245
|
+
await browser.navigateTo('https://example.com/job/123');
|
|
246
|
+
|
|
247
|
+
// Start job
|
|
248
|
+
await browser.click('#start-button');
|
|
249
|
+
|
|
250
|
+
// Wait for status to change (with longer timeout)
|
|
251
|
+
await browser.expect('.status').toHaveText('Processing', { timeout: 2000 });
|
|
252
|
+
await browser.expect('.status').toHaveText('Complete', { timeout: 30000 });
|
|
253
|
+
|
|
254
|
+
// Verify completion
|
|
255
|
+
await browser.expect('.success-icon').toBeVisible();
|
|
256
|
+
});
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
## Tables and Lists
|
|
260
|
+
|
|
261
|
+
### Table Row Interaction
|
|
262
|
+
|
|
263
|
+
```typescript
|
|
264
|
+
it('should interact with table rows', async () => {
|
|
265
|
+
await browser.navigateTo('https://example.com/users');
|
|
266
|
+
|
|
267
|
+
// Find specific row by text
|
|
268
|
+
const rows = await browser.findAll('table tbody tr');
|
|
269
|
+
|
|
270
|
+
for (const row of rows) {
|
|
271
|
+
const text = await row.getText();
|
|
272
|
+
if (text.includes('john@example.com')) {
|
|
273
|
+
// Click edit button in this row
|
|
274
|
+
await browser.findChild(row, '.edit-button').then(btn => btn.click());
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Verify edit modal opened
|
|
280
|
+
await browser.expect('.edit-modal').toBeVisible();
|
|
281
|
+
});
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
### Sorting and Filtering
|
|
285
|
+
|
|
286
|
+
```typescript
|
|
287
|
+
it('should sort table by column', async () => {
|
|
288
|
+
await browser.navigateTo('https://example.com/products');
|
|
289
|
+
|
|
290
|
+
// Click column header to sort
|
|
291
|
+
await browser.click('th.price-column');
|
|
292
|
+
|
|
293
|
+
// Verify sort indicator
|
|
294
|
+
await browser.expect('th.price-column .sort-asc').toBeVisible();
|
|
295
|
+
|
|
296
|
+
// Get first row price
|
|
297
|
+
const firstPrice = await browser.getText('tbody tr:first-child .price');
|
|
298
|
+
|
|
299
|
+
// Click again to reverse sort
|
|
300
|
+
await browser.click('th.price-column');
|
|
301
|
+
await browser.expect('th.price-column .sort-desc').toBeVisible();
|
|
302
|
+
|
|
303
|
+
// Verify order changed
|
|
304
|
+
const newFirstPrice = await browser.getText('tbody tr:first-child .price');
|
|
305
|
+
expect(newFirstPrice).not.toBe(firstPrice);
|
|
306
|
+
});
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
### List Filtering
|
|
310
|
+
|
|
311
|
+
```typescript
|
|
312
|
+
it('should filter list items', async () => {
|
|
313
|
+
await browser.navigateTo('https://example.com/products');
|
|
314
|
+
|
|
315
|
+
// Type in search box
|
|
316
|
+
await browser.type('#search-input', 'widget');
|
|
317
|
+
|
|
318
|
+
// Wait for filter to apply
|
|
319
|
+
await browser.sleep(300); // Debounce time
|
|
320
|
+
|
|
321
|
+
// Verify filtered results
|
|
322
|
+
const items = await browser.findAllWithTimeout('.product-item');
|
|
323
|
+
|
|
324
|
+
for (const item of items) {
|
|
325
|
+
const text = await item.getText();
|
|
326
|
+
expect(text.toLowerCase()).toContain('widget');
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
## File Uploads
|
|
332
|
+
|
|
333
|
+
### Single File Upload
|
|
334
|
+
|
|
335
|
+
```typescript
|
|
336
|
+
it('should upload file', async () => {
|
|
337
|
+
await browser.navigateTo('https://example.com/upload');
|
|
338
|
+
|
|
339
|
+
// Set file input value (absolute path)
|
|
340
|
+
const filePath = '/absolute/path/to/test-file.pdf';
|
|
341
|
+
const fileInput = await browser.find('input[type="file"]');
|
|
342
|
+
await fileInput.sendKeys(filePath);
|
|
343
|
+
|
|
344
|
+
// Submit upload
|
|
345
|
+
await browser.click('#upload-button');
|
|
346
|
+
|
|
347
|
+
// Verify upload success
|
|
348
|
+
await browser.expect('.upload-success').toBeVisible();
|
|
349
|
+
await browser.expect('.file-name').toHaveText('test-file.pdf');
|
|
350
|
+
});
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
### Drag and Drop Upload
|
|
354
|
+
|
|
355
|
+
```typescript
|
|
356
|
+
it('should upload via drag and drop', async () => {
|
|
357
|
+
await browser.navigateTo('https://example.com/upload');
|
|
358
|
+
|
|
359
|
+
// Simulate drag and drop (complex, usually requires JS injection)
|
|
360
|
+
const dropzone = await browser.find('.dropzone');
|
|
361
|
+
await browser.driver.executeScript(`
|
|
362
|
+
const event = new DragEvent('drop', { bubbles: true });
|
|
363
|
+
const file = new File(['content'], 'test.txt', { type: 'text/plain' });
|
|
364
|
+
Object.defineProperty(event, 'dataTransfer', {
|
|
365
|
+
value: { files: [file] }
|
|
366
|
+
});
|
|
367
|
+
arguments[0].dispatchEvent(event);
|
|
368
|
+
`, dropzone);
|
|
369
|
+
|
|
370
|
+
// Verify upload
|
|
371
|
+
await browser.expect('.uploaded-file').toHaveText('test.txt');
|
|
372
|
+
});
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
## Drag and Drop
|
|
376
|
+
|
|
377
|
+
### Reordering List Items
|
|
378
|
+
|
|
379
|
+
```typescript
|
|
380
|
+
it('should reorder items by drag and drop', async () => {
|
|
381
|
+
await browser.navigateTo('https://example.com/sortable-list');
|
|
382
|
+
|
|
383
|
+
// Drag first item to third position
|
|
384
|
+
await browser.dragTo(
|
|
385
|
+
'.list-item:nth-child(1)',
|
|
386
|
+
'.list-item:nth-child(3)'
|
|
387
|
+
);
|
|
388
|
+
|
|
389
|
+
// Verify new order
|
|
390
|
+
const firstItem = await browser.getText('.list-item:nth-child(1)');
|
|
391
|
+
const secondItem = await browser.getText('.list-item:nth-child(2)');
|
|
392
|
+
|
|
393
|
+
expect(firstItem).toBe('Item 2');
|
|
394
|
+
expect(secondItem).toBe('Item 3');
|
|
395
|
+
});
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
### Moving Between Containers
|
|
399
|
+
|
|
400
|
+
```typescript
|
|
401
|
+
it('should move item between lists', async () => {
|
|
402
|
+
await browser.navigateTo('https://example.com/kanban');
|
|
403
|
+
|
|
404
|
+
// Drag from "To Do" to "In Progress"
|
|
405
|
+
await browser.dragTo(
|
|
406
|
+
'#todo .task-card:first-child',
|
|
407
|
+
'#in-progress'
|
|
408
|
+
);
|
|
409
|
+
|
|
410
|
+
// Verify task moved
|
|
411
|
+
const todoTasks = await browser.findAll('#todo .task-card');
|
|
412
|
+
const inProgressTasks = await browser.findAll('#in-progress .task-card');
|
|
413
|
+
|
|
414
|
+
expect(inProgressTasks.length).toBeGreaterThan(0);
|
|
415
|
+
});
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
## Keyboard Navigation
|
|
419
|
+
|
|
420
|
+
### Tab Navigation
|
|
421
|
+
|
|
422
|
+
```typescript
|
|
423
|
+
it('should navigate form with Tab key', async () => {
|
|
424
|
+
await browser.navigateTo('https://example.com/form');
|
|
425
|
+
|
|
426
|
+
// Focus first input
|
|
427
|
+
await browser.click('#firstName');
|
|
428
|
+
await browser.expect('#firstName').toHaveFocus();
|
|
429
|
+
|
|
430
|
+
// Tab to next field
|
|
431
|
+
await browser.sendKey(Key.TAB);
|
|
432
|
+
await browser.expect('#lastName').toHaveFocus();
|
|
433
|
+
|
|
434
|
+
// Tab to email
|
|
435
|
+
await browser.sendKey(Key.TAB);
|
|
436
|
+
await browser.expect('#email').toHaveFocus();
|
|
437
|
+
});
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
### Keyboard Shortcuts
|
|
441
|
+
|
|
442
|
+
```typescript
|
|
443
|
+
it('should use keyboard shortcuts', async () => {
|
|
444
|
+
await browser.navigateTo('https://example.com/editor');
|
|
445
|
+
|
|
446
|
+
// Type some text
|
|
447
|
+
await browser.click('.editor');
|
|
448
|
+
await browser.type('.editor', 'Sample text');
|
|
449
|
+
|
|
450
|
+
// Select all (Ctrl+A / Cmd+A)
|
|
451
|
+
await browser.sendControlKeyCombination('a');
|
|
452
|
+
|
|
453
|
+
// Copy (Ctrl+C / Cmd+C)
|
|
454
|
+
await browser.sendControlKeyCombination('c');
|
|
455
|
+
|
|
456
|
+
// Move to another field
|
|
457
|
+
await browser.click('#output');
|
|
458
|
+
|
|
459
|
+
// Paste (Ctrl+V / Cmd+V)
|
|
460
|
+
await browser.sendControlKeyCombination('v');
|
|
461
|
+
|
|
462
|
+
// Verify paste
|
|
463
|
+
await browser.expect('#output').toHaveValue('Sample text');
|
|
464
|
+
});
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
### Arrow Key Navigation
|
|
468
|
+
|
|
469
|
+
```typescript
|
|
470
|
+
it('should navigate dropdown with arrow keys', async () => {
|
|
471
|
+
await browser.navigateTo('https://example.com/search');
|
|
472
|
+
|
|
473
|
+
// Type to open suggestions
|
|
474
|
+
await browser.type('#search', 'test');
|
|
475
|
+
await browser.expect('.suggestions').toBeVisible();
|
|
476
|
+
|
|
477
|
+
// Arrow down to select first suggestion
|
|
478
|
+
await browser.sendKey(Key.ARROW_DOWN);
|
|
479
|
+
await browser.expect('.suggestion:nth-child(1)').toHaveClass('highlighted');
|
|
480
|
+
|
|
481
|
+
// Arrow down again
|
|
482
|
+
await browser.sendKey(Key.ARROW_DOWN);
|
|
483
|
+
await browser.expect('.suggestion:nth-child(2)').toHaveClass('highlighted');
|
|
484
|
+
|
|
485
|
+
// Press Enter to select
|
|
486
|
+
await browser.sendKey(Key.ENTER);
|
|
487
|
+
await browser.expect('#search').toHaveValue('test result 2');
|
|
488
|
+
});
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
## Visual Testing
|
|
492
|
+
|
|
493
|
+
### Screenshot with Hidden Cursor
|
|
494
|
+
|
|
495
|
+
```typescript
|
|
496
|
+
it('should take clean screenshot', async () => {
|
|
497
|
+
await browser.navigateTo('https://example.com/form');
|
|
498
|
+
|
|
499
|
+
// Fill form
|
|
500
|
+
await browser.type('#username', 'testuser');
|
|
501
|
+
await browser.focus('#username');
|
|
502
|
+
|
|
503
|
+
// Hide cursor for clean screenshot
|
|
504
|
+
await browser.hideCursor();
|
|
505
|
+
|
|
506
|
+
// Take screenshot
|
|
507
|
+
const screenshot = await browser.getScreenshot();
|
|
508
|
+
|
|
509
|
+
// Compare or save screenshot
|
|
510
|
+
// expect(screenshot).toMatchImageSnapshot();
|
|
511
|
+
});
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
### Responsive Testing
|
|
515
|
+
|
|
516
|
+
```typescript
|
|
517
|
+
it('should test responsive layout', async () => {
|
|
518
|
+
// Desktop view
|
|
519
|
+
await browser.resizeWindow(1920, 1080);
|
|
520
|
+
await browser.navigateTo('https://example.com');
|
|
521
|
+
await browser.expect('.desktop-menu').toBeVisible();
|
|
522
|
+
await browser.expect('.mobile-menu').not.toBeVisible();
|
|
523
|
+
|
|
524
|
+
// Mobile view
|
|
525
|
+
await browser.resizeWindow(375, 667);
|
|
526
|
+
await browser.refresh();
|
|
527
|
+
await browser.expect('.mobile-menu').toBeVisible();
|
|
528
|
+
await browser.expect('.desktop-menu').not.toBeVisible();
|
|
529
|
+
});
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
### Waiting for Animations
|
|
533
|
+
|
|
534
|
+
```typescript
|
|
535
|
+
it('should wait for transitions', async () => {
|
|
536
|
+
await browser.navigateTo('https://example.com');
|
|
537
|
+
|
|
538
|
+
// Click to trigger animation
|
|
539
|
+
await browser.click('#expand-panel');
|
|
540
|
+
|
|
541
|
+
// Wait for animation to complete
|
|
542
|
+
await browser.waitForAnimation('.panel-content');
|
|
543
|
+
|
|
544
|
+
// Now safe to take screenshot or interact
|
|
545
|
+
const screenshot = await browser.getScreenshot();
|
|
546
|
+
});
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
## Error Handling
|
|
550
|
+
|
|
551
|
+
### Checking Console Errors
|
|
552
|
+
|
|
553
|
+
```typescript
|
|
554
|
+
it('should have no console errors', async () => {
|
|
555
|
+
// Clear previous logs
|
|
556
|
+
await browser.clearLogs();
|
|
557
|
+
|
|
558
|
+
await browser.navigateTo('https://example.com');
|
|
559
|
+
await browser.click('#load-content');
|
|
560
|
+
|
|
561
|
+
// Check for errors
|
|
562
|
+
const errors = await browser.getErrorLogs();
|
|
563
|
+
expect(errors.length).toBe(0);
|
|
564
|
+
});
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
### Accessibility Testing
|
|
568
|
+
|
|
569
|
+
```typescript
|
|
570
|
+
it('should pass accessibility audit', async () => {
|
|
571
|
+
await browser.navigateTo('https://example.com');
|
|
572
|
+
|
|
573
|
+
// Run a11y check on entire page
|
|
574
|
+
const violations = await browser.getAccessibilityViolations();
|
|
575
|
+
expect(violations.length).toBe(0);
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
it('should pass accessibility audit on modal', async () => {
|
|
579
|
+
await browser.navigateTo('https://example.com');
|
|
580
|
+
await browser.click('#open-modal');
|
|
581
|
+
|
|
582
|
+
// Check only the modal
|
|
583
|
+
const violations = await browser.getAccessibilityViolations('.modal');
|
|
584
|
+
expect(violations.length).toBe(0);
|
|
585
|
+
});
|
|
586
|
+
```
|
|
587
|
+
|
|
588
|
+
## Mobile Testing
|
|
589
|
+
|
|
590
|
+
### Mobile Device Emulation
|
|
591
|
+
|
|
592
|
+
```typescript
|
|
593
|
+
describe('Mobile Tests', () => {
|
|
594
|
+
let browser: Browser;
|
|
595
|
+
|
|
596
|
+
beforeAll(async () => {
|
|
597
|
+
// Start with mobile emulation
|
|
598
|
+
browser = new Browser({
|
|
599
|
+
mobileEmulation: { deviceName: 'iPhone 14 Pro Max' }
|
|
600
|
+
});
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
afterAll(async () => {
|
|
604
|
+
await browser.close();
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
it('should work on mobile', async () => {
|
|
608
|
+
await browser.navigateTo('https://example.com');
|
|
609
|
+
|
|
610
|
+
// Mobile-specific interactions
|
|
611
|
+
await browser.click('.mobile-menu-toggle');
|
|
612
|
+
await browser.expect('.mobile-menu').toBeVisible();
|
|
613
|
+
|
|
614
|
+
await browser.click('.mobile-menu a[href="/products"]');
|
|
615
|
+
await browser.expect('h1').toHaveText('Products');
|
|
616
|
+
});
|
|
617
|
+
});
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
|
|
621
|
+
## Testing Best Practices
|
|
622
|
+
|
|
623
|
+
1. **Browser lifecycle management** - Use `beforeAll`/`afterAll` for browser lifecycle (start once, not per test)
|
|
624
|
+
2. **Use id or css selectors** - CSS Selectors using kendo classes (like ".k-grid", ".k-menu-item") are good choice because we don't change them often and they are required to enable kendo-themes work properly.
|
|
625
|
+
3. **Avoid sleep()** - All basic interactions have build in waits, so no need to use sleep. Use `await browser.wait(<condition>)` if you need to wait for custom conditions. Use `await browser.waitForAnimation() or await browser.waitForAnimationAndClick()` if you expect element to be animated and standard build-in wait make test flaky.
|
|
626
|
+
4. **Avoid Page Object pattern** - Don't use Page Object pattern unless strictly specified. Tests are for simple screens with single components and don't need extra abstractions and complexity.
|
|
627
|
+
5. **Reset window size** - If using `browser.resizeWindow()` in any test, set default size in `beforeEach` to avoid flakiness that depends on test order.
|
|
628
|
+
6. **Check invisibility correctly** - Use `expect(await browser.isNotVisible('.k-grid')).toBe(true)` or `await browser.expect('.k-grid').not.toBeVisible()` to check elements are NOT visible. DON'T use `expect(await browser.isVisible('.k-grid')).toBe(false)` because it waits up to 10 seconds for element to become visible, wasting time when testing absence.
|
|
629
|
+
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@progress/kendo-e2e",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.12.0",
|
|
4
4
|
"description": "Kendo UI end-to-end test utilities.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
},
|
|
9
9
|
"typings": "dist/index.d.ts",
|
|
10
10
|
"files": [
|
|
11
|
-
"dist"
|
|
11
|
+
"dist",
|
|
12
|
+
"docs"
|
|
12
13
|
],
|
|
13
14
|
"scripts": {
|
|
14
15
|
"lint": "npx eslint ./src/**/*.ts ./tests/**/*.ts",
|