diff --git a/lib/src/timeline/timeline.dart b/lib/src/timeline/timeline.dart index 5ddad756..8a4f0a7f 100644 --- a/lib/src/timeline/timeline.dart +++ b/lib/src/timeline/timeline.dart @@ -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'; @@ -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; @@ -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, @@ -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) { @@ -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', @@ -176,7 +181,6 @@ class Timeline { return; } for (final process in _toBeProcessedScreenshots.toList()) { - stdout.write('.'); await process(); } } @@ -185,27 +189,41 @@ class Timeline { /// /// Prints the timeline to console, as link to a html file or plain text Future _onPostTest() async { + Future 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; @@ -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; } diff --git a/test/timeline/drag/act_drag_timeline_test_bodies.dart b/test/timeline/drag/act_drag_timeline_test_bodies.dart index 0e32dcc0..f623ba5d 100644 --- a/test/timeline/drag/act_drag_timeline_test_bodies.dart +++ b/test/timeline/drag/act_drag_timeline_test_bodies.dart @@ -16,10 +16,8 @@ class ActDragTimelineTestBodies { static const _passingDragAmount = 23; static const _passingOffset = Offset(0, -2300); - static Future liveWithoutError( - WidgetTester tester, { - bool isGlobal = false, - }) async { + static Future liveWithoutError(WidgetTester tester) async { + final isGlobal = timeline.mode == TimelineMode.live; final output = await captureConsoleOutput(() async { if (!isGlobal) { timeline.mode = TimelineMode.live; @@ -37,10 +35,8 @@ class ActDragTimelineTestBodies { ); } - static Future offWithoutError( - WidgetTester tester, { - bool isGlobal = false, - }) async { + static Future offWithoutError(WidgetTester tester) async { + final isGlobal = timeline.mode == TimelineMode.off; final output = await captureConsoleOutput(() async { if (!isGlobal) { timeline.mode = TimelineMode.off; @@ -54,12 +50,12 @@ class ActDragTimelineTestBodies { expect(lines.length, isGlobal ? 0 : 1); } - static Future recordTurnOff( - WidgetTester tester, { - bool isGlobal = false, - }) async { + static Future 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); @@ -67,14 +63,14 @@ class ActDragTimelineTestBodies { expect(lines.first, contains('⏸︎ - Timeline recording is off')); } - static Future recordNoError( - WidgetTester tester, { - bool isGlobal = false, - }) async { + static Future 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); @@ -83,9 +79,26 @@ class ActDragTimelineTestBodies { expect(lines.length, 0, reason: output); } - static Future liveWithoutErrorPrintsHTML({ - bool isGlobal = false, - }) async { + static Future 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 liveWithoutErrorPrintsHTML() async { + final isGlobal = timeline.mode == TimelineMode.live; final stdout = await _outputFromDragTestProcess( title: 'Live timeline - without error, prints HTML', timelineMode: TimelineMode.live, @@ -115,9 +128,8 @@ class ActDragTimelineTestBodies { ); } - static Future liveWithoutErrorPrintsHTMLNoDuplicates({ - bool isGlobal = false, - }) async { + static Future liveWithoutErrorPrintsHTMLNoDuplicates() async { + final isGlobal = timeline.mode == TimelineMode.live; final stdout = await _outputFromDragTestProcess( isGlobalMode: isGlobal, title: 'Live timeline - without error, no duplicates, prints HTML', @@ -154,9 +166,8 @@ class ActDragTimelineTestBodies { ); } - static Future liveWithErrorPrintsHTMLNoDuplicates({ - bool isGlobal = false, - }) async { + static Future liveWithErrorPrintsHTMLNoDuplicates() async { + final isGlobal = timeline.mode == TimelineMode.live; final stdout = await _outputFromDragTestProcess( isGlobalMode: isGlobal, title: 'Live timeline - with error, no duplicates, prints HTML', @@ -200,12 +211,11 @@ class ActDragTimelineTestBodies { ); } - static Future recordWithErrorPrintsHTML({ - bool isGlobal = false, - }) async { + static Future 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, ); diff --git a/test/timeline/drag/global/global_always_timeline_drag_test.dart b/test/timeline/drag/global/global_always_timeline_drag_test.dart new file mode 100644 index 00000000..123acdcb --- /dev/null +++ b/test/timeline/drag/global/global_always_timeline_drag_test.dart @@ -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(); + }); + }); +} diff --git a/test/timeline/drag/global/global_live_timeline_drag_test.dart b/test/timeline/drag/global/global_live_timeline_drag_test.dart index fb9e547b..17237bd4 100644 --- a/test/timeline/drag/global/global_live_timeline_drag_test.dart +++ b/test/timeline/drag/global/global_live_timeline_drag_test.dart @@ -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(); + }); }); } diff --git a/test/timeline/drag/global/global_off_timeline_drag_test.dart b/test/timeline/drag/global/global_off_timeline_drag_test.dart index 4105c3e8..98d53331 100644 --- a/test/timeline/drag/global/global_off_timeline_drag_test.dart +++ b/test/timeline/drag/global/global_off_timeline_drag_test.dart @@ -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); }); } diff --git a/test/timeline/drag/global/global_record_timeline_drag_test.dart b/test/timeline/drag/global/global_record_timeline_drag_test.dart index 42bf377b..e92a8855 100644 --- a/test/timeline/drag/global/global_record_timeline_drag_test.dart +++ b/test/timeline/drag/global/global_record_timeline_drag_test.dart @@ -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(); }); } diff --git a/test/timeline/tap/act_tap_timeline_test_bodies.dart b/test/timeline/tap/act_tap_timeline_test_bodies.dart index 6f2facbd..6c39d765 100644 --- a/test/timeline/tap/act_tap_timeline_test_bodies.dart +++ b/test/timeline/tap/act_tap_timeline_test_bodies.dart @@ -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(); @@ -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'], diff --git a/test/timeline/tap/global/global_record_timeline_tap_test.dart b/test/timeline/tap/global/global_record_timeline_tap_test.dart index 165ac11f..32d4eba2 100644 --- a/test/timeline/tap/global/global_record_timeline_tap_test.dart +++ b/test/timeline/tap/global/global_record_timeline_tap_test.dart @@ -3,14 +3,14 @@ import 'package:spot/src/timeline/timeline.dart'; import '../act_tap_timeline_test_bodies.dart'; void main() { - globalTimelineMode = TimelineMode.record; - testWidgets('Global: record, without error', (tester) async { + globalTimelineMode = TimelineMode.reportOnError; + testWidgets('Global: reportOnError, without error', (tester) async { await ActTapTimelineTestBodies.recordWithoutError( tester: tester, isGlobalMode: true, ); }); - test('Global: record, with error', () async { + test('Global: reportOnError, with error', () async { await ActTapTimelineTestBodies.recordWithError(isGlobalMode: true); }); } diff --git a/test/timeline/tap/local/local_timeline_tap_test.dart b/test/timeline/tap/local/local_timeline_tap_test.dart index 24051484..a801a918 100644 --- a/test/timeline/tap/local/local_timeline_tap_test.dart +++ b/test/timeline/tap/local/local_timeline_tap_test.dart @@ -34,7 +34,7 @@ void main() { test('Throws when global mode is changed during test', () async { await ActTapTimelineTestBodies.throwOnGlobalTimelineChange( initialGlobalMode: TimelineMode.live, - globalTimelineModeToSwitch: TimelineMode.record, + globalTimelineModeToSwitch: TimelineMode.reportOnError, ); }); } diff --git a/test/timeline/timeline_test_shared.dart b/test/timeline/timeline_test_shared.dart index a86bae69..c2138905 100644 --- a/test/timeline/timeline_test_shared.dart +++ b/test/timeline/timeline_test_shared.dart @@ -10,8 +10,13 @@ String localTimelineInitiator(TimelineMode timelineMode) { return switch (timelineMode) { TimelineMode.live => 'timeline.mode = TimelineMode.live;\nexpect(timeline.mode, TimelineMode.live);', + TimelineMode.always => + 'timeline.mode = TimelineMode.always;\nexpect(timeline.mode, TimelineMode.always);', + // ignore: deprecated_member_use_from_same_package TimelineMode.record => 'timeline.mode = TimelineMode.record;\nexpect(timeline.mode, TimelineMode.record);', + TimelineMode.reportOnError => + 'timeline.mode = TimelineMode.reportOnError;\nexpect(timeline.mode, TimelineMode.reportOnError);', TimelineMode.off => 'timeline.mode = TimelineMode.off;\nexpect(timeline.mode, TimelineMode.off);', }; @@ -20,6 +25,10 @@ String localTimelineInitiator(TimelineMode timelineMode) { String globalTimelineInitiator(TimelineMode timelineMode) { return switch (timelineMode) { TimelineMode.live => 'globalTimelineMode = TimelineMode.live;', + TimelineMode.always => 'globalTimelineMode = TimelineMode.always;', + TimelineMode.reportOnError => + 'globalTimelineMode = TimelineMode.reportOnError;', + // ignore: deprecated_member_use_from_same_package TimelineMode.record => 'globalTimelineMode = TimelineMode.record;', TimelineMode.off => 'globalTimelineMode = TimelineMode.off;', };