Skip to content

Commit 9c2c47d

Browse files
BowlerrDorianMazur
andauthored
test: refactor e2e tests to use helper for checking status message (#746)
* feat: refactor e2e tests to use expectCredentialsSavedMessage helper for consistency * fix: await expect in expectCredentialsSavedMessage for proper async handling * feat: replace matchLoadInfo with expectCredentialsLoadedMessage for improved clarity and consistency * refactor: handle expecting regex per platform * fix: add return statement in expectRegexText * feat: implement ResetDevice helper for consistent app reset in e2e tests * refactor: simplify removing platform-based expectRegexTest - due to `ResetDevice` properly resetting our tests * refactor: replace expectRegexText with waitForRegexText and revert delete on reset * refactor: implement expectRegexText with retry logic for improved reliability * refactor: streamline wait times in authHelpers * revert: ResetDevice helper * fix: increase wait times in biometrics and passcode entry for improved reliability * chore: remove testId --------- Co-authored-by: MazurDorian <mazur.dorian15@gmail.com>
1 parent d3c3f47 commit 9c2c47d

File tree

7 files changed

+145
-86
lines changed

7 files changed

+145
-86
lines changed

KeychainExample/e2e/testCases/accessControlTest.spec.js

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1-
import { by, device, element, expect, waitFor } from 'detox';
2-
import { matchLoadInfo } from '../utils/matchLoadInfo';
1+
import { by, device, element, expect } from 'detox';
32
import {
43
waitForAuthValidity,
54
enterBiometrics,
65
enterPasscode,
76
} from '../utils/authHelpers';
7+
import {
8+
expectCredentialsLoadedMessage,
9+
expectCredentialsSavedMessage,
10+
expectCredentialsResetMessage,
11+
} from '../utils/statusMessageHelpers';
812

913
describe('Access Control', () => {
1014
beforeEach(async () => {
@@ -28,16 +32,14 @@ describe('Access Control', () => {
2832
await enterPasscode();
2933
// Hide keyboard if open
3034
await element(by.text('Keychain Example')).tap();
31-
await waitFor(element(by.text(/^Credentials saved! .*$/)))
32-
.toExist()
33-
.withTimeout(4000);
35+
await expectCredentialsSavedMessage();
3436

3537
await waitForAuthValidity();
3638
await element(by.text('Load')).tap();
3739
await enterPasscode();
3840
// Hide keyboard if open
3941
await element(by.text('Keychain Example')).tap();
40-
await matchLoadInfo(
42+
await expectCredentialsLoadedMessage(
4143
'testUsernamePasscode',
4244
'testPasswordPasscode',
4345
'KeystoreAESGCM'
@@ -69,15 +71,16 @@ describe('Access Control', () => {
6971
await element(by.text('Save')).tap();
7072
await enterBiometrics();
7173

72-
await waitFor(element(by.text(/^Credentials saved! .*$/)))
73-
.toExist()
74-
.withTimeout(3000);
74+
await expectCredentialsSavedMessage();
7575

7676
await waitForAuthValidity();
7777
await element(by.text('Load')).tap();
7878
await enterBiometrics();
7979

80-
await matchLoadInfo('testUsernameBiometrics', 'testPasswordBiometrics');
80+
await expectCredentialsLoadedMessage(
81+
'testUsernameBiometrics',
82+
'testPasswordBiometrics'
83+
);
8184
}
8285
);
8386

@@ -91,7 +94,10 @@ describe('Access Control', () => {
9194
).toBeVisible();
9295
await element(by.text('Load')).tap();
9396
await enterBiometrics();
94-
await matchLoadInfo('testUsernameBiometrics', 'testPasswordBiometrics');
97+
await expectCredentialsLoadedMessage(
98+
'testUsernameBiometrics',
99+
'testPasswordBiometrics'
100+
);
95101
}
96102
);
97103

@@ -112,11 +118,12 @@ describe('Access Control', () => {
112118

113119
await expect(element(by.text('Save'))).toBeVisible();
114120
await element(by.text('Save')).tap();
115-
await waitFor(element(by.text(/^Credentials saved! .*$/)))
116-
.toExist()
117-
.withTimeout(3000);
121+
await expectCredentialsSavedMessage();
118122
await element(by.text('Load')).tap();
119-
await matchLoadInfo('testUsernameAny', 'testPasswordAny');
123+
await expectCredentialsLoadedMessage(
124+
'testUsernameAny',
125+
'testPasswordAny'
126+
);
120127
}
121128
);
122129

@@ -129,7 +136,10 @@ describe('Access Control', () => {
129136
element(by.text('hasGenericPassword: true'))
130137
).toBeVisible();
131138
await element(by.text('Load')).tap();
132-
await matchLoadInfo('testUsernameAny', 'testPasswordAny');
139+
await expectCredentialsLoadedMessage(
140+
'testUsernameAny',
141+
'testPasswordAny'
142+
);
133143
}
134144
);
135145
});
@@ -139,6 +149,6 @@ describe('Access Control', () => {
139149
// Hide keyboard
140150

141151
await element(by.text('Reset')).tap();
142-
await expect(element(by.text(/^Credentials Reset!$/))).toBeVisible();
152+
await expectCredentialsResetMessage();
143153
});
144154
});

KeychainExample/e2e/testCases/securityLevelTest.spec.js

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1-
import { by, device, element, expect, waitFor } from 'detox';
2-
import { matchLoadInfo } from '../utils/matchLoadInfo';
1+
import { by, element, expect, device } from 'detox';
2+
import {
3+
expectCredentialsLoadedMessage,
4+
expectCredentialsSavedMessage,
5+
expectCredentialsResetMessage,
6+
} from '../utils/statusMessageHelpers';
37

48
describe(':android:Security Level', () => {
59
beforeEach(async () => {
@@ -19,11 +23,9 @@ describe(':android:Security Level', () => {
1923

2024
await expect(element(by.text('Save'))).toBeVisible();
2125
await element(by.text('Save')).tap();
22-
await waitFor(element(by.text(/^Credentials saved! .*$/)))
23-
.toExist()
24-
.withTimeout(3000);
26+
await expectCredentialsSavedMessage();
2527
await element(by.text('Load')).tap();
26-
await matchLoadInfo(
28+
await expectCredentialsLoadedMessage(
2729
'testUsernameAny',
2830
'testPasswordAny',
2931
undefined,
@@ -46,11 +48,9 @@ describe(':android:Security Level', () => {
4648

4749
await expect(element(by.text('Save'))).toBeVisible();
4850
await element(by.text('Save')).tap();
49-
await waitFor(element(by.text(/^Credentials saved! .*$/)))
50-
.toExist()
51-
.withTimeout(3000);
51+
await expectCredentialsSavedMessage();
5252
await element(by.text('Load')).tap();
53-
await matchLoadInfo(
53+
await expectCredentialsLoadedMessage(
5454
'testUsernameSoftware',
5555
'testPasswordSoftware',
5656
undefined,
@@ -74,11 +74,9 @@ describe(':android:Security Level', () => {
7474

7575
await expect(element(by.text('Save'))).toBeVisible();
7676
await element(by.text('Save')).tap();
77-
await waitFor(element(by.text(/^Credentials saved! .*$/)))
78-
.toExist()
79-
.withTimeout(3000);
77+
await expectCredentialsSavedMessage();
8078
await element(by.text('Load')).tap();
81-
await matchLoadInfo(
79+
await expectCredentialsLoadedMessage(
8280
'testUsernameHardware',
8381
'testPasswordHardware',
8482
undefined,
@@ -93,6 +91,6 @@ describe(':android:Security Level', () => {
9391
// Hide keyboard
9492

9593
await element(by.text('Reset')).tap();
96-
await expect(element(by.text(/^Credentials Reset!$/))).toBeVisible();
94+
await expectCredentialsResetMessage();
9795
});
9896
});

KeychainExample/e2e/testCases/storageTypesTest.spec.js

Lines changed: 16 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1-
import { by, device, element, expect, waitFor } from 'detox';
2-
import { matchLoadInfo } from '../utils/matchLoadInfo';
1+
import { by, element, expect, device } from 'detox';
32
import { enterBiometrics, waitForAuthValidity } from '../utils/authHelpers';
43

4+
import {
5+
expectCredentialsLoadedMessage,
6+
expectCredentialsSavedMessage,
7+
expectCredentialsResetMessage,
8+
} from '../utils/statusMessageHelpers';
9+
510
describe(':android:Storage Types', () => {
611
beforeEach(async () => {
712
await device.launchApp({ newInstance: true });
@@ -20,11 +25,9 @@ describe(':android:Storage Types', () => {
2025

2126
await expect(element(by.text('Save'))).toBeVisible();
2227
await element(by.text('Save')).tap();
23-
await waitFor(element(by.text(/^Credentials saved! .*$/)))
24-
.toExist()
25-
.withTimeout(3000);
28+
await expectCredentialsSavedMessage();
2629
await element(by.text('Load')).tap();
27-
await matchLoadInfo(
30+
await expectCredentialsLoadedMessage(
2831
'testUsernameAESCBC',
2932
'testPasswordAESCBC',
3033
'KeystoreAESCBC',
@@ -46,13 +49,11 @@ describe(':android:Storage Types', () => {
4649
await expect(element(by.text('Save'))).toBeVisible();
4750
await element(by.text('Save')).tap();
4851
await enterBiometrics();
49-
await waitFor(element(by.text(/^Credentials saved! .*$/)))
50-
.toExist()
51-
.withTimeout(3000);
52+
await expectCredentialsSavedMessage();
5253
await waitForAuthValidity();
5354
await element(by.text('Load')).tap();
5455
await enterBiometrics();
55-
await matchLoadInfo(
56+
await expectCredentialsLoadedMessage(
5657
'testUsernameAESGCM',
5758
'testPasswordAESGCM',
5859
'KeystoreAESGCM',
@@ -79,11 +80,9 @@ describe(':android:Storage Types', () => {
7980

8081
await expect(element(by.text('Save'))).toBeVisible();
8182
await element(by.text('Save')).tap();
82-
await waitFor(element(by.text(/^Credentials saved! .*$/)))
83-
.toExist()
84-
.withTimeout(3000);
83+
await expectCredentialsSavedMessage();
8584
await element(by.text('Load')).tap();
86-
await matchLoadInfo(
85+
await expectCredentialsLoadedMessage(
8786
'testUsernameAESGCMNoAuth',
8887
'testPasswordAESGCMNoAuth',
8988
'KeystoreAESGCM_NoAuth',
@@ -105,15 +104,10 @@ describe(':android:Storage Types', () => {
105104

106105
await expect(element(by.text('Save'))).toBeVisible();
107106
await element(by.text('Save')).tap();
108-
await waitFor(element(by.text(/^Credentials saved! .*$/)))
109-
.toExist()
110-
.withTimeout(5000);
107+
await expectCredentialsSavedMessage();
111108
await element(by.text('Load')).tap();
112109
await enterBiometrics();
113-
await waitFor(element(by.text(/^Credentials loaded! .*$/)))
114-
.toExist()
115-
.withTimeout(5000);
116-
await matchLoadInfo(
110+
await expectCredentialsLoadedMessage(
117111
'testUsernameRSA',
118112
'testPasswordRSA',
119113
'KeystoreRSAECB',
@@ -127,6 +121,6 @@ describe(':android:Storage Types', () => {
127121
// Hide keyboard
128122

129123
await element(by.text('Reset')).tap();
130-
await expect(element(by.text(/^Credentials Reset!$/))).toExist();
124+
await expectCredentialsResetMessage();
131125
});
132126
});
Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import { device } from 'detox';
21
import cp from 'child_process';
2+
import { device } from 'detox';
33

44
// Wait for 5 seconds to ensure auth validity period has expired
55
export const waitForAuthValidity = async () => {
6-
await new Promise((resolve) => setTimeout(resolve, 5500)); // Added 500ms buffer
6+
await new Promise((resolve) => setTimeout(resolve, 5500)); // buffer needed for auth validity period
77
};
88

99
export const enterBiometrics = async () => {
@@ -12,16 +12,14 @@ export const enterBiometrics = async () => {
1212
if (device.getPlatform() === 'android') {
1313
await new Promise((resolve) => setTimeout(resolve, 1000));
1414
cp.spawnSync('adb', ['-e', 'emu', 'finger', 'touch', '1']);
15-
await new Promise((resolve) => setTimeout(resolve, 500));
1615
}
1716
};
1817

1918
export const enterPasscode = async () => {
2019
if (device.getPlatform() === 'android') {
21-
await new Promise((resolve) => setTimeout(resolve, 1500));
20+
await new Promise((resolve) => setTimeout(resolve, 1000));
2221
cp.spawnSync('adb', ['shell', 'input', 'text', '1111']);
23-
await new Promise((resolve) => setTimeout(resolve, 2000));
22+
await new Promise((resolve) => setTimeout(resolve, 1000));
2423
cp.spawnSync('adb', ['shell', 'input', 'keyevent', '66']);
25-
await new Promise((resolve) => setTimeout(resolve, 1500));
2624
}
2725
};
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { by, element, waitFor, expect } from 'detox';
2+
3+
async function retry<T>(
4+
operation: () => Promise<T>,
5+
maxAttempts: number = 3,
6+
delayMs: number = 1000
7+
): Promise<T> {
8+
let attempts = 0;
9+
10+
while (attempts < maxAttempts) {
11+
try {
12+
return await operation();
13+
} catch (error) {
14+
attempts++;
15+
if (attempts === maxAttempts) {
16+
throw error;
17+
}
18+
await new Promise((resolve) => setTimeout(resolve, delayMs));
19+
}
20+
}
21+
throw new Error('Unreachable code');
22+
}
23+
24+
export async function expectRegexText(regex: RegExp, timeout?: number) {
25+
try {
26+
return await retry(async () =>
27+
timeout
28+
? waitFor(element(by.text(regex)))
29+
.toBeVisible()
30+
.withTimeout(timeout)
31+
: expect(element(by.text(regex))).toBeVisible()
32+
);
33+
} catch (error) {
34+
throw new Error(`Failed to find text matching ${regex}: ${error}`);
35+
}
36+
}

KeychainExample/e2e/utils/matchLoadInfo.ts

Lines changed: 0 additions & 26 deletions
This file was deleted.
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { expectRegexText } from './detoxHelpers';
2+
3+
const TIMEOUT = 10000;
4+
5+
function buildLoadedCredentialsRegex(
6+
username: string,
7+
password: string,
8+
storage?: string,
9+
service?: string
10+
): RegExp {
11+
let pattern = '^Credentials loaded! .*';
12+
// Conditionally add storage if provided.
13+
if (storage) {
14+
pattern += `"storage":"${storage}",`;
15+
}
16+
// Always add password and username.
17+
pattern += `"password":"${password}","username":"${username}"`;
18+
// Conditionally add service if provided.
19+
if (service) {
20+
pattern += `,"service":"${service}"`;
21+
}
22+
pattern += '.*$';
23+
return new RegExp(pattern);
24+
}
25+
26+
export async function expectCredentialsSavedMessage() {
27+
const regex = /^Credentials saved! .*$/;
28+
await expectRegexText(regex, TIMEOUT);
29+
}
30+
31+
export async function expectCredentialsResetMessage() {
32+
const regex = /^Credentials Reset!$/;
33+
await expectRegexText(regex, TIMEOUT);
34+
}
35+
36+
export async function expectCredentialsLoadedMessage(
37+
username: string,
38+
password: string,
39+
storage?: string,
40+
service?: string
41+
) {
42+
const regex = buildLoadedCredentialsRegex(
43+
username,
44+
password,
45+
storage,
46+
service
47+
);
48+
await expectRegexText(regex, TIMEOUT);
49+
}

0 commit comments

Comments
 (0)