@@ -19,39 +19,42 @@ Write your own steps using [gherkin syntax](https://cucumber.io/docs/gherkin/ref
1919then create associated [ step definitions] ( https://cucumber.io/docs/guides/overview/#what-is-gherkin ) ,
2020set 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
109112the 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
113116implementing 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:
221225Write as many gherkin scenarii as you need, play with cucumber tags to run some of your scenarii, and explore all
222226options 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.
236240You will find more information about the goal of
237241the `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
247252No need to create a `WidgetCucumberWorld` object, the package provides one named `currentWorld`, accessible from the
248253`context` object :
254+
249255` ` ` dart
250256context.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,
258264such 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,
260267AccountBucket, ...).
261268
262269` Buckets` are abstract and generic, so to use them, you need to create a class which implements the `Bucket`
263270class :
271+
264272` ` ` dart
265273class 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,
271280and 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
279291Then 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
292306Finder 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
318332The `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
322369Applications 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
325372widget 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,
341390before/after steps, ...). More information about Hooks [here](https://pub.dev/packages/gherkin#hooks).
342391
343392This package supplies a Hook named `WidgetHooks` to improve reporting and provide more information on test failure such
344393as screenshots and widget tree rendering. Add this Hook in your `TestConfiguration` to enjoy its features :
345394
346395` ` ` dart
347396TestConfiguration()
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
408458
409459On 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
422473To benefit from supplied reporters, they need to be added on the `TestConfiguration` :
474+
423475` ` ` dart
424476TestConfiguration()
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