narada 0.2.1__tar.gz → 0.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {narada-0.2.1 → narada-0.2.2}/PKG-INFO +1 -1
- {narada-0.2.1 → narada-0.2.2}/pyproject.toml +1 -1
- {narada-0.2.1 → narada-0.2.2}/src/narada/environment.py +38 -5
- {narada-0.2.1 → narada-0.2.2}/tests/test_cloud_browser.py +92 -1
- {narada-0.2.1 → narada-0.2.2}/.gitignore +0 -0
- {narada-0.2.1 → narada-0.2.2}/README.md +0 -0
- {narada-0.2.1 → narada-0.2.2}/src/narada/__init__.py +0 -0
- {narada-0.2.1 → narada-0.2.2}/src/narada/agent.py +0 -0
- {narada-0.2.1 → narada-0.2.2}/src/narada/config.py +0 -0
- {narada-0.2.1 → narada-0.2.2}/src/narada/py.typed +0 -0
- {narada-0.2.1 → narada-0.2.2}/src/narada/utils.py +0 -0
- {narada-0.2.1 → narada-0.2.2}/src/narada/version.py +0 -0
- {narada-0.2.1 → narada-0.2.2}/tests/test_agent.py +0 -0
- {narada-0.2.1 → narada-0.2.2}/tests/test_browser_environment_login.py +0 -0
- {narada-0.2.1 → narada-0.2.2}/tests/test_client.py +0 -0
- {narada-0.2.1 → narada-0.2.2}/tests/test_input_variables.py +0 -0
- {narada-0.2.1 → narada-0.2.2}/tests/test_window_human_interaction.py +0 -0
|
@@ -970,8 +970,7 @@ class BrowserEnvironment(BaseBrowserEnvironment):
|
|
|
970
970
|
)
|
|
971
971
|
|
|
972
972
|
async def _initialize(self) -> None:
|
|
973
|
-
self.
|
|
974
|
-
self._playwright = await self._playwright_context_manager.__aenter__()
|
|
973
|
+
await self._start_playwright()
|
|
975
974
|
if self._attach_to_existing:
|
|
976
975
|
await self._initialize_in_existing_browser_window()
|
|
977
976
|
else:
|
|
@@ -997,6 +996,10 @@ class BrowserEnvironment(BaseBrowserEnvironment):
|
|
|
997
996
|
finally:
|
|
998
997
|
await self._stop_playwright()
|
|
999
998
|
|
|
999
|
+
async def _start_playwright(self) -> None:
|
|
1000
|
+
self._playwright_context_manager = async_playwright()
|
|
1001
|
+
self._playwright = await self._playwright_context_manager.__aenter__()
|
|
1002
|
+
|
|
1000
1003
|
async def _stop_playwright(self) -> None:
|
|
1001
1004
|
if self._playwright_context_manager is None:
|
|
1002
1005
|
return
|
|
@@ -1480,8 +1483,7 @@ class CloudBrowserEnvironment(BaseBrowserEnvironment):
|
|
|
1480
1483
|
``#narada-browser-window-id`` (extension install retries apply). ``config`` controls
|
|
1481
1484
|
interactive prompts and related behavior.
|
|
1482
1485
|
"""
|
|
1483
|
-
self.
|
|
1484
|
-
self._playwright = await self._playwright_context_manager.__aenter__()
|
|
1486
|
+
await self._start_playwright()
|
|
1485
1487
|
|
|
1486
1488
|
request_body = {
|
|
1487
1489
|
"require_extension": True,
|
|
@@ -1557,6 +1559,10 @@ class CloudBrowserEnvironment(BaseBrowserEnvironment):
|
|
|
1557
1559
|
# Re-raise the original connection error
|
|
1558
1560
|
raise
|
|
1559
1561
|
|
|
1562
|
+
async def _start_playwright(self) -> None:
|
|
1563
|
+
self._playwright_context_manager = async_playwright()
|
|
1564
|
+
self._playwright = await self._playwright_context_manager.__aenter__()
|
|
1565
|
+
|
|
1560
1566
|
async def _stop_playwright(self) -> None:
|
|
1561
1567
|
if self._playwright_context_manager is None:
|
|
1562
1568
|
return
|
|
@@ -1597,6 +1603,7 @@ class CloudBrowserEnvironment(BaseBrowserEnvironment):
|
|
|
1597
1603
|
session_id: str,
|
|
1598
1604
|
login_url: str,
|
|
1599
1605
|
cdp_auth_headers: dict[str, str],
|
|
1606
|
+
expected_browser_window_id: str | None = None,
|
|
1600
1607
|
) -> None:
|
|
1601
1608
|
assert self._playwright is not None
|
|
1602
1609
|
|
|
@@ -1608,6 +1615,23 @@ class CloudBrowserEnvironment(BaseBrowserEnvironment):
|
|
|
1608
1615
|
# Navigate to login URL (provided by backend with custom token)
|
|
1609
1616
|
context = browser.contexts[0]
|
|
1610
1617
|
initialization_page = context.pages[0]
|
|
1618
|
+
if expected_browser_window_id is not None:
|
|
1619
|
+
# Put the backend-owned browser ID into sessionStorage before hydration
|
|
1620
|
+
# so AgentCore sessions use the right Firestore route when needed.
|
|
1621
|
+
expected_browser_window_id_json = json.dumps(expected_browser_window_id)
|
|
1622
|
+
await context.add_init_script(
|
|
1623
|
+
script=f"""
|
|
1624
|
+
(() => {{
|
|
1625
|
+
const expectedBrowserWindowId = {expected_browser_window_id_json};
|
|
1626
|
+
try {{
|
|
1627
|
+
sessionStorage.setItem(
|
|
1628
|
+
"naradaBrowserWindowId",
|
|
1629
|
+
expectedBrowserWindowId
|
|
1630
|
+
);
|
|
1631
|
+
}} catch (_error) {{}}
|
|
1632
|
+
}})();
|
|
1633
|
+
"""
|
|
1634
|
+
)
|
|
1611
1635
|
await initialization_page.goto(
|
|
1612
1636
|
login_url, timeout=15_000, wait_until="domcontentloaded"
|
|
1613
1637
|
)
|
|
@@ -1628,7 +1652,7 @@ class CloudBrowserEnvironment(BaseBrowserEnvironment):
|
|
|
1628
1652
|
raise
|
|
1629
1653
|
logging.info("Waiting for Narada extension to be installed...")
|
|
1630
1654
|
await asyncio.sleep(1)
|
|
1631
|
-
except NaradaTimeoutError:
|
|
1655
|
+
except (NaradaTimeoutError, NaradaExtensionUnauthenticatedError):
|
|
1632
1656
|
if attempt == max_attempts - 1:
|
|
1633
1657
|
raise
|
|
1634
1658
|
# If browser window ID is not found, reload the page and try again
|
|
@@ -1637,6 +1661,15 @@ class CloudBrowserEnvironment(BaseBrowserEnvironment):
|
|
|
1637
1661
|
login_url, timeout=15_000, wait_until="domcontentloaded"
|
|
1638
1662
|
)
|
|
1639
1663
|
|
|
1664
|
+
if (
|
|
1665
|
+
expected_browser_window_id is not None
|
|
1666
|
+
and browser_window_id != expected_browser_window_id
|
|
1667
|
+
):
|
|
1668
|
+
raise RuntimeError(
|
|
1669
|
+
"Initialized cloud session reported browserWindowId "
|
|
1670
|
+
f"{browser_window_id!r}, expected {expected_browser_window_id!r}."
|
|
1671
|
+
)
|
|
1672
|
+
|
|
1640
1673
|
self._browser_window_id = browser_window_id
|
|
1641
1674
|
self._session_id = session_id
|
|
1642
1675
|
self._context = context
|
|
@@ -80,7 +80,9 @@ def _build_cloud_environment_with_page(page: AsyncMock) -> CloudBrowserEnvironme
|
|
|
80
80
|
auth_headers={"x-api-key": "test-key"},
|
|
81
81
|
config=BrowserConfig(interactive=False),
|
|
82
82
|
)
|
|
83
|
-
browser = SimpleNamespace(
|
|
83
|
+
browser = SimpleNamespace(
|
|
84
|
+
contexts=[SimpleNamespace(pages=[page], add_init_script=AsyncMock())]
|
|
85
|
+
)
|
|
84
86
|
env._playwright = SimpleNamespace(
|
|
85
87
|
chromium=SimpleNamespace(connect_over_cdp=AsyncMock(return_value=browser))
|
|
86
88
|
)
|
|
@@ -236,6 +238,35 @@ async def test_extension_action_request_includes_action_execution_id(
|
|
|
236
238
|
assert action_execution_id.startswith("action_")
|
|
237
239
|
|
|
238
240
|
|
|
241
|
+
@pytest.mark.asyncio
|
|
242
|
+
async def test_remote_browser_environment_with_cloud_session_stops_session_by_default(
|
|
243
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
244
|
+
) -> None:
|
|
245
|
+
import narada.environment as environment_module
|
|
246
|
+
|
|
247
|
+
stop_cloud_browser_session = AsyncMock()
|
|
248
|
+
monkeypatch.setattr(
|
|
249
|
+
environment_module,
|
|
250
|
+
"_stop_cloud_browser_session",
|
|
251
|
+
stop_cloud_browser_session,
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
env = RemoteBrowserEnvironment(
|
|
255
|
+
browser_window_id="browser-window-123",
|
|
256
|
+
cloud_browser_session_id="session-123",
|
|
257
|
+
auth_headers={"x-api-key": "test-key"},
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
await env.close()
|
|
261
|
+
|
|
262
|
+
stop_cloud_browser_session.assert_awaited_once_with(
|
|
263
|
+
base_url=env._base_url,
|
|
264
|
+
auth_headers={"x-api-key": "test-key"},
|
|
265
|
+
session_id="session-123",
|
|
266
|
+
timeout=None,
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
|
|
239
270
|
@pytest.mark.asyncio
|
|
240
271
|
async def test_lambda_environment_uses_backend_initialization(
|
|
241
272
|
monkeypatch: pytest.MonkeyPatch,
|
|
@@ -312,6 +343,7 @@ async def test_cloud_browser_environment_uses_domcontentloaded_for_login_navigat
|
|
|
312
343
|
) -> None:
|
|
313
344
|
page = AsyncMock()
|
|
314
345
|
env = _build_cloud_environment_with_page(page)
|
|
346
|
+
context = env._playwright.chromium.connect_over_cdp.return_value.contexts[0]
|
|
315
347
|
|
|
316
348
|
wait_for_browser_window_id = AsyncMock(return_value="browser-window-123")
|
|
317
349
|
monkeypatch.setattr(
|
|
@@ -335,10 +367,69 @@ async def test_cloud_browser_environment_uses_domcontentloaded_for_login_navigat
|
|
|
335
367
|
BrowserConfig(interactive=False),
|
|
336
368
|
timeout=30_000,
|
|
337
369
|
)
|
|
370
|
+
context.add_init_script.assert_not_awaited()
|
|
338
371
|
assert env.browser_window_id == "browser-window-123"
|
|
339
372
|
assert env.cloud_browser_session_id == "session-123"
|
|
340
373
|
|
|
341
374
|
|
|
375
|
+
@pytest.mark.asyncio
|
|
376
|
+
async def test_cloud_browser_environment_seeds_expected_browser_window_id_before_navigation(
|
|
377
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
378
|
+
) -> None:
|
|
379
|
+
page = AsyncMock()
|
|
380
|
+
env = _build_cloud_environment_with_page(page)
|
|
381
|
+
context = env._playwright.chromium.connect_over_cdp.return_value.contexts[0]
|
|
382
|
+
events: list[str] = []
|
|
383
|
+
|
|
384
|
+
async def add_init_script(*args, **kwargs) -> None:
|
|
385
|
+
events.append("seed")
|
|
386
|
+
|
|
387
|
+
async def goto(*args, **kwargs) -> None:
|
|
388
|
+
events.append("goto")
|
|
389
|
+
|
|
390
|
+
context.add_init_script.side_effect = add_init_script
|
|
391
|
+
page.goto.side_effect = goto
|
|
392
|
+
wait_for_browser_window_id = AsyncMock(return_value="backend-window-123")
|
|
393
|
+
monkeypatch.setattr(
|
|
394
|
+
env, "_wait_for_cloud_browser_window_id", wait_for_browser_window_id
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
await env._initialize_cloud_browser_window(
|
|
398
|
+
cdp_websocket_url="wss://agentcore.example.test/session-123",
|
|
399
|
+
session_id="session-123",
|
|
400
|
+
login_url="https://app.narada.ai/chat?customToken=test-token",
|
|
401
|
+
cdp_auth_headers={"Authorization": "signed-cdp"},
|
|
402
|
+
expected_browser_window_id="backend-window-123",
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
assert events[:2] == ["seed", "goto"]
|
|
406
|
+
script = context.add_init_script.await_args.kwargs["script"]
|
|
407
|
+
assert "naradaBrowserWindowId" in script
|
|
408
|
+
assert "backend-window-123" in script
|
|
409
|
+
assert env.browser_window_id == "backend-window-123"
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
@pytest.mark.asyncio
|
|
413
|
+
async def test_cloud_browser_environment_rejects_unexpected_seeded_browser_window_id(
|
|
414
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
415
|
+
) -> None:
|
|
416
|
+
page = AsyncMock()
|
|
417
|
+
env = _build_cloud_environment_with_page(page)
|
|
418
|
+
wait_for_browser_window_id = AsyncMock(return_value="frontend-window-123")
|
|
419
|
+
monkeypatch.setattr(
|
|
420
|
+
env, "_wait_for_cloud_browser_window_id", wait_for_browser_window_id
|
|
421
|
+
)
|
|
422
|
+
|
|
423
|
+
with pytest.raises(RuntimeError, match="expected 'backend-window-123'"):
|
|
424
|
+
await env._initialize_cloud_browser_window(
|
|
425
|
+
cdp_websocket_url="wss://agentcore.example.test/session-123",
|
|
426
|
+
session_id="session-123",
|
|
427
|
+
login_url="https://app.narada.ai/chat?customToken=test-token",
|
|
428
|
+
cdp_auth_headers={"Authorization": "signed-cdp"},
|
|
429
|
+
expected_browser_window_id="backend-window-123",
|
|
430
|
+
)
|
|
431
|
+
|
|
432
|
+
|
|
342
433
|
@pytest.mark.asyncio
|
|
343
434
|
async def test_cloud_browser_environment_uses_domcontentloaded_for_retry_navigation(
|
|
344
435
|
monkeypatch: pytest.MonkeyPatch,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|