Skip to content

Commit

Permalink
Add Timeline and ScreenshotAnnotator (#57)
Browse files Browse the repository at this point in the history
Co-authored-by: Pascal Welsch <[email protected]>
  • Loading branch information
danielmolnar and passsy authored Nov 7, 2024
1 parent b3f0fc2 commit 08df699
Show file tree
Hide file tree
Showing 36 changed files with 3,695 additions and 245 deletions.
Binary file added lib/assets/Roboto-Regular.ttf
Binary file not shown.
9 changes: 9 additions & 0 deletions lib/spot.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export 'package:spot/src/screenshot/screenshot.dart'
Screenshot,
SelectorScreenshotExtension,
SnapshotScreenshotExtension,
TimelineSyncScreenshot,
takeScreenshot;
export 'package:spot/src/spot/default_selectors.dart'
show DefaultWidgetMatchers, DefaultWidgetSelectors;
Expand Down Expand Up @@ -92,6 +93,14 @@ export 'package:spot/src/spot/widget_selector.dart'
// ignore: deprecated_member_use_from_same_package
SingleWidgetSelector,
WidgetSelector;
export 'package:spot/src/timeline/timeline.dart'
show
Timeline,
TimelineEvent,
TimelineEventType,
TimelineMode,
globalTimelineMode,
timeline;
export 'package:spot/src/widgets/align.g.dart';
export 'package:spot/src/widgets/anytext.g.dart';
export 'package:spot/src/widgets/circularprogressindicator.g.dart';
Expand Down
82 changes: 72 additions & 10 deletions lib/src/act/act.dart
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import 'dart:async';
import 'dart:io';

import 'package:dartx/dartx_io.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:spot/spot.dart';
import 'package:spot/src/act/gestures.dart';
import 'package:spot/src/screenshot/screenshot_annotator.dart';
import 'package:spot/src/spot/snapshot.dart';

/// Top level entry point to interact with widgets on the screen.
Expand Down Expand Up @@ -74,6 +75,11 @@ class Act {
}

/// Triggers a tap event on a given widget.
/// If a [Timeline] is running, an annotated screenshot, indicating the tap
/// position, is added to the timeline.
///
/// See also:
/// - [Timeline]
Future<void> tap(WidgetSelector selector) async {
// Check if widget is in the widget tree. Throws if not.
final snapshot = selector.snapshot()..existsOnce();
Expand Down Expand Up @@ -103,9 +109,18 @@ class Act {
_reportPartialCoverage(pokablePositions, snapshot);

final positionToTap = pokablePositions.mostCenterHittablePosition!;

final binding = TestWidgetsFlutterBinding.instance;

if (timeline.mode != TimelineMode.off) {
final screenshot = timeline.takeScreenshotSync();
timeline.addEvent(
details: 'Tap ${selector.toStringBreadcrumb()}',
eventType: 'Tap Event',
screenshot: screenshot,
color: Colors.blue,
);
}

// Finally, tap the widget by sending a down and up event.
final downEvent = PointerDownEvent(position: positionToTap);
binding.handlePointerEvent(downEvent);
Expand Down Expand Up @@ -189,24 +204,69 @@ class Act {

bool isVisible = isTargetVisible();

final dragPosition = pokablePositions.mostCenterHittablePosition!;

void addDragEvent(String details, {double? direction}) {
if (timeline.mode != TimelineMode.off) {
final screenshot = timeline.takeScreenshotSync(
annotators: [
CrosshairAnnotator(centerPosition: dragPosition),
if (direction != null) ...[
ArrowAnnotator(
start: dragPosition - const Offset(40, 0),
end: dragPosition -
const Offset(40, 0) +
Offset(0, direction),
),
ArrowAnnotator(
start: dragPosition + const Offset(40, 0),
end: dragPosition +
const Offset(40, 0) +
Offset(0, direction),
),
],
],
);
timeline.addEvent(
eventType: 'Drag Event',
details: details,
screenshot: screenshot,
color: Colors.blue,
);
}
}

if (isVisible) {
addDragEvent('Widget $targetName found without dragging.');
return;
}
final dragPosition = pokablePositions.mostCenterHittablePosition!;

int iterations = 0;
while (iterations < maxIteration && !isVisible) {
final direction = moveStep.dy < 0 ? 'downwards' : 'upwards';

addDragEvent(
'Scrolling $direction from $dragPosition in order to find $targetName.',
direction: moveStep.dy,
);

int dragCount = 0;
while (dragCount < maxIteration && !isVisible) {
await gestures.drag(dragPosition, moveStep);
await binding.pump(duration);
iterations++;
dragCount++;
isVisible = isTargetVisible();
}

final totalDragged = moveStep * iterations.toDouble();
final totalDragged = moveStep * dragCount.toDouble();
final resultString = isVisible ? 'found' : 'not found';
final message =
"Target $targetName $resultString after $dragCount drags. "
"Total dragged offset: $totalDragged";

addDragEvent(message);

if (!isVisible) {
throw TestFailure(
"$targetName is not visible after dragging $iterations times and a total dragged offset of $totalDragged.",
"$targetName is not visible after dragging $dragCount times and a total dragged offset of $totalDragged.",
);
}
});
Expand Down Expand Up @@ -654,7 +714,9 @@ extension on HitTestEntry {
/// widgets and are not intercepted by [LiveTestWidgetsFlutterBinding].
///
/// See [LiveTestWidgetsFlutterBinding.shouldPropagateDevicePointerEvents].
T _alwaysPropagateDevicePointerEvents<T>(T Function() block) {
Future<T> _alwaysPropagateDevicePointerEvents<T>(
FutureOr<T> Function() block,
) async {
final binding = WidgetsBinding.instance;
final live = binding is LiveTestWidgetsFlutterBinding;

Expand All @@ -668,7 +730,7 @@ T _alwaysPropagateDevicePointerEvents<T>(T Function() block) {
binding.shouldPropagateDevicePointerEvents = true;
}
try {
return block();
return await block();
} finally {
if (live) {
binding.shouldPropagateDevicePointerEvents = previousPropagateValue;
Expand Down
Loading

0 comments on commit 08df699

Please sign in to comment.