Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ where X.Y.Z is the semver of the most recent choreographer release.

### Fixed
- Build the `ChromeNotFoundError` message as one string, so it no longer prints as a tuple [[#314](https://github.com/plotly/choreographer/pull/314)], with thanks to @Blizzeq for the contribution!
- Escape the URL in the page-ready check, so a backtick no longer counts as a loaded page and a `${...}` sequence no longer runs as JavaScript [[#317](https://github.com/plotly/choreographer/pull/317)]


## [1.4.0] -- 2026-09-16
Expand Down
7 changes: 4 additions & 3 deletions src/choreographer/protocol/devtools_async_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from __future__ import annotations

import asyncio
import json
from typing import TYPE_CHECKING

import logistro
Expand Down Expand Up @@ -34,9 +35,9 @@ async def _check_document_ready(session: Session, url: str) -> BrowserResponse:
new Promise((resolve) => {
if (
(document.readyState === 'complete') &&
(window.location==`""" # CONCATENATE!
f"{url!s}"
"""`)
(window.location==""" # CONCATENATE!
f"{json.dumps(url)}"
""")
){
resolve("Was complete");
} else {
Expand Down
17 changes: 17 additions & 0 deletions tests/test_devtools_async_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,23 @@ async def test_create_and_wait(browser):
await create_and_wait(browser, url="http://192.0.2.1:9999", timeout=0.5)


@pytest.mark.asyncio
async def test_create_and_wait_escapes_url(browser):
"""Test that create_and_wait treats JS template characters in the URL as text."""
_logger.info("testing create_and_wait with template characters...")

# Test 1: A backtick used to end the template literal in the ready check.
# The resulting SyntaxError was counted as a load, so this returned a tab
with pytest.raises(asyncio.TimeoutError):
await create_and_wait(browser, url="http://192.0.2.1:9999/`", timeout=0.5)

# Test 2: A ${...} sequence used to run as JavaScript in the page
url = "about:blank?${window.injected=1}"
tab = await create_and_wait(browser, url=url, timeout=5.0)
result = await execute_js_and_wait(tab, "typeof window.injected", timeout=5.0)
assert result["result"]["result"]["value"] == "undefined"


@pytest.mark.asyncio
async def test_navigate_and_wait(browser):
"""Test navigate_and_wait with both valid data URL and bad URL."""
Expand Down
Loading