From 6025b31f13ab63ed534fa29639025fc4d0a75652 Mon Sep 17 00:00:00 2001 From: tan Date: Sat, 30 Nov 2024 23:29:44 +0700 Subject: [PATCH 1/2] feat: add new command to get text --- lib/get_text_command.dart | 212 ++++++++++++++++++++++++++++++++++++++ lib/main.dart | 78 ++++++++++---- 2 files changed, 271 insertions(+), 19 deletions(-) create mode 100644 lib/get_text_command.dart diff --git a/lib/get_text_command.dart b/lib/get_text_command.dart new file mode 100644 index 0000000..b92b3f2 --- /dev/null +++ b/lib/get_text_command.dart @@ -0,0 +1,212 @@ +import 'dart:convert'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_driver/driver_extension.dart'; +import 'package:flutter_driver/src/common/find.dart'; +import 'package:flutter_driver/src/common/message.dart'; +import 'package:flutter_test/flutter_test.dart'; + +class Base64URL { + static String encode(String str) { + String base64 = base64Encode(utf8.encode(str)); + return base64.replaceAll('+', '-').replaceAll('/', '_').replaceAll('=', ''); + } + + static String decode(String str) { + String base64 = str.replaceAll('-', '+').replaceAll('_', '/'); + + // Add padding if needed + switch (base64.length % 4) { + case 2: + base64 += '=='; + break; + case 3: + base64 += '='; + break; + } + + return utf8.decode(base64Decode(base64)); + } +} + +class FinderHelper { + static SerializableFinder deserializeBase64(String base64Str) { + try { + // Decode base64 to JSON string + final jsonStr = Base64URL.decode(base64Str); + if (kDebugMode) { + print('decoded string: $jsonStr'); + } + + // Parse JSON + final dynamic finderData = json.decode(jsonStr); + + if (finderData is! Map) { + throw Exception('finder is not valid'); + } + + if (!finderData.containsKey('finderType')) { + throw Exception('Invalid finder format: missing finderType'); + } + + final String finderType = finderData['finderType'] as String; + + switch (finderType) { + case 'ByText': + return ByText(finderData['text'] as String); + + case 'ByType': + return ByType(finderData['type'] as String); + + case 'ByValueKey': + final keyType = finderData['keyValueType'] as String?; + final keyValue = finderData['keyValueString'] as String; + + if (keyType == 'int') { + return ByValueKey(int.parse(keyValue)); + } + return ByValueKey(keyValue); + + case 'Ancestor': + // Parse of and matching which are JSON strings + final ofJson = json.decode(finderData['of'] as String); + final matchingJson = json.decode(finderData['matching'] as String); + + return Ancestor( + of: deserializeBase64(Base64URL.encode(json.encode(ofJson))), + matching: + deserializeBase64(Base64URL.encode(json.encode(matchingJson))), + matchRoot: finderData['matchRoot'] == 'true', + firstMatchOnly: finderData['firstMatchOnly'] == 'true', + ); + + case 'Descendant': + final ofJson = json.decode(finderData['of'] as String); + final matchingJson = json.decode(finderData['matching'] as String); + + return Descendant( + of: deserializeBase64(Base64URL.encode(json.encode(ofJson))), + matching: + deserializeBase64(Base64URL.encode(json.encode(matchingJson))), + matchRoot: finderData['matchRoot'] == 'true', + firstMatchOnly: finderData['firstMatchOnly'] == 'true', + ); + + default: + throw Exception('Unsupported finder type: $finderType'); + } + } catch (e) { + throw Exception('Error deserializing finder: $e'); + } + } +} + +class GetTextCommandExtension extends CommandExtension { + String getTextFromWidget(Text widget) { + // If direct text data is available, return it + if (widget.data != null) { + return widget.data!; + } + // Otherwise get text from textSpan + if (widget.textSpan != null) { + return getTextFromTextSpan(widget.textSpan!); + } + return 'TEXT_NOT_FOUND'; + } + + String getTextFromTextSpan(InlineSpan span) { + if (span is TextSpan) { + final List textParts = []; + + // Add current text if not null + if (span.text != null) { + textParts.add(span.text!); + } + + // Add children's text recursively + if (span.children != null) { + for (final InlineSpan child in span.children!) { + textParts.add(getTextFromTextSpan(child)); + } + } + + return textParts.join(''); + } + return 'WIDGET_IS_NOT_TEXT_SPAN'; + } + + @override + Future call( + Command command, + WidgetController prober, + CreateFinderFactory finderFactory, + CommandHandlerFactory handlerFactory) async { + final GetTextCommand dragCommand = command as GetTextCommand; + + // Create finder for Text widget + final type = dragCommand.base64Element; + // decodeBase64 to json + SerializableFinder serializableFinder = + FinderHelper.deserializeBase64(type); + + final Finder finder = finderFactory.createFinder(serializableFinder); + + // Get the widget element + final Element element = prober.element(finder); + + // Get text data from Text widget + String textData = ''; + + if (element.widget is Text) { + textData = getTextFromWidget(element.widget as Text); + } else { + textData = 'Found element is not a Text widget'; + } + + return GetTextResult(true, data: {'text': textData}); + } + + @override + String get commandKind => 'getTextWithCommandExtension'; + + @override + Command deserialize( + Map params, + DeserializeFinderFactory finderFactory, + DeserializeCommandFactory commandFactory) { + if (kDebugMode) { + print('Available keys: ${params.keys.toList()}'); + print('Params content: $params'); + } + return GetTextCommand.deserialize(params); + } +} + +class GetTextCommand extends Command { + final String base64Element; + + GetTextCommand(this.base64Element); + + @override + String get kind => 'getTextWithCommandExtension'; + + GetTextCommand.deserialize(Map params) + : base64Element = params['findBy']!; +} + +class GetTextResult extends Result { + final bool success; + final Map? data; + + const GetTextResult(this.success, {this.data}); + + @override + Map toJson() { + return { + 'success': success, + if (data != null) ...data!, + }; + } +} diff --git a/lib/main.dart b/lib/main.dart index 0f25b4d..db0e7f2 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,13 +1,12 @@ +import 'package:demo/get_text_command.dart'; import 'package:flutter/material.dart'; import 'package:flutter_driver/driver_extension.dart'; import 'package:flutter_driver/src/extension/extension.dart'; import 'extended_commands.dart'; - void main() { - enableFlutterDriverExtension( - commands: [DragCommandExtension()]); + enableFlutterDriverExtension(commands: [DragCommandExtension(), GetTextCommandExtension()]); runApp(const MyApp()); } @@ -29,6 +28,7 @@ class MyApp extends StatelessWidget { class MyHomePage extends StatefulWidget { const MyHomePage({super.key, required this.title}); + final String title; @override @@ -45,22 +45,62 @@ class _MyHomePageState extends State { title: Text(widget.title), backgroundColor: Theme.of(context).colorScheme.inversePrimary, ), - body: ReorderableListView( - padding: const EdgeInsets.all(8), - children: items.map((item) { - return ListTile( - key: ValueKey(item), - title: Text(item), - tileColor: Colors.grey.shade200, - ); - }).toList(), - onReorder: (oldIndex, newIndex) { - setState(() { - if (newIndex > oldIndex) newIndex -= 1; - final item = items.removeAt(oldIndex); - items.insert(newIndex, item); - }); - }, + body: Column( + children: [ + // Original ReorderableListView + Expanded( + child: ReorderableListView( + padding: const EdgeInsets.all(8), + children: items.map((item) { + return ListTile( + key: ValueKey(item), + title: Text(item), + tileColor: Colors.grey.shade200, + ); + }).toList(), + onReorder: (oldIndex, newIndex) { + setState(() { + if (newIndex > oldIndex) newIndex -= 1; + final item = items.removeAt(oldIndex); + items.insert(newIndex, item); + }); + }, + ), + ), + // TextSpan example + const Padding( + padding: EdgeInsets.all(16.0), + child: Text.rich( + TextSpan( + children: [ + TextSpan( + text: 'Amount: ', + style: TextStyle( + fontSize: 18, + color: Colors.grey, + ), + ), + TextSpan( + text: '100', + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: Colors.blue, + ), + ), + TextSpan( + text: ' USD', + style: TextStyle( + fontSize: 16, + color: Colors.grey, + ), + ), + ], + ), + key: ValueKey('amount'), + ), + ), + ], ), ); } From 31c31f2aa4bc89757a7a532e0ff5f2337b12f9c1 Mon Sep 17 00:00:00 2001 From: tan Date: Sun, 1 Dec 2024 22:11:31 +0700 Subject: [PATCH 2/2] handling get text error and rename drag command --- ...ended_commands.dart => drag_commands.dart} | 0 lib/get_text_command.dart | 62 +++++-------------- lib/main.dart | 4 +- test_driver/app.dart | 2 +- 4 files changed, 18 insertions(+), 50 deletions(-) rename lib/{extended_commands.dart => drag_commands.dart} (100%) diff --git a/lib/extended_commands.dart b/lib/drag_commands.dart similarity index 100% rename from lib/extended_commands.dart rename to lib/drag_commands.dart diff --git a/lib/get_text_command.dart b/lib/get_text_command.dart index b92b3f2..b852335 100644 --- a/lib/get_text_command.dart +++ b/lib/get_text_command.dart @@ -1,6 +1,5 @@ import 'dart:convert'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_driver/driver_extension.dart'; @@ -36,9 +35,6 @@ class FinderHelper { try { // Decode base64 to JSON string final jsonStr = Base64URL.decode(base64Str); - if (kDebugMode) { - print('decoded string: $jsonStr'); - } // Parse JSON final dynamic finderData = json.decode(jsonStr); @@ -104,37 +100,8 @@ class FinderHelper { } class GetTextCommandExtension extends CommandExtension { - String getTextFromWidget(Text widget) { - // If direct text data is available, return it - if (widget.data != null) { - return widget.data!; - } - // Otherwise get text from textSpan - if (widget.textSpan != null) { - return getTextFromTextSpan(widget.textSpan!); - } - return 'TEXT_NOT_FOUND'; - } - - String getTextFromTextSpan(InlineSpan span) { - if (span is TextSpan) { - final List textParts = []; - - // Add current text if not null - if (span.text != null) { - textParts.add(span.text!); - } - - // Add children's text recursively - if (span.children != null) { - for (final InlineSpan child in span.children!) { - textParts.add(getTextFromTextSpan(child)); - } - } - - return textParts.join(''); - } - return 'WIDGET_IS_NOT_TEXT_SPAN'; + String? getTextFromWidget(Text widget) { + return widget.data ?? widget.textSpan?.toPlainText(); } @override @@ -156,16 +123,21 @@ class GetTextCommandExtension extends CommandExtension { // Get the widget element final Element element = prober.element(finder); - // Get text data from Text widget - String textData = ''; - - if (element.widget is Text) { - textData = getTextFromWidget(element.widget as Text); - } else { - textData = 'Found element is not a Text widget'; + // if element is not a Text widget, return false with error + if (element.widget is! Text) { + return const GetTextResult(false, data: { + 'errorCode': 'NOT_A_TEXT_WIDGET', + 'error': 'Found element is not a Text widget' + }); } - return GetTextResult(true, data: {'text': textData}); + final text = getTextFromWidget(element.widget as Text); + return text != null + ? GetTextResult(true, data: {'text': text}) + : const GetTextResult(false, data: { + 'errorCode': 'NO_TEXT_CONTENT', + 'error': 'No text content found' + }); } @override @@ -176,10 +148,6 @@ class GetTextCommandExtension extends CommandExtension { Map params, DeserializeFinderFactory finderFactory, DeserializeCommandFactory commandFactory) { - if (kDebugMode) { - print('Available keys: ${params.keys.toList()}'); - print('Params content: $params'); - } return GetTextCommand.deserialize(params); } } diff --git a/lib/main.dart b/lib/main.dart index db0e7f2..6b5cfc3 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,9 +1,9 @@ -import 'package:demo/get_text_command.dart'; import 'package:flutter/material.dart'; import 'package:flutter_driver/driver_extension.dart'; import 'package:flutter_driver/src/extension/extension.dart'; -import 'extended_commands.dart'; +import 'drag_commands.dart'; +import 'get_text_command.dart'; void main() { enableFlutterDriverExtension(commands: [DragCommandExtension(), GetTextCommandExtension()]); diff --git a/test_driver/app.dart b/test_driver/app.dart index b48f220..4e8d33c 100644 --- a/test_driver/app.dart +++ b/test_driver/app.dart @@ -1,4 +1,4 @@ -import 'package:demo/extended_commands.dart'; +import 'package:demo/drag_commands.dart'; import 'package:flutter_driver/driver_extension.dart'; import 'package:demo/main.dart' as app;