@progress/kendo-e2e 4.11.2 → 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.
@@ -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.11.2",
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",
@@ -60,7 +61,7 @@
60
61
  "@axe-core/webdriverjs": "4.11.0",
61
62
  "@types/selenium-webdriver": "^4.35.3",
62
63
  "commander": "^14.0.2",
63
- "glob": "^11.0.3",
64
+ "glob": "^11.1.0",
64
65
  "http-server": "^14.1.1",
65
66
  "js-beautify": "^1.15.4",
66
67
  "jsdom": "^16.7.0",