Command
首先是最基础的一个概念Command
。它定义在lib/src/common/message.dart
中。
abstract class Command {
final Duration? timeout;
String get kind;
bool get requiresRootWidgetAttached => true;
const Command({ this.timeout });
// serialize deserialize
}
timeout
是等待command运行完成的最大等待时间,默认是nulgooglel,kind用来标记command的类型,而requiresRoo手势识别tW源码精灵永久兑换码idgetAttached
表示Command是否需要确保Widget树在运行前已经初始手势舞教学视频简单化完成。龚俊
Result
Result和Command对应,表示Command的运行结果。
abstract class Result {
const Result();
static const Result empty = _EmptyResult();
Map<String, dynamic> toJson();
}
通过将构造函数加上const来保证子类也是接口const,通过重写toJson
方法,来将结果序列化。_EmptyResult
就是通过重写这个返回一个空map。
finder
上面提到了command,那么comma源码之家nd有一个同样是抽象类的子类CommandWithTarget
。
abstract class CommandWithTarget extends Command {
final SerializableFinder finder;
@override
Map<String, String> serialize() =>
super.serialize()..addAll(finder.serialize());
}
它在command的基础上加了一个SerializableFinder
类型的finder属性。而SerializableFinde手势舞视频r
长下面这样。
abstract class SerializableFinder {
const SerializableFinder();
String get finderType;
@mustCallSuper
Map<String, String> serialize() => <String, String>{
'finderType': finderType,
};
}
它是flutter driver finders的基类,用于描述driver如何寻找元素。通过继承Serializab宫颈癌leFinder
就可以实现许多特定的finder。flutter driver中的finder有如下几种:
- ByToo工龄越长退休金越多吗ltipMessage 通过工具提示组件定位
- BySgoogleemanticsLabel 通过语义化标签
- ByText 通过文字定位
- ByValueKey 通过key进行定位
- B手势舞yType 通过组件类型
- PageBack 寻找Material或者Cupertino scaffold上的返回按钮
- Descendant 通过子组件定位源码精灵永久兑换码
- Ancestor 通过父组件定位
当然这些finder仅仅是包含了一些定义,和序列化反序列化的方法,但是并没有具体的查找操作。此外,对于finder的构建,有一个专门的工厂mixin DeserializeFinderFactory
。通过finderType来反序列化生成对应的finder。
mixin DeserializeFinderFactory {
/// Deserializes the finder from JSON generated by [SerializableFinder.serialize].
SerializableFinder deserializeFinder(Map<String, String> json) {
final String? finderType = json['finderType'];
switch (finderType) {
case 'ByType': return ByType.deserialize(json);
case 'ByValueKey': return ByValueKey.deserialize(json);
case 'ByTooltipMessage': return ByTooltipMessage.deserialize(json);
case 'BySemanticsLabel': return BySemanticsLabel.deserialize(json);
case 'ByText': return ByText.deserialize(json);
case 'PageBack': return const PageBack();
case 'Descendant': return Descendant.deserialize(json, this);
case 'Ancestor': return Ancestor.deserialize(json, this);
}
throw DriverError('Unsupported search specification type $finderType');
}
}
此外还有一个CreateFinderFactory
的mixingoogle翻译,它用于从Sergoogle中国iaGooglelizableFinder
创建Finder
。而Finder
是flutter_test中的一个抽象类。额,为接口测试用例设计什么要这样做呢,再往下看看应该就明白了。
常用Command/CommandWithTarget
手势相关:
- Tap
- Scroll 参数有每次移动的dx和dy,duration以及frequency
- ScrollIntoView 滚动手势舞finder定位的widget的可滚动父组件,直到widget完全可见。google网站登录入口
文本相关:
- GetText
- Entgoogle网站登录入口erText 这是一个Command,不包含findergoogle翻译属性
CommandHandlerFactorgoogle中国y
通过浏览command相关的源码,可以看到command只是一个定义,包含了一些属性,以及序列化手势和反序列化的方法,具体运行的操作,并没有包含其中。实际上运行的具体操作是在CommandH龚俊andlerFactory
这个mixin中定义的。
可以简单看一下tap
的实现。
Future<Result> _tap(Command command, WidgetController prober, CreateFinderFactoryfinderFactory) async {
final Tap tapCommand = command as Tap;
final Finder computedFinder = await waitForElement(
finderFactory.createFinder(tapCommand.finder).hitTestable(),
);
await prober.tap(computedFinder);
return Result.empty;
}
调用probe宫颈癌r的tap方法源码网站来实现点击。有个很有意思的工资超过5000怎么扣税地方,我们前面看了flutter_driver里的手势相关的操作,只有tap和scroll,稍微基google础一点的长按手势也没有支持,但是实际使用的是WidgetController
里的tap方手势密码法,可WidgetController
里是提供了longPress、drag等手势的实现。Google一下,大多数人都是用scroll来验证longPress,感觉有一点点怪。此源码时代外WidgetController
是flutter_test
包的中的类,看来要想知道具体怎么实现模拟点击那些操作,得看看flutter_testgoogle
的源码。模拟长按的测试代码如下:
test('test button longpress', () async {
final SerializableFinder btn = find.byValueKey('button');
await driver.waitFor(btn);
await driver.scroll(btn, 0, 0, Duration(milliseconds: 500));
});
FlutterDri手势识别ver
FlutterDriver
是一个抽象类,它有两个具体的实现WebFlutterDriver
和VMServiceFlutterDrive手势变化r
。以VMServiceFlutterDriver
为例进行分析。
connectTo
通过创建VmService client来连接到flutter应用。并通过client获取到main isolate。而创建Vm源码编辑器下载Service的在_waitAndConnect
这个方法中。构造函数如下。
VmService VmService(
Stream<dynamic> inStream,
void Function(String) writeMessage, {
Log? log,
Future<dynamic> Function()? disposeHandler,
Future<dynamic>? streamClosed,
})
通过WebSocket创建stream。
socket = await WebSocket.connect(webSocketUrl, headers: headers);
final StreamController<dynamic> controller = StreamController<dynamic>();
final Completer<void> streamClosedCompleter = Completer<void>();
socket.listen(
(dynamic data) => controller.add(data),
onDone: () => streamClosedCompleter.complete(),
);
final vms.VmService service = vms.VmService(
controller.stream,
socket.add,
disposeHandler: () => socket!.close(),
streamClosed: streamClosedCompleter.future
);
之后便是通过创建的VMService来发送指令。
final Future<Map<String, dynamic>> future = _serviceClient.callServiceExtension(
_flutterExtensionMethodName,
isolateId: _appIsolate.id,
args: serialized,
);
调用callServiceExtension
来调用特定服务的协议接口英文拓展,传入的args为序列化之后的command信息。在调用这个方法之前,需要先在vmservice中注册相关的服务。这也就是为什么需要在main函数里enableFlutterDriverExtension()
。
注册服接口英文务
Flutter应用是运行在Dar龚俊t VM上的,Dart VM内部提供了一套Web服务VMServ接口和抽象类的区别ice,通过 JSON-RPC 2.0 协议来访问Dart VM服务协议的库,使用VMService可以帮助我们获取app中的数据。Flutte手势舞教程视频慢动作r中使用registerServicGoogleeExtension
方法来完成服务注册,之后我们就可以在app 外公司让员工下班发手机电量截图部通过VMS源码编辑器下载ervice来调用对应的服务。
void registerServiceExtension({
required String name,
required ServiceExtensionCallback callback,
}) {
final String methodName = 'ext.flutter.$name';
developer.registerExtension(methodName, (String method, Map<String, String> parameters) async {
// 代码省略
late Map<String, dynamic> result;
try {
result = await callback(parameters);
} catch (exception, stack) {
}
result['type'] = '_extensionType';
result['method'] = method;
return developer.ServiceExtensionResponse.result(json.encode(result));
});
}
registerServiceExtension
就是注册方法,接受的入参就是服务名字 和 回调。
服务名字:就是 Flutter
和 Dart Vm
能够认识的服务标示,方法名字就是 VM 可以调用到的名字。
回调:就是 VM
调用服务名字时,Flutter 做出的google翻译反应
。
这里注意一点,我们传递的名字会被 包装成 ex源码交易平台t.flutter.$名字
的形式。
注册会调用 developer
的 registerExtension
方法。developer 是一个开发者包,里面有一个比较基础的 API
。最后调用的是native
的一个方法。
external _registerExtension(String method, ServiceExtensionHandler handler);
_DriverBinding
flutter_driver里注册服务是在enableFlutterDriverExtension
函数里完成的,里面调用了_DriverBinding
的构造函数。
class _DriverBinding extends BindingBase with SchedulerBinding, ServicesBinding, GestureBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding, TestDefaultBinaryMessengerBinding {
_DriverBinding(this._handler, this._silenceErrors, this._enableTextEntryEmulation, this.finders, this.commands);
final DataHandler? _handler;
final bool _silenceErrors;
final bool _enableTextEntryEmulation;
final List<FinderExtension>? finders;
final List<CommandExtension>? commands;
@override
void initServiceExtensions() {
super.initServiceExtensions();
final FlutterDriverExtension extension = FlutterDriverExtension(_handler, _silenceErrors, _enableTextEntryEmulation, finders: finders ?? const <FinderExtension>[], commands: commands ?? const <CommandExtension>[]);
registerServiceExtension(
name: _extensionMethodName,
callback: extension.call,
);
if (kIsWeb) {
registerWebServiceExtension(extension.call);
}
}
}
那么看代码可能有点迷惑。首先我们从它的父类入手。因为在调用子类的构造函数的时候,会调用父类的构造函数。下面是BindingBase
的构造函数,里面
BindingBase() {
developer.Timeline.startSync('Framework initialization');
assert(!_debugInitialized);
initInstances();
assert(_debugInitialized);
assert(!_debugServiceExtensionsRegistered);
initServiceExtensions();
assert(_debugServiceExtensionsRegistered);
developer.postEvent('Flutter.FrameworkInitialization', <String, String>{});
developer.Timeline.finishSync();
}
在构造函数中调用了initServiceExtensions
方法,这对应了_DriverBi手势变化nding
中重写的initServiceExtensions
方手势语法。并在此方法中进行服务注册,将FlutterDriverExtension
的call方法作为源码精灵永久兑换码参数传入。在call源码交易平台方法中,根据params中的command信息,调用handleCommand方法进行处理。而han手势识别dleCommand接口卡方法是通过混入CommandHandlerFactory
得google到的,并通过重写添加了对command的一些判断。
@override
Future<Result> handleCommand(Command command, WidgetController prober, CreateFinderFactory finderFactory) {
final String kind = command.kind;
if (_commandExtensions.containsKey(kind)) {
return _commandExtensions[kind]!.call(command, prober, finderFactory, this);
}
return super.handleCommand(command, prober, finderFactory);
}
这里的设计挺好的,可以借鉴一下,_commandExtensions
的存在提供了外部添加自定义Command的google接口,也就是在FlutterDriverExtension
构造函数中的一个可选参数command,通过在此传入一些自定义的Command,可以实现自定义Command和对应处理方法的功能。
最后再提一源码编辑器下载嘴,在Flutte接口测试rDriverExtension构造函数中还做了一件事,那就是registerTextInput
。有了它才可以输入框中google网站登录入口进行输入操作。
总结一下
最后大概总结一下flutter_driver的原理。首先,在需要进行UI自动化测试的Fluttegoogleplayr应用的main函数运行前,先向main isolate中注册我们的服务,也就是FlutterDriverExtension
中的call方法,用于处理command。然后是通过websocket,创建vmservice的c接口自动化lient,连公司让员工下班发手机电量截图接到我们的flutter应用。之后就是通接口crc错误计数过这个client来调用服务拓展,将command传递过去。具体的操作都是flutter 应用结合flutter_test
包来完成的。
参考文章
- Flutter 必知必会系列 —— mixin 和 BindingBase 的巧妙配合
- Da源码编辑器r接口crc错误计数t VM Service 实战