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:
parent
6dea7e3e10
commit
c9a11e9c82
1 changed files with 74 additions and 33 deletions
|
|
@ -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(() =>
|
||||
|
|
|
|||
Loading…
Reference in a new issue