diff --git a/dev/e2e_app/integration_test/common.dart b/dev/e2e_app/integration_test/common.dart index 2037e4007..15d43c7a2 100644 --- a/dev/e2e_app/integration_test/common.dart +++ b/dev/e2e_app/integration_test/common.dart @@ -19,6 +19,7 @@ void patrol( String description, Future Function(PatrolIntegrationTester) callback, { bool? skip, + List tags = const [], NativeAutomatorConfig? nativeAutomatorConfig, LiveTestWidgetsFlutterBindingFramePolicy framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.fadePointers, @@ -30,5 +31,6 @@ void patrol( framePolicy: framePolicy, skip: skip, callback, + tags: tags, ); } diff --git a/dev/e2e_app/integration_test/example_test.dart b/dev/e2e_app/integration_test/example_test.dart index 4698a854c..096142ad0 100644 --- a/dev/e2e_app/integration_test/example_test.dart +++ b/dev/e2e_app/integration_test/example_test.dart @@ -29,4 +29,31 @@ void main() { expect($('Hello, Flutter!'), findsOneWidget); }, ); + + patrol( + 'short test with two tags', + tags: ['smoke', 'fume'], + ($) async { + await createApp($); + + await $(FloatingActionButton).tap(); + expect($(#counterText).text, '1'); + await $(FloatingActionButton).tap(); + expect($(#counterText).text, '2'); + }, + ); + + patrol( + 'short test with tag', + tags: ['smoke'], + ($) async { + await createApp($); + + await $(FloatingActionButton).tap(); + expect($(#counterText).text, '1'); + + await $(#textField).enterText('Hello, Flutter!'); + expect($('Hello, Flutter!'), findsOneWidget); + }, + ); } diff --git a/dev/e2e_app/pubspec.lock b/dev/e2e_app/pubspec.lock index 14644de4f..4ec538357 100644 --- a/dev/e2e_app/pubspec.lock +++ b/dev/e2e_app/pubspec.lock @@ -446,7 +446,7 @@ packages: path: "../../packages/patrol" relative: true source: path - version: "3.9.0" + version: "3.10.0" patrol_finders: dependency: transitive description: diff --git a/docs/compatibility-table.mdx b/docs/compatibility-table.mdx index c2857e9e7..213a09c4d 100644 --- a/docs/compatibility-table.mdx +++ b/docs/compatibility-table.mdx @@ -13,7 +13,8 @@ the table below to assess which version you should use. | patrol | patrol_cli | | -------------- | -------------- | -| 3.6.0 - | 2.6.5 - | +| 3.10.0 - | 3.1.0 - | +| 3.6.0 - | 2.6.5 - 3.0.1 | | 3.4.0 - 3.5.2 | 2.6.0 - 2.6.4 | | 3.0.0 - 3.3.0 | 2.3.0 - 2.5.0 | | 2.3.0 - 2.3.2 | 2.2.0 - 2.2.2 | diff --git a/packages/patrol/CHANGELOG.md b/packages/patrol/CHANGELOG.md index c8749ba40..41b722cd6 100644 --- a/packages/patrol/CHANGELOG.md +++ b/packages/patrol/CHANGELOG.md @@ -1,10 +1,11 @@ -## Unreleased +## 3.10.0 - Implement `enableBluetooth` and `disableBluetooth` methods for Android > 11. (#2254) - Implement `enableAirplaneMode` and `disableAirplaneMode` methods for Android. (#2254) - Implement `enableLocation` and `disableLocation` methods for Android. (#2259) - Fix opening settings app with clean state on iOS. (#2275) - Add native skip. (#2278) +- Add `tags` and `exclude-tags`. (#2286) ## 3.9.0 diff --git a/packages/patrol/android/src/main/kotlin/pl/leancode/patrol/contracts/Contracts.kt b/packages/patrol/android/src/main/kotlin/pl/leancode/patrol/contracts/Contracts.kt index 2cf87d080..ada5e6b1d 100644 --- a/packages/patrol/android/src/main/kotlin/pl/leancode/patrol/contracts/Contracts.kt +++ b/packages/patrol/android/src/main/kotlin/pl/leancode/patrol/contracts/Contracts.kt @@ -123,7 +123,8 @@ class Contracts { val name: String, val type: GroupEntryType, val entries: List, - val skip: Boolean + val skip: Boolean, + val tags: List ) data class ListDartTestsResponse ( diff --git a/packages/patrol/android/src/test/kotlin/pl/leancode/patrol/ContractsExtensionsTest.kt b/packages/patrol/android/src/test/kotlin/pl/leancode/patrol/ContractsExtensionsTest.kt index ad4e6e4a5..e25f95ac3 100644 --- a/packages/patrol/android/src/test/kotlin/pl/leancode/patrol/ContractsExtensionsTest.kt +++ b/packages/patrol/android/src/test/kotlin/pl/leancode/patrol/ContractsExtensionsTest.kt @@ -9,11 +9,11 @@ fun dartTestGroup( name: String, entries: List, ): DartGroupEntry { - return DartGroupEntry(name, GroupEntryType.group, entries, false) + return DartGroupEntry(name, GroupEntryType.group, entries, false, listOf()) } fun dartTestCase(name: String): DartGroupEntry { - return DartGroupEntry(name, GroupEntryType.test, listOf(), false) + return DartGroupEntry(name, GroupEntryType.test, listOf(), false, listOf()) } class DartTestGroupExtensionsTest { diff --git a/packages/patrol/darwin/Classes/AutomatorServer/Contracts.swift b/packages/patrol/darwin/Classes/AutomatorServer/Contracts.swift index 81fc1567a..d5c56f1eb 100644 --- a/packages/patrol/darwin/Classes/AutomatorServer/Contracts.swift +++ b/packages/patrol/darwin/Classes/AutomatorServer/Contracts.swift @@ -123,6 +123,7 @@ public struct DartGroupEntry: Codable { public var type: GroupEntryType public var entries: [DartGroupEntry] public var skip: Bool + public var tags: [String] } public struct ListDartTestsResponse: Codable { diff --git a/packages/patrol/lib/src/common.dart b/packages/patrol/lib/src/common.dart index 551e1b64c..8589b01b5 100644 --- a/packages/patrol/lib/src/common.dart +++ b/packages/patrol/lib/src/common.dart @@ -1,5 +1,6 @@ import 'dart:io' as io; +import 'package:boolean_selector/boolean_selector.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:meta/meta.dart'; @@ -169,12 +170,15 @@ DartGroupEntry createDartTestGroup( String name = '', int level = 0, int maxTestCaseLength = global_state.maxTestLength, + String? tags, + String? excludeTags, }) { final groupDTO = DartGroupEntry( name: name, type: GroupEntryType.group, entries: [], skip: parentGroup.metadata.skip, + tags: parentGroup.metadata.tags.toList(), ); for (final entry in parentGroup.entries) { @@ -203,14 +207,34 @@ DartGroupEntry createDartTestGroup( throw StateError('Test is not allowed to be defined at level $level'); } + if (tags != null) { + final includeTagsSelector = BooleanSelector.parse(tags); + + // If the user provided tags, skip tests that don't match all of them. + if (!includeTagsSelector.evaluate(entry.metadata.tags.contains)) { + continue; + } + } + + if (excludeTags != null) { + final excludeTagsSelector = BooleanSelector.parse(excludeTags); + + // Skip tests that do match any tags the user wants to exclude. + if (excludeTagsSelector.evaluate(entry.metadata.tags.contains)) { + continue; + } + } + groupDTO.entries.add( DartGroupEntry( name: name, type: GroupEntryType.test, entries: [], skip: entry.metadata.skip, + tags: entry.metadata.tags.toList(), ), ); + case Group _: groupDTO.entries.add( createDartTestGroup( @@ -218,6 +242,8 @@ DartGroupEntry createDartTestGroup( name: name, level: level + 1, maxTestCaseLength: maxTestCaseLength, + tags: tags, + excludeTags: excludeTags, ), ); } diff --git a/packages/patrol/lib/src/native/contracts/contracts.dart b/packages/patrol/lib/src/native/contracts/contracts.dart index 6142c8e25..d7bc9649b 100644 --- a/packages/patrol/lib/src/native/contracts/contracts.dart +++ b/packages/patrol/lib/src/native/contracts/contracts.dart @@ -224,6 +224,7 @@ class DartGroupEntry with EquatableMixin { required this.type, required this.entries, required this.skip, + required this.tags, }); factory DartGroupEntry.fromJson(Map json) => @@ -233,6 +234,7 @@ class DartGroupEntry with EquatableMixin { final GroupEntryType type; final List entries; final bool skip; + final List tags; Map toJson() => _$DartGroupEntryToJson(this); @@ -242,6 +244,7 @@ class DartGroupEntry with EquatableMixin { type, entries, skip, + tags, ]; } diff --git a/packages/patrol/lib/src/native/contracts/contracts.g.dart b/packages/patrol/lib/src/native/contracts/contracts.g.dart index e94d108d7..43928232c 100644 --- a/packages/patrol/lib/src/native/contracts/contracts.g.dart +++ b/packages/patrol/lib/src/native/contracts/contracts.g.dart @@ -14,6 +14,7 @@ DartGroupEntry _$DartGroupEntryFromJson(Map json) => .map((e) => DartGroupEntry.fromJson(e as Map)) .toList(), skip: json['skip'] as bool, + tags: (json['tags'] as List).map((e) => e as String).toList(), ); Map _$DartGroupEntryToJson(DartGroupEntry instance) => @@ -22,6 +23,7 @@ Map _$DartGroupEntryToJson(DartGroupEntry instance) => 'type': _$GroupEntryTypeEnumMap[instance.type]!, 'entries': instance.entries, 'skip': instance.skip, + 'tags': instance.tags, }; const _$GroupEntryTypeEnumMap = { diff --git a/packages/patrol/pubspec.yaml b/packages/patrol/pubspec.yaml index 68cbc6d71..ce18cab07 100644 --- a/packages/patrol/pubspec.yaml +++ b/packages/patrol/pubspec.yaml @@ -2,7 +2,7 @@ name: patrol description: > Powerful Flutter-native UI testing framework overcoming limitations of existing Flutter testing tools. Ready for action! -version: 3.9.0 +version: 3.10.0 homepage: https://patrol.leancode.co repository: https://github.com/leancodepl/patrol/tree/master/packages/patrol issue_tracker: https://github.com/leancodepl/patrol/issues @@ -17,6 +17,7 @@ environment: flutter: '>=3.22.0' dependencies: + boolean_selector: ^2.1.1 equatable: ^2.0.5 flutter: sdk: flutter diff --git a/packages/patrol/test/internals_test.dart b/packages/patrol/test/internals_test.dart index cadda704f..e6558ad3d 100644 --- a/packages/patrol/test/internals_test.dart +++ b/packages/patrol/test/internals_test.dart @@ -3,6 +3,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:patrol/src/common.dart' show createDartTestGroup, deduplicateGroupEntryName; import 'package:patrol/src/native/contracts/contracts.dart'; +import 'package:test_api/backend.dart'; import 'package:test_api/src/backend/group.dart'; import 'package:test_api/src/backend/invoker.dart' show LocalTest; import 'package:test_api/src/backend/metadata.dart'; @@ -63,16 +64,19 @@ void main() { name: '', type: GroupEntryType.group, skip: false, + tags: [], entries: [ DartGroupEntry( name: 'example_test', type: GroupEntryType.group, skip: false, + tags: [], entries: [ DartGroupEntry( name: 'alpha', type: GroupEntryType.group, skip: false, + tags: [], entries: [ _testEntry('first'), _testEntry('second'), @@ -82,6 +86,7 @@ void main() { name: 'bravo', type: GroupEntryType.group, skip: false, + tags: [], entries: [ _testEntry('first'), _testEntry('second'), @@ -93,6 +98,7 @@ void main() { name: 'open_app_test', type: GroupEntryType.group, skip: false, + tags: [], entries: [ _testEntry('open maps'), _testEntry('open browser'), @@ -137,17 +143,20 @@ void main() { name: '', type: GroupEntryType.group, skip: false, + tags: [], entries: [ DartGroupEntry( name: 'example_test', type: GroupEntryType.group, skip: false, + tags: [], entries: [ _testEntry('alpha'), DartGroupEntry( name: 'bravo', type: GroupEntryType.group, skip: false, + tags: [], entries: [ _testEntry('first'), _testEntry('second'), @@ -158,6 +167,7 @@ void main() { name: 'delta', type: GroupEntryType.group, skip: false, + tags: [], entries: [ _testEntry('first'), _testEntry('second'), @@ -199,11 +209,13 @@ void main() { name: '', type: GroupEntryType.group, skip: false, + tags: [], entries: [ DartGroupEntry( name: 'example_test', type: GroupEntryType.group, skip: false, + tags: [], entries: [ _testEntry('alpha'), _testEntry('zielony'), @@ -234,7 +246,7 @@ void main() { }); group('skip group of tests', () { - test('skip test', () { + test('skip test param should be passed in DartGroupEntry', () { // given final topLevelGroup = Group.root([ LocalTest('patrol_test_explorer', Metadata.empty, () {}), @@ -267,27 +279,252 @@ void main() { DartGroupEntry( name: '', type: GroupEntryType.group, + skip: false, + tags: [], entries: [ DartGroupEntry( name: 'example_test', type: GroupEntryType.group, + skip: true, + tags: [], entries: [ _testEntry('alpha'), ], - skip: true, ), DartGroupEntry( name: 'example2_test', type: GroupEntryType.group, + skip: false, + tags: [], entries: [ _testEntry('alpha'), _testEntry('bravo first'), _testEntry('bravo second'), ], + ), + ], + ), + ), + ); + }); + }); + + group('test with tags', () { + test('filter test, when tags are null', () { + // given + final topLevelGroup = Group.root([ + LocalTest('patrol_test_explorer', Metadata.empty, () {}), + Group( + 'example_test', + [ + _localTest( + 'example_test alpha', + metadata: Metadata(tags: ['tag1']), + ), + _localTest( + 'example_test bravo first', + metadata: Metadata(tags: ['tag2']), + ), + _localTest( + 'example_test bravo second', + metadata: Metadata(tags: ['tag3']), + ), + ], + ), + ]); + + // when + final dartTestGroup = createDartTestGroup( + topLevelGroup, + ); + + // then + expect( + dartTestGroup, + equals( + DartGroupEntry( + name: '', + type: GroupEntryType.group, + skip: false, + tags: [], + entries: [ + DartGroupEntry( + name: 'example_test', + type: GroupEntryType.group, skip: false, + tags: [], + entries: [ + _testEntry('alpha', tags: ['tag1']), + _testEntry('bravo first', tags: ['tag2']), + _testEntry('bravo second', tags: ['tag3']), + ], ), ], + ), + ), + ); + }); + + test('filter included tags', () { + // given + final topLevelGroup = Group.root([ + LocalTest('patrol_test_explorer', Metadata.empty, () {}), + Group( + 'example_test', + [ + _localTest( + 'example_test alpha', + metadata: Metadata(tags: ['tag1']), + ), + _localTest( + 'example_test bravo first', + metadata: Metadata(tags: ['tag2']), + ), + _localTest( + 'example_test bravo second', + metadata: Metadata(tags: ['tag3']), + ), + ], + ), + ]); + + // when + final dartTestGroup = createDartTestGroup( + topLevelGroup, + tags: 'tag1', + ); + + // then + expect( + dartTestGroup, + equals( + DartGroupEntry( + name: '', + type: GroupEntryType.group, skip: false, + tags: [], + entries: [ + DartGroupEntry( + name: 'example_test', + type: GroupEntryType.group, + skip: false, + tags: [], + entries: [ + _testEntry('alpha', tags: ['tag1']), + ], + ), + ], + ), + ), + ); + }); + + test('filter excluded tags', () { + // given + final topLevelGroup = Group.root([ + LocalTest('patrol_test_explorer', Metadata.empty, () {}), + Group( + 'example_test', + [ + _localTest( + 'example_test alpha', + metadata: Metadata(tags: ['tag1']), + ), + _localTest( + 'example_test bravo first', + metadata: Metadata(tags: ['tag2']), + ), + _localTest( + 'example_test bravo second', + metadata: Metadata(tags: ['tag3']), + ), + ], + ), + ]); + + // when + final dartTestGroup = createDartTestGroup( + topLevelGroup, + excludeTags: 'tag1', + ); + + // then + expect( + dartTestGroup, + equals( + DartGroupEntry( + name: '', + type: GroupEntryType.group, + skip: false, + tags: [], + entries: [ + DartGroupEntry( + name: 'example_test', + type: GroupEntryType.group, + skip: false, + tags: [], + entries: [ + _testEntry('bravo first', tags: ['tag2']), + _testEntry('bravo second', tags: ['tag3']), + ], + ), + ], + ), + ), + ); + }); + + test('filter included and excluded tags', () { + // given + final topLevelGroup = Group.root([ + LocalTest('patrol_test_explorer', Metadata.empty, () {}), + Group( + 'example_test', + [ + _localTest( + 'example_test alpha', + metadata: Metadata(tags: ['tag1']), + ), + _localTest( + 'example_test bravo first', + metadata: Metadata(tags: ['tag1', 'tag2']), + ), + _localTest( + 'example_test bravo second', + metadata: Metadata(tags: ['tag3']), + ), + ], + ), + ]); + + // when + final dartTestGroup = createDartTestGroup( + topLevelGroup, + tags: 'tag1 || tag3', + excludeTags: 'tag2', + ); + + // then + expect( + dartTestGroup, + equals( + DartGroupEntry( + name: '', + type: GroupEntryType.group, + skip: false, + tags: [], + entries: [ + DartGroupEntry( + name: 'example_test', + type: GroupEntryType.group, + skip: false, + tags: [], + entries: [ + _testEntry('alpha', tags: ['tag1']), + _testEntry('bravo second', tags: ['tag3']), + ], + ), + ], ), ), ); @@ -295,13 +532,19 @@ void main() { }); } -LocalTest _localTest(String name) => LocalTest(name, Metadata.empty, () {}); +LocalTest _localTest(String name, {Metadata? metadata}) => + LocalTest(name, metadata ?? Metadata.empty, () {}); -DartGroupEntry _testEntry(String name, {bool skip = false}) { +DartGroupEntry _testEntry( + String name, { + bool skip = false, + List tags = const [], +}) { return DartGroupEntry( name: name, type: GroupEntryType.test, entries: [], skip: skip, + tags: tags, ); } diff --git a/packages/patrol_cli/CHANGELOG.md b/packages/patrol_cli/CHANGELOG.md index 2f18b49d3..18949f79d 100644 --- a/packages/patrol_cli/CHANGELOG.md +++ b/packages/patrol_cli/CHANGELOG.md @@ -1,3 +1,8 @@ +## 3.1.0 +- Add `tags` and `exclude-tags`. (#2286) + +This version requires version 3.10.0 of `patrol` package. + ## 3.0.1 - Fallback to read `java` version from `JAVA_HOME` when `flutter doctor` doesn't print any. diff --git a/packages/patrol_cli/lib/src/base/constants.dart b/packages/patrol_cli/lib/src/base/constants.dart index 1604a0509..3b97ef275 100644 --- a/packages/patrol_cli/lib/src/base/constants.dart +++ b/packages/patrol_cli/lib/src/base/constants.dart @@ -1,3 +1,3 @@ /// Version of Patrol CLI. Must be kept in sync with pubspec.yaml. /// If you update this, make sure that compatibility-table.mdx is updated (if needed) -const version = '3.0.1'; +const version = '3.1.0'; diff --git a/packages/patrol_cli/lib/src/commands/build_android.dart b/packages/patrol_cli/lib/src/commands/build_android.dart index e834f8790..2dc1edaf3 100644 --- a/packages/patrol_cli/lib/src/commands/build_android.dart +++ b/packages/patrol_cli/lib/src/commands/build_android.dart @@ -36,6 +36,8 @@ class BuildAndroidCommand extends PatrolCommand { usesLabelOption(); usesWaitOption(); usesPortOptions(); + usesTagsOption(); + usesExcludeTagsOption(); usesAndroidOptions(); } @@ -83,9 +85,17 @@ class BuildAndroidCommand extends PatrolCommand { _logger.detail('Received test target: $t'); } + final tags = stringArg('tags'); + final excludeTags = stringArg('exclude-tags'); + if (tags != null) { + _logger.detail('Received tag(s): $tags'); + } + if (excludeTags != null) { + _logger.detail('Received exclude tag(s): $excludeTags'); + } final entrypoint = _testBundler.bundledTestFile; if (boolArg('generate-bundle')) { - _testBundler.createTestBundle(targets); + _testBundler.createTestBundle(targets, tags, excludeTags); } final flavor = stringArg('flavor') ?? config.android.flavor; diff --git a/packages/patrol_cli/lib/src/commands/build_ios.dart b/packages/patrol_cli/lib/src/commands/build_ios.dart index 05b6cc0da..ce5f11715 100644 --- a/packages/patrol_cli/lib/src/commands/build_ios.dart +++ b/packages/patrol_cli/lib/src/commands/build_ios.dart @@ -36,6 +36,8 @@ class BuildIOSCommand extends PatrolCommand { usesLabelOption(); usesWaitOption(); usesPortOptions(); + usesTagsOption(); + usesExcludeTagsOption(); usesIOSOptions(); argParser.addFlag( @@ -87,9 +89,17 @@ class BuildIOSCommand extends PatrolCommand { _logger.detail('Received test target: $t'); } + final tags = stringArg('tags'); + final excludeTags = stringArg('exclude-tags'); + if (tags != null) { + _logger.detail('Received tag(s): $tags'); + } + if (excludeTags != null) { + _logger.detail('Received exclude tag(s): $excludeTags'); + } final entrypoint = _testBundler.bundledTestFile; if (boolArg('generate-bundle')) { - _testBundler.createTestBundle(targets); + _testBundler.createTestBundle(targets, tags, excludeTags); } final flavor = stringArg('flavor') ?? config.ios.flavor; diff --git a/packages/patrol_cli/lib/src/commands/build_macos.dart b/packages/patrol_cli/lib/src/commands/build_macos.dart index cb5c421c1..d6f94bebc 100644 --- a/packages/patrol_cli/lib/src/commands/build_macos.dart +++ b/packages/patrol_cli/lib/src/commands/build_macos.dart @@ -36,6 +36,8 @@ class BuildMacOSCommand extends PatrolCommand { usesLabelOption(); usesWaitOption(); usesPortOptions(); + usesTagsOption(); + usesExcludeTagsOption(); usesMacOSOptions(); } @@ -83,9 +85,17 @@ class BuildMacOSCommand extends PatrolCommand { _logger.detail('Received test target: $t'); } + final tags = stringArg('tags'); + final excludeTags = stringArg('exclude-tags'); + if (tags != null) { + _logger.detail('Received tag(s): $tags'); + } + if (excludeTags != null) { + _logger.detail('Received exclude tag(s): $excludeTags'); + } final entrypoint = _testBundler.bundledTestFile; if (boolArg('generate-bundle')) { - _testBundler.createTestBundle(targets); + _testBundler.createTestBundle(targets, tags, excludeTags); } final flavor = stringArg('flavor') ?? config.ios.flavor; diff --git a/packages/patrol_cli/lib/src/commands/develop.dart b/packages/patrol_cli/lib/src/commands/develop.dart index d9e1effd8..27396b136 100644 --- a/packages/patrol_cli/lib/src/commands/develop.dart +++ b/packages/patrol_cli/lib/src/commands/develop.dart @@ -53,6 +53,7 @@ class DevelopCommand extends PatrolCommand { usesLabelOption(); usesWaitOption(); usesPortOptions(); + usesTagsOption(); usesUninstallOption(); diff --git a/packages/patrol_cli/lib/src/commands/test.dart b/packages/patrol_cli/lib/src/commands/test.dart index 6e69aad6e..4ee205926 100644 --- a/packages/patrol_cli/lib/src/commands/test.dart +++ b/packages/patrol_cli/lib/src/commands/test.dart @@ -49,6 +49,8 @@ class TestCommand extends PatrolCommand { usesLabelOption(); usesWaitOption(); usesPortOptions(); + usesTagsOption(); + usesExcludeTagsOption(); usesUninstallOption(); @@ -100,9 +102,17 @@ class TestCommand extends PatrolCommand { _logger.detail('Received test target: $t'); } + final tags = stringArg('tags'); + final excludeTags = stringArg('exclude-tags'); + if (tags != null) { + _logger.detail('Received tag(s): $tags'); + } + if (excludeTags != null) { + _logger.detail('Received exclude tag(s): $excludeTags'); + } final entrypoint = _testBundler.bundledTestFile; if (boolArg('generate-bundle')) { - _testBundler.createTestBundle(targets); + _testBundler.createTestBundle(targets, tags, excludeTags); } final androidFlavor = stringArg('flavor') ?? config.android.flavor; diff --git a/packages/patrol_cli/lib/src/runner/patrol_command.dart b/packages/patrol_cli/lib/src/runner/patrol_command.dart index c03b6808c..8b0b08396 100644 --- a/packages/patrol_cli/lib/src/runner/patrol_command.dart +++ b/packages/patrol_cli/lib/src/runner/patrol_command.dart @@ -131,6 +131,22 @@ abstract class PatrolCommand extends Command { ); } + void usesTagsOption() { + argParser.addOption( + 'tags', + help: 'Tags to filter the tests by.', + valueHelp: '(chrome || firefox) && !slow', + ); + } + + void usesExcludeTagsOption() { + argParser.addOption( + 'exclude-tags', + help: 'Tags to exclude the tests by.', + valueHelp: 'safari', + ); + } + void usesAndroidOptions() { argParser.addOption( 'package-name', diff --git a/packages/patrol_cli/lib/src/test_bundler.dart b/packages/patrol_cli/lib/src/test_bundler.dart index bc8674c7a..67151bcc1 100644 --- a/packages/patrol_cli/lib/src/test_bundler.dart +++ b/packages/patrol_cli/lib/src/test_bundler.dart @@ -15,7 +15,11 @@ class TestBundler { final Logger _logger; /// Creates an entrypoint for use with `patrol test` and `patrol build`. - void createTestBundle(List testFilePaths) { + void createTestBundle( + List testFilePaths, + String? tags, + String? excludeTags, + ) { if (testFilePaths.isEmpty) { throw ArgumentError('testFilePaths must not be empty'); } @@ -82,7 +86,10 @@ Future main() async { // Maybe somewhat counterintuitively, this callback runs *after* the calls // to group() below. final topLevelGroup = Invoker.current!.liveTest.groups.first; - final dartTestGroup = createDartTestGroup(topLevelGroup); + final dartTestGroup = createDartTestGroup(topLevelGroup, + tags: ${tags != null ? "'$tags'" : null}, + excludeTags: ${excludeTags != null ? "'$excludeTags'" : null}, + ); testExplorationCompleter.complete(dartTestGroup); print('patrol_test_explorer: obtained Dart-side test hierarchy:'); printGroupStructure(dartTestGroup); diff --git a/packages/patrol_cli/pubspec.yaml b/packages/patrol_cli/pubspec.yaml index f8ced06dd..f2964f566 100644 --- a/packages/patrol_cli/pubspec.yaml +++ b/packages/patrol_cli/pubspec.yaml @@ -1,7 +1,7 @@ name: patrol_cli description: > Command-line tool for Patrol, a powerful Flutter-native UI testing framework. -version: 3.0.1 # Must be kept in sync with constants.dart +version: 3.1.0 # Must be kept in sync with constants.dart homepage: https://patrol.leancode.co repository: https://github.com/leancodepl/patrol/tree/master/packages/patrol_cli issue_tracker: https://github.com/leancodepl/patrol/issues?q=is%3Aopen+is%3Aissue+label%3A%22package%3A+patrol_cli%22 diff --git a/packages/patrol_cli/test/test_bundler_test.dart b/packages/patrol_cli/test/test_bundler_test.dart index 8033e393e..36b889406 100644 --- a/packages/patrol_cli/test/test_bundler_test.dart +++ b/packages/patrol_cli/test/test_bundler_test.dart @@ -40,7 +40,14 @@ void _test(Platform platform) { }); test('throws ArgumentError when no tests are given', () { - expect(() => testBundler.createTestBundle([]), throwsArgumentError); + expect( + () => testBundler.createTestBundle( + [], + null, + null, + ), + throwsArgumentError, + ); }); test('generates imports from relative paths', () { diff --git a/packages/patrol_devtools_extension/lib/api/contracts.dart b/packages/patrol_devtools_extension/lib/api/contracts.dart index 6142c8e25..d7bc9649b 100644 --- a/packages/patrol_devtools_extension/lib/api/contracts.dart +++ b/packages/patrol_devtools_extension/lib/api/contracts.dart @@ -224,6 +224,7 @@ class DartGroupEntry with EquatableMixin { required this.type, required this.entries, required this.skip, + required this.tags, }); factory DartGroupEntry.fromJson(Map json) => @@ -233,6 +234,7 @@ class DartGroupEntry with EquatableMixin { final GroupEntryType type; final List entries; final bool skip; + final List tags; Map toJson() => _$DartGroupEntryToJson(this); @@ -242,6 +244,7 @@ class DartGroupEntry with EquatableMixin { type, entries, skip, + tags, ]; } diff --git a/packages/patrol_devtools_extension/lib/api/contracts.g.dart b/packages/patrol_devtools_extension/lib/api/contracts.g.dart index e94d108d7..43928232c 100644 --- a/packages/patrol_devtools_extension/lib/api/contracts.g.dart +++ b/packages/patrol_devtools_extension/lib/api/contracts.g.dart @@ -14,6 +14,7 @@ DartGroupEntry _$DartGroupEntryFromJson(Map json) => .map((e) => DartGroupEntry.fromJson(e as Map)) .toList(), skip: json['skip'] as bool, + tags: (json['tags'] as List).map((e) => e as String).toList(), ); Map _$DartGroupEntryToJson(DartGroupEntry instance) => @@ -22,6 +23,7 @@ Map _$DartGroupEntryToJson(DartGroupEntry instance) => 'type': _$GroupEntryTypeEnumMap[instance.type]!, 'entries': instance.entries, 'skip': instance.skip, + 'tags': instance.tags, }; const _$GroupEntryTypeEnumMap = { diff --git a/schema.dart b/schema.dart index 2e4f8305c..681c9acd5 100644 --- a/schema.dart +++ b/schema.dart @@ -12,6 +12,7 @@ class DartGroupEntry { late GroupEntryType type; late List entries; late bool skip; + late List tags; } enum GroupEntryType { group, test }