fix: add retry mechanism (5x/5s) to auth pre-join verification and Join button click, verify no name input on auth page

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
ValueOn AG 2026-02-16 15:23:41 +01:00
parent 6dea7e3e10
commit c9a11e9c82

View file

@ -129,10 +129,11 @@ export class JoinProcedure {
this._logger.info(`Starting lobby join flow... (authenticated: ${this._isAuthenticated})`);
if (this._isAuthenticated) {
// Authenticated join: name comes from Microsoft account, no name input needed
// Wait for the pre-join page to load (look for Join now button)
this._logger.info('Authenticated join - skipping name input, waiting for Join button...');
await this._page.waitForTimeout(3000);
// Authenticated join: wait for the authenticated pre-join page.
// Proof that we're on the RIGHT page: no name input field exists
// (the anonymous page has input[placeholder="Type your name"]).
// Retry up to 5 times, every 5 seconds.
await this._waitForAuthenticatedPreJoinPage();
} else {
// Anonymous join: enter bot name in the name input field
await this._enterBotName();
@ -142,6 +143,41 @@ export class JoinProcedure {
await this._clickJoinNow();
}
/**
* Wait for the authenticated pre-join page to be ready.
*
* Verification: The authenticated page does NOT have a name input field.
* If a name input (placeholder="Type your name") exists, we're still on the
* anonymous page and need to wait for the redirect to complete.
*
* Retries 5 times, every 5 seconds.
*/
private async _waitForAuthenticatedPreJoinPage(): Promise<void> {
const maxRetries = 5;
const retryIntervalMs = 5000;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
const url = this._page.url();
const hasNameInput = await this._page.$('input[placeholder="Type your name"]');
const hasJoinButton = await this._page.$('#prejoin-join-button, button[data-tid="prejoin-join-button"], button:has-text("Join now")');
if (hasJoinButton && !hasNameInput) {
this._logger.info(`Authenticated pre-join page confirmed (attempt ${attempt}/${maxRetries}): Join button present, no name input. URL: ${url.substring(0, 100)}`);
return;
}
const nameStatus = hasNameInput ? 'name input FOUND (wrong page)' : 'no name input';
const joinStatus = hasJoinButton ? 'Join button found' : 'no Join button';
this._logger.info(`Waiting for authenticated pre-join page (attempt ${attempt}/${maxRetries}): ${nameStatus}, ${joinStatus}. URL: ${url.substring(0, 100)}`);
if (attempt < maxRetries) {
await this._page.waitForTimeout(retryIntervalMs);
}
}
this._logger.warn('Could not confirm authenticated pre-join page after all retries. Proceeding anyway...');
}
/**
* Enter the bot name in the name input field.
* Primary selector: input[placeholder="Type your name"] (confirmed by Recall.ai).
@ -200,56 +236,61 @@ export class JoinProcedure {
private async _clickJoinNow(): Promise<void> {
this._logger.info('Clicking Join now...');
// First, dismiss any "no audio/video" modal that may be blocking
await this._dismissNoAudioVideoModal();
const maxRetries = 5;
const retryIntervalMs = 5000;
// Teams v2 uses stable IDs for the join button. Wait with multiple selectors.
// The button may take time to render in the Teams v2 SPA after auth redirect.
const joinSelectors = [
'#prejoin-join-button', // Teams v2 stable ID
'button[data-tid="prejoin-join-button"]', // Teams v2 data-tid
'button:has-text("Join now")', // Text-based (light-meetings)
'button:has-text("Join meeting")', // Alternative text
];
const combinedSelector = joinSelectors.join(', ');
try {
await this._page.waitForSelector(combinedSelector, { timeout: 20000, state: 'visible' });
const button = await this._page.$(combinedSelector);
if (button) {
await button.click();
this._logger.info('Clicked "Join now" button');
await this._page.waitForTimeout(2000);
await this._dismissNoAudioVideoModal();
return;
}
} catch {
this._logger.info('Join button not found with combined selectors, trying text fallbacks...');
}
for (let attempt = 1; attempt <= maxRetries; attempt++) {
// Dismiss any "no audio/video" modal that may be blocking
await this._dismissNoAudioVideoModal();
// Last resort fallback: any button with "Join" text
const fallbackSelectors = [
'button:has-text("Join")',
'[data-tid="joinButton"]',
];
for (const selector of fallbackSelectors) {
try {
const button = await this._page.$(selector);
await this._page.waitForSelector(combinedSelector, { timeout: 5000, state: 'visible' });
const button = await this._page.$(combinedSelector);
if (button) {
await button.click();
this._logger.info(`Clicked join button (fallback: ${selector})`);
this._logger.info(`Clicked "Join now" button (attempt ${attempt}/${maxRetries})`);
await this._page.waitForTimeout(2000);
await this._dismissNoAudioVideoModal();
return;
}
} catch {
// Continue
// Button not found this attempt
}
// Fallback: any button with "Join" text
const fallbackSelectors = ['button:has-text("Join")', '[data-tid="joinButton"]'];
for (const selector of fallbackSelectors) {
try {
const button = await this._page.$(selector);
if (button && await button.isVisible()) {
await button.click();
this._logger.info(`Clicked join button fallback: ${selector} (attempt ${attempt}/${maxRetries})`);
await this._page.waitForTimeout(2000);
await this._dismissNoAudioVideoModal();
return;
}
} catch {
// Continue
}
}
const url = this._page.url();
this._logger.info(`Join button not found (attempt ${attempt}/${maxRetries}). URL: ${url.substring(0, 100)}`);
if (attempt < maxRetries) {
await this._page.waitForTimeout(retryIntervalMs);
}
}
// Diagnostic info for debugging
// All retries exhausted — throw with diagnostic info
const currentUrl = this._page.url();
const title = await this._page.title();
const bodyText = await this._page.evaluate(() =>