diff --git a/src/bot/authProcedure.ts b/src/bot/authProcedure.ts index 0e4d29b..bab795e 100644 --- a/src/bot/authProcedure.ts +++ b/src/bot/authProcedure.ts @@ -121,23 +121,21 @@ export class AuthProcedure { /** * Wait for the email input field. * - * Hybrid flow: Teams inline modal has input#emailOrPhonerm [data-testid="emailInput"] - * Direct flow: MS login portal has input[name="loginfmt"] + * Uses universal selectors that work on both: + * - MS login portal (login.microsoftonline.com): input[name="loginfmt"] + * - Teams inline modal (light-meetings): input#emailOrPhonerm */ - private async _waitForEmailInput(isInlineFlow: boolean) { + private async _waitForEmailInput(_isInlineFlow: boolean) { this._logger.info('Waiting for email input field...'); - // Primary selectors — exact IDs/attributes from the real HTML - const selectors = isInlineFlow - ? [ - 'input#emailOrPhonerm', // Teams inline modal (exact ID) - 'input[data-testid="emailInput"]', // Teams inline modal (data-testid) - 'input[placeholder="Enter your email"]', // Teams inline modal (placeholder) - ] - : [ - 'input[name="loginfmt"]', // MS login portal - 'input[type="email"]', // MS login portal fallback - ]; + // Universal selectors — match both MS login portal and Teams inline modal + const selectors = [ + 'input[name="loginfmt"]', // MS login portal (primary) + 'input[type="email"]', // MS login portal fallback + 'input#emailOrPhonerm', // Teams inline modal (exact ID) + 'input[data-testid="emailInput"]', // Teams inline modal (data-testid) + 'input[placeholder="Enter your email"]', // Teams inline modal (placeholder) + ]; const combinedSelector = selectors.join(', '); @@ -161,53 +159,37 @@ export class AuthProcedure { /** * Click the "Next" button after entering email. * - * Hybrid flow: button[data-testid="authLoginDialogNextButton"] — initially disabled, - * becomes enabled after email is typed. We wait for it to be enabled. - * Direct flow: input[type="submit"] on the MS login portal. + * Tries all known button selectors from both MS login portal and Teams inline modal. + * Works regardless of which page we're on. */ - private async _clickNextButton(isInlineFlow: boolean): Promise { - if (isInlineFlow) { - // Teams inline modal: wait for the Next button to become enabled - const nextSelector = 'button[data-testid="authLoginDialogNextButton"]'; - this._logger.info('Waiting for Teams Next button to become enabled...'); + private async _clickNextButton(_isInlineFlow: boolean): Promise { + // Try all known Next/Submit button selectors (MS login portal + Teams inline modal) + const selectors = [ + 'input[type="submit"]', // MS login portal + 'button[type="submit"]', // MS login portal alt + 'button[data-testid="authLoginDialogNextButton"]', // Teams inline modal + ]; + + for (const selector of selectors) { try { - await this._page.waitForSelector(`${nextSelector}:not([disabled])`, { - timeout: 10000, + const button = await this._page.waitForSelector(selector, { + timeout: 5000, state: 'visible', }); - await this._page.click(nextSelector); - this._logger.info('Clicked Teams Next button'); - // After clicking Next, Teams redirects to login.microsoftonline.com - // Wait for the redirect to begin - await this._page.waitForTimeout(3000); - } catch { - this._logger.warn('Teams Next button not found or not enabled, pressing Enter'); - await this._page.keyboard.press('Enter'); - await this._page.waitForTimeout(3000); - } - } else { - // MS login portal: click submit button - const selectors = [ - 'input[type="submit"]', - 'button[type="submit"]', - ]; - for (const selector of selectors) { - try { - const button = await this._page.$(selector); - if (button && await button.isVisible()) { - await button.click(); - this._logger.info(`Clicked Next: ${selector}`); - await this._page.waitForTimeout(2000); - return; - } - } catch { - // Continue + if (button) { + await button.click(); + this._logger.info(`Clicked Next: ${selector}`); + await this._page.waitForTimeout(3000); + return; } + } catch { + // Selector not found, try next } - this._logger.warn('No Next button found, pressing Enter'); - await this._page.keyboard.press('Enter'); - await this._page.waitForTimeout(2000); } + + this._logger.warn('No Next button found via selectors, pressing Enter'); + await this._page.keyboard.press('Enter'); + await this._page.waitForTimeout(3000); } /** diff --git a/src/bot/authTestProcedure.ts b/src/bot/authTestProcedure.ts index 40d6cab..1c58227 100644 --- a/src/bot/authTestProcedure.ts +++ b/src/bot/authTestProcedure.ts @@ -195,18 +195,26 @@ async function _runVariant( waitUntil: 'domcontentloaded', timeout: 30000, }); - await page.waitForTimeout(3000); - _log('info', `After Teams redirect: ${page.url().substring(0, 120)}`); + + // Wait for Teams to redirect to login.microsoftonline.com (up to 30s) + _log('info', 'Waiting for login redirect to login.microsoftonline.com...'); + try { + await page.waitForURL('**/login.microsoftonline.com/**', { timeout: 30000 }); + _log('info', `Redirected to login: ${page.url().substring(0, 150)}`); + } catch { + _log('warn', `No login redirect after 30s, current URL: ${page.url().substring(0, 150)}`); + } // Step 2: Login if (botAccountEmail && botAccountPassword) { authAttempted = true; - const onLoginPage = page.url().includes('login.microsoftonline.com') || page.url().includes('login.live.com'); - _log('info', `On login page: ${onLoginPage}, authenticating as ${botAccountEmail}`); + _log('info', `Authenticating as ${botAccountEmail} (skipNavigation=true to preserve OAuth context)`); const authProcedure = new AuthProcedure(page, logger); - // skipNavigation=true because we're already on the login page after teams.microsoft.com redirect - authSuccess = await authProcedure.authenticateWithMicrosoft(botAccountEmail, botAccountPassword, !onLoginPage); + // Always skipNavigation=true: we're already on login.microsoftonline.com with the correct + // OAuth parameters (client_id, redirect_uri for Teams). Navigating to plain + // login.microsoftonline.com would lose this context. + authSuccess = await authProcedure.authenticateWithMicrosoft(botAccountEmail, botAccountPassword, true); _log(authSuccess ? 'info' : 'warn', `Auth result: ${authSuccess ? 'success' : 'failed'}`); }