feat(reporters): add progress reporter

This commit is contained in:
Jon Samwell 2018-10-29 13:52:29 +11:00
parent 03ca8d5f09
commit 508f40cc80
14 changed files with 130 additions and 64 deletions

View File

@ -248,9 +248,9 @@ Hooks are custom bits of code that can be run at certain points with the test ru
#### reporters
*Requried*
*Required*
Reporters are classes that are able to report on the status of the test run. This could be a simple as merely logging scenerio result to the console. There are a number of built-in reporter:
Reporters are classes that are able to report on the status of the test run. This could be a simple as merely logging scenario result to the console. There are a number of built-in reporter:
- `StdoutReporter` : Logs all messages from the test run to the standard output (console).
- `ProgressReporter` : Logs the progress of the test run marking each step with a scenario as either passed, skipped or failed.
@ -669,7 +669,24 @@ Future<void> main() {
A reporter is a class that is able to report on the progress of the test run. In it simplest form it could just print messages to the console or be used to tell a build server such as TeamCity of the progress of the test run. The library has a number of built in reporters.
- StdOut - prints all messages to the console.
- `StdoutReporter` - prints all messages from the test run to the console.
- `ProgressReporter` - prints the result of each scenario and step to the console - colours the output.
You can create your own custom reporter by inheriting from the base `Reporter` class and overriding the one or many of the methods to direct the output message. The `Reporter` defines the following methods that can be overridden. All methods must return a `Future<void>` and can be async.
- `onTestRunStarted`
- `onTestRunFinished`
- `onFeatureStarted`
- `onFeatureFinished`
- `onScenarioStarted`
- `onScenarioFinished`
- `onStepStarted`
- `onStepFinished`
- `onException`
- `message`
- `dispose`
Once you have created your custom reporter don't forget to add it to the `reporters` configuration file property.
*Note*: PR's of new reporters are *always* welcome.

View File

@ -9,12 +9,12 @@ import 'steps/tap_button_n_times_step.dart';
Future<void> main() {
final config = FlutterTestConfiguration()
..features = [Glob(r"test_driver/features/*.feature")]
..reporters = [StdoutReporter()]
..reporters = [ProgressReporter()]
..hooks = [HookExample()]
..stepDefinitions = [TapButtonNTimesStep(), GivenIPickAColour()]
..customStepParameterDefinitions = [ColourParameter()]
..restartAppBetweenScenarios = true
..targetAppPath = "test_driver/app.dart"
..exitAfterTestRun = false;
..exitAfterTestRun = true;
return GherkinRunner().execute(config);
}

View File

@ -1,17 +1,21 @@
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_gherkin/flutter_gherkin.dart';
class ColourParameter extends CustomParameter<Color> {
enum Colour {
red,
green,
blue
}
class ColourParameter extends CustomParameter<Colour> {
ColourParameter()
: super("colour", RegExp(r"red|green|blue", caseSensitive: true), (c) {
switch (c.toLowerCase()) {
case "red":
return Colors.red;
return Colour.red;
case "green":
return Colors.green;
return Colour.green;
case "blue":
return Colors.blue;
return Colour.blue;
}
});
}

View File

@ -1,10 +1,9 @@
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_gherkin/flutter_gherkin.dart';
import 'colour_parameter.dart';
class GivenIPickAColour extends Given1<Color> {
class GivenIPickAColour extends Given1<Colour> {
@override
Future<void> executeStep(Color input1) async {
Future<void> executeStep(Colour input1) async {
// TODO: implement executeStep
}

View File

@ -21,6 +21,7 @@ export "src/reporters/reporter.dart";
export "src/reporters/message_level.dart";
export "src/reporters/messages.dart";
export "src/reporters/stdout_reporter.dart";
export "src/reporters/progress_reporter.dart";
// Hooks
export "src/hooks/hook.dart";

View File

@ -115,9 +115,9 @@ class FeatureFileRunner {
world?.dispose();
await _reporter.onScenarioFinished(
ScenarioFinishedMessage(scenario.name, scenario.debug, scenarioPassed));
await _hook.onAfterScenario(_config, scenario.name);
_reporter.onScenarioFinished(
FinishedMessage(Target.scenario, scenario.name, scenario.debug));
return scenarioPassed;
}

View File

@ -26,7 +26,9 @@ class FlutterTestConfiguration extends TestConfiguration {
Platform.environment['VM_SERVICE_URL'];
final driver = await FlutterDriver.connect(
dartVmServiceUrl: dartVmServiceUrl,
isolateReadyTimeout: Duration(seconds: 30));
isolateReadyTimeout: Duration(seconds: 30),
logCommunicationToFile: false,
printCommunication: false);
return driver;
}

View File

@ -18,8 +18,8 @@ class AggregatedReporter extends Reporter {
}
@override
Future<void> onTestRunfinished() async {
await _invokeReporters((r) async => await r.onTestRunfinished());
Future<void> onTestRunFinished() async {
await _invokeReporters((r) async => await r.onTestRunFinished());
}
@override

View File

@ -26,3 +26,11 @@ class StepFinishedMessage extends FinishedMessage {
String name, RunnableDebugInformation context, this.result)
: super(Target.step, name, context);
}
class ScenarioFinishedMessage extends FinishedMessage {
final bool passed;
ScenarioFinishedMessage(
String name, RunnableDebugInformation context, this.passed)
: super(Target.scenario, name, context);
}

View File

@ -1,52 +1,88 @@
import 'package:flutter_gherkin/flutter_gherkin.dart';
import 'package:flutter_gherkin/src/gherkin/runnables/debug_information.dart';
import 'package:flutter_gherkin/src/gherkin/steps/step_run_result.dart';
import 'package:flutter_gherkin/src/reporters/message_level.dart';
import 'package:flutter_gherkin/src/reporters/messages.dart';
class ProgressReporter extends StdoutReporter {
Future<void> onStepFinished(FinishedMessage message) async {}
static const String PASS_COLOR = "\u001b[33;32m"; // green
@override
Future<void> onScenarioStarted(StartedMessage message) async {
printMessage(
"Running scenario: ${_getNameAndContext(message.name, message.context)}",
StdoutReporter.WARN_COLOR);
}
@override
Future<void> onScenarioFinished(ScenarioFinishedMessage message) async {
printMessage(
"${message.passed ? 'PASSED' : 'FAILED'}: Scenario ${_getNameAndContext(message.name, message.context)}",
message.passed ? PASS_COLOR : StdoutReporter.FAIL_COLOR);
}
@override
Future<void> onStepFinished(StepFinishedMessage message) async {
printMessage(
[
" ",
_getStatePrefixIcon(message.result.result),
_getNameAndContext(message.name, message.context),
_getExecutionDuration(message.result),
_getErrorMessage(message.result)
].join((" ")),
_getMessageColour(message.result.result));
}
Future<void> message(String message, MessageLevel level) async {
// ignore messages
}
String getStatePrefixIcon() {
return "√|×|e!";
String _getErrorMessage(StepResult stepResult) {
if (stepResult is ErroredStepResult) {
return "\n${stepResult.exception}\n${stepResult.stackTrace}";
} else {
return "";
}
}
String getContext(RunnableDebugInformation context) {
return "# ${context.filePath}:${context.lineNumber}";
String _getNameAndContext(String name, RunnableDebugInformation context) {
return "$name # ${context.filePath.replaceAll(RegExp(r"\.\\"), "")}:${context.lineNumber}";
}
String _getExecutionDuration(StepResult stepResult) {
return "took ${stepResult.elapsedMilliseconds}ms";
}
String _getStatePrefixIcon(StepExecutionResult result) {
switch (result) {
case StepExecutionResult.pass:
return "";
case StepExecutionResult.error:
case StepExecutionResult.fail:
case StepExecutionResult.timeout:
return "×";
case StepExecutionResult.skipped:
return "-";
}
return "";
}
String _getMessageColour(StepExecutionResult result) {
switch (result) {
case StepExecutionResult.pass:
return PASS_COLOR;
case StepExecutionResult.fail:
return StdoutReporter.FAIL_COLOR;
case StepExecutionResult.error:
return StdoutReporter.FAIL_COLOR;
case StepExecutionResult.skipped:
return StdoutReporter.WARN_COLOR;
case StepExecutionResult.timeout:
return StdoutReporter.FAIL_COLOR;
}
return StdoutReporter.RESET_COLOR;
}
}
// And I click on the "change job" link # src\step-definitions\interactions\click-on-element.step.ts:13
// × And I fill the "finish date" field with "1 December 2020" # src\step-definitions\interactions\fill-the-field-with.step.ts:11
// WebDriverError: unknown error: cannot focus element
// (Session info: chrome=70.0.3538.67)
// (Driver info: chromedriver=2.43.600210 (68dcf5eebde37173d4027fa8635e332711d2874a),platform=Windows NT 10.0.16299 x86_64)
// at Object.checkLegacyResponse (C:\easilog\webapp-tests\node_modules\selenium-webdriver\lib\error.js:546:15)
// at parseHttpResponse (C:\easilog\webapp-tests\node_modules\selenium-webdriver\lib\http.js:509:13)
// at doSend.then.response (C:\easilog\webapp-tests\node_modules\selenium-webdriver\lib\http.js:441:30)
// at process._tickCallback (internal/process/next_tick.js:68:7)
// From: Task: WebElement.sendKeys()
// at thenableWebDriverProxy.schedule (C:\easilog\webapp-tests\node_modules\selenium-webdriver\lib\webdriver.js:807:17)
// at WebElement.schedule_ (C:\easilog\webapp-tests\node_modules\selenium-webdriver\lib\webdriver.js:2010:25)
// at WebElement.sendKeys (C:\easilog\webapp-tests\node_modules\selenium-webdriver\lib\webdriver.js:2174:19)
// at actionFn (C:\easilog\webapp-tests\node_modules\protractor\built\element.js:89:44)
// at Array.map (<anonymous>)
// at actionResults.getWebElements.then (C:\easilog\webapp-tests\node_modules\protractor\built\element.js:461:65)
// at ManagedPromise.invokeCallback_ (C:\easilog\webapp-tests\node_modules\selenium-webdriver\lib\promise.js:1376:14)
// at TaskQueue.execute_ (C:\easilog\webapp-tests\node_modules\selenium-webdriver\lib\promise.js:3084:14)
// at TaskQueue.executeNext_ (C:\easilog\webapp-tests\node_modules\selenium-webdriver\lib\promise.js:3067:27)
// at asyncRun (C:\easilog\webapp-tests\node_modules\selenium-webdriver\lib\promise.js:2927:27)
// at C:\easilog\webapp-tests\node_modules\selenium-webdriver\lib\promise.js:668:7
// at process._tickCallback (internal/process/next_tick.js:68:7)Error
// at ElementArrayFinder.applyAction_ (C:\easilog\webapp-tests\node_modules\protractor\built\element.js:459:27)
// at ElementArrayFinder.(anonymous function).args [as sendKeys] (C:\easilog\webapp-tests\node_modules\protractor\built\element.js:91:29)
// at ElementFinder.(anonymous function).args [as sendKeys] (C:\easilog\webapp-tests\node_modules\protractor\built\element.js:831:22)
// at LoginPageObject.<anonymous> (C:\easilog\webapp-tests\src\pages\base.page.ts:101:23)
// at step (C:\easilog\webapp-tests\src\pages\base.page.js:42:23)
// at Object.next (C:\easilog\webapp-tests\src\pages\base.page.js:23:53)
// at fulfilled (C:\easilog\webapp-tests\src\pages\base.page.js:14:58)
// at process._tickCallback (internal/process/next_tick.js:68:7)
// - And I fill the "started date" field with "1 October 2021" # src\step-definitions\interactions\fill-the-field-with.step.ts:11
// - And I fill the "country" field with "United Kingdom" # src\step-definitions\interactions\fill-the-field-with.step.ts:11

View File

@ -3,11 +3,11 @@ import 'package:flutter_gherkin/src/reporters/messages.dart';
abstract class Reporter {
Future<void> onTestRunStarted() async {}
Future<void> onTestRunfinished() async {}
Future<void> onTestRunFinished() async {}
Future<void> onFeatureStarted(StartedMessage message) async {}
Future<void> onFeatureFinished(FinishedMessage message) async {}
Future<void> onScenarioStarted(StartedMessage message) async {}
Future<void> onScenarioFinished(FinishedMessage message) async {}
Future<void> onScenarioFinished(ScenarioFinishedMessage message) async {}
Future<void> onStepStarted(StartedMessage message) async {}
Future<void> onStepFinished(StepFinishedMessage message) async {}
Future<void> onException(Exception exception, StackTrace stackTrace) async {}

View File

@ -5,13 +5,12 @@ import 'package:flutter_gherkin/src/reporters/reporter.dart';
class StdoutReporter extends Reporter {
static const String NEUTRAL_COLOR = "\u001b[33;34m"; // blue
static const String DEBUG_COLOR = "\u001b[1;30m"; // gray
static const String PASS_COLOR = "\u001b[33;32m"; // green
static const String FAIL_COLOR = "\u001b[33;31m"; // red
static const String WARN_COLOR = "\u001b[33;10m"; // yellow
static const String RESET_COLOR = "\u001b[33;0m";
Future<void> message(String message, MessageLevel level) async {
print(message, getColour(level));
printMessage(message, getColour(level));
}
String getColour(MessageLevel level) {
@ -29,7 +28,7 @@ class StdoutReporter extends Reporter {
}
}
void print(String message, [String colour]) {
void printMessage(String message, [String colour]) {
stdout.writeln(
"${colour == null ? RESET_COLOR : colour}$message$RESET_COLOR");
}

View File

@ -67,7 +67,7 @@ class GherkinRunner {
await runner.run(featureFile);
}
} finally {
await _reporter.onTestRunfinished();
await _reporter.onTestRunFinished();
}
await _hook.onAfterRun(config);

View File

@ -46,7 +46,7 @@ void main() {
expect(reporter1.onStepStartedInvocationCount, 1);
expect(reporter2.onStepStartedInvocationCount, 1);
await aggregatedReporter.onTestRunfinished();
await aggregatedReporter.onTestRunFinished();
expect(reporter1.onTestRunfinishedInvocationCount, 1);
expect(reporter2.onTestRunfinishedInvocationCount, 1);