From 5fe120aa6d7522bfdd3fe905db5111858328bb0c Mon Sep 17 00:00:00 2001 From: pdenert Date: Thu, 1 Aug 2024 17:28:14 +0200 Subject: [PATCH] Add tags support --- dev/e2e_app/integration_test/common.dart | 2 ++ .../integration_test/example_test.dart | 23 +++++++++++++ dev/e2e_app/pubspec.lock | 2 +- .../pl/leancode/patrol/contracts/Contracts.kt | 3 +- .../Classes/AutomatorServer/Contracts.swift | 1 + packages/patrol/lib/src/common.dart | 25 +++++++++----- .../lib/src/native/contracts/contracts.dart | 3 ++ .../lib/src/native/contracts/contracts.g.dart | 2 ++ packages/patrol/pubspec.yaml | 2 +- packages/patrol/test/internals_test.dart | 33 ++++++++++++++++--- .../patrol_cli/lib/src/base/constants.dart | 2 +- .../lib/src/commands/build_android.dart | 4 ++- .../lib/src/commands/build_ios.dart | 4 ++- .../lib/src/commands/build_macos.dart | 4 ++- .../patrol_cli/lib/src/commands/develop.dart | 1 + .../patrol_cli/lib/src/commands/test.dart | 5 ++- .../lib/src/runner/patrol_command.dart | 8 +++++ packages/patrol_cli/lib/src/test_bundler.dart | 5 +-- packages/patrol_cli/pubspec.yaml | 2 +- .../patrol_cli/test/test_bundler_test.dart | 2 +- .../lib/api/contracts.dart | 3 ++ .../lib/api/contracts.g.dart | 2 ++ schema.dart | 1 + 23 files changed, 114 insertions(+), 25 deletions(-) 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..df88419e5 100644 --- a/dev/e2e_app/integration_test/example_test.dart +++ b/dev/e2e_app/integration_test/example_test.dart @@ -29,4 +29,27 @@ void main() { expect($('Hello, Flutter!'), findsOneWidget); }, ); + + patrol( + 'counter state is the same after going to Home and switching apps with tag', + tags: ['smoke'], + ($) async { + await createApp($); + + await $(FloatingActionButton).tap(); + expect($(#counterText).text, '1'); + + await $(#textField).enterText('Hello, Flutter!'); + expect($('Hello, Flutter!'), findsOneWidget); + + await $.native.pressHome(); + await $.native.openApp(); + + expect($(#counterText).text, '1'); + await $(FloatingActionButton).tap(); + + expect($(#counterText).text, '2'); + 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/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/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..ed255c2d5 100644 --- a/packages/patrol/lib/src/common.dart +++ b/packages/patrol/lib/src/common.dart @@ -169,12 +169,14 @@ DartGroupEntry createDartTestGroup( String name = '', int level = 0, int maxTestCaseLength = global_state.maxTestLength, + List tags = const [], }) { 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 +205,20 @@ DartGroupEntry createDartTestGroup( throw StateError('Test is not allowed to be defined at level $level'); } - groupDTO.entries.add( - DartGroupEntry( - name: name, - type: GroupEntryType.test, - entries: [], - skip: entry.metadata.skip, - ), - ); + final addTest = tags.isEmpty || tags.any(entry.metadata.tags.contains); + + if (addTest) { + 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 +226,7 @@ DartGroupEntry createDartTestGroup( name: name, level: level + 1, maxTestCaseLength: maxTestCaseLength, + tags: tags, ), ); } 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..e605b8812 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 diff --git a/packages/patrol/test/internals_test.dart b/packages/patrol/test/internals_test.dart index cadda704f..41d65c28b 100644 --- a/packages/patrol/test/internals_test.dart +++ b/packages/patrol/test/internals_test.dart @@ -63,16 +63,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 +85,7 @@ void main() { name: 'bravo', type: GroupEntryType.group, skip: false, + tags: [], entries: [ _testEntry('first'), _testEntry('second'), @@ -93,6 +97,7 @@ void main() { name: 'open_app_test', type: GroupEntryType.group, skip: false, + tags: [], entries: [ _testEntry('open maps'), _testEntry('open browser'), @@ -137,17 +142,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 +166,7 @@ void main() { name: 'delta', type: GroupEntryType.group, skip: false, + tags: [], entries: [ _testEntry('first'), _testEntry('second'), @@ -199,11 +208,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 +245,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,41 +278,53 @@ 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'), ], - skip: false, ), ], - skip: false, ), ), ); }); }); + + // group('test with tags', () { + // final topLevelGroup + // }) } LocalTest _localTest(String name) => LocalTest(name, 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/lib/src/base/constants.dart b/packages/patrol_cli/lib/src/base/constants.dart index 1604a0509..8b9d37db2 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.0.2'; diff --git a/packages/patrol_cli/lib/src/commands/build_android.dart b/packages/patrol_cli/lib/src/commands/build_android.dart index e834f8790..d468100ed 100644 --- a/packages/patrol_cli/lib/src/commands/build_android.dart +++ b/packages/patrol_cli/lib/src/commands/build_android.dart @@ -36,6 +36,7 @@ class BuildAndroidCommand extends PatrolCommand { usesLabelOption(); usesWaitOption(); usesPortOptions(); + usesTagsOption(); usesAndroidOptions(); } @@ -83,9 +84,10 @@ class BuildAndroidCommand extends PatrolCommand { _logger.detail('Received test target: $t'); } + final tags = stringsArg('tags'); final entrypoint = _testBundler.bundledTestFile; if (boolArg('generate-bundle')) { - _testBundler.createTestBundle(targets); + _testBundler.createTestBundle(targets, tags); } 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..bf41c766f 100644 --- a/packages/patrol_cli/lib/src/commands/build_ios.dart +++ b/packages/patrol_cli/lib/src/commands/build_ios.dart @@ -36,6 +36,7 @@ class BuildIOSCommand extends PatrolCommand { usesLabelOption(); usesWaitOption(); usesPortOptions(); + usesTagsOption(); usesIOSOptions(); argParser.addFlag( @@ -87,9 +88,10 @@ class BuildIOSCommand extends PatrolCommand { _logger.detail('Received test target: $t'); } + final tags = stringsArg('tags'); final entrypoint = _testBundler.bundledTestFile; if (boolArg('generate-bundle')) { - _testBundler.createTestBundle(targets); + _testBundler.createTestBundle(targets, tags); } 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..1bcbf0d64 100644 --- a/packages/patrol_cli/lib/src/commands/build_macos.dart +++ b/packages/patrol_cli/lib/src/commands/build_macos.dart @@ -36,6 +36,7 @@ class BuildMacOSCommand extends PatrolCommand { usesLabelOption(); usesWaitOption(); usesPortOptions(); + usesTagsOption(); usesMacOSOptions(); } @@ -83,9 +84,10 @@ class BuildMacOSCommand extends PatrolCommand { _logger.detail('Received test target: $t'); } + final tags = stringsArg('tags'); final entrypoint = _testBundler.bundledTestFile; if (boolArg('generate-bundle')) { - _testBundler.createTestBundle(targets); + _testBundler.createTestBundle(targets, tags); } 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..10589ee07 100644 --- a/packages/patrol_cli/lib/src/commands/test.dart +++ b/packages/patrol_cli/lib/src/commands/test.dart @@ -49,6 +49,7 @@ class TestCommand extends PatrolCommand { usesLabelOption(); usesWaitOption(); usesPortOptions(); + usesTagsOption(); usesUninstallOption(); @@ -100,9 +101,11 @@ class TestCommand extends PatrolCommand { _logger.detail('Received test target: $t'); } + final tags = stringsArg('tags'); + _logger.detail('Received ${tags.length} tag(s)'); final entrypoint = _testBundler.bundledTestFile; if (boolArg('generate-bundle')) { - _testBundler.createTestBundle(targets); + _testBundler.createTestBundle(targets, tags); } 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..32ffb7865 100644 --- a/packages/patrol_cli/lib/src/runner/patrol_command.dart +++ b/packages/patrol_cli/lib/src/runner/patrol_command.dart @@ -131,6 +131,14 @@ abstract class PatrolCommand extends Command { ); } + void usesTagsOption() { + argParser.addMultiOption( + 'tags', + help: 'Tags to filter the tests by.', + valueHelp: 'smoke,regression', + ); + } + 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..083849b3f 100644 --- a/packages/patrol_cli/lib/src/test_bundler.dart +++ b/packages/patrol_cli/lib/src/test_bundler.dart @@ -15,7 +15,7 @@ class TestBundler { final Logger _logger; /// Creates an entrypoint for use with `patrol test` and `patrol build`. - void createTestBundle(List testFilePaths) { + void createTestBundle(List testFilePaths, List tags) { if (testFilePaths.isEmpty) { throw ArgumentError('testFilePaths must not be empty'); } @@ -82,7 +82,8 @@ 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.map((tag) => '"$tag"').join(', ')}]); + // final dartTestGroup = createDartTestGroup(topLevelGroup); 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..e287dc973 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.0.2 # 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..585227b12 100644 --- a/packages/patrol_cli/test/test_bundler_test.dart +++ b/packages/patrol_cli/test/test_bundler_test.dart @@ -40,7 +40,7 @@ void _test(Platform platform) { }); test('throws ArgumentError when no tests are given', () { - expect(() => testBundler.createTestBundle([]), throwsArgumentError); + expect(() => testBundler.createTestBundle([], []), 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 }