Profile Creation Feature Tests

This commit is contained in:
Sarah Jamie Lewis 2022-01-28 13:28:59 -08:00
parent eddca2e974
commit 1652094e05
13 changed files with 106 additions and 2087 deletions

View File

@ -0,0 +1,44 @@
Feature: Basic Profile Management
Scenario: Error on Creating a Profile without a Display Name
Given I wait until the widget with type 'ProfileMgrView' is present
And I tap the button with tooltip "Add new profile"
Then I expect the text 'Display Name' to be present
And I expect the text 'New Password' to be present
And I expect the text 'Please enter a display name' to be absent
Then I tap the "button" widget with label "Add new profile"
And I expect the text 'Please enter a display name' to be present
And I take a screenshot
Scenario: Create Unencrypted Profile
Given I wait until the widget with type 'ProfileMgrView' is present
And I tap the button with tooltip "Add new profile"
Then I expect the text 'Display Name' to be present
And I expect the text 'New Password' to be present
And I take a screenshot
Then I tap the "passwordCheckBox" widget
And I expect the text 'New Password' to be absent
And I take a screenshot
Then I fill the "displayNameFormElement" field with "Alice (<h1>hello</h1>)"
Then I tap the "button" widget with label "Add new profile"
And I expect a "ProfileRow" widget with text "Alice (<h1>hello</h1>)"
And I take a screenshot
Then I tap the "ProfileRow" widget with label "Alice (<h1>hello</h1>)"
And I expect the text 'Alice (<h1>hello</h1>) » Conversations' to be present
And I take a screenshot
Scenario: Create Encrypted Profile
Given I wait until the widget with type 'ProfileMgrView' is present
And I tap the button with tooltip "Add new profile"
Then I expect the text 'Display Name' to be present
And I expect the text 'New Password' to be present
And I take a screenshot
Then I fill the "displayNameFormElement" field with "Alice (Encrypted)"
Then I fill the "passwordFormElement" field with "password1"
Then I fill the "confirmPasswordFormElement" field with "password1"
And I take a screenshot
Then I tap the "button" widget with label "Add new profile"
And I expect a "ProfileRow" widget with text "Alice (Encrypted)"
And I take a screenshot
Then I tap the "ProfileRow" widget with label "Alice (Encrypted)"
And I expect the text 'Alice (Encrypted) » Conversations' to be present
And I take a screenshot

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,72 +0,0 @@
//import 'package:flutter_gherkin/flutter_gherkin_integration_test.dart'; // notice new import name
import 'package:flutter_gherkin/flutter_gherkin.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:gherkin/gherkin.dart';
// The application under test.
import 'package:cwtch/main.dart' as app;
import 'package:glob/glob.dart';
import 'hooks/env.dart';
import 'steps/chat.dart';
import 'steps/files.dart';
import 'steps/form_elements.dart';
import 'steps/overrides.dart';
import 'steps/text.dart';
import 'steps/utils.dart';
part 'gherkin_suite_test.g.dart';
const REPLACED_BY_SCRIPT = <String>['integration_test/features/**.feature'];
@GherkinTestSuite(executionOrder: ExecutionOrder.alphabetical, featurePaths: <String>['./integration_test/features/05_p2p_chat/01_add_remove_block_archive.feature','./integration_test/features/05_p2p_chat/02_proto_invites.feature','./integration_test/features/05_p2p_chat/03_send_receive.feature','./integration_test/features/05_p2p_chat/04_special_messages.feature','./integration_test/features/05_p2p_chat/05_overlays_invite.feature','./integration_test/features/05_p2p_chat/06_overlays_file.feature','./integration_test/features/05_p2p_chat/07_overlays_image.feature'])
void main() {
executeTestSuite(
FlutterTestConfiguration.DEFAULT([])
..reporters = [
StdoutReporter(MessageLevel.error)
..setWriteLineFn(print)
..setWriteFn(print),
ProgressReporter()
..setWriteLineFn(print)
..setWriteFn(print),
TestRunSummaryReporter()
..setWriteLineFn(print)
..setWriteFn(print),
JsonReporter(
writeReport: (_, __) => Future<void>.value(),
),
]
..customStepParameterDefinitions = [
SwitchStateParameter(),
]
..stepDefinitions = [
// chat elements
ExpectReply(),
// form elements
CheckSwitchState(),
CheckSwitchStateWithText(),
DropdownChoose(),
// utils
TakeScreenshot(),
// overrides
TapWidgetWithType(),
TapFirstWidget(),
WaitUntilTypeExists(),
ExpectTextToBePresent(),
ExpectWidgetWithTextWithin(),
WaitUntilTextExists(),
SwipeOnType(),
// text
TorVersionPresent(),
TooltipTap(),
// files
FolderExists(),
FileExists(),
]
..hooks = [
ResetCwtchEnvironment(),
AttachScreenshotOnFailedStepHook(),
],
(World world) => app.main(),
);
}

