diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 97e1fce..4e9fc0e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.15.15 + rev: v0.15.21 hooks: - id: ruff args: @@ -13,7 +13,7 @@ repos: - id: end-of-file-fixer - id: debug-statements - repo: https://github.com/pre-commit/mirrors-mypy - rev: v2.1.0 + rev: v2.2.0 hooks: - id: mypy exclude: ^docs/examples/ diff --git a/pyproject.toml b/pyproject.toml index 01f5c61..d399707 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,7 +30,7 @@ dependencies = [ "anytree >= 2.9.0", "cached_property", "playwright >= 1.54.0", - "wait_for", + "wait-for>=2.0", ] [project.optional-dependencies] diff --git a/src/widgetastic/browser.py b/src/widgetastic/browser.py index 55cab5c..1e83e94 100644 --- a/src/widgetastic/browser.py +++ b/src/widgetastic/browser.py @@ -82,13 +82,14 @@ def logger(self): """ return create_widget_logger(type(self).__name__, self.browser.logger) - def ensure_page_safe(self, timeout: Union[int, None] = None) -> None: + def ensure_page_safe(self, timeout: Optional[Union[int, float]] = None) -> None: """Waits for the page to be quiescent, replacing the old JS-based check. Args: - timeout: Provide timeout in seconds. + timeout: Maximum time in seconds to wait for networkidle. + Accepts int or float. If None, Playwright's default timeout (30s) is used. """ - timeout_ms = 0 if timeout is None else timeout * 1000 + timeout_ms = timeout * 1000 if timeout is not None else None self.browser.page.wait_for_load_state("networkidle", timeout=timeout_ms) def after_click(self, element: Locator, locator: LocatorAlias) -> None: @@ -1414,7 +1415,12 @@ def check(self, locator: LocatorAlias, *args, **kwargs) -> None: """ self.logger.debug("check: %r", locator) el = self.element(locator, *args, **kwargs) - el.check() + # el.check() + if not el.is_checked(): + # Use JS click instead of el.check() to avoid a coordinate-based + # click regression in Playwright 1.61+ / headless Chrome 149 where + # check() reports "did not change its state" on deeply scrolled pages. + el.evaluate("el => el.click()") def uncheck(self, locator: LocatorAlias, *args, **kwargs) -> None: """Uncheck an element (Checkboxes/ Radio buttons) specified by the locator. @@ -1426,7 +1432,10 @@ def uncheck(self, locator: LocatorAlias, *args, **kwargs) -> None: """ self.logger.debug("uncheck: %r", locator) el = self.element(locator, *args, **kwargs) - el.uncheck() + # el.uncheck() + if el.is_checked(): + # Use JS click instead of el.uncheck() — same reason as check() above. + el.evaluate("el => el.click()") # ========================= DRAG & DROP OPERATIONS ========================= def drag_and_drop(self, source: LocatorAlias, target: LocatorAlias) -> None: diff --git a/uv.lock b/uv.lock index 06c0538..2420dc6 100644 --- a/uv.lock +++ b/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 3 +revision = 2 requires-python = ">=3.10" resolution-markers = [ "python_full_version >= '3.12'", @@ -629,15 +629,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, ] -[[package]] -name = "parsedatetime" -version = "2.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a8/20/cb587f6672dbe585d101f590c3871d16e7aec5a576a1694997a3777312ac/parsedatetime-2.6.tar.gz", hash = "sha256:4cb368fbb18a0b7231f4d76119165451c8d2e35951455dfee97c62a87b04d455", size = 60114, upload-time = "2020-05-31T23:50:57.443Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9d/a4/3dd804926a42537bf69fb3ebb9fd72a50ba84f807d95df5ae016606c976c/parsedatetime-2.6-py3-none-any.whl", hash = "sha256:cb96edd7016872f58479e35879294258c71437195760746faffedb692aef000b", size = 42548, upload-time = "2020-05-31T23:50:56.315Z" }, -] - [[package]] name = "platformdirs" version = "4.10.0" @@ -1204,14 +1195,11 @@ wheels = [ [[package]] name = "wait-for" -version = "1.2.0" +version = "2.0" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "parsedatetime" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/59/c9/d3421164561ecb2a30c63586530fe228a86ca19bd5523a5026571e7e29f9/wait_for-1.2.0.tar.gz", hash = "sha256:5642975f1fc5850acb55684b2d7842bd820fb068e725cd4ffff4bf3eba8e2788", size = 13434, upload-time = "2021-09-29T07:40:44.605Z" } +sdist = { url = "https://files.pythonhosted.org/packages/73/ad/e20f348aadb6d4542fc80e3e46274cccf83f807c318b05abd1944440cc7c/wait_for-2.0.tar.gz", hash = "sha256:d76af89ceea8745866ce11dd82a4d5bb335e317656e9e6cb08b8c514b20dfe14", size = 18634, upload-time = "2026-06-30T16:46:54.227Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/39/e7/8bdec28f3a0c8d6bce3c5130ca1d782dcd124d62b0c099c2ba71dc656d15/wait_for-1.2.0-py2.py3-none-any.whl", hash = "sha256:1129f3350e29b0600889e24328d685a6bff048c8f4cabce28ef7632ed40c5d91", size = 10681, upload-time = "2021-09-29T07:40:43.206Z" }, + { url = "https://files.pythonhosted.org/packages/36/22/71a70e65386a71e0295b4b79e48f5bb73108b052e1142d3a567f36cac022/wait_for-2.0-py3-none-any.whl", hash = "sha256:b1799a8ef2060c676453497823682b80911e54aeefeb634dd1f2085348e42985", size = 13852, upload-time = "2026-06-30T16:46:52.852Z" }, ] [[package]] @@ -1445,6 +1433,6 @@ requires-dist = [ { name = "sphinx", marker = "extra == 'docs'" }, { name = "sphinx-autobuild", marker = "extra == 'docs'" }, { name = "sphinx-rtd-theme", marker = "extra == 'docs'" }, - { name = "wait-for" }, + { name = "wait-for", specifier = ">=2.0" }, ] provides-extras = ["dev", "docs", "test"]