or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

assertions.mdbrowser-automation.mdclient-functions.mdelement-selection.mdindex.mdprogrammatic-api.mdrequest-interception.mduser-roles.md

client-functions.mddocs/

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

```