diff --git a/examples/personal-workspace-browser/goal-suggestions.mjs b/examples/personal-workspace-browser/goal-suggestions.mjs index a67425755f..88fbfe2876 100644 --- a/examples/personal-workspace-browser/goal-suggestions.mjs +++ b/examples/personal-workspace-browser/goal-suggestions.mjs @@ -130,7 +130,10 @@ export const goalSuggestionsScenario = { await other.getByTestId("goal-vitals").waitFor({ state: "visible", timeout: 15_000 }); const vitals = (await other.getByTestId("goal-vitals").innerText()).replace(/\s+/g, " "); assert.match(vitals, /0 等你决定|可选/, `the lane shows the item as optional: ${vitals}`); - assert.match(vitals, /已列出 5 项中的 0 项/, `the lane shows the partial listing truthfully: ${vitals}; listed: ${(await other.locator(".goal-overview").innerText().catch(() => "")).replace(/\s+/g, " ").slice(0, 400)}`); + // The work vital marks its unknown total; its help carries the partial counts. + await other.getByTestId("goal-vitals").getByRole("button", { name: "工作 · 总数未知", exact: true }).focus(); + await other.getByRole("tooltip").getByText(/已列出 5 项中的 0 项/).waitFor(); + await other.keyboard.press("Escape"); await other.getByRole("navigation", { name: "Goal 视图" }).getByRole("button", { name: "对话", exact: true }).click(); const otherSummary = other.locator(".personal-composer-tools > summary"); await otherSummary.waitFor({ state: "visible", timeout: 15_000 }); diff --git a/examples/personal-workspace-browser/goal-workspace-m2.mjs b/examples/personal-workspace-browser/goal-workspace-m2.mjs index 775c498edb..75fa95be4a 100644 --- a/examples/personal-workspace-browser/goal-workspace-m2.mjs +++ b/examples/personal-workspace-browser/goal-workspace-m2.mjs @@ -152,7 +152,10 @@ export const goalWorkspaceM2Scenario = { await page.setViewportSize({width:1440,height:1000}); const expandStart = performance.now(); for (const summary of await now.locator('.goal-work-more > summary').all()) {await summary.focus();await page.keyboard.press('Enter');} - assert.equal(await now.locator('.goal-owner-group [data-todo-id]:visible').count(),200,"All work remains reachable in the original owner order"); + // Each toggle renders asynchronously; the budget below covers all of them. + const visibleWork = () => now.locator('.goal-owner-group [data-todo-id]:visible').count(); + for (const deadline = Date.now() + 5000; await visibleWork() !== 200 && Date.now() < deadline;) await page.waitForTimeout(50); + assert.equal(await visibleWork(),200,"All work remains reachable in the original owner order"); const expandMs = performance.now() - expandStart; assert.ok(expandMs < 2000, `Full 200-item expansion exceeded 2s budget: ${expandMs}ms`); await now.getByRole('button',{name:'关系图',exact:true}).click(); @@ -257,6 +260,9 @@ async function verifyRemoteSelection(browser, url) { assert.equal(await observation.getByText('Archived totals have not been independently reconciled.',{exact:true}).count(),0,"Wrong-Goal observations never survive a refresh"); await page.getByRole('button',{name:'Goal 信息',exact:true}).click(); const unavailableDrawer = page.locator('.personal-context-drawer'); + // Opening moves focus to the drawer title on the next frame; focusing the + // help first would let that handoff blur and dismiss its tooltip. + await page.waitForFunction(() => document.activeElement?.id === 'personal-drawer-title'); await unavailableDrawer.getByRole('button',{name:'验收观察未获取',exact:true}).focus(); await page.getByRole('tooltip').getByText('验收观测不可用,Goal 是否达成仍未知。',{exact:true}).waitFor(); await page.keyboard.press('Escape'); diff --git a/examples/personal-workspace-browser/managed_agent_fixture.py b/examples/personal-workspace-browser/managed_agent_fixture.py index c9a8c7eea4..143de7b828 100644 --- a/examples/personal-workspace-browser/managed_agent_fixture.py +++ b/examples/personal-workspace-browser/managed_agent_fixture.py @@ -2,6 +2,7 @@ from __future__ import annotations import json +import os from pathlib import Path import runpy import socket @@ -9,14 +10,21 @@ import tempfile import threading -import pytest - REPO = Path(__file__).resolve().parents[2] sys.path[:0] = [str(REPO), str(REPO / "tests/control_plane")] from canonical_authority_fixture import isolate_sqlite_runtime, promoted_create_fixture from loopx.chat_server import serve_chat +class ProcessPatch: + """This fixture owns its process; the browser lane installs no test extras.""" + setattr = staticmethod(setattr) + + @staticmethod + def setenv(name: str, value: str) -> None: + os.environ[name] = value + + class SyntheticManagedSource: transport_ref = "synthetic-managed" cancelled = staticmethod(lambda: False) @@ -84,8 +92,8 @@ def calls(self): return [json.loads(line) for line in path.read_text().splitlines()] if path.exists() else [] -with tempfile.TemporaryDirectory(prefix="loopx-managed-ui-") as directory, pytest.MonkeyPatch.context() as patch: - root = Path(directory) +with tempfile.TemporaryDirectory(prefix="loopx-managed-ui-") as directory: + root, patch = Path(directory), ProcessPatch isolate_sqlite_runtime(root, patch) patch.setenv("CODEX_HOME", str(root / "synthetic-model-home")) registry, _, _ = promoted_create_fixture(root, provider="sqlite", goal_id="product-release", registered_agents=["worker", "peer"]) diff --git a/examples/personal-workspace-browser/research-finding-reader.mjs b/examples/personal-workspace-browser/research-finding-reader.mjs index e55a14ac16..4f8654d2ec 100644 --- a/examples/personal-workspace-browser/research-finding-reader.mjs +++ b/examples/personal-workspace-browser/research-finding-reader.mjs @@ -117,7 +117,10 @@ export const researchFindingReaderScenario = { assert.equal(await group.locator("h4").innerText(), "合成研究:质量、延迟和未知成本应如何一起判断?"); assert.match(await group.locator("header").innerText(), /20 条决策记录.*已反驳 3.*暂定结论 4/s); const decisions = group.locator(".research-trail-list button"); - if (width === 1440) assert.equal(await decisions.count(), 3, "A question starts with three records, not repeated title cards"); + // Returning from an older finding expands its question to that exact + // decision; research-history owns the default three-record view. + if (width === 1440) assert.ok(await decisions.evaluateAll(nodes => nodes.some(node => node === document.activeElement + && /比较 2:/.test(node.getAttribute("aria-label") ?? ""))), "Returning keeps the decision just read in view and focused"); await group.scrollIntoViewIfNeeded(); await page.screenshot({path: resolve(outputDir, `research-decision-groups-${width}.png`), animations: "disabled"}); const older = decisions.nth(1);