0
# Client Functions
1
2
TestCafe's ClientFunction feature allows you to execute custom JavaScript code in the browser context and return results to the test context, enabling access to browser APIs and DOM manipulation that goes beyond standard TestCafe actions.
3
4
## Capabilities
5
6
### Basic Client Functions
7
8
Execute JavaScript code in the browser and return results to tests.
9
10
```javascript { .api }
11
/**
12
* Creates a function that executes in browser context
13
* @param fn - JavaScript function to execute in browser
14
* @param options - Configuration options for the client function
15
* @returns ClientFunction that can be called from tests
16
*/
17
function ClientFunction(fn: Function, options?: ClientFunctionOptions): ClientFunction;
18
19
interface ClientFunction {
20
/** Execute the client function and return result */
21
(): Promise<any>;
22
23
/** Create a new client function with additional options */
24
with(options: ClientFunctionOptions): ClientFunction;
25
}
26
27
interface ClientFunctionOptions {
28
/** Object containing dependencies for the client function */
29
dependencies?: {[key: string]: any};
30
31
/** Execution timeout in milliseconds */
32
timeout?: number;
33
}
34
```
35
36
**Usage Examples:**
37
38
```javascript
39
import { ClientFunction } from 'testcafe';
40
41
fixture('Client Functions')
42
.page('https://example.com');
43
44
// Simple client function
45
const getPageTitle = ClientFunction(() => document.title);
46
47
const getWindowSize = ClientFunction(() => ({
48
width: window.innerWidth,
49
height: window.innerHeight
50
}));
51
52
const getCurrentUrl = ClientFunction(() => window.location.href);
53
54
test('Basic client functions', async t => {
55
const title = await getPageTitle();
56
const windowSize = await getWindowSize();
57
const url = await getCurrentUrl();
58
59
await t.expect(title).contains('Example');
60
await t.expect(windowSize.width).gt(0);
61
await t.expect(url).contains('example.com');
62
});
63
64
// Client function with return value
65
const countElements = ClientFunction((selector) => {
66
return document.querySelectorAll(selector).length;
67
});
68
69
test('Count elements', async t => {
70
const divCount = await countElements('div');
71
const buttonCount = await countElements('button');
72
73
await t.expect(divCount).gt(0);
74
await t.expect(buttonCount).gte(1);
75
});
76
```
77
78
### Client Functions with Dependencies
79
80
Pass data from test context to client functions using dependencies.
81
82
```javascript { .api }
83
/**
84
* Client function with dependencies
85
* @param fn - Function with parameters matching dependency keys
86
* @param options - Options including dependencies object
87
* @returns ClientFunction with injected dependencies
88
*/
89
function ClientFunction(
90
fn: (...args: any[]) => any,
91
options: { dependencies: {[key: string]: any} }
92
): ClientFunction;
93
```
94
95
**Usage Examples:**
96
97
```javascript
98
// Client function with dependencies
99
const searchElements = ClientFunction((searchText, tagName) => {
100
const elements = document.querySelectorAll(tagName);
101
const matches = [];
102
103
for (let element of elements) {
104
if (element.textContent.includes(searchText)) {
105
matches.push({
106
text: element.textContent,
107
tagName: element.tagName,
108
id: element.id
109
});
110
}
111
}
112
113
return matches;
114
}, {
115
dependencies: {
116
searchText: 'example text',
117
tagName: 'div'
118
}
119
});
120
121
test('Client function with dependencies', async t => {
122
const results = await searchElements;
123
124
await t.expect(results.length).gte(0);
125
126
if (results.length > 0) {
127
await t.expect(results[0].text).contains('example text');
128
}
129
});
130
131
// Dynamic dependencies
132
const dynamicFunction = ClientFunction((className, attribute) => {
133
const elements = document.querySelectorAll(`.${className}`);
134
return Array.from(elements).map(el => el.getAttribute(attribute));
135
});
136
137
test('Dynamic dependencies', async t => {
138
// Different calls with different dependencies
139
const classNames = await dynamicFunction.with({
140
dependencies: { className: 'menu-item', attribute: 'data-id' }
141
})();
142
143
const urls = await dynamicFunction.with({
144
dependencies: { className: 'external-link', attribute: 'href' }
145
})();
146
147
await t.expect(classNames.length).gte(0);
148
await t.expect(urls.length).gte(0);
149
});
150
```
151
152
### DOM Manipulation
153
154
Use client functions to manipulate the DOM directly.
155
156
```javascript { .api }
157
// Client functions for DOM manipulation
158
const setElementValue = ClientFunction((selector, value) => {
159
const element = document.querySelector(selector);
160
if (element) {
161
element.value = value;
162
element.dispatchEvent(new Event('input', { bubbles: true }));
163
element.dispatchEvent(new Event('change', { bubbles: true }));
164
}
165
});
166
167
const triggerCustomEvent = ClientFunction((selector, eventType, eventData) => {
168
const element = document.querySelector(selector);
169
if (element) {
170
const event = new CustomEvent(eventType, { detail: eventData });
171
element.dispatchEvent(event);
172
}
173
});
174
175
const addCSSClass = ClientFunction((selector, className) => {
176
const elements = document.querySelectorAll(selector);
177
elements.forEach(el => el.classList.add(className));
178
});
179
```
180
181
**Usage Examples:**
182
183
```javascript
184
test('DOM manipulation', async t => {
185
// Set input value directly
186
await setElementValue('#hidden-input', 'test-value');
187
188
// Verify value was set
189
const inputValue = await ClientFunction(() =>
190
document.querySelector('#hidden-input').value
191
)();
192
193
await t.expect(inputValue).eql('test-value');
194
195
// Trigger custom events
196
await triggerCustomEvent('#custom-element', 'customEvent', {
197
data: 'test-data'
198
});
199
200
// Add CSS classes
201
await addCSSClass('.highlight-target', 'highlighted');
202
203
// Verify class was added
204
const hasClass = await ClientFunction(() =>
205
document.querySelector('.highlight-target').classList.contains('highlighted')
206
)();
207
208
await t.expect(hasClass).ok();
209
});
210
```
211
212
### Browser API Access
213
214
Access browser APIs not available through standard TestCafe actions.
215
216
```javascript { .api }
217
// Access various browser APIs
218
const getLocalStorage = ClientFunction((key) => {
219
return localStorage.getItem(key);
220
});
221
222
const setLocalStorage = ClientFunction((key, value) => {
223
localStorage.setItem(key, value);
224
});
225
226
const getSessionStorage = ClientFunction((key) => {
227
return sessionStorage.getItem(key);
228
});
229
230
const getCookies = ClientFunction(() => {
231
return document.cookie;
232
});
233
234
const getGeolocation = ClientFunction(() => {
235
return new Promise((resolve, reject) => {
236
if (!navigator.geolocation) {
237
reject(new Error('Geolocation not supported'));
238
return;
239
}
240
241
navigator.geolocation.getCurrentPosition(
242
position => resolve({
243
latitude: position.coords.latitude,
244
longitude: position.coords.longitude
245
}),
246
error => reject(error)
247
);
248
});
249
});
250
```
251
252
**Usage Examples:**
253
254
```javascript
255
test('Browser API access', async t => {
256
// Test localStorage
257
await setLocalStorage('testKey', 'testValue');
258
const storedValue = await getLocalStorage('testKey');
259
await t.expect(storedValue).eql('testValue');
260
261
// Test sessionStorage
262
const sessionValue = await getSessionStorage('existingKey');
263
console.log('Session value:', sessionValue);
264
265
// Test cookies
266
const cookies = await getCookies();
267
await t.expect(cookies).typeOf('string');
268
269
// Test geolocation (with user permission)
270
try {
271
const location = await getGeolocation();
272
await t.expect(location.latitude).typeOf('number');
273
await t.expect(location.longitude).typeOf('number');
274
} catch (error) {
275
console.log('Geolocation not available:', error.message);
276
}
277
});
278
279
// Test browser capabilities
280
const getBrowserInfo = ClientFunction(() => ({
281
userAgent: navigator.userAgent,
282
language: navigator.language,
283
platform: navigator.platform,
284
cookieEnabled: navigator.cookieEnabled,
285
onLine: navigator.onLine,
286
screenWidth: screen.width,
287
screenHeight: screen.height
288
}));
289
290
test('Browser information', async t => {
291
const browserInfo = await getBrowserInfo();
292
293
await t.expect(browserInfo.userAgent).typeOf('string');
294
await t.expect(browserInfo.cookieEnabled).typeOf('boolean');
295
await t.expect(browserInfo.screenWidth).gt(0);
296
});
297
```
298
299
### Async Client Functions
300
301
Handle asynchronous operations in client functions.
302
303
```javascript { .api }
304
// Async client function
305
const waitForElement = ClientFunction((selector, timeout = 5000) => {
306
return new Promise((resolve, reject) => {
307
const element = document.querySelector(selector);
308
if (element) {
309
resolve(element.textContent);
310
return;
311
}
312
313
const observer = new MutationObserver(() => {
314
const element = document.querySelector(selector);
315
if (element) {
316
observer.disconnect();
317
resolve(element.textContent);
318
}
319
});
320
321
observer.observe(document.body, {
322
childList: true,
323
subtree: true
324
});
325
326
setTimeout(() => {
327
observer.disconnect();
328
reject(new Error(`Element ${selector} not found within ${timeout}ms`));
329
}, timeout);
330
});
331
});
332
333
const fetchData = ClientFunction((url) => {
334
return fetch(url)
335
.then(response => response.json())
336
.then(data => data)
337
.catch(error => ({ error: error.message }));
338
});
339
```
340
341
**Usage Examples:**
342
343
```javascript
344
test('Async client functions', async t => {
345
// Wait for dynamic element
346
try {
347
await t.click('#load-content-button');
348
349
const elementText = await waitForElement('.dynamic-content', 10000);
350
await t.expect(elementText).contains('loaded');
351
352
} catch (error) {
353
console.log('Dynamic element not loaded:', error.message);
354
}
355
356
// Fetch data in browser context
357
const apiData = await fetchData('/api/test-data');
358
359
if (apiData.error) {
360
console.log('API error:', apiData.error);
361
} else {
362
await t.expect(apiData).typeOf('object');
363
}
364
});
365
366
// Async client function with polling
367
const waitForCondition = ClientFunction((checkFn, timeout = 5000) => {
368
return new Promise((resolve, reject) => {
369
const start = Date.now();
370
371
function check() {
372
try {
373
const result = checkFn();
374
if (result) {
375
resolve(result);
376
return;
377
}
378
} catch (error) {
379
// Continue polling on error
380
}
381
382
if (Date.now() - start > timeout) {
383
reject(new Error('Condition not met within timeout'));
384
return;
385
}
386
387
setTimeout(check, 100);
388
}
389
390
check();
391
});
392
});
393
394
test('Polling client function', async t => {
395
await t.click('#start-process-button');
396
397
const result = await waitForCondition(() => {
398
const status = document.querySelector('.process-status');
399
return status && status.textContent === 'complete';
400
}, 15000);
401
402
await t.expect(result).ok();
403
});
404
```
405
406
### Error Handling
407
408
Handle errors in client functions and provide fallbacks.
409
410
```javascript { .api }
411
// Client function with error handling
412
const safeExecute = ClientFunction((operation) => {
413
try {
414
return operation();
415
} catch (error) {
416
return { error: error.message };
417
}
418
});
419
420
const robustGetElement = ClientFunction((selector) => {
421
try {
422
const element = document.querySelector(selector);
423
if (!element) {
424
return { error: 'Element not found', selector };
425
}
426
427
return {
428
exists: true,
429
text: element.textContent,
430
visible: element.offsetParent !== null,
431
tagName: element.tagName
432
};
433
} catch (error) {
434
return { error: error.message, selector };
435
}
436
});
437
```
438
439
**Usage Examples:**
440
441
```javascript
442
test('Client function error handling', async t => {
443
// Safe execution with error handling
444
const result1 = await safeExecute(() => {
445
return document.querySelector('#existing-element').textContent;
446
});
447
448
if (result1.error) {
449
console.log('Error accessing element:', result1.error);
450
} else {
451
await t.expect(result1).typeOf('string');
452
}
453
454
// Robust element access
455
const elementInfo = await robustGetElement('#maybe-missing-element');
456
457
if (elementInfo.error) {
458
console.log('Element access failed:', elementInfo.error);
459
// Fallback behavior
460
await t.navigateTo('/alternative-page');
461
} else {
462
await t.expect(elementInfo.exists).ok();
463
await t.expect(elementInfo.text).typeOf('string');
464
}
465
});
466
467
// Retry logic for client functions
468
const retryClientFunction = async (clientFn, maxRetries = 3) => {
469
for (let i = 0; i < maxRetries; i++) {
470
try {
471
return await clientFn();
472
} catch (error) {
473
if (i === maxRetries - 1) throw error;
474
await new Promise(resolve => setTimeout(resolve, 1000));
475
}
476
}
477
};
478
479
test('Client function retry', async t => {
480
const unstableFunction = ClientFunction(() => {
481
// Simulate intermittent failure
482
if (Math.random() < 0.7) {
483
throw new Error('Random failure');
484
}
485
return 'success';
486
});
487
488
const result = await retryClientFunction(unstableFunction, 5);
489
await t.expect(result).eql('success');
490
});
491
```
492
493
### Performance and Optimization
494
495
Best practices for client function performance and reliability.
496
497
```javascript { .api }
498
// Optimized client functions
499
const batchElementInfo = ClientFunction((selectors) => {
500
return selectors.map(selector => {
501
const element = document.querySelector(selector);
502
return element ? {
503
selector,
504
text: element.textContent,
505
visible: element.offsetParent !== null,
506
bounds: element.getBoundingClientRect()
507
} : { selector, exists: false };
508
});
509
});
510
511
const cachedClientFunction = (() => {
512
let cache = new Map();
513
514
return ClientFunction((key, computeFn) => {
515
if (cache.has(key)) {
516
return cache.get(key);
517
}
518
519
const result = computeFn();
520
cache.set(key, result);
521
return result;
522
});
523
})();
524
```
525
526
**Usage Examples:**
527
528
```javascript
529
test('Optimized client functions', async t => {
530
// Batch multiple element queries
531
const selectors = ['.header', '.content', '.footer'];
532
const elementsInfo = await batchElementInfo(selectors);
533
534
elementsInfo.forEach(info => {
535
if (info.exists) {
536
console.log(`${info.selector}: ${info.text}`);
537
}
538
});
539
540
// Cached computation
541
const expensiveResult = await cachedClientFunction('heavy-calc', () => {
542
// Simulate expensive calculation
543
let result = 0;
544
for (let i = 0; i < 1000000; i++) {
545
result += Math.sqrt(i);
546
}
547
return result;
548
});
549
550
await t.expect(expensiveResult).gt(0);
551
552
// Second call uses cached result
553
const cachedResult = await cachedClientFunction('heavy-calc', () => {
554
throw new Error('Should not execute');
555
});
556
557
await t.expect(cachedResult).eql(expensiveResult);
558
});
559
```