Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor!: make the Fluttium driver agnostic to where it runs #293

Draft
wants to merge 9 commits into
base: main
Choose a base branch
from
2 changes: 1 addition & 1 deletion actions/log_action/analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -1 +1 @@
include: package:very_good_analysis/analysis_options.4.0.0.yaml
include: package:very_good_analysis/analysis_options.5.1.0.yaml
2 changes: 1 addition & 1 deletion actions/log_action/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@ dev_dependencies:
flutter_test:
sdk: flutter
mocktail: ">=0.3.0 <2.0.0"
very_good_analysis: ^4.0.0
very_good_analysis: ^5.1.0
2 changes: 1 addition & 1 deletion bricks/fluttium_launcher/hooks/analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -1 +1 @@
include: package:very_good_analysis/analysis_options.4.0.0.yaml
include: package:very_good_analysis/analysis_options.5.1.0.yaml
2 changes: 1 addition & 1 deletion bricks/fluttium_launcher/hooks/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ dependencies:

dev_dependencies:
test: ^1.19.2
very_good_analysis: ^4.0.0
very_good_analysis: ^5.1.0
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,9 @@ import 'package:fluttium_interfaces/fluttium_interfaces.dart';
{{#actions}}
import 'package:{{name.snakeCase()}}/{{name.snakeCase()}}.dart' as {{name.snakeCase()}};{{/actions}}

Future<void> run(WidgetsBinding binding) async {
Future<Tester> run(WidgetsBinding binding) async {
final registry = Registry();{{#actions}}
{{name.snakeCase()}}.register(registry);{{/actions}}

final tester = Tester(binding, registry);
await tester.ready();

final actions = await tester.convert([{{#steps}}
UserFlowStep.fromJson({{{step}}}),{{/steps}}
]);

for (final action in actions) {
await action();
}
return Tester(binding, registry);
}
2 changes: 1 addition & 1 deletion bricks/fluttium_test_runner/hooks/analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -1 +1 @@
include: package:very_good_analysis/analysis_options.4.0.0.yaml
include: package:very_good_analysis/analysis_options.5.1.0.yaml
2 changes: 1 addition & 1 deletion bricks/fluttium_test_runner/hooks/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ dependencies:

dev_dependencies:
test: ^1.19.2
very_good_analysis: ^4.0.0
very_good_analysis: ^5.1.0
2 changes: 1 addition & 1 deletion docs/docs/getting-started/initializing-fluttium.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Once we run `fluttium init`, we should have a `fluttium.yaml` that looks like:
# The following defines the environment for your Fluttium project. It includes
# the version of Fluttium that the project requires.
environment:
fluttium: '>=0.1.0 <0.2.0'
fluttium: '>=0.1.0 <1.0.0'

# The driver can be configured with default values. Uncomment the following
# lines to automatically run Fluttium using a different flavor and dart-defines.
Expand Down
2 changes: 1 addition & 1 deletion example/analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
include: package:very_good_analysis/analysis_options.4.0.0.yaml
include: package:very_good_analysis/analysis_options.5.1.0.yaml
linter:
rules:
public_member_api_docs: false
5 changes: 5 additions & 0 deletions example/flows/counter_flow.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,23 @@ description: Testing the counter page
---
# Validate that the app page is displayed
- expectEnvironmentText:
- takeScreenshot: "screenshots/app_page.png"
# Go to the counter page
- pressOn: "Counter"
# Validate that the counter page is displayed and the counter is 0
- expectVisible: "Counter"
- takeScreenshot: "screenshots/counter_page.png"
- expectVisible: 0
# Increment and decrement the counter
- pressOn: "Increment"
- takeScreenshot: "screenshots/counter_1.png"
- expectVisible: 1
- pressOn: "Increment"
- expectVisible: 2
- takeScreenshot: "screenshots/counter_2.png"
- pressOn: "Decrement"
- expectVisible: 1
- takeScreenshot: "screenshots/counter_1_again.png"
# Return to the app page
- pressOn: "Back"
- expectEnvironmentText:
1 change: 1 addition & 0 deletions example/flows/progress_flow.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ description: Testing the progress page
# Start the progress
- pressOn: "Start"
- expectVisible: "Progress: \\d+%"
- takeScreenshot: "screenshots/progress.png"
- expectVisible: "Done"
# Return to the app page
- pressOn: "Back"
Expand Down
3 changes: 3 additions & 0 deletions example/flows/simple_menu_flow.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ description: Testing the simple menu page
- pressOn: "Simple Menu"
# Validate that the simple menu page is displayed and the menu button is visible
- expectVisible: "Simple Menu"
- generateSemanticsTree:
path: "semantics_tree.json"
prettify: true
- expectVisible: "Show Menu"
# Press the button using a long press
- longPressOn: "Show Menu"
Expand Down
2 changes: 1 addition & 1 deletion example/lib/simple_menu/view/simple_menu.dart
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class _SimpleMenuPageState extends State<SimpleMenuPage> {
child: Row(
children: [Text('Menu Item 1')],
),
)
),
],
context: context,
position: _getRelativeRect(widgetKey),
Expand Down
2 changes: 1 addition & 1 deletion example/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ dev_dependencies:
flutter_test:
sdk: flutter
mocktail: ^1.0.0
very_good_analysis: ^4.0.0
very_good_analysis: ^5.1.0

flutter:
uses-material-design: true
2 changes: 1 addition & 1 deletion packages/fluttium/analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -1 +1 @@
include: package:very_good_analysis/analysis_options.4.0.0.yaml
include: package:very_good_analysis/analysis_options.5.1.0.yaml
2 changes: 1 addition & 1 deletion packages/fluttium/lib/src/actions/clear_text.dart
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ class ClearText extends Action {
SystemChannels.textInput.codec.encodeMethodCall(
const MethodCall('TextInputClient.performSelectors', [
-1,
['deleteBackward:']
['deleteBackward:'],
]),
),
);
Expand Down
4 changes: 0 additions & 4 deletions packages/fluttium/lib/src/actions/scroll.dart
Original file line number Diff line number Diff line change
Expand Up @@ -52,16 +52,12 @@ class Scroll extends Action {
switch (direction) {
case AxisDirection.up:
scrollDelta = Offset(0, -speed);
break;
case AxisDirection.right:
scrollDelta = Offset(speed, 0);
break;
case AxisDirection.down:
scrollDelta = Offset(0, speed);
break;
case AxisDirection.left:
scrollDelta = Offset(-speed, 0);
break;
}

final end = clock.now().add(timeout);
Expand Down
46 changes: 25 additions & 21 deletions packages/fluttium/lib/src/registry.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,22 @@ import 'package:fluttium/fluttium.dart';
/// The registry of all the actions a [Tester] can perform.
/// {@endtemplate}
class Registry {
final Map<String, ActionRegistration> _actions = {
'tapOn': ActionRegistration(PressOn.new, shortHand: #text),
'inputText': ActionRegistration(WriteText.new, shortHand: #text),
// TODO(wolfen): deprecate the above action keys

'pressOn': ActionRegistration(PressOn.new, shortHand: #text),
'longPressOn': ActionRegistration(LongPressOn.new, shortHand: #text),
'clearText': ActionRegistration(ClearText.new, shortHand: #characters),
'writeText': ActionRegistration(WriteText.new, shortHand: #text),
'expectVisible': ActionRegistration(ExpectVisible.new, shortHand: #text),
'expectNotVisible':
ActionRegistration(ExpectNotVisible.new, shortHand: #text),
'takeScreenshot': ActionRegistration(TakeScreenshot.new, shortHand: #path),
'wait': ActionRegistration(Wait.new, shortHand: #milliseconds),
'scroll': ActionRegistration(
/// {@macro registry}
Registry() {
registerAction('pressOn', PressOn.new, shortHandIs: #text);
registerAction('longPressOn', LongPressOn.new, shortHandIs: #text);
registerAction('clearText', ClearText.new, shortHandIs: #characters);
registerAction('writeText', WriteText.new, shortHandIs: #text);
registerAction('expectVisible', ExpectVisible.new, shortHandIs: #text);
registerAction(
'expectNotVisible',
ExpectNotVisible.new,
shortHandIs: #text,
);
registerAction('takeScreenshot', TakeScreenshot.new, shortHandIs: #path);
registerAction('wait', Wait.new, shortHandIs: #milliseconds);
registerAction(
'scroll',
({
required String within,
required String until,
Expand All @@ -38,10 +39,11 @@ class Registry {
timeout: timeout,
),
aliases: const [
Alias(['in'], #within)
Alias(['in'], #within),
],
),
'swipe': ActionRegistration(
);
registerAction(
'swipe',
({
required String within,
required String until,
Expand All @@ -57,10 +59,12 @@ class Registry {
timeout: timeout,
),
aliases: const [
Alias(['in'], #within)
Alias(['in'], #within),
],
),
};
);
}

final Map<String, ActionRegistration> _actions = {};

/// Map of all the action that are registered.
UnmodifiableMapView<String, ActionRegistration> get actions =>
Expand Down
102 changes: 62 additions & 40 deletions packages/fluttium/lib/src/tester.dart
Original file line number Diff line number Diff line change
@@ -1,67 +1,90 @@
import 'dart:convert';
import 'dart:developer';

import 'package:clock/clock.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart' hide Action;
import 'package:fluttium/fluttium.dart';
import 'package:fluttium_interfaces/fluttium_interfaces.dart';
import 'package:fluttium_protocol/fluttium_protocol.dart';

/// {@template tester}
/// The tester that is used to execute the actions in a flow file.
/// {@endtemplate}
class Tester {
/// {@macro tester}
Tester(this._binding, this._registry, {Emitter? emitter})
: _emitter = emitter ?? Emitter(),
_semanticsHandle = _binding.ensureSemantics();
Tester(this._binding, this._registry)
: _semanticsHandle = _binding.ensureSemantics() {
registerExtension(
'ext.fluttium.ready',
(method, parameters) async {
try {
// await ready();
return ServiceExtensionResponse.result(
json.encode({'ready': true}),
);
} catch (err) {
return ServiceExtensionResponse.result(
json.encode({'ready': false, 'reason': err}),
);
}
},
);

final Emitter _emitter;
registerExtension(
'ext.fluttium.getActionDescription',
(method, parameters) async {
final action = _registry.getAction(
parameters['name']!,
json.decode(parameters['arguments']!),
);
return ServiceExtensionResponse.result(
json.encode({'description': action.description()}),
);
},
);

registerExtension(
'ext.fluttium.executeAction',
(method, parameters) async {
final action = _registry.getAction(
parameters['name']!,
json.decode(parameters['arguments']!),
);
try {
_storedFiles.clear();
final result = await action.execute(this);
return ServiceExtensionResponse.result(
json.encode({'success': result, 'files': _storedFiles}),
);
} catch (err) {
return ServiceExtensionResponse.result(
json.encode({'success': false, 'reason': err}),
);
}
},
);
}

final WidgetsBinding _binding;

final SemanticsHandle _semanticsHandle;

final Registry _registry;

final Map<String, dynamic> _storedFiles = {};

/// The files that were stored in the current action.
Map<String, dynamic> get storedFiles => Map.unmodifiable(_storedFiles);

SemanticsOwner get _semanticsOwner => _binding.pipelineOwner.semanticsOwner!;

/// The current screen's media query information.
MediaQueryData get mediaQuery =>
MediaQueryData.fromView(_binding.platformDispatcher.views.first);

/// Converts the [steps] into a list of executable actions.
Future<List<Future<void> Function()>> convert(
List<UserFlowStep> steps,
) async {
return Future.wait(
steps.map((step) async {
try {
final action = _registry.getAction(step.actionName, step.arguments);
final actionRepresentation = action.description();
await _emitter.announce(actionRepresentation);

return () async {
try {
await _emitter.start(actionRepresentation);
if (await action.execute(this)) {
return _emitter.done(actionRepresentation);
}
return _emitter.fail(actionRepresentation);
} catch (err) {
return _emitter.fail(actionRepresentation, reason: '$err');
}
};
} catch (err) {
await _emitter.fatal('$err');
rethrow;
}
}).toList(),
);
}

/// Store binary data with the given [fileName].
Future<void> storeFile(String fileName, Uint8List bytes) async {
await _emitter.store(fileName, bytes);
Future<void> storeFile(String fileName, List<int> bytes) async {
_storedFiles[fileName] = base64.encode(bytes);
}

/// Dispatch an event to the targets found by a hit test on its position.
Expand Down Expand Up @@ -169,8 +192,7 @@ class Tester {

/// Wait for the semantics tree to be fully build.
Future<void> ready() async {
while (_binding.pipelineOwner.semanticsOwner == null ||
_binding.pipelineOwner.semanticsOwner!.rootSemanticsNode == null) {
while (_binding.pipelineOwner.semanticsOwner?.rootSemanticsNode == null) {
await _binding.endOfFrame;
}
}
Expand Down
3 changes: 1 addition & 2 deletions packages/fluttium/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,10 @@ dependencies:
flutter:
sdk: flutter
fluttium_interfaces: ^0.1.0
fluttium_protocol: ^0.1.0

dev_dependencies:
fake_async: ^1.3.1
flutter_test:
sdk: flutter
mocktail: ^0.3.0
very_good_analysis: ^4.0.0
very_good_analysis: ^5.1.0
2 changes: 0 additions & 2 deletions packages/fluttium/pubspec_overrides.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
dependency_overrides:
fluttium_protocol:
path: ../fluttium_protocol
fluttium_interfaces:
path: ../fluttium_interfaces
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Matcher get isDeleteBackward {
'method': 'TextInputClient.performSelectors',
'args': [
-1,
['deleteBackward:']
['deleteBackward:'],
],
});

Expand Down
2 changes: 1 addition & 1 deletion packages/fluttium/test/helpers/matchers/is_text.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Matcher isText(String text) {
TextEditingValue(
text: text,
selection: TextSelection.collapsed(offset: text.length),
).toJSON()
).toJSON(),
],
});

Expand Down
Loading
Loading