-
-
Notifications
You must be signed in to change notification settings - Fork 184
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add getText enhancement for complex text widgets (#755)
* feat: add getText enhancement for complex text widgets * update description for new command * fix indentation and readme * update extension command files and readme
- Loading branch information
Showing
4 changed files
with
299 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
import 'dart:async'; | ||
import 'package:flutter/material.dart'; | ||
import 'package:flutter_driver/driver_extension.dart'; | ||
import 'package:flutter_driver/src/common/message.dart'; | ||
import 'package:flutter_driver/src/extension/extension.dart'; | ||
import 'package:flutter_test/flutter_test.dart'; | ||
|
||
|
||
class DragCommand extends Command { | ||
final double startX; | ||
final double startY; | ||
final double endX; | ||
final double endY; | ||
final Duration duration; | ||
|
||
DragCommand(this.startX, this.startY, this.endX, this.endY, this.duration); | ||
|
||
@override | ||
String get kind => 'dragAndDropWithCommandExtension'; | ||
|
||
DragCommand.deserialize(Map<String, String> params) | ||
: startX = double.parse(params['startX']!), | ||
startY = double.parse(params['startY']!), | ||
endX = double.parse(params['endX']!), | ||
endY = double.parse(params['endY']!), | ||
duration = Duration(milliseconds: int.parse(params['duration']!)); | ||
} | ||
|
||
|
||
class DragResult extends Result { | ||
final bool success; | ||
|
||
const DragResult(this.success); | ||
|
||
@override | ||
Map<String, dynamic> toJson() { | ||
return { | ||
'success': success, | ||
}; | ||
} | ||
} | ||
|
||
|
||
class DragCommandExtension extends CommandExtension { | ||
@override | ||
Future<Result> call(Command command, WidgetController prober, | ||
CreateFinderFactory finderFactory, CommandHandlerFactory handlerFactory) async { | ||
final DragCommand dragCommand = command as DragCommand; | ||
|
||
final Offset startLocation = Offset(dragCommand.startX, dragCommand.startY); | ||
final Offset offset = Offset(dragCommand.endX - dragCommand.startX, dragCommand.endY - dragCommand.startY); | ||
|
||
await prober.timedDragFrom(startLocation, offset, dragCommand.duration); | ||
|
||
return const DragResult(true); | ||
} | ||
|
||
@override | ||
String get commandKind => 'dragAndDropWithCommandExtension'; | ||
|
||
@override | ||
Command deserialize( | ||
Map<String, String> params, | ||
DeserializeFinderFactory finderFactory, | ||
DeserializeCommandFactory commandFactory) { | ||
return DragCommand.deserialize(params); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,180 @@ | ||
import 'dart:convert'; | ||
|
||
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); | ||
|
||
// Parse JSON | ||
final dynamic finderData = json.decode(jsonStr); | ||
|
||
if (finderData is! Map<String, dynamic>) { | ||
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) { | ||
return widget.data ?? widget.textSpan?.toPlainText(); | ||
} | ||
|
||
@override | ||
Future<Result> 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); | ||
|
||
// 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' | ||
}); | ||
} | ||
|
||
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 | ||
String get commandKind => 'getTextWithCommandExtension'; | ||
|
||
@override | ||
Command deserialize( | ||
Map<String, String> params, | ||
DeserializeFinderFactory finderFactory, | ||
DeserializeCommandFactory commandFactory) { | ||
return GetTextCommand.deserialize(params); | ||
} | ||
} | ||
|
||
class GetTextCommand extends Command { | ||
final String base64Element; | ||
|
||
GetTextCommand(this.base64Element); | ||
|
||
@override | ||
String get kind => 'getTextWithCommandExtension'; | ||
|
||
GetTextCommand.deserialize(Map<String, String> params) | ||
: base64Element = params['findBy']!; | ||
} | ||
|
||
class GetTextResult extends Result { | ||
final bool success; | ||
final Map<String, dynamic>? data; | ||
|
||
const GetTextResult(this.success, {this.data}); | ||
|
||
@override | ||
Map<String, dynamic> toJson() { | ||
return <String, dynamic>{ | ||
'success': success, | ||
if (data != null) ...data!, | ||
}; | ||
} | ||
} |