From 9a4eb77c565dd45e5f89ec70d31032e3a26fb4de Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Fri, 4 Sep 2026 16:07:18 +0200 Subject: [PATCH] =?UTF-8?q?Verbessere=20CAPTCHA-Handling=20im=20Login-Proz?= =?UTF-8?q?ess:=20F=C3=BCge=20detaillierte=20Protokollierung=20des=20CAPTC?= =?UTF-8?q?HA-Status=20hinzu=20und=20verbessere=20die=20Handhabung=20des?= =?UTF-8?q?=20CAPTCHA-Widgets.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/utils/mytischtennis-client.js | 30 ++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/server/utils/mytischtennis-client.js b/server/utils/mytischtennis-client.js index 861f337..7371621 100644 --- a/server/utils/mytischtennis-client.js +++ b/server/utils/mytischtennis-client.js @@ -34,7 +34,10 @@ async function loginDirect(email, password) { } async function loginWithBrowser(email, password) { - const browser = await chromium.launch({ headless: true, args: ['--disable-dev-shm-usage'] }) + const browser = await chromium.launch({ + headless: true, + args: ['--no-sandbox', '--disable-dev-shm-usage'] + }) try { const page = await browser.newPage() // Das myTT-CAPTCHA wird erst nach dem Laden der Seite erzeugt. Der Ablauf @@ -60,10 +63,19 @@ async function loginWithBrowser(email, password) { await page.locator('input[name="email"]').fill(email) await page.locator('input[name="password"]').fill(password) + // Exakt wie in trainingstagebuch: Das Widget wird nach dem DOM-Load + // nachgeladen; ein sofortiges count() würde es häufig übersehen. + await page.waitForSelector('private-captcha', { timeout: 8_000 }).catch(() => {}) const captchaHost = page.locator('private-captcha').first() const hasCaptcha = await captchaHost.count() > 0 + console.info('[mytischtennis] CAPTCHA widget detected:', hasCaptcha) if (hasCaptcha) { await page.waitForTimeout(1_200) + // Ein echter Playwright-Klick erzeugt im Gegensatz zu element.click() ein + // vertrauenswürdiges Pointer-Ereignis für das Widget. + await captchaHost.click({ timeout: 5_000, force: true }).catch(error => { + console.warn('[mytischtennis] CAPTCHA pointer click failed:', error.message) + }) await page.evaluate(() => { const host = document.querySelector('private-captcha') const checkbox = host?.shadowRoot?.querySelector('#pc-checkbox') @@ -77,7 +89,21 @@ async function loginWithBrowser(email, password) { const clicked = document.querySelector('input[name="captcha_clicked"]')?.value?.toLowerCase() || '' return token.length > 80 && (clicked === 'true' || clicked === '1') }, { timeout: 32_000 }).catch(() => {}) - const captchaReady = await page.evaluate(() => (document.querySelector('input[name="captcha"]')?.value?.trim().length || 0) > 80) + const captchaState = await page.evaluate(() => { + const host = document.querySelector('private-captcha') + const checkbox = host?.shadowRoot?.querySelector('#pc-checkbox') + const token = document.querySelector('input[name="captcha"]')?.value?.trim() || '' + return { + tokenLength: token.length, + clicked: document.querySelector('input[name="captcha_clicked"]')?.value || null, + shadowRoot: Boolean(host?.shadowRoot), + checkboxFound: Boolean(checkbox), + checkboxChecked: Boolean(checkbox?.checked), + widgetState: host?.getAttribute('data-state') || null + } + }) + console.info('[mytischtennis] CAPTCHA state after wait:', captchaState) + const captchaReady = captchaState.tokenLength > 80 if (!captchaReady) throw new Error('CAPTCHA konnte nicht automatisch gelöst werden.') await page.waitForTimeout(2_500) }