View File

@ -50,7 +50,9 @@ void main() {
TakeScreenshot(),
// overrides
TapWidgetWithType(),
TapFirstWidget(),
TapWidgetWithLabel(),
ExpectWidgetWithText(),
TapButtonWithText(),
WaitUntilTypeExists(),
ExpectTextToBePresent(),
ExpectWidgetWithTextWithin(),

View File

@ -1,265 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'gherkin_suite_test.dart';
// **************************************************************************
// GherkinSuiteTestGenerator
// **************************************************************************
class _CustomGherkinIntegrationTestRunner extends GherkinIntegrationTestRunner {
_CustomGherkinIntegrationTestRunner(
TestConfiguration configuration,
Future<void> Function(World) appMainFunction,
) : super(configuration, appMainFunction);
@override
void onRun() {
testFeature2();
}
void testFeature2() {
runFeature(
'Sending and receiving chat messages:',
<String>['@env:aliceandbob1'],
() {
runScenario(
'Bob receives the message from Alice',
<String>['@env:aliceandbob1'],
(TestDependencies dependencies) async {
await runStep(
'Given I wait until the widget with type "ProfileRow" is present',
<String>[],
null,
dependencies,
);
await runStep(
'And I wait for 4 seconds',
<String>[],
null,
dependencies,
);
await runStep(
'Given I tap the button that contains the text "Alice"',
<String>[],
null,
dependencies,
);
await runStep(
'And I tap the button that contains the text "Bob"',
<String>[],
null,
dependencies,
);
await runStep(
'And I wait until the text "Contact is offline, messages can\'t be delivered right now" is absent',
<String>[],
null,
dependencies,
);
await runStep(
'When I fill the "txtCompose" field with "hello! this is a test!"',
<String>[],
null,
dependencies,
);
await runStep(
'And I tap the "btnSend" button',
<String>[],
null,
dependencies,
);
await runStep(
'Then I expect a "MessageBubble" widget with text "hello! this is a test!\u202F" to be present within 5 seconds',
<String>[],
null,
dependencies,
);
await runStep(
'And I tap the back button',
<String>[],
null,
dependencies,
);
await runStep(
'And I tap the back button',
<String>[],
null,
dependencies,
);
await runStep(
'Given I tap the button that contains the text "Bob"',
<String>[],
null,
dependencies,
);
await runStep(
'And I tap the button that contains the text "Alice"',
<String>[],
null,
dependencies,
);
await runStep(
'Then I expect a "MessageBubble" widget with text "hello! this is a test!\u202F" to be present within 5 seconds',
<String>[],
null,
dependencies,
);
},
onBefore: () async => onBeforeRunFeature(
'Sending and receiving chat messages',
<String>['@env:aliceandbob1'],
),
onAfter: null,
);
runScenario(
'Bob replies to a message from Alice',
<String>['@env:aliceandbob1'],
(TestDependencies dependencies) async {
await runStep(
'Given I wait until the widget with type "ProfileRow" is present',
<String>[],
null,
dependencies,
);
await runStep(
'And I wait for 4 seconds',
<String>[],
null,
dependencies,
);
await runStep(
'Given I tap the button that contains the text "Alice"',
<String>[],
null,
dependencies,
);
await runStep(
'And I tap the button that contains the text "Bob"',
<String>[],
null,
dependencies,
);
await runStep(
'And I wait until the text "Contact is offline, messages can\'t be delivered right now" is absent',
<String>[],
null,
dependencies,
);
await runStep(
'When I fill the "txtCompose" field with "hello! this is a test!"',
<String>[],
null,
dependencies,
);
await runStep(
'And I tap the "btnSend" button',
<String>[],
null,
dependencies,
);
await runStep(
'Then I expect a "MessageBubble" widget with text "hello! this is a test!\u202F" to be present within 5 seconds',
<String>[],
null,
dependencies,
);
await runStep(
'And I tap the back button',
<String>[],
null,
dependencies,
);
await runStep(
'And I tap the back button',
<String>[],
null,
dependencies,
);
await runStep(
'Given I tap the button that contains the text "Bob"',
<String>[],
null,
dependencies,
);
await runStep(
'And I tap the button that contains the text "Alice"',
<String>[],
null,
dependencies,
);
await runStep(
'And I tap the button with tooltip "Reply to this message"',
<String>[],
null,
dependencies,
);
await runStep(
'And I fill the "txtCompose" field with "yay the test worked"',
<String>[],
null,
dependencies,
);
await runStep(
'And I tap the "btnSend" button',
<String>[],
null,
dependencies,
);
await runStep(
'Then I expect to see the message "yay the test worked\u202F" replying to "hello! this is a test!" within 5 seconds',
<String>[],
null,
dependencies,
);
await runStep(
'And I take a screenshot',
<String>[],
null,
dependencies,
);
},
onBefore: null,
onAfter: () async => onAfterRunFeature(
'Sending and receiving chat messages',
),
);
},
);
}
}
void executeTestSuite(
TestConfiguration configuration,
Future<void> Function(World) appMainFunction,
) {
_CustomGherkinIntegrationTestRunner(configuration, appMainFunction).run();
}

View File

@ -1,20 +0,0 @@
var reporter = require('cucumber-html-reporter');
var options = {
theme: 'bootstrap',
jsonFile: 'gherkin/reports/integration_response_data.json',
output: 'gherkin/reports/index.html',
reportSuiteAsScenarios: true,
scenarioTimestamp: true,
launchReport: true,
metadata: {
"App Version":"0.3.2",
"Test Environment": "STAGING",
"Browser": "Chrome 54.0.2840.98",
"Platform": "Windows 10",
"Parallel": "Scenarios",
"Executed": "Remote"
}
};
reporter.generate(options);

View File

@ -27,14 +27,46 @@ StepDefinitionGeneric TapWidgetWithType() {
);
}
StepDefinitionGeneric TapFirstWidget() {
StepDefinitionGeneric TapWidgetWithLabel() {
return given2<String, String, FlutterWorld>(
RegExp(r'I tap the {string} widget with label {string}$'),
(ofType, text, context) async {
final finder = context.world.appDriver.findByDescendant(
context.world.appDriver.findBy(widgetTypeByName(ofType), FindType.type),
context.world.appDriver.findBy(text, FindType.text),
firstMatchOnly: true);
//Text wdg = await context.world.appDriver.widget(finder, ExpectedWidgetResultType.first);
//print(wdg.debugDescribeChildren().first.)
await context.world.appDriver.tap(finder);
await context.world.appDriver.waitForAppToSettle();
},
);
}
StepDefinitionGeneric ExpectWidgetWithText() {
return given2<String, String, FlutterWorld>(
RegExp(r'I expect a {string} widget with text {string}$'),
(ofType, text, context) async {
final finder = context.world.appDriver.findByDescendant(
context.world.appDriver.findBy(widgetTypeByName(ofType), FindType.type),
context.world.appDriver.findBy(text, FindType.text),
firstMatchOnly: true);
//Text wdg = await context.world.appDriver.widget(finder, ExpectedWidgetResultType.first);
//print(wdg.debugDescribeChildren().first.)
await context.world.appDriver.isPresent(finder);
await context.world.appDriver.waitForAppToSettle();
},
);
}
StepDefinitionGeneric TapButtonWithText() {
return given1<String, FlutterWorld>(
RegExp(r'I tap the first {string} (?:button|element|label|icon|field|text|widget)$'),
RegExp(r'I tap the {string} (?:button|element|label|icon|field|text|widget)$'),
(input1, context) async {
final finder = context.world.appDriver.findByDescendant(
context.world.appDriver.findBy(Flwtch, FindType.type),
context.world.appDriver.findBy(input1, FindType.key),
firstMatchOnly: true);
context.world.appDriver.findBy(Flwtch, FindType.type),
context.world.appDriver.findBy(input1, FindType.key),
firstMatchOnly: true);
//Text wdg = await context.world.appDriver.widget(finder, ExpectedWidgetResultType.first);
//print(wdg.debugDescribeChildren().first.)
await context.world.appDriver.tap(finder);
@ -195,6 +227,10 @@ Type widgetTypeByName(String input1) {
return ProfileRow;
case "TorIcon":
return TorIcon;
case "button":
return ElevatedButton;
case "ProfileRow":
return ProfileRow;
default:
throw("Unknown type $input1. add to integration_test/features/overrides.dart");
}

View File

@ -104,6 +104,7 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
height: 20,
),
CwtchTextField(
key: Key("displayNameFormElement"),
controller: ctrlrNick,
autofocus: false,
hintText: AppLocalizations.of(context)!.yourDisplayName,
@ -146,6 +147,7 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
visible: Provider.of<ProfileInfoState>(context).onion.isEmpty,
child: Column(mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[
Checkbox(
key: Key("passwordCheckBox"),
value: usePassword,
fillColor: MaterialStateProperty.all(theme.current().defaultButtonColor),
activeColor: theme.current().defaultButtonActiveColor,
@ -179,6 +181,7 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
height: 20,
),
CwtchPasswordField(
key: Key("currentPasswordFormElement"),
controller: ctrlrOldPass,
autoFillHints: [AutofillHints.newPassword],
validator: (value) {
@ -204,6 +207,7 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
height: 20,
),
CwtchPasswordField(
key: Key("passwordFormElement"),
controller: ctrlrPass,
validator: (value) {
// Password field can be empty when just updating the profile, not on creation
@ -224,6 +228,7 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
height: 20,
),
CwtchPasswordField(
key: Key("confirmPasswordFormElement"),
controller: ctrlrPass2,
validator: (value) {
// Password field can be empty when just updating the profile, not on creation

View File

@ -9,20 +9,26 @@ const hints = [AutofillHints.password];
// Provides a styled Password Input Field for use in Form Widgets.
// Callers must provide a text controller, label helper text and a validator.
class CwtchPasswordField extends StatefulWidget {
CwtchPasswordField({required this.controller, required this.validator, this.action, this.autofocus = false, this.autoFillHints = hints});
CwtchPasswordField({required this.controller, required this.validator, this.action, this.autofocus = false, this.autoFillHints = hints, this.key});
final TextEditingController controller;
final FormFieldValidator validator;
final Function(String)? action;
final bool autofocus;
final Iterable<String> autoFillHints;
final Key? key;
@override
_CwtchTextFieldState createState() => _CwtchTextFieldState();
_CwtchPasswordTextFieldState createState() => _CwtchPasswordTextFieldState();
}
class _CwtchTextFieldState extends State<CwtchPasswordField> {
class _CwtchPasswordTextFieldState extends State<CwtchPasswordField> {
bool obscureText = true;
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
// todo: translations
@ -42,7 +48,6 @@ class _CwtchTextFieldState extends State<CwtchPasswordField> {
autofillHints: widget.autoFillHints,
autovalidateMode: AutovalidateMode.always,
onFieldSubmitted: widget.action,
textInputAction: TextInputAction.unspecified,
enableSuggestions: false,
autocorrect: false,
decoration: InputDecoration(

View File

@ -8,7 +8,7 @@ doNothing(String x) {}
// Provides a styled Text Field for use in Form Widgets.
// Callers must provide a text controller, label helper text and a validator.
class CwtchTextField extends StatefulWidget {
CwtchTextField({required this.controller, this.hintText = "", this.validator, this.autofocus = false, this.onChanged = doNothing, this.number = false, this.multiLine = false});
CwtchTextField({required this.controller, this.hintText = "", this.validator, this.autofocus = false, this.onChanged = doNothing, this.number = false, this.multiLine = false, this.key});
final TextEditingController controller;
final String hintText;
final FormFieldValidator? validator;
@ -16,6 +16,7 @@ class CwtchTextField extends StatefulWidget {
final bool autofocus;
final bool multiLine;
final bool number;
final Key? key;
@override
_CwtchTextFieldState createState() => _CwtchTextFieldState();

View File

@ -1,10 +1,9 @@
#!/bin/bash
pkill tor
paths=$(find . -wholename "./integration_test/features/05*/*.feature" | sort | sed -z "s/\\n/','/g;s/,'$//;s/^/'/")
paths=$(find . -wholename "./integration_test/features/*/$1*.feature" | sort | sed -z "s/\\n/','/g;s/,'$//;s/^/'/")
sed "s|featurePaths: REPLACED_BY_SCRIPT|featurePaths: <String>[$paths]|" integration_test/gherkin_suite_test.editable.dart > integration_test/gherkin_suite_test.dart
flutter pub run build_runner clean
flutter pub run build_runner build --delete-conflicting-outputs
LD_LIBRARY_PATH=/home/erinn/Android/Goprojects/libcwtch-go/ CWTCH_HOME=./integration_test/env/temp/ flutter drive --driver=test_driver/integration_test_driver.dart --target=integration_test/gherkin_suite_test.dart
LD_LIBRARY_PATH=./linux/ CWTCH_HOME=./integration_test/env/temp/ flutter drive --driver=test_driver/integration_test_driver.dart --target=integration_test/gherkin_suite_test.dart
node index2.js
xdg-open integration_test/gherkin/reports/cucumber_report.html