{"id":13548413,"url":"https://github.com/passsy/spot","last_synced_at":"2025-04-09T15:07:34.620Z","repository":{"id":61975481,"uuid":"532097222","full_name":"passsy/spot","owner":"passsy","description":"Flutter widget test toolkit - spot, act, validate. Better selectors, automatic screenshots, chainable.","archived":false,"fork":false,"pushed_at":"2025-02-28T17:30:33.000Z","size":2942,"stargazers_count":93,"open_issues_count":21,"forks_count":5,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-04-09T15:07:24.506Z","etag":null,"topics":["flutter","hacktoberfest","screenshot","widget-testing"],"latest_commit_sha":null,"homepage":"https://pub.dev/packages/spot","language":"Dart","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/passsy.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":["passsy"]}},"created_at":"2022-09-02T22:37:52.000Z","updated_at":"2025-02-12T11:12:05.000Z","dependencies_parsed_at":"2024-01-05T13:51:20.921Z","dependency_job_id":"1c019d44-dcf5-4a32-bc8c-7c41df425820","html_url":"https://github.com/passsy/spot","commit_stats":{"total_commits":7,"total_committers":2,"mean_commits":3.5,"dds":0.4285714285714286,"last_synced_commit":"aac118add3188ac2da8b22a78db3d3dc1d8ff2ae"},"previous_names":[],"tags_count":20,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/passsy%2Fspot","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/passsy%2Fspot/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/passsy%2Fspot/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/passsy%2Fspot/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/passsy","download_url":"https://codeload.github.com/passsy/spot/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248055284,"owners_count":21040157,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["flutter","hacktoberfest","screenshot","widget-testing"],"created_at":"2024-08-01T12:01:10.018Z","updated_at":"2025-04-09T15:07:34.596Z","avatar_url":"https://github.com/passsy.png","language":"Dart","funding_links":["https://github.com/sponsors/passsy"],"categories":["Dart"],"sub_categories":[],"readme":"# Spot\n\n[![pub](https://img.shields.io/pub/v/spot?include_prereleases)](https://pub.dev/packages/spot)\n\n![DALL·E generated spot repo header image](https://github.com/user-attachments/assets/490103a5-4ac6-41f9-9b4f-4333e3e5ff66)\n\nSpot is a toolkit for Flutter widget tests.\u003cbr/\u003e\nIt simplifies queries and assertions against the widget tree (better finder API called `spot`) and\nvisualizes the steps of a widget test as HTML report with automatic screenshots, the `Timeline`.\n\n🖼️ Automatic screenshots during widget tests (Timeline)\u003cbr/\u003e\n⛓️ Chainable widget selectors\u003cbr/\u003e\n💙 Useful error messages (with full tree dump)\u003cbr/\u003e\n🌱 Opt-in, works with plain `testWidgets()`\u003cbr/\u003e\n💫 Full compatibility with `integration_test`\u003cbr/\u003e\n\n- [Get started](#get-started)\n- [Timeline](#timeline)\n    - [Overview](#overview)\n    - [Add custom events to the Timeline](#add-custom-events-to-the-timeline)\n    - [Change Timeline mode](#change-timeline-mode)\n    - [Timeline in console on CI](#timeline-in-console-on-ci)\n- [Screenshots](#screenshots)\n    - [Manual Screenshots](#manual-screenshots)\n    - [Load Fonts](#load-fonts)\n- [spot - Widget selectors](#widget-selectors-spot)\n    - [Chain selectors](#chain-selectors)\n    - [Selectors](#selectors)\n    - [Better errors](#better-errors)\n    - [Matchers](#matchers)\n    - [Selectors vs. Matchers](#selectors-vs-matchers)\n    - [Find offstage widgets](#find-offstage-widgets)\n- [act - tap, drag, type](#act-tap-drag-type-click)\n  - [tap](#tap)\n  - [tapAt](#tapAt)\n  - [enterText](#entertext)\n  - [dragUntilVisible](#draguntilvisible)\n  - [more act functions](#more-act-functions)\n- [Roadmap](#roadmap)\n- [Project state](#project-state)\n- [License](#license)\n\n## Get started\n\n```bash\nflutter pub add dev:spot\n```\n\n1\\. Replace widget assertions (`find`) with `spot`.\u003cbr/\u003e\n2\\. Replace interactions like `tester.tap()` with `act.tap()` to interact with widgets.\n\nWith every call with `spot` or `act`, spot captures the current frame and adds it to the Timeline HTML report.\n\n```dart\nimport 'package:flutter/material.dart';\nimport 'package:flutter_test/flutter_test.dart';\nimport 'package:spot/spot.dart';\n\nvoid main() {\n  testWidgets('existing Widget test', (tester) async {\n     await tester.pumpWidget(MyApp());\n  \n     // await tester.tap(find.byType(ElevatedButton));\n     await act.tap(spot\u003cElevatedButton\u003e());\n\n     // expect(find.text('monde'), findsOneWidget); // 🇫🇷\n     spot\u003cScaffold\u003e().spotText('monde').existsOnce(); // 🇫🇷\n\n     // Automatically generates a timeline report on error\n  });\n}\n```\n\nWhen your test fails, spot generates the Timeline HTML report with all assertions (`spot`) and gestures (`act`), automatic screenshots and more information.\n\n```bash\nGenerating timeline report\nView timeline here: file:///var/folders/0j/p0s0zrv91tgd33zrxb98c0440000gn/T/ecsTKx/existing-widget.html\n```\n\nYou can open the local Timeline report in your browser.\n\n![Timeline](https://github.com/user-attachments/assets/48a1a754-f56a-4c61-a4ef-b6e564ee1282)\n\n## Timeline\n\n### Overview\n\nThe Timeline is a visual representation of the widget test run, rendered as an HTML report.\nIt shows all interactions with the spot package, like `spot` and `act`.\nThe focus on screenshots with annotations makes it easy to follow what happened during the test run.\nAt any point in the timeline, it is possible to jump back into the test code.\n\nThe Timeline is constructed during a widget test with the first interaction with `spot`.\nThe more frames of a test are asserted with spot, the more detailed the Timeline becomes.\n\nBy default, the Timeline is automatically generated when a test fails.\nThe path to the HTML file is printed to the console.\n\nSuccessful tests skip the Timeline generation (and extra work).\n\nWidget tests without any call to `spot` do **not** generate a Timeline.\n\n### Add custom events to the Timeline\n\nYou can add custom events to the Timeline to better understand what is happening in your test.\nThe timeline API is completely open and allows adding any event you want.\n\n```dart\ntimeline.addEvent(\n  eventType: 'Received fake server response',\n  details: 'HTTP 200\\n{\"message\": \"Hello World\"}',\n  color: Colors.orange,\n  screenshot: timeline.takeScreenshotSync(),\n);\n```\n\n![Custom Timeline event](https://github.com/user-attachments/assets/fed7b032-76c0-44ab-b8b3-f33b81f420da)\n\n### Change Timeline mode\n\nSpot automatically generates a Timeline HTML report when a test fails.\nChange this behavior by adjusting the `TimelineMode`, e.g. during development, to always generate the timeline or skip it for parts of a test.\n\n`TimelineMode` defines the following values:\n\n- `off`: No events will be recorded\n- `reportOnError` _(default)_: Only generate a Timeline report when a test fails\n- `always`: Always generate the Timeline report at the end of the test\n- `live`: Print all Timeline events to console as they happen\n\nThere are three ways to change the `TimelineMode`:\n\n#### Single test\n\n```dart\n\nvoid main() {\n  testWidgets('my widget test', (tester) async {\n    timeline.mode = TimelineMode.always;\n    /* ... */\n  });\n\n  testWidgets('complex test', (tester) async {\n    timeline.mode = TimelineMode.off;\n    /* a long setup which should not be recorded */\n    timeline.mode = TimelineMode.reportOnError;\n    \n    // relevant test code\n  });\n}\n```\n\n#### Entire file\n\nChanging the `globalTimelineMode` only a default at the beginning of each test.\nIt can be changed by each test individually.\n\n```dart\nvoid main() {\n  globalTimelineMode = TimelineMode.off;\n  \n  testWidgets('my widget test', (tester) async {/* ... */});\n\n  testWidgets('another test', (tester) async {\n    // overwrites the global mode\n    timeline.mode = TimelineMode.always;\n    // consle: View timeline here: file:///var/folders/0j/p0s0zrv91t...\n  });\n}\n```\n\n#### Global\n\n```bash\nflutter test --dart-define=SPOT_TIMELINE_MODE=always\n```\n\n### Timeline in console on CI\n\nOn CI servers, it might be hard to access the HTML reports.\nThe only output is often the console output.\nUnless the reports are explicitly archived after a run, they are usually inaccessible.\n\nSpot automatically [detects CI](https://pub.dev/packages/ci) systems and dumps the Timeline to the console when a test fails.\nThat might be ugly to read, but all information is better than none.\n\nTo disable this behavior, set `SPOT_TIMELINE_MODE=off` as an environment variable.\n\n```yaml\n# Github Actions\njobs:\n  test:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v2\n      - uses: subosito/flutter-action@v2\n        with:\n          channel: stable\n      - name: Run tests\n        run: flutter test --dart-define=SPOT_TIMELINE_MODE=off\n```\n\n## Screenshots\n\n### Manual Screenshots\n\nThe Timeline automatically captures screenshots. But those are always for the entire screen and are not available during the test itself.\n\nUse `await takeScreenshot()` to get the current pixels on the virtual screen.\n\n`takeScreenshot` also takes a `selector` parameter to screenshot a single widget. This is useful to check the actual rasterized image (pixels) of a widget.\n\n```dart\nimport 'package:flutter/material.dart';\nimport 'package:flutter_test/flutter_test.dart';\nimport 'package:spot/spot.dart';\n\nvoid main() {\n  testWidgets('Take screenshots', (tester) async {\n    tester.pumpWidget(MyApp());\n    \n    // Take a screenshot of the entire screen\n    await takeScreenshot();\n    // console:\n    // Screenshot file:///var/folders/0j/p0s0zrv91tgd33zrxb88c0440000gn/T/spot/screenshot_test:10-s83dv.png\n    //   taken at main.\u003cfn\u003e file:///Users/pascalwelsch/Projects/passsy/spot/test/spot/screenshot_test.dart:10:10\n    \n    // Take a screenshot of a single widget\n    await spot\u003cAppBar\u003e().takeScreenshot();\n    await takeScreenshot(selector: spot\u003cAppBar\u003e());\n    // console:\n    // Screenshot file:///var/folders/0j/p0s0zrv91tgd33zrxb88c0440000gn/T/spot/screenshot_test:16-w8UPv.png\n    //   taken at main.\u003cfn\u003e file:///Users/pascalwelsch/Projects/passsy/spot/test/spot/screenshot_test.dart:16:24\n  });\n}\n```\n\n### Load Fonts\n\nThe Timeline shows a rich report of the significant events during the test with screenshots.\nTo better understand what's shown on the screenshots, it's important to load the fonts from your app, otherwise Flutter renders the text with the unreadable `Ahem` default font.\n\nUse `await loadAppFonts()` to load the fonts defined in the `pubspec.yaml`.\n\n```dart\nvoid main() {\n  testWidgets('my widget test', (tester) async {\n    await loadAppFonts();\n    /* ... */\n  });\n}\n```\n\nAdditionally, `loadAppFonts()` loads the `Roboto` font, which is the default font in Flutter tests.\n\n| before | after |\n|--------|-------|\n| ![golden_testImage](https://github.com/user-attachments/assets/385e7760-c3ee-42d7-a3bd-6c5b906b7452) | ![golden_masterImage](https://github.com/user-attachments/assets/8261f27e-5a38-43c2-bff9-cfe01496215c) |\n\n## Widget selectors `spot`\n\n### Chain selectors\n\nYou know exactly where your widgets are.\nLike a button in the AppBar or a Text in a Dialog.\nSpot allows you to chain matchers, narrowing down the search space.\n\nChaining allows spot to create better error messages for you.\nSpot follows the chain of your selectors and can tell you exactly where the widget is missing.\nLike: `Could not find \"IconButton\" in \"AppBar\", but found these widgets instead: \u003cAppBar-widget-tree\u003e.`\n\n```dart\nspot\u003cAppBar\u003e().spot\u003cIconButton\u003e();\nspot\u003cIconButton\u003e(parents: [spot\u003cAppBar\u003e()]);\n```\n\nBoth syntax are identical. The first is shorter for when you only need a single parent.\nThe second allows checking for multiple parents, which is only required for rare use cases.\n\n### Selectors\n\nSpot has two features, creating **selectors** and asserting on them with **matchers**.\n\nA selector is a query to find a set of widgets.\nLike a SQL query, or a CSS selector.\nIt is only a description of what to search for, without actually doing the search.\n\nSelectors can be rather complex, it is therefore recommended to **reuse** them.\nYou can even save them top-level and reuse them across multiple tests.\n\n```dart\nspot\u003cElevatedButton\u003e();\n\nfinal WidgetSelector\u003cTextField\u003e textFields = \n    spot\u003cLoginScreen\u003e().spot\u003cLoginForm\u003e().spot\u003cTextField\u003e();\n\nfinal WidgetSelector\u003cTextField\u003e usernameTextField =\n    spot\u003cTextField\u003e(\n      parents: [\n        spot\u003cTextWithLabel\u003e(\n          children: [\n            spotText('Username'),\n          ],\n        ),\n      ],\n    );\n```\n\nA `WidgetSelector` may return 0, 1 or N widgets.\nDepending on how many widgets you expect to find, you should use the corresponding matchers.\n\n### Better errors\n\nBy chaining widget selectors, spot can provide better errors by searching the parent scope first for potential candidates.\nThis can save a lot of time when debugging failing widget tests.\n\nHere, the settings icon could not be found in the `AppBar`.\nClassic widget tests would show the following error using `findsOneWidget`.\n\n```text\nexpect(find.byIcon(Icons.settings), findsOneWidget);\n\n\u003e\u003e\u003e Expected: exactly one matching node in the widget tree\n\u003e\u003e\u003e   Actual: _WidgetIconFinder:\u003czero widgets with icon \"IconData(U+0E57F)\" (ignoring offstage widgets)\u003e\n\u003e\u003e\u003e    Which: means none were found but one was expected\n```\n\nThe error message above is not really helpful, because the actual error is not that there's no icon, but the `Icons.home` instead of `Icons.settings`.\n\nThe spot error message is much more helpful, showing two potential candidates in the `AppBar`.\n\n```text\nCould not find AppBar ᗕ Icon Widget with icon: \"IconData(U+0E57F)\" in widget tree, expected exactly\n1\nA less specific search (Icon with parent AppBar) discovered 2 matches!\n\nView timeline here: file:///var/folders/0j/p0s0zrv91tgd33zrxb88c0440000gn/T/hDEgVS/timeline-narrow-down-search-down-the-tree.html\n```\n\nSpot was able to find two Icon Widgets in the AppBar (with the wrong icon). They are presented in the Timeline report, highlighted in the screenshot.\n\n![Home Icon is the error](https://github.com/user-attachments/assets/9929d827-e8d0-4d01-aeeb-68eb6912d248)\n\nA picture is worth a thousand lines of code.\n\n#### Complex Example\n\n```dart\nimport 'package:flutter/material.dart';\nimport 'package:flutter_test/flutter_test.dart';\nimport 'package:spot/spot.dart';\n\nvoid main() {\n  testWidgets('Widget test with spot', (tester) async {\n    // Create widget selectors for elements in the widget tree\n    final scaffold = spot\u003cMaterialApp\u003e().spot\u003cScaffold\u003e();\n    final appBar = scaffold.spot\u003cAppBar\u003e();\n\n    // Assert for values of widgets\n    appBar.spotText('Dash').hasFontSize(14).hasFontColor(Colors.black87);\n\n    // Find widgets based on child widgets\n    appBar\n        .spot\u003cIconButton\u003e(children: [spotIcon(Icons.home)])\n        .existsOnce()\n        .hasTooltip('home');\n\n    // Find widgets based on multiple parent widgets\n    spot\u003cIcon\u003e(parents: [appBar, spot\u003cIconButton\u003e()])\n        .existsExactlyNTimes(2)\n        .all((icon) {\n      icon.hasColorWhere((color) =\u003e color.equals(Colors.black));\n    });\n\n    // Interact with widgets using `act`\n    final button = spot\u003cFloatingActionButton\u003e();\n    await act.tap(button);\n\n    final text = spot\u003cTextField\u003e();\n    await act.enterText(text, 'Hello World');\n    \n    // at the end of a failed test, spot will generate a Timeline HTML file\n  });\n}\n\n```\n\n### Matchers\n\nAfter creating a selector, you want to assert the widgets it found.\nThe `snapshot()` method creates a `WidgetSnapshot` of the widget tree at that point in time and finds all widgets that match the selector.\n\n#### Quantity matchers\n\nThe easiest matchers are the quantity matchers. They allow checking how many widgets were found.\n\n- `existsOnce()` asserts that exactly one widget was found\n- `doesNotExist()` asserts that no widget was found\n- `existsExactlyNTimes(n)` asserts that exactly `n` widgets were found\n- `existsAtLeastOnce()` asserts that at least one widget was found\n- `existsAtMostOnce()` asserts that at most one widget was found\n\n```dart\n\nfinal selector = spot\u003cElevatedButton\u003e();\n\n// calls snapshot() internally\nfinal matchOne = selector.existsOnce(); \nfinal matchMultiple = selector.existsExactlyNTimes(5);\n\nselector.doesNotExist(); // end, nothing to match on \n```\n\n#### Property matchers\n\nThe property matchers allow asserting on the properties of the widgets.\nYou don't have to use `expect()`, instead you can use the `has*`/`is*` matchers directly.\n\n```dart\nspot\u003cTooltip\u003e()\n    .existsOnce() // takes snapshot and asserts quantity\n    // start your chain of matchers\n    .hasMessage('Favorite')\n    .hasShowDurationWhere(\n      (it) =\u003e it.isGreaterOrEqual(Duration(seconds: 1000)),\n    )\n    .hasTriggerMode(TooltipTriggerMode.longPress);\n```\n\nTo match multiple widgets use `all()` or `any()`\n\n```dart\nspot\u003cAppBar\u003e().spot\u003cTooltip\u003e().existsAtLeastOnce()\n    .all((tooltip) =\u003e tooltip\n      .hasShowDurationWhere((it) =\u003e it.isGreaterOrEqual(Duration(seconds: 1000)))\n      .hasTriggerMode(TooltipTriggerMode.longPress)\n    );\n```\n\n### Selectors vs. Matchers\n\nIt is recommended to use matchers instead of selectors once you have narrowed down the search space to the widget you want to assert on.\nThis makes the error messages much clearer.\nInstead of `widget not found` you'll get `Found ToolTip with message 'Settings' but expected 'Favorite'` as error message.\n\n```dart\n// DON'T\nspot\u003cTooltip\u003e()\n    .withMessage('Favorite') // selector\n    .withTriggerMode(TooltipTriggerMode.longPress) // selector\n    .existsOnce();\n\n// DO\nspot\u003cTooltip\u003e()\n    .existsOnce()\n    .hasMessage('Favorite') // matcher\n    .hasTriggerMode(TooltipTriggerMode.longPress); // matcher\n```\n\n### Find offstage widgets\n\nBy default, `spot()` only finds widgets that are \"onstage\", not hidden with the [`Offstage`](https://api.flutter.dev/flutter/widgets/Offstage-class.html) widget.\n\nTo find offstage widgets, start your widget selector with `spotOffstage()`.\nSearch for both - the on- and offstage widgets - with `spotAllWidgets()`.\n\nFor existing selectors, use `overrideWidgetPresence(WidgetPresence presence)` to modify the presence to `offstage`, `onstage` or `combined`.\n\n```dart\nimport 'package:flutter/material.dart';\nimport 'package:flutter_test/flutter_test.dart';\nimport 'package:spot/spot.dart';\n\nvoid main() {\n  testWidgets('Spot offstage and combined widgets', (tester) async {\n    await tester.pumpWidget(\n      MaterialApp(\n        home: Row(\n          children: [\n            Text('a'),\n            Text('b'),\n            Offstage(child: Text('c')),\n          ],\n        ),\n      ),\n    );\n    \n    spot\u003cText\u003e().withText('a').existsOnce();\n    spot\u003cText\u003e().withText('c').doesNotExist();\n    spot\u003cText\u003e().withText('c').overrideWidgetPresence(WidgetPresence.offstage).existsOnce();\n    \n    spotOffstage().spot\u003cText\u003e().existsAtMostNTimes(3);\n    spotOffstage().spotText('c').existsOnce();\n    spotOffstage().overrideWidgetPresence(WidgetPresence.onstage).spotText('c').doesNotExist();\n    \n    spotAllWidgets().spotText('a').existsOnce();\n    spotAllWidgets().spotText('c').existsOnce();\n    spotOffstage().overrideWidgetPresence(WidgetPresence.combined).spotText('a').existsOnce();\n    spotOffstage().overrideWidgetPresence(WidgetPresence.combined).spotText('c').existsOnce();\n  });\n}\n```\n\n## act - tap, drag, type, click\n\nTo interact with widgets, use the `act` API.\nThe collection of functions mimics user interactions with the UI.\nIt tries to reach feature parity with the `WidgetTester` API, while drastically improving common pitfalls and error messages.\n\n### tap\n\nTriggers a tap event (down + up) on the found widget.\n\n```dart\n// flutter_test\n// await tester.tap(find.byType(ElevatedButton));\n\n// spot\nawait act.tap(spot\u003cElevatedButton\u003e());\n```\n\nTapping a widget looks almost identical to the `WidgetTester` API but with a few improvements.\n\n- Checks that the widget is within the window bounds\n- When partially visible, automatically taps the visible part (and prints a warning when \u003c80% tappable)\n- When fully covered, shows which widgets overlay it\n- Adds screenshot with crosshair annotation to the Timeline\n- pumps automatically after the tap\n- When multiple widgets are found, it prints a useful error message\n\n### tapAt\n\n```dart\nawait act.tapAt(const Offset(100, 100));\n```\n\nTaps the screen (down + up) at `position` on the global coordinate system and pumps a frame.\n\n- Checks that `position` is within the window viewport\n- Lists all widgets reacting to the hitTest in the timeline\n- pumps automatically after the tap\n\n### enterText\n\n```dart\nawait act.enterText(spot\u003cEmailAddress\u003e(), 'dash@wiredash.com');\n```\n\nThe `act.enterText()` automatically searches for the first `EditableText` widget within the provide selector (`spot\u003cEmailAddress\u003e()`).\nIt then enters the text in one go (like pasting it) and pumps the widget tree.\n\n### dragUntilVisible\n\n```dart\nawait act.dragUntilVisible(\n  dragStart: spot\u003cLongListScreen\u003e().spot\u003cScrollable\u003e(),\n  dragTarget: spotText('Item 32'),\n  moveStep: const Offset(0, -100),\n);\n```\n\nThe `act.dragUntilVisible()` continuously drags from the center of `dragStart` until it reaches `dragTarget`.\n\nThe direction is determined by the `moveStep` parameter.\nScrolling towards the end (bottom) of a list is archived with a negative `dy` (`Offset(0, -100)`).\nscrolling towards the beginning (top) requires a positive `dy` (`Offset(0, 100)`).\n\nScroll faster by increasing the `moveStep` value. Make sure to keep it below the height of the viewport or the `dragTarget` might be missed.\nCheck the Timeline for screenshots of the dragged area. It will help to determine good values for `moveStep`, minimizing the number of drags.\n\nIncrease `maxIteration` (default `30`) for very long lists if the item is not found within that many drags.\n\n### more act functions\n\nPlease [create an issues](https://github.com/passsy/spot/issues/new) when you miss a function or have a suggestion for a new one. Any contribution in this direction is welcome.\n\n## Roadmap\n\n- ✅ Make chainable `WidgetSelector`s\n- ✅ Print full widget tree when assertions fail\n- ✅ Allow defining `WidgetSelector` with children\n- ✅ Allow defining `WidgetSelector` with parents\n- ✅ Interop with `Finder` API\n- ✅ Match properties of widgets (via `DiagnosticsNode`)\n- ✅ Allow matching of nested properties (with checks API)\n- ✅ Generate code for custom properties for Flutter widgets\n- ✅ Allow generating code for properties of 3rd party widgets\n- ✅ Interact with widgets (`act`)\n- ✅ Allow manually printing a screenshot at certain points\n- ✅ Negate child matchers\n- ✅ Simplify `WidgetSelector` API\n- ✅ Create screenshot when test fails\n- ✅️ Create interactive HTML page with all widgets and matchers when test fails\n- ✅ Automatically create report with screenshots of all user interactions\n- ✅ `loadAppFonts()`\n- ⬜️ More `act` features, feature parity with `WidgetTester`\n- ⬜️ Combine multiple WidgetSelectors with `and`\n- ⬜️ Become the de facto Widget selector API for [patrol](https://pub.dev/packages/patrol)\n- ⬜️ Single pixel color testing\n- ⬜️ `pumpSmart()`\n\n## Project state\n\n100% production ready.\n\n- The `WidgetSelector` API is stable\n- The existing `act` API is limited, but what exists is great.\n- The Timeline is in its early stages but will be improved over time.\n\n## License\n\n```text\nCopyright 2022 Pascal Welsch\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n   http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpasssy%2Fspot","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpasssy%2Fspot","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpasssy%2Fspot/lists"}