update OTP integration tests

This commit is contained in:
Adam Velebil 2024-02-08 14:21:05 +01:00
parent b49ac0bb4e
commit 4f186bb2fc
No known key found for this signature in database
GPG Key ID: C9B1E4A3CBBD2E10
8 changed files with 107 additions and 29 deletions

View File

@ -2,16 +2,33 @@
tags: tags:
# Tests which we want to run on desktop # Tests which we want to run on desktop
desktop: desktop:
timeout: none
# Tests which we want to run on Android # Tests which we want to run on Android
android: android:
timeout: none
# Tests consuming quiet a lot of time # Tests consuming quiet a lot of time
slow: slow:
timeout: none
# Minimal tests # Minimal tests
# quick verification that the framework is working # quick verification that the framework is working
minimal: minimal:
timeout: none
# OATH tests # OATH tests
oath: oath:
timeout: none
# OTP tests
otp:
timeout: none
# PIV tests
piv:
timeout: none
# Management tests
management:
timeout: none

View File

@ -14,6 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
@Tags(['desktop', 'management'])
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart'; import 'package:integration_test/integration_test.dart';

View File

@ -14,12 +14,14 @@
* limitations under the License. * limitations under the License.
*/ */
@Tags(['android', 'desktop', 'oath']) @Tags(['desktop', 'otp'])
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart'; import 'package:integration_test/integration_test.dart';
import 'package:yubico_authenticator/app/views/keys.dart'; import 'package:yubico_authenticator/app/views/keys.dart';
import 'package:yubico_authenticator/otp/keys.dart'; import 'package:yubico_authenticator/otp/keys.dart';
import 'package:yubico_authenticator/otp/models.dart';
import 'utils/otp_test_util.dart';
import 'utils/test_util.dart'; import 'utils/test_util.dart';
void main() { void main() {
@ -27,21 +29,12 @@ void main() {
binding.framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.fullyLive; binding.framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.fullyLive;
group('OTP UI tests', () { group('OTP UI tests', () {
appTest('OTP menu items exist', (WidgetTester tester) async {
await tester.tap(find.byKey(otpAppDrawer));
await tester.shortWait();
await tester.tap(find.byKey(configureYubiOtp).hitTestable());
await tester.shortWait();
});
appTest('Yubico OTP slot 1', (WidgetTester tester) async { appTest('Yubico OTP slot 1', (WidgetTester tester) async {
await tester.tap(find.byKey(otpAppDrawer).hitTestable()); await tester.tap(find.byKey(otpAppDrawer).hitTestable());
await tester.shortWait(); await tester.shortWait();
//verify "Slot 1 is empty" //verify "Slot 1 is empty"
await tester.openSlotMenu(SlotId.one);
// we are missing the right click on top of the correct slot
await tester.tap(find.byKey(configureYubiOtp).hitTestable()); await tester.tap(find.byKey(configureYubiOtp).hitTestable());
await tester.shortWait(); await tester.shortWait();
@ -65,7 +58,7 @@ void main() {
// verify "Slot 1 is configured" // verify "Slot 1 is configured"
// we are missing the right click on top of the correct slot await tester.openSlotMenu(SlotId.one);
await tester.tap(find.byKey(configureChalResp).hitTestable()); await tester.tap(find.byKey(configureChalResp).hitTestable());
await tester.shortWait(); await tester.shortWait();
@ -85,9 +78,9 @@ void main() {
// verify "Slot 2 is empty" // verify "Slot 2 is empty"
// we are missing the right click on top of the correct slot await tester.openSlotMenu(SlotId.two);
await tester.tap(find.byKey(configureYubiOtp).hitTestable()); await tester.tap(find.byKey(configureStatic).hitTestable());
await tester.shortWait(); await tester.shortWait();
// this generates and saves static password // this generates and saves static password
@ -105,9 +98,9 @@ void main() {
// verify "Slot 2 is configured" // verify "Slot 2 is configured"
// we are missing the right click on top of the correct slot await tester.openSlotMenu(SlotId.two);
await tester.tap(find.byKey(configureYubiOtp).hitTestable()); await tester.tap(find.byKey(configureHotp).hitTestable());
await tester.shortWait(); await tester.shortWait();
// this writes and saves oath secret // this writes and saves oath secret
@ -127,17 +120,14 @@ void main() {
// verify "Slot 2 is configured" // verify "Slot 2 is configured"
// taps swap // taps swap
await tester.tap(find.byKey(actionsIconButtonKey).hitTestable()); await tester.tapSwapSlotsButton();
await tester.shortWait(); await tester.tap(find.byKey(swapButton).hitTestable());
await tester.tap(find.byKey(swapSlots).hitTestable());
await tester.shortWait();
await tester.tap(find.byKey(swap).hitTestable());
await tester.shortWait(); await tester.shortWait();
// verify "Slot 1 is configured" // verify "Slot 1 is configured"
// verify "Slot 2 is configured" // verify "Slot 2 is configured"
}); });
appTest('Delete Credentials', (WidgetTester tester) async { appTest('Delete Credentials', (WidgetTester tester) async {
await tester.tap(find.byKey(otpAppDrawer).hitTestable()); await tester.tap(find.byKey(otpAppDrawer).hitTestable());
await tester.shortWait(); await tester.shortWait();
@ -145,15 +135,24 @@ void main() {
// verify "Slot 1 is configured" // verify "Slot 1 is configured"
// verify "Slot 2 is configured" // verify "Slot 2 is configured"
// we need to right click on slot 1 await tester.openSlotMenu(SlotId.one);
await tester.tap(find.byKey(deleteAction).hitTestable()); await tester.tap(find.byKey(deleteAction).hitTestable());
await tester.shortWait(); await tester.longWait();
await tester.tap(find.byKey(deleteButton).hitTestable()); await tester.tap(find.byKey(deleteButton).hitTestable());
await tester.shortWait();
// wait for any toasts to be gone
await tester.pump(const Duration(seconds: 3));
var closeFinder = find.byKey(closeButton);
if (closeFinder.evaluate().isNotEmpty) {
// close the view
await tester.tap(closeFinder);
await tester.shortWait();
}
// we need to right click on slot 2 // we need to right click on slot 2
await tester.openSlotMenu(SlotId.two);
await tester.tap(find.byKey(deleteAction).hitTestable()); await tester.tap(find.byKey(deleteAction).hitTestable());
await tester.shortWait(); await tester.longWait();
await tester.tap(find.byKey(deleteButton).hitTestable()); await tester.tap(find.byKey(deleteButton).hitTestable());
await tester.shortWait(); await tester.shortWait();

View File

@ -14,6 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
@Tags(['desktop', 'piv'])
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart'; import 'package:integration_test/integration_test.dart';

View File

@ -0,0 +1,49 @@
/*
* Copyright (C) 2024 Yubico.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import 'package:flutter_test/flutter_test.dart';
import 'package:yubico_authenticator/app/views/keys.dart';
import 'package:yubico_authenticator/otp/keys.dart';
import 'package:yubico_authenticator/otp/models.dart';
import 'test_util.dart';
extension OathFunctions on WidgetTester {
/// Opens the menu of specific OTP Slot, either by tapping the button or
/// by tapping the list item
Future<void> openSlotMenu(SlotId slotId) async {
final menuButtonFinder = find.byKey(getOpenMenuButtonKey(slotId));
if (menuButtonFinder.evaluate().isNotEmpty) {
await tap(menuButtonFinder);
} else {
await tap(find.byKey(getAppListItemKey(slotId)));
}
await longWait();
}
/// tap the Swap Slots button - either first open the action menu,
/// or try to find the button on visible screen
Future<void> tapSwapSlotsButton() async {
final actionButtonFinder = find.byKey(actionsIconButtonKey);
if (actionButtonFinder.evaluate().isNotEmpty) {
await tap(actionButtonFinder);
await shortWait();
}
await tap(find.byKey(swapSlots).hitTestable());
await longWait();
}
}

View File

@ -16,6 +16,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'models.dart';
const _prefix = 'otp.keys'; const _prefix = 'otp.keys';
const _keyAction = '$_prefix.actions'; const _keyAction = '$_prefix.actions';
const _slotAction = '$_prefix.slot.actions'; const _slotAction = '$_prefix.slot.actions';
@ -34,7 +36,7 @@ const deleteAction = Key('$_slotAction.delete');
const saveButton = Key('$_prefix.save'); const saveButton = Key('$_prefix.save');
const deleteButton = Key('$_prefix.delete'); const deleteButton = Key('$_prefix.delete');
const swap = Key('$_prefix.swap'); const swapButton = Key('$_prefix.swap');
const secretField = Key('$_prefix.secret'); const secretField = Key('$_prefix.secret');
const publicIdField = Key('$_prefix.public_id'); const publicIdField = Key('$_prefix.public_id');
@ -43,3 +45,9 @@ const privateIdField = Key('$_prefix.private_id');
const useSerial = Key('$_prefix.use_serial'); const useSerial = Key('$_prefix.use_serial');
const generatePrivateId = Key('$_prefix.generate_private_id'); const generatePrivateId = Key('$_prefix.generate_private_id');
const generateSecretKey = Key('$_prefix.generate_secret_key'); const generateSecretKey = Key('$_prefix.generate_secret_key');
Key getOpenMenuButtonKey(SlotId slotId) =>
Key('$_prefix.open_slot_menu_slot_${slotId.name}');
Key getAppListItemKey(SlotId slotId) =>
Key('$_prefix.app_list_item_slot_${slotId.name}');

View File

@ -32,6 +32,7 @@ import '../../core/state.dart';
import '../../management/models.dart'; import '../../management/models.dart';
import '../../widgets/list_title.dart'; import '../../widgets/list_title.dart';
import '../features.dart' as features; import '../features.dart' as features;
import '../keys.dart';
import '../models.dart'; import '../models.dart';
import '../state.dart'; import '../state.dart';
import 'actions.dart'; import 'actions.dart';
@ -197,6 +198,7 @@ class _SlotListItem extends ConsumerWidget {
final hasFeature = ref.watch(featureProvider); final hasFeature = ref.watch(featureProvider);
return AppListItem( return AppListItem(
key: getAppListItemKey(slot),
otpSlot, otpSlot,
selected: selected, selected: selected,
leading: CircleAvatar( leading: CircleAvatar(
@ -209,6 +211,7 @@ class _SlotListItem extends ConsumerWidget {
trailing: expanded trailing: expanded
? null ? null
: OutlinedButton( : OutlinedButton(
key: getOpenMenuButtonKey(slot),
onPressed: Actions.handler(context, OpenIntent(otpSlot)), onPressed: Actions.handler(context, OpenIntent(otpSlot)),
child: const Icon(Icons.more_horiz), child: const Icon(Icons.more_horiz),
), ),

View File

@ -36,7 +36,7 @@ class SwapSlotsDialog extends ConsumerWidget {
title: Text(l10n.s_swap_slots), title: Text(l10n.s_swap_slots),
actions: [ actions: [
TextButton( TextButton(
key: swap, key: swapButton,
onPressed: () async { onPressed: () async {
await ref.read(otpStateProvider(devicePath).notifier).swapSlots(); await ref.read(otpStateProvider(devicePath).notifier).swapSlots();
await ref.read(withContextProvider)((context) async { await ref.read(withContextProvider)((context) async {