Skip to content

Commit 9da429e

Browse files
committed
Update Readme & example lib
1 parent 4d4580c commit 9da429e

File tree

4 files changed

+150
-87
lines changed

4 files changed

+150
-87
lines changed

README.md

Lines changed: 105 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -19,39 +19,42 @@ Write your own steps using [gherkin syntax](https://cucumber.io/docs/gherkin/ref
1919
then create associated [step definitions](https://cucumber.io/docs/guides/overview/#what-is-gherkin),
2020
set up the configuration and you are ready to describe actions and assertions of your application's widgets.
2121

22-
***
22+
---
2323

2424
## Table of Contents
2525

2626
<!-- TOC -->
27+
2728
* [Table of Contents](#table-of-contents)
2829
* [-- Features](#---features)
2930
* [-- Getting started](#---getting-started)
30-
* [🥒 Add `gherkin_widget_extension` dependency](#-add-gherkin_widget_extension-dependency)
31-
* [✏️ Write a scenario](#%EF%B8%8F-write-a-scenario)
32-
* [🔗 Declare step definitions](#-declare-step-definitions)
33-
* [⚙️ Add some configuration](#%EF%B8%8F-add-some-configuration)
34-
* [Package distinctive features](#package-distinctive-features)
35-
* [`..hooks`](#hooks)
36-
* [`..reporters`](#reporters)
37-
* [🧪 Set up the test runner](#-set-up-the-test-runner)
38-
* [🪄 Run your tests](#-run-your-tests)
39-
* [🎬️ Let's go!](#%EF%B8%8F-lets-go)
31+
* [🥒 Add `gherkin_widget_extension` dependency](#-add-gherkin_widget_extension-dependency)
32+
* [✏️ Write a scenario](#%EF%B8%8F-write-a-scenario)
33+
* [🔗 Declare step definitions](#-declare-step-definitions)
34+
* [⚙️ Add some configuration](#%EF%B8%8F-add-some-configuration)
35+
* [Package distinctive features](#package-distinctive-features)
36+
* [`..hooks`](#hooks)
37+
* [`..reporters`](#reporters)
38+
* [🧪 Set up the test runner](#-set-up-the-test-runner)
39+
* [🪄 Run your tests](#-run-your-tests)
40+
* [🎬️ Let's go!](#%EF%B8%8F-lets-go)
4041
* [-- Usage](#---usage)
41-
* [🌎 `WidgetCucumberWorld` advantages](#-widgetcucumberworld-advantages)
42-
* [🪣 Buckets for data](#-buckets-for-data)
43-
* [👁️ Don't forget the accessibility](#%EF%B8%8F-dont-forget-the-accessibility)
44-
* [🔄 Loading data for widgets with `JsonLoader`](#-loading-data-for-widgets-with-jsonloader)
45-
* [📸 A Hook for screenshot and widget tree rendering](#-a-hook-for-screenshot-and-widget-tree-rendering)
46-
* [📋 Widget test reporters](#-widget-test-reporters)
47-
* [`MonochromePrinter`](#monochromeprinter)
48-
* [`WidgetStdoutReporter`](#widgetstdoutreporter)
49-
* [`WidgetTestRunSummaryReporter`](#widgettestrunsummaryreporter)
50-
* [`XmlReporter`](#xmlreporter)
51-
* [Add reporters in test configuration](#add-reporters-in-test-configuration)
42+
* [🌎 `WidgetCucumberWorld` advantages](#-widgetcucumberworld-advantages)
43+
* [🪣 Buckets for data](#-buckets-for-data)
44+
* [👁️ Don't forget the accessibility](#%EF%B8%8F-dont-forget-the-accessibility)
45+
* [🧩 `WidgetObject` pattern and usage of `TestWidgets` class](#-widgetobject-pattern-and-usage-of-testwidgets-class)
46+
* [🔄 Loading data for widgets with `JsonLoader`](#-loading-data-for-widgets-with-jsonloader)
47+
* [📸 A Hook for screenshot and widget tree rendering](#-a-hook-for-screenshot-and-widget-tree-rendering)
48+
* [📋 Widget test reporters](#-widget-test-reporters)
49+
* [`MonochromePrinter`](#monochromeprinter)
50+
* [`WidgetStdoutReporter`](#widgetstdoutreporter)
51+
* [`WidgetTestRunSummaryReporter`](#widgettestrunsummaryreporter)
52+
* [`XmlReporter`](#xmlreporter)
53+
* [Add reporters in test configuration](#add-reporters-in-test-configuration)
54+
5255
<!-- TOC -->
5356

54-
***
57+
---
5558

5659
## -- Features
5760

@@ -63,7 +66,7 @@ set up the configuration and you are ready to describe actions and assertions of
6366
* Screenshot and widget tree dumped in a file on test failure,
6467
* [Gherkin reporters](https://pub.dev/packages/gherkin#reporters) adapted for widget tests,
6568

66-
***
69+
---
6770

6871
## -- Getting started
6972

@@ -109,7 +112,7 @@ validates
109112
the form, When the user applies his choice, ...) and the `then` steps assert everything assertable on the screen
110113
(text, state, semantics, ...).
111114

112-
In the `test` folder, create a `step_definitions` folder and within this folder, create a `steps.dart` file and start
115+
In the `test` folder, create a `step_definitions` folder and within this folder, create a `steps.dart` file and start
113116
implementing step definitions:
114117

115118
```dart
@@ -125,6 +128,7 @@ StepDefinitionGeneric<WidgetCucumberWorld> givenAFreshApp() {
125128
```
126129

127130
> #### 💡 _Advice_
131+
>
128132
> For better understanding, one of good practices advises to split step definitions files according to gherkin keywords
129133
> (all Given step definitions within the same file `given_steps.dart`, all When step definitions within the same file
130134
> `when_steps.dart`, etc...). Organizing those files into folders representing the feature is a plus.
@@ -221,7 +225,7 @@ You should see these kind of logs:
221225
Write as many gherkin scenarii as you need, play with cucumber tags to run some of your scenarii, and explore all
222226
options the [gherkin](https://pub.dev/packages/gherkin) package supply.
223227

224-
***
228+
---
225229

226230
## -- Usage
227231

@@ -236,7 +240,8 @@ scenario is done, no matter its status.
236240
You will find more information about the goal of
237241
the `CucumberWorld` [here](https://pub.dev/packages/gherkin#createworld).
238242

239-
The class `WidgetCucumberWorld` inherits from the `CucumberWorld` class of the gherkin package and exposes the following items:
243+
The class `WidgetCucumberWorld` inherits from the `CucumberWorld` class of the gherkin package and exposes the following
244+
items:
240245

241246
* `WidgetTester tester`: allows to interact with the widget (tap, pump, get elements, ...),
242247
* `SemanticsHandle semantics`: created at the beginning of the test, enables interactions with the `Semantic` widget,
@@ -246,6 +251,7 @@ The class `WidgetCucumberWorld` inherits from the `CucumberWorld` class of the g
246251

247252
No need to create a `WidgetCucumberWorld` object, the package provides one named `currentWorld`, accessible from the
248253
`context` object:
254+
249255
```dart
250256
context.world.tester;
251257
```
@@ -256,32 +262,40 @@ context.world.tester;
256262

257263
`Buckets` allows you to organize your data in the `CucumberWorld`. Indeed, if your application has divergent use cases,
258264
such as tickets sells, customer account or traffic information, etc., you might not want to store all test data together
259-
within the `CucumberWorld`: you may use `Buckets` to store data according to their business domain (ex: TicketSellBucket,
265+
within the `CucumberWorld`: you may use `Buckets` to store data according to their business domain (ex:
266+
TicketSellBucket,
260267
AccountBucket, ...).
261268

262269
`Buckets` are abstract and generic, so to use them, you need to create a class which implements the `Bucket`
263270
class:
271+
264272
```dart
265273
class ExampleBucket implements Bucket {
266274
String? buttonAction;
267275
int nbActions = 0;
268276
}
269277
```
270-
In this example, the bucket name is `ExampleBucket` and stores two values: the name of the button to interact with,
278+
279+
In this example, the bucket name is `ExampleBucket` and stores two values: the name of the button to interact with,
271280
and the number of times it was tapped.
272281

273-
Before storing data into a `Bucket`, it requires to be initialized through the `WidgetCucumberWorld` object named
282+
Before storing data into a `Bucket`, it requires to be initialized through the `WidgetCucumberWorld` object named
274283
`currentWorld`:
284+
275285
```dart
276-
currentWorld.bucket = ExampleBucket();
286+
currentWorld.bucket =
287+
288+
ExampleBucket();
277289
```
278290

279291
Then use `currentWorld` for setting and accessing to the `Bucket`'s data:
292+
280293
```dart
281-
currentWorld.readBucket<ExampleBucket>().nbActions = count;
294+
currentWorld.readBucket<ExampleBucket>
295+
().nbActions = count;
282296
```
283-
Keep in mind that bucket type is required to use it and access to its data (here `<ExampleBucket>`).
284297

298+
Keep in mind that bucket type is required to use it and access to its data (here `<ExampleBucket>`).
285299

286300
### 👁️ Don't forget the accessibility
287301

@@ -290,10 +304,10 @@ widget semantics:
290304

291305
```dart
292306
Finder widgetWithSemanticLabel(
293-
Type widgetType,
294-
String semanticLabel,
295-
{bool skipOffstage = true,
296-
Matcher? semanticMatcher}
307+
Type widgetType,
308+
String semanticLabel,
309+
{bool skipOffstage = true,
310+
Matcher? semanticMatcher}
297311
)
298312
```
299313

@@ -317,6 +331,39 @@ expect(widgetToFind, findsOneWidget);
317331

318332
The `expect` raises an `AssertionError` if no corresponding widget exists.
319333

334+
### 🧩 `WidgetObject` pattern and usage of `TestWidgets` class
335+
336+
Organizing the test code according the "Widget Object" pattern can be helpful for maintenance and code clarity.
337+
The Widget Object pattern (invented for this package) is strongly inspired by the PageObject pattern and applied to
338+
Flutter widget tests.
339+
340+
In test automation, the Page Object pattern requires to create one class per page (or screen) which gathers not only all
341+
methods interacting with this page (click, check, ...) but also selectors to this page (how to target a given element on
342+
this page). This way, understanding and maintenance are eased.
343+
344+
In the widget test context, pieces of screen are tested (or portion of a widget tree), not pages. But, keeping the page
345+
object logic is still helpful.
346+
347+
The abstract class `TestWidget` can be used as an interface to "widget object" classes to force the implementation of the
348+
most important method:
349+
350+
* `pumpItPump()` - indicates how pump the widget to test
351+
352+
Example:
353+
354+
```dart
355+
class MyWidgetObject implements TestWidgets {
356+
@override
357+
Future<void> pumpItUp() async {
358+
var widgetToPump = const MaterialTestWidget(
359+
child: MyApp(),
360+
);
361+
pumpWidget(widgetToPump);
362+
}
363+
}
364+
```
365+
366+
320367
### 🔄 Loading data for widgets with `JsonLoader`
321368

322369
Applications often use data coming from an API to build components in its screens and widget test cannot depend on
@@ -325,8 +372,9 @@ This way, API Json responses can be stored into files and loaded through the pro
325372
widget to test.
326373

327374
```dart
328-
var jsonMap = await JsonLoader.loadJson("path/to/json/folder");
375+
var jsonMap = awaitJsonLoader.loadJson("path/to/json/folder");
329376
```
377+
330378
`jsonMap` contains a map where keys are json filenames and values the json files content.
331379

332380
### 📸 A Hook for screenshot and widget tree rendering
@@ -337,20 +385,22 @@ var jsonMap = await JsonLoader.loadJson("path/to/json/folder");
337385
> 📣 The `flutter_test_config.dart` must exist at the root of `test` directory (
338386
> See [🧪 Set up the test runner](#-set-up-the-test-runner) paragraph).
339387

340-
Hooks contain methods executed before or after specific milestones during a test driven by Cucumber (before/after scenario,
388+
Hooks contain methods executed before or after specific milestones during a test driven by Cucumber (before/after
389+
scenario,
341390
before/after steps, ...). More information about Hooks [here](https://pub.dev/packages/gherkin#hooks).
342391

343392
This package supplies a Hook named `WidgetHooks` to improve reporting and provide more information on test failure such
344393
as screenshots and widget tree rendering. Add this Hook in your `TestConfiguration` to enjoy its features :
345394

346395
```dart
347396
TestConfiguration()
348-
..hooks = [WidgetHooks(dumpFolderPath: 'widget_tests_report_folder')]
397+
..hooks = [
398+
WidgetHooks(dumpFolderPath:'widget_tests_report_folder')
399+
]
349400
```
350401

351402
**Parameter `dumpFolderPath` is mandatory**: it represents the report folder where screenshots and widget rendering will
352-
be
353-
stored on test failure.
403+
be stored on test failure.
354404

355405
> 📣 This package provides a custom Widget called `MaterialTestWidget`. This widget must encapsulate the widget to pump
356406
> to enable screenshots and widget rendering.
@@ -371,12 +421,12 @@ This reporter is in charge of:
371421

372422
* printing the name of the running scenario with its file location,
373423
* printing each step with its status and time duration
374-
* `√` if step succeeded
375-
* `×` if step failed
376-
* `-` if step skipped
424+
* `√` if step succeeded
425+
* `×` if step failed
426+
* `-` if step skipped
377427
* printing the scenario execution result
378-
* PASSED
379-
* FAILED
428+
* PASSED
429+
* FAILED
380430
* handling text coloration
381431

382432
_Example:_
@@ -407,6 +457,7 @@ The details for each test execution is also available:
407457
![](md_assets/gitlab_ci_report_details.png)
408458

409459
On failure, the `System output` contains:
460+
410461
* a recap of steps with their status (passed, failed or skipped)
411462
* stacktrace of the exception
412463
* print of the widget rendering
@@ -420,13 +471,14 @@ On failure, a link to the screenshot is also provided.
420471
#### Add reporters in test configuration
421472

422473
To benefit from supplied reporters, they need to be added on the `TestConfiguration`:
474+
423475
```dart
424476
TestConfiguration()
425-
..reporters = [
426-
WidgetStdoutReporter(),
427-
WidgetTestRunSummaryReporter(),
428-
XmlReporter(dirRoot: Directory.current.path)
429-
]
477+
..reporters = [
478+
WidgetStdoutReporter(),
479+
WidgetTestRunSummaryReporter(),
480+
XmlReporter(dirRoot:Directory.current.path)
481+
]
430482
```
431483

432484
<!--
@@ -441,4 +493,4 @@ XmlReporter(dirRoot: Directory.current.path)
441493

442494
🚧 _More details soon..._
443495

444-
-->
496+
-->

0 commit comments

Comments
 (0)