@houwert/conductor 0.16.0 → 0.17.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.
- package/dist/commands/assert-visible.js +2 -1
- package/dist/commands/clipboard.js +84 -0
- package/dist/commands/crashes.js +262 -0
- package/dist/commands/debug.js +244 -0
- package/dist/commands/flow-record.js +58 -0
- package/dist/commands/gestures.js +196 -0
- package/dist/commands/inspect.js +41 -1
- package/dist/commands/metro.js +109 -0
- package/dist/commands/network.js +194 -0
- package/dist/commands/profile.js +300 -0
- package/dist/commands/run-sequence.js +124 -0
- package/dist/commands/scroll-until-visible.js +17 -0
- package/dist/commands/start-device.js +18 -0
- package/dist/commands/tap.js +2 -1
- package/dist/commands/workspace.js +162 -0
- package/dist/drivers/android.js +9 -0
- package/dist/drivers/direct-ios-selector.js +70 -0
- package/dist/drivers/element-resolver.js +189 -0
- package/dist/drivers/flow-recorder.js +135 -0
- package/dist/drivers/flow-runner.js +5 -3
- package/dist/drivers/ios.js +87 -2
- package/dist/drivers/metro-cdp.js +291 -0
- package/dist/drivers/metro-scripts.js +399 -0
- package/dist/drivers/wait.js +20 -2
- package/dist/index.js +262 -1
- package/dist/runner.js +18 -0
- package/package.json +1 -1
- package/proto/conductor_android.proto +29 -0
package/dist/index.js
CHANGED
|
@@ -30,6 +30,15 @@ const daemon_js_1 = require("./commands/daemon.js");
|
|
|
30
30
|
const install_js_1 = require("./commands/install.js");
|
|
31
31
|
const device_pool_js_1 = require("./commands/device-pool.js");
|
|
32
32
|
const run_parallel_js_1 = require("./commands/run-parallel.js");
|
|
33
|
+
const run_sequence_js_1 = require("./commands/run-sequence.js");
|
|
34
|
+
const gestures_js_1 = require("./commands/gestures.js");
|
|
35
|
+
const workspace_js_1 = require("./commands/workspace.js");
|
|
36
|
+
const debug_js_1 = require("./commands/debug.js");
|
|
37
|
+
const network_js_1 = require("./commands/network.js");
|
|
38
|
+
const flow_record_js_1 = require("./commands/flow-record.js");
|
|
39
|
+
const profile_js_1 = require("./commands/profile.js");
|
|
40
|
+
const crashes_js_1 = require("./commands/crashes.js");
|
|
41
|
+
const flow_recorder_js_1 = require("./drivers/flow-recorder.js");
|
|
33
42
|
const foreground_app_js_1 = require("./commands/foreground-app.js");
|
|
34
43
|
const list_apps_js_1 = require("./commands/list-apps.js");
|
|
35
44
|
const copy_app_js_1 = require("./commands/copy-app.js");
|
|
@@ -47,6 +56,8 @@ const stop_device_js_1 = require("./commands/stop-device.js");
|
|
|
47
56
|
const delete_device_js_1 = require("./commands/delete-device.js");
|
|
48
57
|
const logs_js_1 = require("./commands/logs.js");
|
|
49
58
|
const memory_js_1 = require("./commands/memory.js");
|
|
59
|
+
const metro_js_1 = require("./commands/metro.js");
|
|
60
|
+
const clipboard_js_1 = require("./commands/clipboard.js");
|
|
50
61
|
const device_picker_js_1 = require("./device-picker.js");
|
|
51
62
|
const update_check_js_1 = require("./update-check.js");
|
|
52
63
|
const pkg_root_js_1 = require("./pkg-root.js");
|
|
@@ -93,8 +104,19 @@ const COMMAND_HELP = {
|
|
|
93
104
|
'daemon-status': daemon_js_1.HELP_DAEMON_STATUS,
|
|
94
105
|
'device-pool': device_pool_js_1.HELP,
|
|
95
106
|
'run-parallel': run_parallel_js_1.HELP,
|
|
107
|
+
'run-sequence': run_sequence_js_1.HELP,
|
|
108
|
+
gestures: gestures_js_1.HELP,
|
|
109
|
+
workspace: workspace_js_1.HELP,
|
|
110
|
+
debug: debug_js_1.HELP,
|
|
111
|
+
network: network_js_1.HELP,
|
|
112
|
+
'flow record': flow_record_js_1.HELP,
|
|
113
|
+
profile: profile_js_1.HELP,
|
|
114
|
+
crashes: crashes_js_1.HELP,
|
|
96
115
|
logs: logs_js_1.HELP,
|
|
97
116
|
memory: memory_js_1.HELP,
|
|
117
|
+
metro: metro_js_1.HELP,
|
|
118
|
+
clipboard: clipboard_js_1.HELP,
|
|
119
|
+
paste: ' paste Trigger OS-level paste (or type clipboard on iOS)',
|
|
98
120
|
};
|
|
99
121
|
const OPTIONS_HELP = `Options:
|
|
100
122
|
--device <id> Target device ID (also keys the session and daemon)
|
|
@@ -133,6 +155,7 @@ async function main() {
|
|
|
133
155
|
'optional',
|
|
134
156
|
'benchmark',
|
|
135
157
|
'dump',
|
|
158
|
+
'tappable',
|
|
136
159
|
'objects',
|
|
137
160
|
'heap',
|
|
138
161
|
'leaks',
|
|
@@ -173,6 +196,24 @@ async function main() {
|
|
|
173
196
|
'vs',
|
|
174
197
|
'top',
|
|
175
198
|
'filter',
|
|
199
|
+
'port',
|
|
200
|
+
'target',
|
|
201
|
+
'at',
|
|
202
|
+
'file',
|
|
203
|
+
'scale',
|
|
204
|
+
'center',
|
|
205
|
+
'degrees',
|
|
206
|
+
'angle',
|
|
207
|
+
'limit',
|
|
208
|
+
'method',
|
|
209
|
+
'body',
|
|
210
|
+
'header',
|
|
211
|
+
'url',
|
|
212
|
+
'out',
|
|
213
|
+
'track',
|
|
214
|
+
'interval',
|
|
215
|
+
'app',
|
|
216
|
+
'since',
|
|
176
217
|
],
|
|
177
218
|
alias: { h: 'help', v: 'verbose', V: 'version' },
|
|
178
219
|
});
|
|
@@ -207,6 +248,8 @@ async function main() {
|
|
|
207
248
|
'copy-app',
|
|
208
249
|
'device-pool',
|
|
209
250
|
'run-parallel',
|
|
251
|
+
'metro',
|
|
252
|
+
'workspace',
|
|
210
253
|
// `logs --list` and `logs --source metro` only query Metro on localhost — no device needed
|
|
211
254
|
// `logs` always needs a device session — Metro discovery is device-scoped.
|
|
212
255
|
// `daemon-stop --all` stops every daemon — no device needed
|
|
@@ -471,7 +514,11 @@ async function main() {
|
|
|
471
514
|
break;
|
|
472
515
|
}
|
|
473
516
|
case 'inspect':
|
|
474
|
-
exitCode = await (0, inspect_js_1.inspect)(opts, sessionName, {
|
|
517
|
+
exitCode = await (0, inspect_js_1.inspect)(opts, sessionName, {
|
|
518
|
+
dump: argv['dump'],
|
|
519
|
+
at: argv['at'],
|
|
520
|
+
tappableOnly: argv['tappable'],
|
|
521
|
+
});
|
|
475
522
|
break;
|
|
476
523
|
case 'focused':
|
|
477
524
|
exitCode = await (0, focused_js_1.focused)(opts, sessionName, {
|
|
@@ -542,17 +589,231 @@ async function main() {
|
|
|
542
589
|
exitCode = await (0, device_pool_js_1.devicePool)(action, releaseId, opts);
|
|
543
590
|
break;
|
|
544
591
|
}
|
|
592
|
+
case 'run-sequence': {
|
|
593
|
+
const file = argv['file'] ?? rest[0];
|
|
594
|
+
exitCode = await (0, run_sequence_js_1.runSequence)(file, opts, sessionName);
|
|
595
|
+
break;
|
|
596
|
+
}
|
|
545
597
|
case 'run-parallel': {
|
|
546
598
|
const flowsDir = argv['flows-dir'] ?? rest[0] ?? '';
|
|
547
599
|
exitCode = await (0, run_parallel_js_1.runParallel)(flowsDir, opts);
|
|
548
600
|
break;
|
|
549
601
|
}
|
|
602
|
+
case 'crashes': {
|
|
603
|
+
const sub = (rest[0] ?? 'list').toLowerCase();
|
|
604
|
+
if (sub === 'list') {
|
|
605
|
+
exitCode = await (0, crashes_js_1.crashesList)(opts, sessionName, {
|
|
606
|
+
app: argv['app'],
|
|
607
|
+
since: argv['since'],
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
else if (sub === 'show') {
|
|
611
|
+
exitCode = await (0, crashes_js_1.crashesShow)(rest[1] ?? '', opts);
|
|
612
|
+
}
|
|
613
|
+
else if (sub === 'tail') {
|
|
614
|
+
exitCode = await (0, crashes_js_1.crashesTail)(opts, sessionName);
|
|
615
|
+
}
|
|
616
|
+
else {
|
|
617
|
+
console.error('Usage: conductor crashes <list|show|tail>');
|
|
618
|
+
exitCode = 1;
|
|
619
|
+
}
|
|
620
|
+
break;
|
|
621
|
+
}
|
|
622
|
+
case 'profile': {
|
|
623
|
+
const sub = (rest[0] ?? '').toLowerCase();
|
|
624
|
+
const port = argv['port'] !== undefined ? Number(argv['port']) : undefined;
|
|
625
|
+
const targetIndex = argv['target'] !== undefined ? Number(argv['target']) : undefined;
|
|
626
|
+
if (sub === 'cpu') {
|
|
627
|
+
const durationSec = argv['duration'] !== undefined ? Number(argv['duration']) : 10;
|
|
628
|
+
exitCode = await (0, profile_js_1.profileCpu)(opts, sessionName, {
|
|
629
|
+
durationSec,
|
|
630
|
+
out: argv['out'],
|
|
631
|
+
appId: rest[1],
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
else if (sub === 'memory') {
|
|
635
|
+
const trackSec = argv['track'] !== undefined ? Number(argv['track']) : 10;
|
|
636
|
+
const intervalMs = argv['interval'] !== undefined ? Number(argv['interval']) : 1000;
|
|
637
|
+
exitCode = await (0, profile_js_1.profileMemory)(opts, sessionName, {
|
|
638
|
+
trackSec,
|
|
639
|
+
intervalMs,
|
|
640
|
+
appId: rest[1],
|
|
641
|
+
});
|
|
642
|
+
}
|
|
643
|
+
else if (sub === 'react') {
|
|
644
|
+
const sub2 = (rest[1] ?? '').toLowerCase();
|
|
645
|
+
const top = argv['top'] !== undefined ? Number(argv['top']) : 20;
|
|
646
|
+
if (sub2 === 'start') {
|
|
647
|
+
exitCode = await (0, profile_js_1.profileReactStart)(opts, sessionName, { port, targetIndex });
|
|
648
|
+
}
|
|
649
|
+
else if (sub2 === 'stop') {
|
|
650
|
+
exitCode = await (0, profile_js_1.profileReactStop)(opts, sessionName, { port, targetIndex }, top);
|
|
651
|
+
}
|
|
652
|
+
else {
|
|
653
|
+
console.error('Usage: conductor profile react <start|stop>');
|
|
654
|
+
exitCode = 1;
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
else {
|
|
658
|
+
console.error('Usage: conductor profile <cpu|memory|react> [args]');
|
|
659
|
+
exitCode = 1;
|
|
660
|
+
}
|
|
661
|
+
break;
|
|
662
|
+
}
|
|
663
|
+
case 'flow': {
|
|
664
|
+
const sub1 = (rest[0] ?? '').toLowerCase();
|
|
665
|
+
if (sub1 !== 'record') {
|
|
666
|
+
console.error('Usage: conductor flow record <start|finish|echo|status>');
|
|
667
|
+
exitCode = 1;
|
|
668
|
+
break;
|
|
669
|
+
}
|
|
670
|
+
const sub2 = (rest[1] ?? '').toLowerCase();
|
|
671
|
+
exitCode = await (0, flow_record_js_1.flowRecord)(sub2, rest.slice(2).map(String), opts, sessionName, argv);
|
|
672
|
+
break;
|
|
673
|
+
}
|
|
674
|
+
case 'network': {
|
|
675
|
+
const sub = (rest[0] ?? '').toLowerCase();
|
|
676
|
+
const port = argv['port'] !== undefined ? Number(argv['port']) : undefined;
|
|
677
|
+
const targetIndex = argv['target'] !== undefined ? Number(argv['target']) : undefined;
|
|
678
|
+
if (sub === 'logs') {
|
|
679
|
+
const limit = argv['limit'] !== undefined ? Number(argv['limit']) : undefined;
|
|
680
|
+
exitCode = await (0, network_js_1.networkLogs)(opts, sessionName, { port, targetIndex, limit });
|
|
681
|
+
}
|
|
682
|
+
else if (sub === 'request') {
|
|
683
|
+
const url = rest[1] ?? argv['url'] ?? '';
|
|
684
|
+
const rawHeaders = argv['header'];
|
|
685
|
+
const headers = Array.isArray(rawHeaders)
|
|
686
|
+
? rawHeaders
|
|
687
|
+
: rawHeaders
|
|
688
|
+
? [rawHeaders]
|
|
689
|
+
: [];
|
|
690
|
+
exitCode = await (0, network_js_1.networkRequest)(url, opts, sessionName, {
|
|
691
|
+
port,
|
|
692
|
+
targetIndex,
|
|
693
|
+
method: argv['method'],
|
|
694
|
+
body: argv['body'],
|
|
695
|
+
headers,
|
|
696
|
+
});
|
|
697
|
+
}
|
|
698
|
+
else {
|
|
699
|
+
console.error('Usage: conductor network <logs|request> [args]');
|
|
700
|
+
exitCode = 1;
|
|
701
|
+
}
|
|
702
|
+
break;
|
|
703
|
+
}
|
|
704
|
+
case 'debug': {
|
|
705
|
+
const sub = (rest[0] ?? '').toLowerCase();
|
|
706
|
+
const debugOpts = {
|
|
707
|
+
port: argv['port'] !== undefined ? Number(argv['port']) : undefined,
|
|
708
|
+
targetIndex: argv['target'] !== undefined ? Number(argv['target']) : undefined,
|
|
709
|
+
};
|
|
710
|
+
if (sub === 'status') {
|
|
711
|
+
exitCode = await (0, debug_js_1.debugStatus)(opts, sessionName, debugOpts);
|
|
712
|
+
}
|
|
713
|
+
else if (sub === 'evaluate' || sub === 'eval') {
|
|
714
|
+
const expr = rest.slice(1).join(' ');
|
|
715
|
+
exitCode = await (0, debug_js_1.debugEvaluate)(expr, opts, sessionName, debugOpts);
|
|
716
|
+
}
|
|
717
|
+
else if (sub === 'component-tree') {
|
|
718
|
+
exitCode = await (0, debug_js_1.debugComponentTree)(opts, sessionName, debugOpts);
|
|
719
|
+
}
|
|
720
|
+
else if (sub === 'inspect-element') {
|
|
721
|
+
const at = rest[1] ?? argv['at'] ?? '';
|
|
722
|
+
exitCode = await (0, debug_js_1.debugInspectElement)(at, opts, sessionName, debugOpts);
|
|
723
|
+
}
|
|
724
|
+
else if (sub === 'log-registry') {
|
|
725
|
+
exitCode = await (0, debug_js_1.debugLogRegistry)(opts, sessionName);
|
|
726
|
+
}
|
|
727
|
+
else if (sub === 'reload') {
|
|
728
|
+
exitCode = await (0, debug_js_1.debugReload)(opts, sessionName, debugOpts);
|
|
729
|
+
}
|
|
730
|
+
else {
|
|
731
|
+
console.error('Usage: conductor debug <status|evaluate|component-tree|inspect-element|log-registry|reload>');
|
|
732
|
+
exitCode = 1;
|
|
733
|
+
}
|
|
734
|
+
break;
|
|
735
|
+
}
|
|
736
|
+
case 'workspace': {
|
|
737
|
+
const sub = (rest[0] ?? 'info').toLowerCase();
|
|
738
|
+
exitCode = await (0, workspace_js_1.workspaceCmd)(sub, opts);
|
|
739
|
+
break;
|
|
740
|
+
}
|
|
741
|
+
case 'pinch':
|
|
742
|
+
exitCode = await (0, gestures_js_1.pinch)(opts, sessionName, {
|
|
743
|
+
scale: argv['scale'] !== undefined ? Number(argv['scale']) : undefined,
|
|
744
|
+
center: argv['center'],
|
|
745
|
+
duration: argv['duration'] !== undefined ? Number(argv['duration']) : undefined,
|
|
746
|
+
angle: argv['angle'] !== undefined ? Number(argv['angle']) : undefined,
|
|
747
|
+
});
|
|
748
|
+
break;
|
|
749
|
+
case 'rotate-gesture':
|
|
750
|
+
exitCode = await (0, gestures_js_1.rotateGesture)(opts, sessionName, {
|
|
751
|
+
degrees: argv['degrees'] !== undefined ? Number(argv['degrees']) : undefined,
|
|
752
|
+
center: argv['center'],
|
|
753
|
+
duration: argv['duration'] !== undefined ? Number(argv['duration']) : undefined,
|
|
754
|
+
});
|
|
755
|
+
break;
|
|
756
|
+
case 'gesture': {
|
|
757
|
+
const file = argv['file'];
|
|
758
|
+
const rawJson = !file ? rest.join(' ') : undefined;
|
|
759
|
+
exitCode = await (0, gestures_js_1.gesture)(rawJson, file, opts, sessionName);
|
|
760
|
+
break;
|
|
761
|
+
}
|
|
762
|
+
case 'clipboard': {
|
|
763
|
+
const sub = (rest[0] ?? '').toLowerCase();
|
|
764
|
+
if (sub === 'read') {
|
|
765
|
+
exitCode = await (0, clipboard_js_1.clipboardRead)(opts, sessionName);
|
|
766
|
+
}
|
|
767
|
+
else if (sub === 'write') {
|
|
768
|
+
const text = rest.slice(1).join(' ');
|
|
769
|
+
exitCode = await (0, clipboard_js_1.clipboardWrite)(text, opts, sessionName);
|
|
770
|
+
}
|
|
771
|
+
else {
|
|
772
|
+
console.error('Usage: conductor clipboard <read|write> [text]');
|
|
773
|
+
exitCode = 1;
|
|
774
|
+
}
|
|
775
|
+
break;
|
|
776
|
+
}
|
|
777
|
+
case 'paste':
|
|
778
|
+
exitCode = await (0, clipboard_js_1.paste)(opts, sessionName);
|
|
779
|
+
break;
|
|
780
|
+
case 'metro': {
|
|
781
|
+
const sub = (rest[0] ?? '').toLowerCase();
|
|
782
|
+
const port = argv['port'] !== undefined ? Number(argv['port']) : undefined;
|
|
783
|
+
const targetIndex = argv['target'] !== undefined ? Number(argv['target']) : undefined;
|
|
784
|
+
const metroSession = argv['device'] ?? 'default';
|
|
785
|
+
if (sub === 'stop') {
|
|
786
|
+
exitCode = await (0, metro_js_1.metroStop)(opts, { port });
|
|
787
|
+
}
|
|
788
|
+
else if (sub === 'reload') {
|
|
789
|
+
exitCode = await (0, metro_js_1.metroReload)(opts, metroSession, { port, targetIndex });
|
|
790
|
+
}
|
|
791
|
+
else {
|
|
792
|
+
console.error('Usage: conductor metro <stop|reload> [--port N] [--target N]');
|
|
793
|
+
exitCode = 1;
|
|
794
|
+
}
|
|
795
|
+
break;
|
|
796
|
+
}
|
|
550
797
|
default:
|
|
551
798
|
// Should be unreachable — unknown commands are caught before device resolution.
|
|
552
799
|
console.error(`Unknown command: ${command}`);
|
|
553
800
|
console.error('Run `conductor --help` for usage.');
|
|
554
801
|
exitCode = 1;
|
|
555
802
|
}
|
|
803
|
+
// Flow recording — append a YAML step for action commands that succeeded.
|
|
804
|
+
if (exitCode === 0 && !NO_DEVICE_COMMANDS.has(command) && command !== 'flow') {
|
|
805
|
+
try {
|
|
806
|
+
const active = await (0, flow_recorder_js_1.getActiveRecording)(sessionName);
|
|
807
|
+
if (active) {
|
|
808
|
+
const step = (0, flow_recorder_js_1.commandToYamlStep)(command, rest.map(String), argv);
|
|
809
|
+
if (step)
|
|
810
|
+
(0, flow_recorder_js_1.appendStep)(active, step);
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
catch {
|
|
814
|
+
// Recording is best-effort — never fail the command for a bookkeeping issue.
|
|
815
|
+
}
|
|
816
|
+
}
|
|
556
817
|
process.exit(exitCode);
|
|
557
818
|
}
|
|
558
819
|
main().catch((err) => {
|
package/dist/runner.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.detectFirstDevice = detectFirstDevice;
|
|
4
4
|
exports.getDriver = getDriver;
|
|
5
|
+
exports.prewarmDriver = prewarmDriver;
|
|
5
6
|
exports.runDirect = runDirect;
|
|
6
7
|
exports.spawnCommand = spawnCommand;
|
|
7
8
|
exports.runInlineFlow = runInlineFlow;
|
|
@@ -194,6 +195,23 @@ async function getDriver(sessionName = 'default') {
|
|
|
194
195
|
_driverCache.set(sessionName, driver);
|
|
195
196
|
return driver;
|
|
196
197
|
}
|
|
198
|
+
/**
|
|
199
|
+
* Eagerly start the device daemon (and its XCTest driver) for `deviceId` so
|
|
200
|
+
* the runner is already warm by the time the first interaction command runs.
|
|
201
|
+
*
|
|
202
|
+
* `start-device` is the natural prewarm point: bringing the driver up there
|
|
203
|
+
* moves the one-off startup cost off the first `tap-on` / `assert-visible`.
|
|
204
|
+
* Best-effort — failures are swallowed since a real command will surface any
|
|
205
|
+
* genuine startup error with a proper message.
|
|
206
|
+
*/
|
|
207
|
+
async function prewarmDriver(deviceId) {
|
|
208
|
+
try {
|
|
209
|
+
await (0, client_js_1.startDaemon)(deviceId);
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
/* best-effort: a later command will report a real failure */
|
|
213
|
+
}
|
|
214
|
+
}
|
|
197
215
|
/**
|
|
198
216
|
* Execute a function with the driver for the given session.
|
|
199
217
|
* Returns a RunResult for consistent error handling across commands.
|
package/package.json
CHANGED
|
@@ -12,6 +12,8 @@ service ConductorDriver {
|
|
|
12
12
|
|
|
13
13
|
rpc tap(TapRequest) returns (TapResponse) {}
|
|
14
14
|
|
|
15
|
+
rpc gesturePath(GesturePathRequest) returns (GesturePathResponse) {}
|
|
16
|
+
|
|
15
17
|
rpc inputText(InputTextRequest) returns (InputTextResponse) {}
|
|
16
18
|
|
|
17
19
|
rpc eraseAllText(EraseAllTextRequest) returns (EraseAllTextResponse) {}
|
|
@@ -76,6 +78,33 @@ message TapRequest {
|
|
|
76
78
|
|
|
77
79
|
message TapResponse {}
|
|
78
80
|
|
|
81
|
+
// One step in a finger's path.
|
|
82
|
+
// x, y pixel coordinates on the device's natural orientation.
|
|
83
|
+
// dt_ms delay in milliseconds since the previous step. For the first
|
|
84
|
+
// step in a path, this is the initial offset before the touch
|
|
85
|
+
// goes down.
|
|
86
|
+
message GestureStep {
|
|
87
|
+
double x = 1;
|
|
88
|
+
double y = 2;
|
|
89
|
+
uint32 dt_ms = 3;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// A single finger's traced path. Must have at least one step. The first step
|
|
93
|
+
// is the down position; subsequent steps are moves; the finger lifts after
|
|
94
|
+
// the last step.
|
|
95
|
+
message GestureFingerPath {
|
|
96
|
+
repeated GestureStep steps = 1;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Multi-finger gesture request. paths.size() == 1 is a single-finger gesture;
|
|
100
|
+
// paths.size() >= 2 dispatches a true multi-touch gesture via
|
|
101
|
+
// UiAutomation.dispatchGesture (API 24+).
|
|
102
|
+
message GesturePathRequest {
|
|
103
|
+
repeated GestureFingerPath paths = 1;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
message GesturePathResponse {}
|
|
107
|
+
|
|
79
108
|
message InputTextRequest {
|
|
80
109
|
string text = 1;
|
|
81
110
|
}
|