From 0c4675fa253dc718f635ab51ee8ad1cb0d329fce Mon Sep 17 00:00:00 2001 From: Lihua <1017343802@qq.com> Date: Tue, 27 Oct 2026 09:58:16 -0700 Subject: [PATCH 1/4] test(workspace): start the managed-session fixture without test extras The dashboard-browser lane installs the checkout without test extras, so the managed-agent fixture exits on `import pytest` before the scenario starts. Replace pytest.MonkeyPatch with a process-scoped setenv/setattr shim; like the sibling browser fixtures, it now needs only the checkout. The fixture owns its process, so nothing has to be restored afterwards. Refs #7384 Co-Authored-By: Claude Opus 5.5 Signed-off-by: Lihua <1017343802@qq.com> --- .../managed_agent_fixture.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) 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"]) From a73a070c8921293b2a6a1d4a50bed8528a7588b1 Mon Sep 17 00:00:00 2001 From: Lihua <1017343802@qq.com> Date: Tue, 27 Oct 2026 09:57:20 -0700 Subject: [PATCH 2/4] test(workspace): wait for Goal work expansion and drawer focus goal-workspace-m2 raced the UI in two places: - it counted visible work right after toggling each owner group, which read 126 of 200 on the hosted runner; poll until every item is visible (the 2s expansion budget still covers the whole wait) - it focused the drawer's help before the drawer's next-frame focus handoff to its title, which blurred and dismissed the tooltip Refs #7384 Co-Authored-By: Claude Opus 5.5 Signed-off-by: Lihua <1017343802@qq.com> --- examples/personal-workspace-browser/goal-workspace-m2.mjs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) 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'); From ef71088f06372af6503216b116f902a85b92d027 Mon Sep 17 00:00:00 2001 From: Lihua <1017343802@qq.com> Date: Tue, 27 Oct 2026 10:26:31 -0700 Subject: [PATCH 3/4] test(research): follow the restored reading context on return #7501 keeps the decision a reader just closed expanded and focused in its question, so a question no longer collapses to three records after an older finding was read. research-finding-reader asserted the collapsed default at exactly that point (20 !== 3). Assert the shipped contract instead: the decision just read stays in view with focus. The default three-record view remains owned by research-history. #7501's dashboard-browser shards were cancelled, so the scenario had not run against it. Refs #7384 Co-Authored-By: Claude Opus 5.5 Signed-off-by: Lihua <1017343802@qq.com> --- .../personal-workspace-browser/research-finding-reader.mjs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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); From bae64527cca4e3378a29162b54ef121e163cc753 Mon Sep 17 00:00:00 2001 From: Lihua <1017343802@qq.com> Date: Tue, 27 Oct 2026 10:31:15 -0700 Subject: [PATCH 4/4] test(workspace): read partial work coverage from the vital's help MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since 63784cfa9 the work vital shows its partial listing inside the contextual help (labelled with the unknown total) instead of inline text, so goal-suggestions no longer found "已列出 5 项中的 0 项" in the vitals' visible text. Open that help and assert the same counts in its tooltip. The scenario runs after goal-workspace-m2 in shard 2, so hosted CI had not reached it since that change. Refs #7384 Co-Authored-By: Claude Opus 5.5 Signed-off-by: Lihua <1017343802@qq.com> --- examples/personal-workspace-browser/goal-suggestions.mjs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 });