0
# Mobile & Device Testing
1
2
Mobile browser emulation, device-specific testing, Android automation, and Electron app automation for comprehensive cross-platform testing.
3
4
## Capabilities
5
6
### Device Emulation
7
8
Pre-configured device descriptors for mobile and desktop browser testing.
9
10
```typescript { .api }
11
/**
12
* Collection of device descriptors for browser emulation
13
*/
14
const devices: Devices;
15
16
interface Devices {
17
/** Device descriptors indexed by name */
18
[key: string]: DeviceDescriptor;
19
}
20
21
interface DeviceDescriptor {
22
/** User agent string */
23
userAgent: string;
24
/** Screen dimensions */
25
viewport: ViewportSize;
26
/** Device pixel ratio */
27
deviceScaleFactor: number;
28
/** Mobile device flag */
29
isMobile: boolean;
30
/** Touch support flag */
31
hasTouch: boolean;
32
/** Default browser type */
33
defaultBrowserType?: 'chromium' | 'firefox' | 'webkit';
34
}
35
36
interface ViewportSize {
37
width: number;
38
height: number;
39
}
40
```
41
42
**Usage Examples:**
43
44
```typescript
45
import { devices, chromium } from 'playwright';
46
47
// Launch with device emulation
48
const browser = await chromium.launch();
49
const context = await browser.newContext({
50
...devices['iPhone 12'],
51
});
52
const page = await context.newPage();
53
54
// Common device presets
55
const iphone = devices['iPhone 12'];
56
const ipad = devices['iPad Pro'];
57
const pixel = devices['Pixel 5'];
58
const galaxy = devices['Galaxy S9+'];
59
const desktopChrome = devices['Desktop Chrome'];
60
const desktopFirefox = devices['Desktop Firefox'];
61
62
console.log('iPhone 12 viewport:', iphone.viewport);
63
console.log('Is mobile:', iphone.isMobile);
64
console.log('Has touch:', iphone.hasTouch);
65
66
// Custom device configuration
67
const customDevice = {
68
userAgent: 'Custom Device User Agent',
69
viewport: { width: 375, height: 812 },
70
deviceScaleFactor: 3,
71
isMobile: true,
72
hasTouch: true
73
};
74
75
const customContext = await browser.newContext(customDevice);
76
```
77
78
### Android Device Automation
79
80
Connect to and automate Android devices and emulators.
81
82
```typescript { .api }
83
/**
84
* Android automation interface
85
*/
86
const _android: Android;
87
88
interface Android {
89
/** List connected Android devices */
90
devices(): Promise<AndroidDevice[]>;
91
/** Set default timeout */
92
setDefaultTimeout(timeout: number): void;
93
/** Connect to device via ADB */
94
connect(wsEndpoint: string, options?: AndroidConnectOptions): Promise<AndroidDevice>;
95
}
96
97
interface AndroidDevice {
98
/** Device model */
99
model(): string;
100
/** Device serial number */
101
serial(): string;
102
/** Launch application */
103
launchBrowser(options?: AndroidLaunchOptions): Promise<BrowserContext>;
104
/** Close device connection */
105
close(): Promise<void>;
106
/** Install APK */
107
installApk(apkPath: string): Promise<void>;
108
/** Get device shell */
109
shell(command: string): Promise<Buffer>;
110
/** Push file to device */
111
push(local: string, remote: string): Promise<void>;
112
/** Pull file from device */
113
pull(remote: string, local: string): Promise<void>;
114
/** Take screenshot */
115
screenshot(): Promise<Buffer>;
116
/** Get web views */
117
webViews(): Promise<AndroidWebView[]>;
118
/** Get web view by selector */
119
webView(selector: AndroidSelector): Promise<AndroidWebView>;
120
/** Tap coordinates */
121
tap(selector: AndroidSelector): Promise<void>;
122
/** Fill text */
123
fill(selector: AndroidSelector, text: string): Promise<void>;
124
/** Press key */
125
press(key: AndroidKey): Promise<void>;
126
/** Scroll */
127
scroll(selector: AndroidSelector, direction: 'down' | 'up' | 'left' | 'right', percent: number): Promise<void>;
128
/** Wait for element */
129
wait(selector: AndroidSelector, options?: AndroidWaitOptions): Promise<AndroidElementInfo>;
130
/** Get element info */
131
info(selector: AndroidSelector): Promise<AndroidElementInfo>;
132
/** Input interface */
133
input: AndroidInput;
134
}
135
136
interface AndroidLaunchOptions {
137
/** Browser package name */
138
pkg?: string;
139
/** Browser activity */
140
activity?: string;
141
/** Wait for network idle */
142
waitForNetworkIdle?: boolean;
143
}
144
145
interface AndroidConnectOptions {
146
/** Connection timeout */
147
timeout?: number;
148
}
149
150
interface AndroidWaitOptions {
151
/** Wait timeout */
152
timeout?: number;
153
/** Element state to wait for */
154
state?: 'gone' | 'present';
155
}
156
```
157
158
**Usage Examples:**
159
160
```typescript
161
import { _android as android } from 'playwright';
162
163
// Connect to Android device
164
const devices = await android.devices();
165
const device = devices[0];
166
167
console.log('Device model:', device.model());
168
console.log('Serial:', device.serial());
169
170
// Launch browser on device
171
const context = await device.launchBrowser();
172
const page = await context.newPage();
173
await page.goto('https://example.com');
174
175
// Native Android automation
176
await device.tap({ text: 'Settings' });
177
await device.fill({ resource: 'com.android.settings:id/search' }, 'wifi');
178
await device.press('KEYCODE_ENTER');
179
180
// Take screenshot
181
const screenshot = await device.screenshot();
182
require('fs').writeFileSync('android-screenshot.png', screenshot);
183
184
// Work with WebViews
185
const webViews = await device.webViews();
186
if (webViews.length > 0) {
187
const webView = webViews[0];
188
const page = await webView.page();
189
await page.click('button');
190
}
191
192
await device.close();
193
```
194
195
### Android Input & Interaction
196
197
Handle Android-specific input methods and UI interactions.
198
199
```typescript { .api }
200
interface AndroidInput {
201
/** Tap coordinates */
202
tap(point: { x: number; y: number }): Promise<void>;
203
/** Swipe gesture */
204
swipe(from: { x: number; y: number }, to: { x: number; y: number }, steps: number): Promise<void>;
205
/** Drag gesture */
206
drag(from: { x: number; y: number }, to: { x: number; y: number }, steps: number): Promise<void>;
207
/** Press key */
208
press(key: AndroidKey): Promise<void>;
209
/** Type text */
210
type(text: string): Promise<void>;
211
}
212
213
interface AndroidWebView {
214
/** Get package name */
215
pkg(): string;
216
/** Get page instance */
217
page(): Promise<Page>;
218
}
219
220
interface AndroidSocket {
221
/** Write data to socket */
222
write(data: Buffer | string): Promise<void>;
223
/** Close socket */
224
close(): Promise<void>;
225
}
226
227
type AndroidElementInfo = {
228
/** Element bounds */
229
bounds: { x: number; y: number; width: number; height: number };
230
/** Element class */
231
clazz: string;
232
/** Element description */
233
desc: string;
234
/** Package name */
235
pkg: string;
236
/** Resource ID */
237
res: string;
238
/** Element text */
239
text: string;
240
/** Element checkable */
241
checkable: boolean;
242
/** Element checked */
243
checked: boolean;
244
/** Element clickable */
245
clickable: boolean;
246
/** Element enabled */
247
enabled: boolean;
248
/** Element focusable */
249
focusable: boolean;
250
/** Element focused */
251
focused: boolean;
252
/** Element long clickable */
253
longClickable: boolean;
254
/** Element scrollable */
255
scrollable: boolean;
256
/** Element selected */
257
selected: boolean;
258
};
259
260
type AndroidSelector = {
261
/** Select by checkable state */
262
checkable?: boolean;
263
/** Select by checked state */
264
checked?: boolean;
265
/** Select by class name */
266
clazz?: string | RegExp;
267
/** Select by clickable state */
268
clickable?: boolean;
269
/** Select by depth */
270
depth?: number;
271
/** Select by description */
272
desc?: string | RegExp;
273
/** Select by enabled state */
274
enabled?: boolean;
275
/** Select by focusable state */
276
focusable?: boolean;
277
/** Select by focused state */
278
focused?: boolean;
279
/** Select by long clickable state */
280
longClickable?: boolean;
281
/** Select by package name */
282
pkg?: string | RegExp;
283
/** Select by resource ID */
284
res?: string | RegExp;
285
/** Select by scrollable state */
286
scrollable?: boolean;
287
/** Select by selected state */
288
selected?: boolean;
289
/** Select by text content */
290
text?: string | RegExp;
291
};
292
293
type AndroidKey =
294
| 'KEYCODE_UNKNOWN' | 'KEYCODE_SOFT_LEFT' | 'KEYCODE_SOFT_RIGHT' | 'KEYCODE_HOME'
295
| 'KEYCODE_BACK' | 'KEYCODE_CALL' | 'KEYCODE_ENDCALL' | '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
296
| 'KEYCODE_STAR' | 'KEYCODE_POUND' | 'KEYCODE_DPAD_UP' | 'KEYCODE_DPAD_DOWN' | 'KEYCODE_DPAD_LEFT' | 'KEYCODE_DPAD_RIGHT'
297
| 'KEYCODE_DPAD_CENTER' | 'KEYCODE_VOLUME_UP' | 'KEYCODE_VOLUME_DOWN' | 'KEYCODE_POWER' | 'KEYCODE_CAMERA'
298
| 'KEYCODE_CLEAR' | 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G' | 'H' | 'I' | 'J' | 'K' | 'L' | 'M' | 'N' | 'O' | 'P' | 'Q' | 'R' | 'S' | 'T' | 'U' | 'V' | 'W' | 'X' | 'Y' | 'Z'
299
| 'KEYCODE_COMMA' | 'KEYCODE_PERIOD' | 'KEYCODE_ALT_LEFT' | 'KEYCODE_ALT_RIGHT' | 'KEYCODE_SHIFT_LEFT' | 'KEYCODE_SHIFT_RIGHT'
300
| 'KEYCODE_TAB' | 'KEYCODE_SPACE' | 'KEYCODE_SYM' | 'KEYCODE_EXPLORER' | 'KEYCODE_ENVELOPE' | 'KEYCODE_ENTER'
301
| 'KEYCODE_DEL' | 'KEYCODE_GRAVE' | 'KEYCODE_MINUS' | 'KEYCODE_EQUALS' | 'KEYCODE_LEFT_BRACKET' | 'KEYCODE_RIGHT_BRACKET'
302
| 'KEYCODE_BACKSLASH' | 'KEYCODE_SEMICOLON' | 'KEYCODE_APOSTROPHE' | 'KEYCODE_SLASH' | 'KEYCODE_AT' | 'KEYCODE_NUM'
303
| 'KEYCODE_HEADSETHOOK' | 'KEYCODE_FOCUS' | 'KEYCODE_PLUS' | 'KEYCODE_MENU' | 'KEYCODE_NOTIFICATION' | 'KEYCODE_SEARCH'
304
| 'KEYCODE_MEDIA_PLAY_PAUSE' | 'KEYCODE_MEDIA_STOP' | 'KEYCODE_MEDIA_NEXT' | 'KEYCODE_MEDIA_PREVIOUS' | 'KEYCODE_MEDIA_REWIND'
305
| 'KEYCODE_MEDIA_FAST_FORWARD' | 'KEYCODE_MUTE';
306
```
307
308
**Usage Examples:**
309
310
```typescript
311
// Android input interactions
312
await device.input.tap({ x: 100, y: 200 });
313
await device.input.swipe(
314
{ x: 100, y: 500 },
315
{ x: 100, y: 200 },
316
10
317
);
318
319
// Press hardware keys
320
await device.input.press('KEYCODE_HOME');
321
await device.input.press('KEYCODE_BACK');
322
await device.input.press('KEYCODE_VOLUME_UP');
323
324
// Type text
325
await device.input.type('Hello Android');
326
327
// Complex selector
328
const element = await device.wait({
329
pkg: /com\.android\.settings/,
330
text: /Wi.*Fi/,
331
clickable: true
332
});
333
334
console.log('Found element:', element.text);
335
console.log('Element bounds:', element.bounds);
336
337
// WebView automation
338
const webViews = await device.webViews();
339
for (const webView of webViews) {
340
console.log('WebView package:', webView.pkg());
341
const page = await webView.page();
342
const title = await page.title();
343
console.log('WebView title:', title);
344
}
345
```
346
347
### Electron Application Automation
348
349
Automate Electron desktop applications.
350
351
```typescript { .api }
352
/**
353
* Electron automation interface
354
*/
355
const _electron: Electron;
356
357
interface Electron {
358
/** Launch Electron application */
359
launch(options: ElectronLaunchOptions): Promise<ElectronApplication>;
360
}
361
362
interface ElectronApplication {
363
/** Get BrowserWindow instance */
364
browserWindow(page: Page): Promise<JSHandle>;
365
/** Close application */
366
close(): Promise<void>;
367
/** Get application context */
368
context(): BrowserContext;
369
/** Evaluate in main process */
370
evaluate<R, Arg>(pageFunction: PageFunction<Arg, R>, arg: Arg): Promise<R>;
371
evaluate<R>(pageFunction: PageFunction<void, R>, arg?: any): Promise<R>;
372
/** First window */
373
firstWindow(options?: ElectronApplicationFirstWindowOptions): Promise<Page>;
374
/** Get all windows */
375
windows(): Page[];
376
/** Wait for event */
377
waitForEvent(event: string, optionsOrPredicate?: WaitForEventOptions): Promise<any>;
378
/** Get process */
379
process(): ChildProcess;
380
}
381
382
interface ElectronLaunchOptions {
383
/** Path to Electron app */
384
executablePath?: string;
385
/** Application arguments */
386
args?: string[];
387
/** Working directory */
388
cwd?: string;
389
/** Environment variables */
390
env?: { [key: string]: string | number | boolean };
391
/** Connection timeout */
392
timeout?: number;
393
/** Accept downloads */
394
acceptDownloads?: boolean;
395
/** Bypass CSP */
396
bypassCSP?: boolean;
397
/** Color scheme */
398
colorScheme?: 'light' | 'dark' | 'no-preference';
399
/** Extra HTTP headers */
400
extraHTTPHeaders?: { [key: string]: string };
401
/** Geolocation */
402
geolocation?: Geolocation;
403
/** HTTP credentials */
404
httpCredentials?: HTTPCredentials;
405
/** Ignore HTTPS errors */
406
ignoreHTTPSErrors?: boolean;
407
/** Locale */
408
locale?: string;
409
/** Offline mode */
410
offline?: boolean;
411
/** Permissions */
412
permissions?: string[];
413
/** Proxy */
414
proxy?: ProxySettings;
415
/** Record HAR */
416
recordHar?: { omitContent?: boolean; path: string };
417
/** Record video */
418
recordVideo?: { dir: string; size?: ViewportSize };
419
/** Reduced motion */
420
reducedMotion?: 'reduce' | 'no-preference';
421
/** Service workers */
422
serviceWorkers?: 'allow' | 'block';
423
/** Storage state */
424
storageState?: string | { cookies: Cookie[]; origins: any[] };
425
/** Strict selectors */
426
strictSelectors?: boolean;
427
/** Timezone */
428
timezoneId?: string;
429
/** User agent */
430
userAgent?: string;
431
/** Viewport */
432
viewport?: ViewportSize | null;
433
}
434
435
interface ElectronApplicationFirstWindowOptions {
436
/** Wait timeout */
437
timeout?: number;
438
}
439
```
440
441
**Usage Examples:**
442
443
```typescript
444
import { _electron as electron } from 'playwright';
445
446
// Launch Electron app
447
const electronApp = await electron.launch({
448
args: ['./my-electron-app']
449
});
450
451
// Get first window
452
const window = await electronApp.firstWindow();
453
await window.waitForLoadState();
454
455
// Interact with main process
456
const version = await electronApp.evaluate(async ({ app }) => {
457
return app.getVersion();
458
});
459
460
console.log('App version:', version);
461
462
// Interact with renderer process (window)
463
await window.click('#menu-file');
464
await window.click('#menu-open');
465
466
// Get BrowserWindow handle
467
const browserWindow = await electronApp.browserWindow(window);
468
const isMaximized = await browserWindow.evaluate(win => win.isMaximized());
469
470
console.log('Window maximized:', isMaximized);
471
472
// Handle multiple windows
473
const allWindows = electronApp.windows();
474
console.log('Window count:', allWindows.length);
475
476
// Wait for new window
477
electronApp.on('window', async (page) => {
478
console.log('New window opened:', await page.title());
479
});
480
481
await electronApp.close();
482
```
483
484
### Device-Specific Testing Patterns
485
486
Common patterns for mobile and device testing.
487
488
```typescript { .api }
489
interface BrowserContext {
490
/** Add geolocation override */
491
setGeolocation(geolocation: Geolocation | null): Promise<void>;
492
/** Set offline mode */
493
setOffline(offline: boolean): Promise<void>;
494
/** Override permissions */
495
grantPermissions(permissions: string[], options?: BrowserContextGrantPermissionsOptions): Promise<void>;
496
/** Clear permissions */
497
clearPermissions(): Promise<void>;
498
}
499
500
interface Page {
501
/** Emulate media features */
502
emulateMedia(options?: PageEmulateMediaOptions): Promise<void>;
503
/** Set viewport size */
504
setViewportSize(viewportSize: ViewportSize): Promise<void>;
505
}
506
507
interface PageEmulateMediaOptions {
508
/** Media type */
509
media?: 'screen' | 'print' | null;
510
/** Color scheme */
511
colorScheme?: 'light' | 'dark' | 'no-preference' | null;
512
/** Reduced motion */
513
reducedMotion?: 'reduce' | 'no-preference' | null;
514
/** Forced colors */
515
forcedColors?: 'active' | 'none' | null;
516
}
517
518
interface BrowserContextGrantPermissionsOptions {
519
/** Origin for permissions */
520
origin?: string;
521
}
522
```
523
524
**Usage Examples:**
525
526
```typescript
527
// Test responsive design
528
const context = await browser.newContext({
529
...devices['iPhone 12'],
530
viewport: { width: 375, height: 812 }
531
});
532
533
const page = await context.newPage();
534
await page.goto('https://example.com');
535
536
// Test different orientations
537
await page.setViewportSize({ width: 812, height: 375 }); // Landscape
538
await page.screenshot({ path: 'landscape.png' });
539
540
await page.setViewportSize({ width: 375, height: 812 }); // Portrait
541
await page.screenshot({ path: 'portrait.png' });
542
543
// Test geolocation
544
await context.setGeolocation({
545
latitude: 37.7749,
546
longitude: -122.4194
547
});
548
await context.grantPermissions(['geolocation']);
549
550
// Test offline behavior
551
await context.setOffline(true);
552
await page.reload();
553
await context.setOffline(false);
554
555
// Test dark mode
556
await page.emulateMedia({ colorScheme: 'dark' });
557
await page.screenshot({ path: 'dark-mode.png' });
558
559
// Test print media
560
await page.emulateMedia({ media: 'print' });
561
await page.screenshot({ path: 'print-view.png' });
562
563
// Test reduced motion
564
await page.emulateMedia({ reducedMotion: 'reduce' });
565
```
566
567
## Types
568
569
### Mobile Testing Configuration
570
571
```typescript { .api }
572
interface Geolocation {
573
/** Latitude coordinate */
574
latitude: number;
575
/** Longitude coordinate */
576
longitude: number;
577
/** Location accuracy in meters */
578
accuracy?: number;
579
}
580
581
interface ProxySettings {
582
/** Proxy server URL */
583
server: string;
584
/** Bypass proxy for these patterns */
585
bypass?: string;
586
/** Username for authentication */
587
username?: string;
588
/** Password for authentication */
589
password?: string;
590
}
591
592
interface WaitForEventOptions {
593
/** Event timeout */
594
timeout?: number;
595
/** Event predicate */
596
predicate?: Function;
597
}
598
599
type PageFunction<Arg, R> = string | ((arg: Arg) => R | Promise<R>);
600
```