@react-native-harness/platform-android 1.2.0-rc.1 → 1.2.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.
@@ -1,700 +0,0 @@
1
- import { EventEmitter } from 'node:events';
2
- import { PassThrough } from 'node:stream';
3
- import { beforeEach, describe, expect, it, vi } from 'vitest';
4
- import { SubprocessError } from '@react-native-harness/tools';
5
- import {
6
- createAvd,
7
- deleteAvd,
8
- emulatorProcess,
9
- getAppUid,
10
- getLogcatTimestamp,
11
- getStartAppArgs,
12
- hasAvd,
13
- installApp,
14
- startEmulator,
15
- uninstallApp,
16
- waitForBoot,
17
- waitForEmulatorDisconnect,
18
- } from '../adb.js';
19
- import * as tools from '@react-native-harness/tools';
20
- import * as environment from '../environment.js';
21
- import * as avdConfig from '../avd-config.js';
22
-
23
- const createAbortError = () =>
24
- new DOMException('The operation was aborted', 'AbortError');
25
-
26
- const createMockChildProcess = () => {
27
- const process = new EventEmitter() as EventEmitter & {
28
- stdout: PassThrough;
29
- stderr: PassThrough;
30
- unref: ReturnType<typeof vi.fn>;
31
- };
32
-
33
- process.stdout = new PassThrough();
34
- process.stderr = new PassThrough();
35
- process.unref = vi.fn();
36
-
37
- return process;
38
- };
39
-
40
- beforeEach(() => {
41
- vi.restoreAllMocks();
42
- });
43
-
44
- describe('getStartAppArgs', () => {
45
- it('maps supported extras to adb am start flags', () => {
46
- expect(
47
- getStartAppArgs('com.example.app', '.MainActivity', {
48
- extras: {
49
- feature_flag: true,
50
- user_id: 42,
51
- mode: 'debug',
52
- },
53
- }),
54
- ).toEqual([
55
- 'shell',
56
- 'am',
57
- 'start',
58
- '-a',
59
- 'android.intent.action.MAIN',
60
- '-c',
61
- 'android.intent.category.LAUNCHER',
62
- '-n',
63
- 'com.example.app/.MainActivity',
64
- '--ez',
65
- 'feature_flag',
66
- 'true',
67
- '--ei',
68
- 'user_id',
69
- '42',
70
- '--es',
71
- 'mode',
72
- 'debug',
73
- ]);
74
- });
75
-
76
- it('rejects unsafe integer extras', () => {
77
- expect(() =>
78
- getStartAppArgs('com.example.app', '.MainActivity', {
79
- extras: {
80
- count: Number.MAX_SAFE_INTEGER + 1,
81
- },
82
- }),
83
- ).toThrow('must be a safe integer');
84
- });
85
-
86
- it('extracts app uid from pm list packages output', async () => {
87
- const spawnSpy = vi.spyOn(tools, 'spawn').mockResolvedValueOnce({
88
- stdout:
89
- 'package:com.other.app uid:10123\npackage:com.example.app uid:10234\n',
90
- } as Awaited<ReturnType<typeof tools.spawn>>);
91
-
92
- await expect(getAppUid('emulator-5554', 'com.example.app')).resolves.toBe(
93
- 10234,
94
- );
95
-
96
- expect(spawnSpy).toHaveBeenCalledWith(expect.stringMatching(/adb$/), [
97
- '-s',
98
- 'emulator-5554',
99
- 'shell',
100
- 'pm',
101
- 'list',
102
- 'packages',
103
- '-U',
104
- ]);
105
- });
106
-
107
- it('reads the device timestamp in logcat format', async () => {
108
- const spawnSpy = vi.spyOn(tools, 'spawn').mockResolvedValueOnce({
109
- stdout: "'03-12 11:35:08.000'\n",
110
- } as Awaited<ReturnType<typeof tools.spawn>>);
111
-
112
- await expect(getLogcatTimestamp('emulator-5554')).resolves.toBe(
113
- '03-12 11:35:08.000',
114
- );
115
-
116
- expect(spawnSpy).toHaveBeenCalledWith(expect.stringMatching(/adb$/), [
117
- '-s',
118
- 'emulator-5554',
119
- 'shell',
120
- 'date',
121
- "+'%m-%d %H:%M:%S.000'",
122
- ]);
123
- });
124
-
125
- it('checks whether an AVD exists', async () => {
126
- vi.spyOn(tools, 'spawn').mockResolvedValueOnce({
127
- stdout: 'Pixel_6_API_33\nPixel_8_API_35\n',
128
- } as Awaited<ReturnType<typeof tools.spawn>>);
129
-
130
- await expect(hasAvd('Pixel_8_API_35')).resolves.toBe(true);
131
- await expect(hasAvd('Missing_AVD')).resolves.toBe(false);
132
- });
133
-
134
- it('installs the app via adb', async () => {
135
- const spawnSpy = vi
136
- .spyOn(tools, 'spawn')
137
- .mockResolvedValueOnce({} as Awaited<ReturnType<typeof tools.spawn>>);
138
-
139
- await installApp('emulator-5554', '/tmp/app.apk');
140
-
141
- expect(spawnSpy).toHaveBeenCalledWith(expect.stringMatching(/adb$/), [
142
- '-s',
143
- 'emulator-5554',
144
- 'install',
145
- '-r',
146
- '/tmp/app.apk',
147
- ]);
148
- });
149
-
150
- it('uninstalls the app via adb', async () => {
151
- const spawnSpy = vi
152
- .spyOn(tools, 'spawn')
153
- .mockResolvedValueOnce({} as Awaited<ReturnType<typeof tools.spawn>>);
154
-
155
- await uninstallApp('emulator-5554', 'com.example.app');
156
-
157
- expect(spawnSpy).toHaveBeenCalledWith(expect.stringMatching(/adb$/), [
158
- '-s',
159
- 'emulator-5554',
160
- 'uninstall',
161
- 'com.example.app',
162
- ]);
163
- });
164
-
165
- it('creates an AVD and appends config overrides', async () => {
166
- vi.spyOn(avdConfig, 'readAvdConfig').mockResolvedValue({});
167
- const spawnSpy = vi
168
- .spyOn(tools, 'spawn')
169
- .mockResolvedValue({} as Awaited<ReturnType<typeof tools.spawn>>)
170
- .mockResolvedValueOnce({
171
- stdout:
172
- 'id: 0 or "pixel_8"\n Name: Pixel 8\nid: 1 or "pixel_6"\n Name: Pixel 6\n',
173
- } as Awaited<ReturnType<typeof tools.spawn>>);
174
- const verifyAndroidEmulatorSdk = vi
175
- .spyOn(environment, 'ensureAndroidSdkPackages')
176
- .mockResolvedValue('/tmp/android-sdk');
177
- vi.spyOn(environment, 'getHostAndroidSystemImageArch').mockReturnValue(
178
- 'x86_64',
179
- );
180
-
181
- await createAvd({
182
- name: 'Pixel_8_API_35',
183
- apiLevel: 35,
184
- profile: 'pixel_8',
185
- diskSize: '1G',
186
- heapSize: '1G',
187
- });
188
-
189
- expect(verifyAndroidEmulatorSdk).toHaveBeenCalledWith([
190
- 'platform-tools',
191
- 'emulator',
192
- 'platforms;android-35',
193
- 'system-images;android-35;default;x86_64',
194
- ]);
195
- expect(spawnSpy).toHaveBeenNthCalledWith(
196
- 1,
197
- expect.stringMatching(/avdmanager$/),
198
- ['list', 'device'],
199
- );
200
- expect(spawnSpy).toHaveBeenNthCalledWith(2, 'bash', [
201
- '-lc',
202
- expect.stringContaining(
203
- 'create avd --force --name "Pixel_8_API_35" --package "system-images;android-35;default;x86_64" --device "pixel_8" -p ',
204
- ),
205
- ]);
206
- expect(spawnSpy).toHaveBeenNthCalledWith(3, 'bash', [
207
- '-lc',
208
- expect.stringContaining(`'avd.ini.encoding=UTF-8' 'path=`),
209
- ]);
210
- expect(spawnSpy).toHaveBeenNthCalledWith(4, 'bash', [
211
- '-lc',
212
- expect.stringContaining(
213
- `'disk.dataPartition.size=1G' 'vm.heapSize=1G' >> `,
214
- ),
215
- ]);
216
- });
217
-
218
- it('recreates the companion ini file when avdmanager leaves it missing', async () => {
219
- vi.spyOn(avdConfig, 'readAvdConfig').mockResolvedValue({});
220
- const spawnSpy = vi
221
- .spyOn(tools, 'spawn')
222
- .mockResolvedValue({} as Awaited<ReturnType<typeof tools.spawn>>)
223
- .mockResolvedValueOnce({
224
- stdout: 'id: 0 or "pixel_6"\n Name: Pixel 6\n',
225
- } as Awaited<ReturnType<typeof tools.spawn>>);
226
- vi.spyOn(environment, 'ensureAndroidSdkPackages').mockResolvedValue(
227
- '/tmp/android-sdk',
228
- );
229
- vi.spyOn(environment, 'getHostAndroidSystemImageArch').mockReturnValue(
230
- 'x86_64',
231
- );
232
-
233
- await createAvd({
234
- name: 'Pixel_6_API_35',
235
- apiLevel: 35,
236
- profile: 'pixel_6',
237
- diskSize: '1G',
238
- heapSize: '1G',
239
- });
240
-
241
- expect(spawnSpy).toHaveBeenNthCalledWith(3, 'bash', [
242
- '-lc',
243
- expect.stringContaining(`'avd.ini.encoding=UTF-8' 'path=`),
244
- ]);
245
- expect(spawnSpy).toHaveBeenNthCalledWith(4, 'bash', [
246
- '-lc',
247
- expect.stringContaining(
248
- `'disk.dataPartition.size=1G' 'vm.heapSize=1G' >> `,
249
- ),
250
- ]);
251
- });
252
-
253
- it('throws an actionable error when the requested AVD profile is unavailable', async () => {
254
- vi.spyOn(tools, 'spawn').mockResolvedValueOnce({
255
- stdout:
256
- 'id: 0 or "pixel_8"\n Name: Pixel 8\nid: 1 or "pixel_6"\n Name: Pixel 6\n',
257
- } as Awaited<ReturnType<typeof tools.spawn>>);
258
- vi.spyOn(environment, 'ensureAndroidSdkPackages').mockResolvedValue(
259
- '/tmp/android-sdk',
260
- );
261
- vi.spyOn(environment, 'getHostAndroidSystemImageArch').mockReturnValue(
262
- 'x86_64',
263
- );
264
-
265
- await expect(
266
- createAvd({
267
- name: 'Pixel_8_Pro_API_35',
268
- apiLevel: 35,
269
- profile: 'pixel_8_pro',
270
- diskSize: '1G',
271
- heapSize: '1G',
272
- }),
273
- ).rejects.toThrow(
274
- 'Android AVD profile "pixel_8_pro" is not available on this machine. Available profiles: pixel_8, pixel_6',
275
- );
276
- });
277
-
278
- it('throws a clear error when avdmanager does not create config.ini', async () => {
279
- vi.spyOn(avdConfig, 'readAvdConfig').mockResolvedValue(null);
280
- vi.spyOn(tools, 'spawn')
281
- .mockResolvedValue({} as Awaited<ReturnType<typeof tools.spawn>>)
282
- .mockResolvedValueOnce({
283
- stdout: 'id: 0 or "pixel_6"\n Name: Pixel 6\n',
284
- } as Awaited<ReturnType<typeof tools.spawn>>);
285
- vi.spyOn(environment, 'ensureAndroidSdkPackages').mockResolvedValue(
286
- '/tmp/android-sdk',
287
- );
288
- vi.spyOn(environment, 'getHostAndroidSystemImageArch').mockReturnValue(
289
- 'x86_64',
290
- );
291
-
292
- await expect(
293
- createAvd({
294
- name: 'Pixel_6_API_35',
295
- apiLevel: 35,
296
- profile: 'pixel_6',
297
- diskSize: '1G',
298
- heapSize: '1G',
299
- }),
300
- ).rejects.toThrow(
301
- 'Android AVD "Pixel_6_API_35" was created, but config.ini was not found at ',
302
- );
303
- });
304
-
305
- it('creates an AVD with arm64 system image packages on arm64 hosts', async () => {
306
- vi.spyOn(avdConfig, 'readAvdConfig').mockResolvedValue({});
307
- vi.spyOn(tools, 'spawn')
308
- .mockResolvedValue({} as Awaited<ReturnType<typeof tools.spawn>>)
309
- .mockResolvedValueOnce({
310
- stdout: 'id: 0 or "pixel_8"\n Name: Pixel 8\n',
311
- } as Awaited<ReturnType<typeof tools.spawn>>);
312
- const ensureAndroidSdkPackages = vi
313
- .spyOn(environment, 'ensureAndroidSdkPackages')
314
- .mockResolvedValue('/tmp/android-sdk');
315
- vi.spyOn(environment, 'getHostAndroidSystemImageArch').mockReturnValue(
316
- 'arm64-v8a',
317
- );
318
-
319
- await createAvd({
320
- name: 'Pixel_8_API_35',
321
- apiLevel: 35,
322
- profile: 'pixel_8',
323
- diskSize: '1G',
324
- heapSize: '1G',
325
- });
326
-
327
- expect(ensureAndroidSdkPackages).toHaveBeenCalledWith([
328
- 'platform-tools',
329
- 'emulator',
330
- 'platforms;android-35',
331
- 'system-images;android-35;default;arm64-v8a',
332
- ]);
333
- });
334
-
335
- it.skip('deletes both AVD directory and ini file', async () => {
336
- await deleteAvd('Pixel_8_API_35');
337
- });
338
-
339
- it('surfaces emulator stdout when startup fails immediately', async () => {
340
- const child = createMockChildProcess();
341
- let launcherReadyResolve: (() => void) | undefined;
342
- const launcherReady = new Promise<void>((resolve) => {
343
- launcherReadyResolve = resolve;
344
- });
345
-
346
- vi.spyOn(tools, 'spawn').mockResolvedValue({
347
- stdout: 'List of devices attached\n\n',
348
- } as Awaited<ReturnType<typeof tools.spawn>>);
349
- vi.spyOn(emulatorProcess, 'startDetachedProcess').mockImplementation(() => {
350
- launcherReadyResolve?.();
351
- return child as unknown as ReturnType<
352
- typeof emulatorProcess.startDetachedProcess
353
- >;
354
- });
355
-
356
- const startPromise = startEmulator('Pixel_8_API_35');
357
- await launcherReady;
358
-
359
- child.stdout.write('Unknown AVD name [Pixel_8_API_35]\n');
360
- child.stdout.end();
361
- child.stderr.end();
362
- child.emit('close', 1, null);
363
-
364
- await expect(startPromise).rejects.toThrow(
365
- 'Unknown AVD name [Pixel_8_API_35]',
366
- );
367
- });
368
-
369
- it('surfaces emulator stderr when startup fails immediately', async () => {
370
- const child = createMockChildProcess();
371
- let launcherReadyResolve: (() => void) | undefined;
372
- const launcherReady = new Promise<void>((resolve) => {
373
- launcherReadyResolve = resolve;
374
- });
375
-
376
- vi.spyOn(tools, 'spawn').mockResolvedValue({
377
- stdout: 'List of devices attached\n\n',
378
- } as Awaited<ReturnType<typeof tools.spawn>>);
379
- vi.spyOn(emulatorProcess, 'startDetachedProcess').mockImplementation(() => {
380
- launcherReadyResolve?.();
381
- return child as unknown as ReturnType<
382
- typeof emulatorProcess.startDetachedProcess
383
- >;
384
- });
385
-
386
- const startPromise = startEmulator('Pixel_8_API_35');
387
- await launcherReady;
388
-
389
- child.stderr.write('emulator: panic: broken config\n');
390
- child.stdout.end();
391
- child.stderr.end();
392
- child.emit('close', 1, null);
393
-
394
- await expect(startPromise).rejects.toThrow(
395
- 'emulator: panic: broken config',
396
- );
397
- });
398
-
399
- it('returns after the emulator appears without waiting for process exit', async () => {
400
- vi.useFakeTimers();
401
- const child = createMockChildProcess();
402
- const spawnSpy = vi.spyOn(tools, 'spawn');
403
-
404
- spawnSpy
405
- .mockResolvedValueOnce({
406
- stdout: 'List of devices attached\nemulator-5554\tdevice\n',
407
- } as Awaited<ReturnType<typeof tools.spawn>>)
408
- .mockResolvedValueOnce({
409
- stdout: 'Pixel_8_API_35\n',
410
- } as Awaited<ReturnType<typeof tools.spawn>>);
411
-
412
- vi.spyOn(emulatorProcess, 'startDetachedProcess').mockReturnValue(
413
- child as unknown as ReturnType<
414
- typeof emulatorProcess.startDetachedProcess
415
- >,
416
- );
417
-
418
- const startPromise = startEmulator('Pixel_8_API_35');
419
-
420
- await vi.runAllTimersAsync();
421
-
422
- await expect(startPromise).resolves.toBeUndefined();
423
- expect(child.unref).toHaveBeenCalled();
424
- });
425
-
426
- it('passes default boot args to the emulator process', async () => {
427
- vi.useFakeTimers();
428
- const child = createMockChildProcess();
429
- vi.spyOn(tools, 'spawn')
430
- .mockResolvedValueOnce({
431
- stdout: 'List of devices attached\nemulator-5554\tdevice\n',
432
- } as Awaited<ReturnType<typeof tools.spawn>>)
433
- .mockResolvedValueOnce({
434
- stdout: 'Pixel_8_API_35\n',
435
- } as Awaited<ReturnType<typeof tools.spawn>>);
436
- const startDetachedProcess = vi
437
- .spyOn(emulatorProcess, 'startDetachedProcess')
438
- .mockReturnValue(
439
- child as unknown as ReturnType<
440
- typeof emulatorProcess.startDetachedProcess
441
- >,
442
- );
443
-
444
- const startPromise = startEmulator('Pixel_8_API_35');
445
- await vi.runAllTimersAsync();
446
- await startPromise;
447
-
448
- expect(startDetachedProcess).toHaveBeenCalledWith(
449
- expect.stringMatching(/emulator$/),
450
- expect.arrayContaining(['-no-snapshot-load', '-no-snapshot-save']),
451
- );
452
- });
453
-
454
- it('passes clean snapshot generation args to the emulator process', async () => {
455
- vi.useFakeTimers();
456
- const child = createMockChildProcess();
457
- vi.spyOn(tools, 'spawn')
458
- .mockResolvedValueOnce({
459
- stdout: 'List of devices attached\nemulator-5554\tdevice\n',
460
- } as Awaited<ReturnType<typeof tools.spawn>>)
461
- .mockResolvedValueOnce({
462
- stdout: 'Pixel_8_API_35\n',
463
- } as Awaited<ReturnType<typeof tools.spawn>>);
464
- const startDetachedProcess = vi
465
- .spyOn(emulatorProcess, 'startDetachedProcess')
466
- .mockReturnValue(
467
- child as unknown as ReturnType<
468
- typeof emulatorProcess.startDetachedProcess
469
- >,
470
- );
471
-
472
- const startPromise = startEmulator(
473
- 'Pixel_8_API_35',
474
- 'clean-snapshot-generation',
475
- );
476
- await vi.runAllTimersAsync();
477
- await startPromise;
478
-
479
- expect(startDetachedProcess).toHaveBeenCalledWith(
480
- expect.stringMatching(/emulator$/),
481
- expect.arrayContaining(['-no-snapshot-load']),
482
- );
483
- expect(startDetachedProcess.mock.calls[0]?.[1]).not.toContain(
484
- '-no-snapshot-save',
485
- );
486
- });
487
-
488
- it('passes snapshot reuse args to the emulator process', async () => {
489
- vi.useFakeTimers();
490
- const child = createMockChildProcess();
491
- vi.spyOn(tools, 'spawn')
492
- .mockResolvedValueOnce({
493
- stdout: 'List of devices attached\nemulator-5554\tdevice\n',
494
- } as Awaited<ReturnType<typeof tools.spawn>>)
495
- .mockResolvedValueOnce({
496
- stdout: 'Pixel_8_API_35\n',
497
- } as Awaited<ReturnType<typeof tools.spawn>>);
498
- const startDetachedProcess = vi
499
- .spyOn(emulatorProcess, 'startDetachedProcess')
500
- .mockReturnValue(
501
- child as unknown as ReturnType<
502
- typeof emulatorProcess.startDetachedProcess
503
- >,
504
- );
505
-
506
- const startPromise = startEmulator('Pixel_8_API_35', 'snapshot-reuse');
507
- await vi.runAllTimersAsync();
508
- await startPromise;
509
-
510
- expect(startDetachedProcess).toHaveBeenCalledWith(
511
- expect.stringMatching(/emulator$/),
512
- expect.arrayContaining(['-no-snapshot-save']),
513
- );
514
- expect(startDetachedProcess.mock.calls[0]?.[1]).not.toContain(
515
- '-no-snapshot-load',
516
- );
517
- });
518
-
519
- it('aborts while waiting for an emulator to boot', async () => {
520
- vi.useFakeTimers();
521
- vi.spyOn(tools, 'spawn').mockResolvedValue({
522
- stdout: 'List of devices attached\n\n',
523
- } as Awaited<ReturnType<typeof tools.spawn>>);
524
- const controller = new AbortController();
525
- const waitPromise = waitForBoot('Pixel_8_API_35', controller.signal);
526
-
527
- await vi.advanceTimersByTimeAsync(1000);
528
- controller.abort(createAbortError());
529
-
530
- await expect(waitPromise).rejects.toBeInstanceOf(DOMException);
531
- });
532
-
533
- it('aborts while waiting for boot completion', async () => {
534
- vi.useFakeTimers();
535
- const spawnSpy = vi.spyOn(tools, 'spawn');
536
- spawnSpy
537
- .mockResolvedValueOnce({
538
- stdout: 'List of devices attached\nemulator-5554\tdevice\n',
539
- } as Awaited<ReturnType<typeof tools.spawn>>)
540
- .mockResolvedValueOnce({
541
- stdout: 'Pixel_8_API_35\n',
542
- } as Awaited<ReturnType<typeof tools.spawn>>)
543
- .mockResolvedValueOnce({
544
- stdout: '0\n',
545
- } as Awaited<ReturnType<typeof tools.spawn>>);
546
- const controller = new AbortController();
547
- const waitPromise = waitForBoot('Pixel_8_API_35', controller.signal);
548
-
549
- await vi.advanceTimersByTimeAsync(1000);
550
- controller.abort(createAbortError());
551
-
552
- await expect(waitPromise).rejects.toBeInstanceOf(DOMException);
553
- });
554
-
555
- it('treats transient adb shell failures as not-yet-booted', async () => {
556
- vi.useFakeTimers();
557
- const spawnSpy = vi.spyOn(tools, 'spawn');
558
- const transientShellError = Object.assign(new Error('adb shell failed'), {
559
- exitCode: 1,
560
- });
561
- Object.setPrototypeOf(transientShellError, SubprocessError.prototype);
562
-
563
- spawnSpy
564
- .mockResolvedValueOnce({
565
- stdout: 'List of devices attached\nemulator-5554\tdevice\n',
566
- } as Awaited<ReturnType<typeof tools.spawn>>)
567
- .mockResolvedValueOnce({
568
- stdout: 'Pixel_8_API_35\n',
569
- } as Awaited<ReturnType<typeof tools.spawn>>)
570
- .mockRejectedValueOnce(transientShellError)
571
- .mockResolvedValueOnce({
572
- stdout: 'List of devices attached\nemulator-5554\tdevice\n',
573
- } as Awaited<ReturnType<typeof tools.spawn>>)
574
- .mockResolvedValueOnce({
575
- stdout: 'Pixel_8_API_35\n',
576
- } as Awaited<ReturnType<typeof tools.spawn>>)
577
- .mockResolvedValueOnce({
578
- stdout: '1\n',
579
- } as Awaited<ReturnType<typeof tools.spawn>>);
580
-
581
- const waitPromise = waitForBoot(
582
- 'Pixel_8_API_35',
583
- new AbortController().signal,
584
- );
585
-
586
- await vi.advanceTimersByTimeAsync(1000);
587
-
588
- await expect(waitPromise).resolves.toBe('emulator-5554');
589
- expect(spawnSpy).toHaveBeenCalledTimes(6);
590
- });
591
-
592
- it('waits for an emulator to disconnect from adb', async () => {
593
- vi.useFakeTimers();
594
- const spawnSpy = vi.spyOn(tools, 'spawn');
595
-
596
- spawnSpy
597
- .mockResolvedValueOnce({
598
- stdout: 'List of devices attached\nemulator-5554\tdevice\n',
599
- } as Awaited<ReturnType<typeof tools.spawn>>)
600
- .mockResolvedValueOnce({
601
- stdout: 'List of devices attached\n\n',
602
- } as Awaited<ReturnType<typeof tools.spawn>>);
603
-
604
- const waitPromise = waitForEmulatorDisconnect(
605
- 'emulator-5554',
606
- new AbortController().signal,
607
- );
608
-
609
- await vi.advanceTimersByTimeAsync(1000);
610
-
611
- await expect(waitPromise).resolves.toBeUndefined();
612
- expect(spawnSpy).toHaveBeenCalledTimes(2);
613
- });
614
-
615
- it('grants requested dangerous permissions to an app', async () => {
616
- const { grantPermissions } = await import('../adb.js');
617
- const spawnSpy = vi
618
- .spyOn(tools, 'spawn')
619
- .mockResolvedValueOnce({
620
- stdout: 'package:com.example.app\n',
621
- } as Awaited<ReturnType<typeof tools.spawn>>)
622
- .mockResolvedValueOnce({
623
- stdout: `requested permissions:\n android.permission.CAMERA\n android.permission.INTERNET\n android.permission.ACCESS_FINE_LOCATION\ninstall permissions:\n`,
624
- } as Awaited<ReturnType<typeof tools.spawn>>)
625
- .mockResolvedValueOnce({
626
- stdout: `Dangerous Permissions:\n permission:android.permission.CAMERA\n permission:android.permission.ACCESS_FINE_LOCATION\n`,
627
- } as Awaited<ReturnType<typeof tools.spawn>>)
628
- .mockResolvedValue({
629
- stdout: '',
630
- } as Awaited<ReturnType<typeof tools.spawn>>);
631
-
632
- await grantPermissions('emulator-5554', 'com.example.app');
633
-
634
- expect(spawnSpy).toHaveBeenCalledTimes(5);
635
- expect(spawnSpy).toHaveBeenNthCalledWith(1, expect.any(String), [
636
- '-s',
637
- 'emulator-5554',
638
- 'shell',
639
- 'pm',
640
- 'list',
641
- 'packages',
642
- 'com.example.app',
643
- ]);
644
- expect(spawnSpy).toHaveBeenNthCalledWith(2, expect.any(String), [
645
- '-s',
646
- 'emulator-5554',
647
- 'shell',
648
- 'dumpsys',
649
- 'package',
650
- 'com.example.app',
651
- ]);
652
- expect(spawnSpy).toHaveBeenNthCalledWith(3, expect.any(String), [
653
- '-s',
654
- 'emulator-5554',
655
- 'shell',
656
- 'pm',
657
- 'list',
658
- 'permissions',
659
- '-g',
660
- '-d',
661
- ]);
662
- expect(spawnSpy).toHaveBeenNthCalledWith(4, expect.any(String), [
663
- '-s',
664
- 'emulator-5554',
665
- 'shell',
666
- 'pm',
667
- 'grant',
668
- 'com.example.app',
669
- 'android.permission.CAMERA',
670
- ]);
671
- expect(spawnSpy).toHaveBeenNthCalledWith(5, expect.any(String), [
672
- '-s',
673
- 'emulator-5554',
674
- 'shell',
675
- 'pm',
676
- 'grant',
677
- 'com.example.app',
678
- 'android.permission.ACCESS_FINE_LOCATION',
679
- ]);
680
- });
681
-
682
- it('does nothing when the app has no grantable dangerous permissions', async () => {
683
- const { grantPermissions } = await import('../adb.js');
684
- const spawnSpy = vi
685
- .spyOn(tools, 'spawn')
686
- .mockResolvedValueOnce({
687
- stdout: 'package:com.example.app\n',
688
- } as Awaited<ReturnType<typeof tools.spawn>>)
689
- .mockResolvedValueOnce({
690
- stdout: `requested permissions:\n android.permission.INTERNET\ninstall permissions:\n`,
691
- } as Awaited<ReturnType<typeof tools.spawn>>)
692
- .mockResolvedValueOnce({
693
- stdout: `Dangerous Permissions:\n permission:android.permission.CAMERA\n`,
694
- } as Awaited<ReturnType<typeof tools.spawn>>);
695
-
696
- await grantPermissions('emulator-5554', 'com.example.app');
697
-
698
- expect(spawnSpy).toHaveBeenCalledTimes(3);
699
- });
700
- });