@progress/kendo-e2e
Version:
Kendo UI end-to-end test utilities.
338 lines (247 loc) • 9.21 kB
Markdown
**kendo-e2e** is a Selenium-based testing library with **automatic waiting** built-in, making your tests faster to write and more reliable.
Traditional Selenium tests are flaky because they require manual waits. kendo-e2e solves this by:
✅ **Automatic waiting** - Every method waits for elements automatically
✅ **No StaleElementReferenceException** - Built-in retry logic
✅ **Modern expect API** - Playwright-style assertions with auto-retry
✅ **Less code** - Write tests faster with intuitive methods
```bash
npm install @kendo/kendo-e2e --save-dev
```
```typescript
import { Browser } from '@kendo/kendo-e2e';
describe('My First Test', () => {
let browser: Browser;
beforeAll(async () => {
browser = new Browser();
});
afterAll(async () => {
await browser.close();
});
it('should load the page and interact', async () => {
// Navigate to URL
await browser.navigateTo('https://example.com');
// Click button (automatically waits for it to appear)
await browser.click('#login-button');
// Type in input (automatically waits and clears)
await browser.type('#username', 'testuser');
await browser.type('#password', 'secret123');
// Submit form
await browser.click('#submit');
// Assert with automatic retry (waits up to 3s by default)
await browser.expect('.welcome-message').toHaveText('Welcome testuser');
await browser.expect('.dashboard').toBeVisible();
});
});
```
| Plain Selenium | kendo-e2e |
|----------------|-----------|
| `driver.findElement(By.css('#btn'))` then manual wait | `await browser.click('#btn')` |
| Manual `WebDriverWait` everywhere | Automatic waiting built-in |
| `StaleElementReferenceException` errors | Automatic retry handles it |
| Complex wait conditions | Simple `expect()` API |
Control browser settings via environment variables:
```bash
BROWSER_NAME=chrome
HEADLESS=true
BROWSER_WIDTH=1920
BROWSER_HEIGHT=1080
```
```typescript
// Mobile emulation
const browser = new Browser({
mobileEmulation: { deviceName: 'iPhone 14 Pro Max' }
});
// Custom window size
const browser = new Browser();
await browser.resizeWindow(1920, 1080);
// BiDi protocol for advanced features
const browser = new Browser({ enableBidi: true });
```
```typescript
// Find single element (waits automatically)
const button = await browser.find('#submit');
const header = await browser.find('.page-header');
// Find all matching elements (no wait)
const items = await browser.findAll('.list-item');
// Find with wait for at least one
const results = await browser.findAllWithTimeout('.search-result');
// Find child within parent
const dialog = await browser.find('.modal');
const closeBtn = await browser.findChild(dialog, '.close-button');
```
```typescript
// Click (waits for visible and enabled)
await browser.click('#button');
// Type text (clears by default)
await browser.type('#input', 'text');
// Type without clearing
await browser.type('#notes', 'more text', { clear: false });
// Type and press Enter
await browser.type('#search', 'query', { sendEnter: true });
// Hover over element
await browser.hover('.menu-item');
// Double-click
await browser.doubleClick('.file-icon');
// Right-click
await browser.contextClick('.item');
// Drag and drop
await browser.dragTo('#source', '#target');
```
```typescript
// Modern expect API (auto-retries)
await browser.expect('#message').toHaveText('Success');
await browser.expect('.modal').toBeVisible();
await browser.expect('.spinner').not.toBeVisible();
// Wait for condition
import { EC } from '@kendo/kendo-e2e';
await browser.wait(EC.isVisible('#element'));
// Wait safely (returns boolean, doesn't throw)
const appeared = await browser.waitSafely(EC.isVisible('.optional'));
if (appeared) {
await browser.click('.optional .close');
}
```
```typescript
import { Key } from '@kendo/kendo-e2e';
// Send single key
await browser.sendKey(Key.ENTER);
await browser.sendKey(Key.TAB);
await browser.sendKey(Key.ESCAPE);
// Key combinations
await browser.sendKeyCombination(Key.CONTROL, 'c'); // Copy
// Cross-platform Ctrl/Cmd
await browser.sendControlKeyCombination('v'); // Paste
```
```typescript
// Fill out a form
await browser.type('#firstName', 'John');
await browser.type('#lastName', 'Doe');
await browser.type('#email', 'john@example.com');
await browser.click('#submit');
// Verify form submission
await browser.expect('.success-message').toBeVisible();
await browser.expect('.success-message').toHaveText('Form submitted successfully');
```
```typescript
// Use expect() for assertions with auto-retry
await browser.expect('#status').toHaveText('Complete');
// Use CSS selectors (simpler, faster)
await browser.click('#submit');
await browser.click('.btn-primary');
// Chain operations naturally
await browser.navigateTo('https://example.com');
await browser.click('#login');
await browser.type('#username', 'user');
await browser.click('#submit');
await browser.expect('.dashboard').toBeVisible();
```
```typescript
// DON'T add manual sleeps - automatic waiting handles it
await browser.sleep(2000); // ❌ Unnecessary
// DON'T use complex XPath when CSS works
await browser.click(By.xpath('//div[@id="submit"]')); // ❌
await browser.click('#submit'); // ✅ Better
// DON'T manually wait before actions
await browser.wait(EC.isVisible('#button'));
await browser.click('#button'); // ❌ click() already waits!
// Just do:
await browser.click('#button'); // ✅
```
Essential checks for every test:
```typescript
it('should work without errors', async () => {
// Clear browser console
await browser.clearLogs();
// Your test actions
await browser.navigateTo('https://example.com');
await browser.click('#trigger-action');
// Check for console errors
const errors = await browser.getErrorLogs();
expect(errors.length).toBe(0);
// Check accessibility
const a11yViolations = await browser.getAccessibilityViolations();
expect(a11yViolations.length).toBe(0);
});
```
- 📚 Read [API Reference](./API_REFERENCE.md) for complete method documentation
- 🎯 Check [Common Patterns](./PATTERNS.md) for real-world examples
```typescript
import { Browser } from '@kendo/kendo-e2e';
describe('Login Flow', () => {
let browser: Browser;
beforeAll(async () => {
browser = new Browser();
});
afterAll(async () => {
await browser.close();
});
beforeEach(async () => {
await browser.navigateTo('https://example.com/login');
await browser.clearLogs();
});
it('should show login form', async () => {
await browser.expect('#login-form').toBeVisible();
await browser.expect('#username').toBeVisible();
await browser.expect('#password').toBeVisible();
});
it('should login successfully', async () => {
await browser.type('#username', 'testuser');
await browser.type('#password', 'password123');
await browser.click('#login-button');
await browser.expect('.welcome-message').toBeVisible();
await browser.expect('.welcome-message').toHaveText('Welcome testuser');
// Verify no console errors
const errors = await browser.getErrorLogs();
expect(errors.length).toBe(0);
});
it('should show error for invalid credentials', async () => {
await browser.type('#username', 'invalid');
await browser.type('#password', 'wrong');
await browser.click('#login-button');
await browser.expect('.error-message').toBeVisible();
await browser.expect('.error-message').toHaveText('Invalid credentials');
});
});
```
- Reduce timeouts for faster feedback: `await browser.expect('#el').toBeVisible({ timeout: 5000 })`
- Use headless mode: `HEADLESS=true npm test`
- Check your selector is correct
- Verify element exists in the DOM when expected
- Try using `findAllWithTimeout` if content loads dynamically
- Use `expect()` instead of manual waits
- Ensure proper `beforeAll`/`afterAll` cleanup (start browser once, not per test)
- Check for timing-dependent logic in your app
- Ensure browser driver is installed
- Check `BROWSER_NAME` environment variable
- Verify browser is installed on your system
- Check [examples](../examples/) folder for more samples
- Review [tests](../tests/) for advanced usage patterns
- Read method JSDoc in your IDE for inline documentation