From b6e50d1e05f6e928dda95ce418587ad42864e52c Mon Sep 17 00:00:00 2001 From: Nikolay Borodin Date: Wed, 4 Mar 2026 22:59:56 +0200 Subject: [PATCH 1/4] Updated deepl DOM handling --- deepl/deepl.py | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/deepl/deepl.py b/deepl/deepl.py index 665f013..ca1dc5a 100644 --- a/deepl/deepl.py +++ b/deepl/deepl.py @@ -148,7 +148,7 @@ async def __translate(self, script: str) -> str: await page.locator( "button[data-testid=translator-source-lang-btn]", - ).dispatch_event("click") + ).click() await ( page.get_by_test_id("translator-source-lang-list") @@ -170,10 +170,10 @@ async def __translate(self, script: str) -> str: .first.dispatch_event("click") ) - await page.fill( - "div[aria-labelledby=translation-source-heading]", - script, + await page.click( + "div[aria-labelledby=translation-source-heading] d-textarea", ) + await page.keyboard.type(script) try: await page.wait_for_function( @@ -187,14 +187,14 @@ async def __translate(self, script: str) -> str: msg = f"Time limit exceeded. ({self.timeout} ms)" raise DeepLCLIPageLoadError(msg) from e - # Wait for translation to complete (check that [...] placeholder is gone) + # Wait for translation to complete (check that progress text is empty) try: await page.wait_for_function( """ () => { - const elem = document.querySelector('d-textarea[aria-labelledby=translation-target-heading]'); - const text = elem?.value ?? ''; - return text.length > 0 && !text.includes('[...]'); + const elem = document.querySelector('#progress-text'); + const text = elem?.textContent ?? ''; + return text.length == 0; } """, timeout=self.timeout, @@ -216,13 +216,8 @@ async def __translate(self, script: str) -> str: msg = "Unable to get translated text" raise DeepLCLIPageLoadError(msg) from e - input_textbox = page.get_by_role("region", name="Source text").locator( - "d-textarea", - ) - output_textbox = page.get_by_role( - "region", - name="Translation results", - ).locator("d-textarea") + input_textbox = page.locator("div[aria-labelledby=translation-source-heading] d-textarea") + output_textbox = page.locator("div[aria-labelledby=translation-target-heading] d-textarea") self.translated_fr_lang = str( await input_textbox.get_attribute("lang"), @@ -251,7 +246,7 @@ def __sanitize_script(self, script: str) -> str: async def __get_browser(self, p: Playwright) -> Browser: """Launch browser executable and get playwright browser object.""" - install([p.chromium], with_deps=True) + install(p.chromium, with_deps=True) return await p.chromium.launch( headless=True, From 984e650ace6bce0edc6c9981e53821f294ef441e Mon Sep 17 00:00:00 2001 From: haruna Date: Thu, 5 Mar 2026 11:01:43 +0900 Subject: [PATCH 2/4] fix: install call for Playwright browser --- deepl/deepl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deepl/deepl.py b/deepl/deepl.py index ca1dc5a..ee49cb4 100644 --- a/deepl/deepl.py +++ b/deepl/deepl.py @@ -246,7 +246,7 @@ def __sanitize_script(self, script: str) -> str: async def __get_browser(self, p: Playwright) -> Browser: """Launch browser executable and get playwright browser object.""" - install(p.chromium, with_deps=True) + install([p.chromium], with_deps=True) return await p.chromium.launch( headless=True, From fb4089b3dd99f25ce963ac1eeb36398295a0edb5 Mon Sep 17 00:00:00 2001 From: Nikolay Borodin Date: Thu, 5 Mar 2026 12:51:19 +0200 Subject: [PATCH 3/4] fix: added additional check for progress-text --- deepl/deepl.py | 1 + 1 file changed, 1 insertion(+) diff --git a/deepl/deepl.py b/deepl/deepl.py index ee49cb4..391b2cd 100644 --- a/deepl/deepl.py +++ b/deepl/deepl.py @@ -189,6 +189,7 @@ async def __translate(self, script: str) -> str: # Wait for translation to complete (check that progress text is empty) try: + await page.wait_for_selector(selector="#progress-text", timeout=self.timeout) await page.wait_for_function( """ () => { From f2c4034ec0fb496c1e7a07c7bd353a926e436c66 Mon Sep 17 00:00:00 2001 From: Nikolay Borodin Date: Mon, 20 Apr 2026 13:39:13 +0200 Subject: [PATCH 4/4] fix: made it work --- deepl/deepl.py | 80 +++++++------------------------------------------- 1 file changed, 11 insertions(+), 69 deletions(-) diff --git a/deepl/deepl.py b/deepl/deepl.py index 391b2cd..72db54d 100644 --- a/deepl/deepl.py +++ b/deepl/deepl.py @@ -6,7 +6,6 @@ from collections.abc import Coroutine from typing import Any -from install_playwright import install from playwright._impl._errors import Error as PlaywrightError from playwright.async_api import ProxySettings, async_playwright from playwright.async_api._generated import Browser, Playwright @@ -130,7 +129,7 @@ async def __translate(self, script: str) -> str: url = "https://www.deepl.com/en/translator" async with page.expect_response(lambda resp: resp.url == url and resp.request.method == "GET") as resp_info: - await page.goto(url) + await page.goto(f"{url}#{self.fr_lang}/{self.to_lang}/{script}") response = await resp_info.value @@ -146,86 +145,30 @@ async def __translate(self, script: str) -> str: msg = f"Maybe Time limit exceeded. ({self.timeout} ms)" raise DeepLCLIPageLoadError(msg) from e - await page.locator( - "button[data-testid=translator-source-lang-btn]", - ).click() - - await ( - page.get_by_test_id("translator-source-lang-list") - .get_by_test_id( - f"translator-lang-option-{self.fr_lang}", - ) - .first.dispatch_event("click") - ) - - await page.locator( - "button[data-testid=translator-target-lang-btn]", - ).dispatch_event("click") - - await ( - page.get_by_test_id("translator-target-lang-list") - .get_by_test_id( - f"translator-lang-option-{self.to_lang}", - ) - .first.dispatch_event("click") - ) - - await page.click( - "div[aria-labelledby=translation-source-heading] d-textarea", - ) - await page.keyboard.type(script) - + # Wait for translation to complete try: await page.wait_for_function( """ () => document.querySelector( 'd-textarea[aria-labelledby=translation-target-heading]')?.value?.length > 0 """, - timeout=self.timeout, ) except PlaywrightError as e: msg = f"Time limit exceeded. ({self.timeout} ms)" raise DeepLCLIPageLoadError(msg) from e - # Wait for translation to complete (check that progress text is empty) - try: - await page.wait_for_selector(selector="#progress-text", timeout=self.timeout) - await page.wait_for_function( - """ - () => { - const elem = document.querySelector('#progress-text'); - const text = elem?.textContent ?? ''; - return text.length == 0; - } - """, - timeout=self.timeout, - ) - except PlaywrightError as e: - msg = f"Translation incomplete after {self.timeout} ms" - raise DeepLCLIPageLoadError(msg) from e + input_textbox = page.get_by_test_id("translator-source-input") + output_textbox = page.get_by_test_id("translator-target-input") - # Get the translated text directly from the value attribute - try: - res = await page.evaluate( - """ - document.querySelector( - 'd-textarea[aria-labelledby=translation-target-heading]' - ).value - """, - ) - except PlaywrightError as e: - msg = "Unable to get translated text" - raise DeepLCLIPageLoadError(msg) from e + # Optional: wait explicitly if still flaky (usually not needed with data-testid) + await input_textbox.wait_for(state="visible", timeout=10000) - input_textbox = page.locator("div[aria-labelledby=translation-source-heading] d-textarea") - output_textbox = page.locator("div[aria-labelledby=translation-target-heading] d-textarea") + self.translated_fr_lang = str(await input_textbox.get_attribute("lang") or "unknown").split("-")[0] + self.translated_to_lang = str(await output_textbox.get_attribute("lang") or "unknown").split("-")[0] - self.translated_fr_lang = str( - await input_textbox.get_attribute("lang"), - ).split("-")[0] - self.translated_to_lang = str( - await output_textbox.get_attribute("lang"), - ).split("-")[0] + editable = output_textbox.locator('div[contenteditable="true"]') + res = await editable.inner_text() + res = res.replace("\n\n", "\n") await browser.close() @@ -247,7 +190,6 @@ def __sanitize_script(self, script: str) -> str: async def __get_browser(self, p: Playwright) -> Browser: """Launch browser executable and get playwright browser object.""" - install([p.chromium], with_deps=True) return await p.chromium.launch( headless=True,