diff --git a/backend/clients/myTischtennisClient.js b/backend/clients/myTischtennisClient.js index 7f346218..89baa595 100644 --- a/backend/clients/myTischtennisClient.js +++ b/backend/clients/myTischtennisClient.js @@ -393,9 +393,15 @@ class MyTischtennisClient { await page.locator('input[name="password"]').first().fill(password, { timeout: 10000 }); console.log('[myTischtennisClient.playwright] Credentials filled'); - // Try to interact with private-captcha if present. + // Try to interact with private-captcha if present (it may render with delay). + try { + await page.waitForSelector('private-captcha', { timeout: 8000 }); + } catch (_e) { + // ignore: captcha host might not be present in all flows + } const captchaHost = page.locator('private-captcha').first(); - if (await captchaHost.count()) { + const hasCaptchaHost = (await captchaHost.count()) > 0; + if (hasCaptchaHost) { try { await page.waitForTimeout(1200); const captchaVisualStateBefore = await page.evaluate(() => { @@ -514,6 +520,34 @@ class MyTischtennisClient { } }); + // Before submit, ensure CAPTCHA fields are actually ready if captcha widget exists. + if (hasCaptchaHost) { + const isCaptchaReadyNow = await page.evaluate(() => { + const captchaField = document.querySelector('input[name="captcha"]'); + const clickedField = document.querySelector('input[name="captcha_clicked"]'); + const captchaValue = (captchaField && captchaField.value ? captchaField.value.trim() : ''); + const clickedValue = (clickedField && clickedField.value ? clickedField.value.toLowerCase() : ''); + return captchaValue.length > 80 && (clickedValue === 'true' || clickedValue === '1'); + }); + + if (!isCaptchaReadyNow) { + try { + await page.waitForFunction(() => { + const captchaField = document.querySelector('input[name="captcha"]'); + const clickedField = document.querySelector('input[name="captcha_clicked"]'); + const captchaValue = (captchaField && captchaField.value ? captchaField.value.trim() : ''); + const clickedValue = (clickedField && clickedField.value ? clickedField.value.toLowerCase() : ''); + return captchaValue.length > 80 && (clickedValue === 'true' || clickedValue === '1'); + }, { timeout: 45000 }); + } catch (_captchaNotReadyErr) { + return { + success: false, + error: 'Playwright-Login fehlgeschlagen: CAPTCHA wurde im Browser nicht als gelöst erkannt' + }; + } + } + } + // Submit form const submitButton = page.locator('button[type="submit"], input[type="submit"]').first(); if (await submitButton.count()) { @@ -525,11 +559,14 @@ class MyTischtennisClient { // Wait for auth cookie after submit (polling avoids timing races). let authCookieObj = null; - const maxAttempts = 20; + const maxAttempts = 80; for (let attempt = 0; attempt < maxAttempts; attempt++) { const cookies = await context.cookies(); - authCookieObj = cookies.find((c) => c.name === 'sb-10-auth-token'); + authCookieObj = cookies.find((c) => c.name === 'sb-10-auth-token') + || cookies.find((c) => /^sb-\d+-auth-token$/.test(c.name)) + || cookies.find((c) => c.name.includes('auth-token')); if (authCookieObj?.value) { + console.log('[myTischtennisClient.playwright] Auth cookie detected:', authCookieObj.name); break; } await page.waitForTimeout(500); @@ -541,6 +578,9 @@ class MyTischtennisClient { if (textContent?.includes('Captcha-Bestätigung fehlgeschlagen')) { errorText = 'Captcha-Bestätigung fehlgeschlagen'; } + if (!errorText && textContent?.includes('Passwort')) { + errorText = 'Login vermutlich fehlgeschlagen (Passwort oder CAPTCHA)'; + } } catch (_e) { // ignore text read errors }