-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathauth.helper.js
More file actions
291 lines (247 loc) · 10.3 KB
/
auth.helper.js
File metadata and controls
291 lines (247 loc) · 10.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
const homePage = require('../spotlights.page');
const verifyPage = require('../verify.page');
const welcomePage = require('../welcome.page');
const headingPage = require('../base/heading.page');
const profilePage = require('../profile.page');
const userSettingsPage = require('../user-settings.page');
const { isAndroid } = require('./selectors');
const { TIMEOUT, PAUSE, REGEX } = require('./constants');
const { restartApp } = require('./app.helper');
const AIRTABLE_EMAIL = process.env.AIRTABLE_EMAIL || 'not-set';
const OTP_TIMEOUT_MS = TIMEOUT.OTP_WAIT_MAX;
const POLL_INTERVAL_MS = PAUSE.OTP_POLL_INTERVAL;
/**
* Checks if the user is already logged in by checking for home page elements
* @returns {Promise<boolean>} true if logged in, false otherwise
*/
async function isLoggedIn() {
try {
// First, positively check if we're on the home page by looking for key elements
const isHomeVisible = await homePage.filterAll.isDisplayed().catch(() => false);
if (isHomeVisible) {
console.info('✓ User is already logged in (home page detected)');
return true;
}
// Check if the spotlight header is visible (another home page indicator)
const isSpotlightVisible = await homePage.spotlightHeader.isDisplayed().catch(() => false);
if (isSpotlightVisible) {
console.info('✓ User is already logged in (spotlight header detected)');
return true;
}
// Check if we're on the profile page (user is logged in but navigated away from home)
const isProfileVisible = await profilePage.profileHeaderTitle.isDisplayed().catch(() => false);
if (isProfileVisible) {
console.info('✓ User is already logged in (profile page detected)');
return true;
}
// If we see the welcome page email input, user is definitely not logged in
const isWelcomeVisible = await welcomePage.emailInput.isDisplayed().catch(() => false);
if (isWelcomeVisible) {
console.info('ℹ User is not logged in (welcome page detected)');
return false;
}
// If none of the above, assume not logged in
console.info('ℹ Unable to determine login state, assuming not logged in');
return false;
} catch (error) {
console.info(`ℹ Error checking login state: ${error.message}, assuming not logged in`);
return false;
}
}
/**
* Performs the complete login flow with OTP verification
* @param {string} email - Email address to login with (defaults to AIRTABLE_EMAIL)
* @returns {Promise<void>}
*/
async function loginWithOTP(email = AIRTABLE_EMAIL) {
console.info(`🔐 Starting login flow for: ${email}`);
// Check if already logged in
if (await isLoggedIn()) {
console.info('✓ Already logged in, skipping login flow');
return;
}
// Check if we're on the welcome page
const isWelcomeVisible = await welcomePage.welcomeTitle.isDisplayed().catch(() => false);
if (!isWelcomeVisible) {
throw new Error('Not on welcome page and not logged in. Cannot proceed with login flow.');
}
// Clear any previous input
await welcomePage.clearText(welcomePage.emailInput).catch(() => {});
// Enter email (typeText now has built-in retry logic)
console.info(`⌨️ Entering email: ${email}`);
await welcomePage.typeText(welcomePage.emailInput, email);
// Final verification - the email should be correct after typeText's retries
// Android uses 'text' attribute, iOS uses 'value'
const textAttribute = isAndroid() ? 'text' : 'value';
const enteredValue = await welcomePage.emailInput.getAttribute(textAttribute);
if (enteredValue !== email) {
throw new Error(`Email entry failed after all retries. Expected: ${email}, Got: ${enteredValue}`);
}
console.info('✓ Email entered successfully');
// Record timestamp before requesting OTP
const beforeOTPRequest = new Date();
console.info(`🕐 Timestamp BEFORE OTP request: ${beforeOTPRequest.toISOString()}`);
// Click continue to trigger OTP request
await welcomePage.click(welcomePage.continueButton);
const afterOTPRequest = new Date();
console.info(`🕐 Timestamp AFTER OTP request: ${afterOTPRequest.toISOString()}`);
// Wait for navigation to verify screen
await verifyPage.verifyTitle.waitForDisplayed({ timeout: TIMEOUT.ELEMENT_DISPLAYED });
console.info('✓ Navigated to verification screen');
// Wait for OTP to arrive via Airtable
console.info('⏳ Waiting for OTP to arrive via Airtable...');
let result;
try {
result = await global.getOTPFromAirtable(
email,
beforeOTPRequest,
OTP_TIMEOUT_MS,
POLL_INTERVAL_MS,
);
const otpRetrievedTime = new Date();
console.info(`✅ Retrieved OTP: ${result.otp} at ${otpRetrievedTime.toISOString()}`);
console.info(`📧 Email subject: ${result.record.fields.Subject}`);
console.info(`📧 Email created at: ${result.record.fields['Created At']}`);
console.info(
`⏱️ Time from request to retrieval: ${(otpRetrievedTime - afterOTPRequest) / 1000}s`,
);
// Verify we got a valid 6-digit OTP
if (!REGEX.OTP_6_DIGITS.test(result.otp)) {
throw new Error(`Invalid OTP format: ${result.otp}`);
}
console.info('✓ OTP verification completed successfully');
} catch (error) {
console.error('❌ OTP retrieval or verification failed:', error.message);
throw error;
}
// Enter OTP into the verification screen
console.info(`⌨️ Entering OTP into verification screen...`);
const beforeSubmit = new Date();
console.info(`🚀 Starting OTP entry at: ${beforeSubmit.toISOString()}`);
await verifyPage.enterOTP(result.otp);
// Wait for auto-submission and navigation to home page
console.info(`⏳ Waiting for auto-submission to complete...`);
await homePage.filterAll.waitForDisplayed({ timeout: TIMEOUT.SCREEN_READY });
console.info('✅ Login completed successfully');
}
/**
* Waits for the app to reach a ready state (either welcome page or home page)
* This should be called before checking login state when the app first launches
* @param {number} timeout - Maximum time to wait in milliseconds
* @returns {Promise<'welcome'|'home'|'unknown'>} The detected app state
*/
async function waitForAppReady(timeout = TIMEOUT.SCREEN_READY) {
console.info('⏳ Waiting for app to reach ready state...');
const startTime = Date.now();
const pollInterval = PAUSE.NAVIGATION;
while (Date.now() - startTime < timeout) {
// Check for home page elements
const isHomeVisible = await homePage.filterAll.isDisplayed().catch(() => false);
if (isHomeVisible) {
console.info('✓ App ready: Home page detected');
return 'home';
}
const isSpotlightVisible = await homePage.spotlightHeader.isDisplayed().catch(() => false);
if (isSpotlightVisible) {
console.info('✓ App ready: Spotlight header detected');
return 'home';
}
// Check for welcome page elements
const isWelcomeVisible = await welcomePage.emailInput.isDisplayed().catch(() => false);
if (isWelcomeVisible) {
console.info('✓ App ready: Welcome page detected');
return 'welcome';
}
const isWelcomeTitleVisible = await welcomePage.welcomeTitle.isDisplayed().catch(() => false);
if (isWelcomeTitleVisible) {
console.info('✓ App ready: Welcome title detected');
return 'welcome';
}
// Check for profile page (user logged in but on profile)
const isProfileVisible = await profilePage.profileHeaderTitle.isDisplayed().catch(() => false);
if (isProfileVisible) {
console.info('✓ App ready: Profile page detected');
return 'home';
}
await browser.pause(pollInterval);
}
console.warn('⚠ App ready state detection timed out');
return 'unknown';
}
/**
* Ensures the user is logged in before running tests
* Call this in beforeEach or before hooks
* @param {string} email - Email address to login with (defaults to AIRTABLE_EMAIL)
* @returns {Promise<void>}
*/
async function ensureLoggedIn(email = AIRTABLE_EMAIL) {
// First wait for app to reach a ready state
let appState = await waitForAppReady();
if (appState === 'home') {
console.info('✓ Already logged in (detected during app ready check)');
return;
}
if (appState === 'welcome') {
console.info('ℹ On welcome page, proceeding with login');
await loginWithOTP(email);
return;
}
// Unknown state - attempt recovery by restarting app
console.warn('⚠️ App in unknown state, attempting recovery via app restart...');
await restartApp();
// Check state after restart
appState = await waitForAppReady();
if (appState === 'home') {
console.info('✓ Recovery successful - already logged in after restart');
return;
}
if (appState === 'welcome') {
console.info('ℹ Recovery successful - on welcome page, proceeding with login');
await loginWithOTP(email);
return;
}
// Still unknown after restart - try the traditional check as last resort
console.warn('⚠️ App state still unknown after restart, attempting traditional login check...');
const loggedIn = await isLoggedIn();
if (!loggedIn) {
await loginWithOTP(email);
}
}
/**
* Logs out the user if they are currently logged in
* Navigates through Profile -> User Settings -> Sign Out
* @returns {Promise<void>}
*/
async function ensureLoggedOut() {
const loggedIn = await isLoggedIn();
if (!loggedIn) {
console.info('✓ User is already logged out');
return;
}
console.info('🔓 User is logged in, performing logout...');
try {
// Navigate to Profile page via account button
await headingPage.navAccountButton.click();
await profilePage.profileHeaderTitle.waitForDisplayed({ timeout: TIMEOUT.ELEMENT_DISPLAYED });
console.info('✓ Navigated to Profile page');
// Navigate to User Settings by clicking current user card
await profilePage.currentUserCard.click();
await userSettingsPage.headerTitle.waitForDisplayed({ timeout: TIMEOUT.ELEMENT_DISPLAYED });
console.info('✓ Navigated to User Settings');
// Click sign out button
await userSettingsPage.signOut();
// Wait for welcome page to confirm logout
await welcomePage.welcomeTitle.waitForDisplayed({ timeout: TIMEOUT.POST_LOGOUT_REDIRECT });
console.info('✅ User successfully logged out');
} catch (error) {
console.error('❌ Logout failed:', error.message);
throw error;
}
}
module.exports = {
isLoggedIn,
loginWithOTP,
ensureLoggedIn,
ensureLoggedOut,
AIRTABLE_EMAIL,
};