Skip to content

Commit

Permalink
Add new timeline modes TimelineMode.always and TimelineMode.reportOnE…
Browse files Browse the repository at this point in the history
…rror (#68)
  • Loading branch information
passsy authored Nov 12, 2024
1 parent ac6bb78 commit 9b2ede1
Show file tree
Hide file tree
Showing 10 changed files with 155 additions and 89 deletions.
78 changes: 51 additions & 27 deletions lib/src/timeline/timeline.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
// ignore_for_file: depend_on_referenced_packages

import 'dart:io';

import 'package:ci/ci.dart';
import 'package:clock/clock.dart';
import 'package:flutter/material.dart';
Expand All @@ -17,7 +15,7 @@ import 'package:test_api/src/backend/invoker.dart';
import 'package:test_api/src/backend/live_test.dart';

TimelineMode _globalTimelineMode =
getTimelineModeFromEnv() ?? TimelineMode.record;
getTimelineModeFromEnv() ?? TimelineMode.reportOnError;

/// Returns the global timeline mode than can be used across multiple tests
TimelineMode get globalTimelineMode => _globalTimelineMode;
Expand All @@ -34,12 +32,15 @@ Example: timeline.mode = $value;
_globalTimelineMode = value;
}

/// Use --dart-define=SPOT_TIMELINE_MODE=live|record|off to set the [TimelineMode]
/// Use --dart-define=SPOT_TIMELINE_MODE=live|always|reportOnError|off to set the [TimelineMode]
/// for all tests
TimelineMode? getTimelineModeFromEnv() {
final mode = const String.fromEnvironment('SPOT_TIMELINE_MODE').toLowerCase();
return switch (mode) {
'live' => TimelineMode.live,
'always' => TimelineMode.always,
'reportOnError' => TimelineMode.reportOnError,
// ignore: deprecated_member_use_from_same_package
'record' => TimelineMode.record,
'off' => TimelineMode.off,
_ => null,
Expand Down Expand Up @@ -103,7 +104,7 @@ class Timeline {

TimelineMode _mode = _globalTimelineMode;

/// The mode of the timeline. Defaults to [TimelineMode.off].
/// The mode of the timeline. Defaults to [TimelineMode.reportOnError].
TimelineMode get mode => _mode;

set mode(TimelineMode value) {
Expand All @@ -115,6 +116,10 @@ class Timeline {
switch (value) {
TimelineMode.live =>
'🔴 - Live! Shows all timeline events as they happen',
TimelineMode.always => '🔴 - Always shows the timeline',
TimelineMode.reportOnError =>
'🔴 - Shows the timeline when the test fails',
// ignore: deprecated_member_use_from_same_package
TimelineMode.record =>
'🔴 - Recording, but only showing on test failure',
TimelineMode.off => '⏸︎ - Timeline recording is off',
Expand Down Expand Up @@ -176,7 +181,6 @@ class Timeline {
return;
}
for (final process in _toBeProcessedScreenshots.toList()) {
stdout.write('.');
await process();
}
}
Expand All @@ -185,27 +189,41 @@ class Timeline {
///
/// Prints the timeline to console, as link to a html file or plain text
Future<void> _onPostTest() async {
Future<void> reportOnError() async {
if (!test.state.result.isPassing) {
// ignore: avoid_print
print('Test failed, generating timeline report');
await processPendingScreenshots();
if (isCI) {
// best for CI, prints the full timeline and doesn't require archiving the html timeline file
printToConsole();
}
// best for humans
printHTML();
}
}

switch (mode) {
case TimelineMode.live:
// during live mode the events are written directly to the console.
// Finalize with html report
await processPendingScreenshots();
printHTML();
case TimelineMode.record:
if (!test.state.result.isPassing) {
// ignore: avoid_print
print('Test failed, generating timeline report');
await processPendingScreenshots();
if (isCI) {
// best for CI, prints the full timeline and doesn't require archiving the html timeline file
printToConsole();
}
// best for humans
printHTML();
} else {
// do nothing
break;
case TimelineMode.always:
// ignore: avoid_print
print('Generating timeline report');
await processPendingScreenshots();
if (isCI) {
// best for CI, prints the full timeline and doesn't require archiving the html timeline file
printToConsole();
}
// best for humans
printHTML();
// ignore: deprecated_member_use_from_same_package
case TimelineMode.record:
await reportOnError();
case TimelineMode.reportOnError:
await reportOnError();
case TimelineMode.off:
// do nothing
break;
Expand Down Expand Up @@ -272,19 +290,25 @@ class TimelineEvent {
final Color color;
}

/// The mode of the timeline.
/// Available modes:
/// - [TimelineMode.live] - The timeline is recording and printing events as they happen.
/// - [TimelineMode.record] - The timeline is recording but not printing events unless the test fails.
/// - [TimelineMode.off] - The timeline is not recording.
/// The mode of the [Timeline] and how it should be generated
enum TimelineMode {
/// The timeline is recording and printing events as they happen.
/// The timeline is recording and printing events to the console as they happen.
/// The timeline is also generated at the end of the test.
live,

/// Always prints the timeline at the end of the test
always,

/// In case the test fails, the timeline is generated at the end of the test.
reportOnError,

/// The timeline is recording but not printing events unless the test fails.
///
/// Deprecated: It's the same as `reportOnError` but the new name is more descriptive.
@Deprecated('Use reportOnError')
record,

/// The timeline is not recording.
/// No events will be recorded, the timeline is not generated after the test
off;
}

Expand Down
76 changes: 43 additions & 33 deletions test/timeline/drag/act_drag_timeline_test_bodies.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,8 @@ class ActDragTimelineTestBodies {
static const _passingDragAmount = 23;
static const _passingOffset = Offset(0, -2300);

static Future<void> liveWithoutError(
WidgetTester tester, {
bool isGlobal = false,
}) async {
static Future<void> liveWithoutError(WidgetTester tester) async {
final isGlobal = timeline.mode == TimelineMode.live;
final output = await captureConsoleOutput(() async {
if (!isGlobal) {
timeline.mode = TimelineMode.live;
Expand All @@ -37,10 +35,8 @@ class ActDragTimelineTestBodies {
);
}

static Future<void> offWithoutError(
WidgetTester tester, {
bool isGlobal = false,
}) async {
static Future<void> offWithoutError(WidgetTester tester) async {
final isGlobal = timeline.mode == TimelineMode.off;
final output = await captureConsoleOutput(() async {
if (!isGlobal) {
timeline.mode = TimelineMode.off;
Expand All @@ -54,27 +50,27 @@ class ActDragTimelineTestBodies {
expect(lines.length, isGlobal ? 0 : 1);
}

static Future<void> recordTurnOff(
WidgetTester tester, {
bool isGlobal = false,
}) async {
static Future<void> recordTurnOff(WidgetTester tester) async {
final isGlobal = timeline.mode == TimelineMode.off;
final output = await captureConsoleOutput(() async {
timeline.mode = TimelineMode.off;
if (!isGlobal) {
timeline.mode = TimelineMode.off;
}
await _testBody(tester);
});
final lines = output.split('\n')..removeWhere((line) => line.isEmpty);
expect(lines.length, 1);
expect(lines.first, contains('⏸︎ - Timeline recording is off'));
}

static Future<void> recordNoError(
WidgetTester tester, {
bool isGlobal = false,
}) async {
static Future<void> recordNoError(WidgetTester tester) async {
final isGlobal = timeline.mode == TimelineMode.reportOnError;
final output = await captureConsoleOutput(() async {
// Won't change anything, since it's default. Here to make sure
// nothing is printed when the mode doesn't change.
timeline.mode = TimelineMode.record;
if (!isGlobal) {
// Won't change anything, since it's default. Here to make sure
// nothing is printed when the mode doesn't change.
timeline.mode = TimelineMode.reportOnError;
}
await _testBody(tester);
});
final lines = output.split('\n')..removeWhere((line) => line.isEmpty);
Expand All @@ -83,9 +79,26 @@ class ActDragTimelineTestBodies {
expect(lines.length, 0, reason: output);
}

static Future<void> liveWithoutErrorPrintsHTML({
bool isGlobal = false,
}) async {
static Future<void> alwaysNoError(WidgetTester tester) async {
final isGlobal = timeline.mode == TimelineMode.always;
final output = await captureConsoleOutput(() async {
if (!isGlobal) {
// always print the timeline
timeline.mode = TimelineMode.always;
}
await _testBody(tester);
});
final lines = output.split('\n')..removeWhere((line) => line.isEmpty);
if (!isGlobal) {
expect(lines.length, 1);
expect(lines.first, contains('🔴 - Always shows the timeline'));
} else {
expect(lines.length, 0);
}
}

static Future<void> liveWithoutErrorPrintsHTML() async {
final isGlobal = timeline.mode == TimelineMode.live;
final stdout = await _outputFromDragTestProcess(
title: 'Live timeline - without error, prints HTML',
timelineMode: TimelineMode.live,
Expand Down Expand Up @@ -115,9 +128,8 @@ class ActDragTimelineTestBodies {
);
}

static Future<void> liveWithoutErrorPrintsHTMLNoDuplicates({
bool isGlobal = false,
}) async {
static Future<void> liveWithoutErrorPrintsHTMLNoDuplicates() async {
final isGlobal = timeline.mode == TimelineMode.live;
final stdout = await _outputFromDragTestProcess(
isGlobalMode: isGlobal,
title: 'Live timeline - without error, no duplicates, prints HTML',
Expand Down Expand Up @@ -154,9 +166,8 @@ class ActDragTimelineTestBodies {
);
}

static Future<void> liveWithErrorPrintsHTMLNoDuplicates({
bool isGlobal = false,
}) async {
static Future<void> liveWithErrorPrintsHTMLNoDuplicates() async {
final isGlobal = timeline.mode == TimelineMode.live;
final stdout = await _outputFromDragTestProcess(
isGlobalMode: isGlobal,
title: 'Live timeline - with error, no duplicates, prints HTML',
Expand Down Expand Up @@ -200,12 +211,11 @@ class ActDragTimelineTestBodies {
);
}

static Future<void> recordWithErrorPrintsHTML({
bool isGlobal = false,
}) async {
static Future<void> recordWithErrorPrintsHTML() async {
final isGlobal = timeline.mode == TimelineMode.reportOnError;
final stdout = await _outputFromDragTestProcess(
title: 'OnError timeline - with error, prints timeline',
timelineMode: TimelineMode.record,
timelineMode: TimelineMode.reportOnError,
drags: _failingDragAmount,
isGlobalMode: isGlobal,
);
Expand Down
19 changes: 19 additions & 0 deletions test/timeline/drag/global/global_always_timeline_drag_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:spot/src/timeline/timeline.dart';

import '../act_drag_timeline_test_bodies.dart';

void main() {
globalTimelineMode = TimelineMode.always;
group('Global: always', () {
testWidgets('turn off during test', (tester) async {
await ActDragTimelineTestBodies.recordTurnOff(tester);
});
testWidgets('no error print nothing', (tester) async {
await ActDragTimelineTestBodies.alwaysNoError(tester);
});
test('with error, prints timeline and html', () async {
await ActDragTimelineTestBodies.recordWithErrorPrintsHTML();
});
});
}
34 changes: 19 additions & 15 deletions test/timeline/drag/global/global_live_timeline_drag_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,24 @@ import '../act_drag_timeline_test_bodies.dart';

void main() {
globalTimelineMode = TimelineMode.live;
testWidgets('Global: live, without error', (tester) async {
await ActDragTimelineTestBodies.liveWithoutError(
tester,
isGlobal: true,
);
});
test('Global: live - without error, prints HTML, no duplicates', () async {
await ActDragTimelineTestBodies.liveWithoutErrorPrintsHTMLNoDuplicates(
isGlobal: true,
);
});
test('Global: live - with error, prints HTML, no duplicates', () async {
await ActDragTimelineTestBodies.liveWithErrorPrintsHTMLNoDuplicates(
isGlobal: true,
);
group('Global: live', () {
testWidgets('turn off during test', (tester) async {
await ActDragTimelineTestBodies.recordTurnOff(tester);
});
testWidgets('no error print nothing', (tester) async {
await ActDragTimelineTestBodies.alwaysNoError(tester);
});
test('with error, prints timeline and html', () async {
await ActDragTimelineTestBodies.recordWithErrorPrintsHTML();
});
testWidgets('without error', (tester) async {
await ActDragTimelineTestBodies.liveWithoutError(tester);
});
test('without error, prints HTML, no duplicates', () async {
await ActDragTimelineTestBodies.liveWithoutErrorPrintsHTMLNoDuplicates();
});
test('with error, prints HTML, no duplicates', () async {
await ActDragTimelineTestBodies.liveWithErrorPrintsHTMLNoDuplicates();
});
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ import '../act_drag_timeline_test_bodies.dart';
void main() {
globalTimelineMode = TimelineMode.off;
testWidgets('Global: off does not record', (tester) async {
await ActDragTimelineTestBodies.offWithoutError(tester, isGlobal: true);
await ActDragTimelineTestBodies.offWithoutError(tester);
});
}
14 changes: 7 additions & 7 deletions test/timeline/drag/global/global_record_timeline_drag_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ import 'package:spot/src/timeline/timeline.dart';
import '../act_drag_timeline_test_bodies.dart';

void main() {
globalTimelineMode = TimelineMode.record;
testWidgets('Global: record, turn off during test', (tester) async {
await ActDragTimelineTestBodies.recordTurnOff(tester, isGlobal: true);
globalTimelineMode = TimelineMode.reportOnError;
testWidgets('Global: reportOnError, turn off during test', (tester) async {
await ActDragTimelineTestBodies.recordTurnOff(tester);
});
testWidgets('Global: record - without error', (tester) async {
await ActDragTimelineTestBodies.recordNoError(tester, isGlobal: true);
testWidgets('Global: reportOnError - without error', (tester) async {
await ActDragTimelineTestBodies.recordNoError(tester);
});
test('Global: record, with error, prints timeline and html', () async {
await ActDragTimelineTestBodies.recordWithErrorPrintsHTML(isGlobal: true);
test('Global: reportOnError, with error, prints timeline and html', () async {
await ActDragTimelineTestBodies.recordWithErrorPrintsHTML();
});
}
4 changes: 2 additions & 2 deletions test/timeline/tap/act_tap_timeline_test_bodies.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class ActTapTimelineTestBodies {
}) async {
final output = await captureConsoleOutput(() async {
if (!isGlobalMode) {
timeline.mode = TimelineMode.record;
timeline.mode = TimelineMode.reportOnError;
}
await tester.pumpWidget(const TimelineTestWidget());
_addButtonSelector.existsOnce();
Expand Down Expand Up @@ -68,7 +68,7 @@ Example: timeline.mode = $globalTimelineModeToSwitch;
}) async {
final stdout = await _outputFromTapTestProcess(
title: 'OnError timeline - with error, prints timeline',
timelineMode: TimelineMode.record,
timelineMode: TimelineMode.reportOnError,
shouldFail: true,
isGlobalMode: isGlobalMode,
captureStart: ['Timeline of test'],
Expand Down
Loading

0 comments on commit 9b2ede1

Please sign in to comment.