fix(canvas): scope test selectors to panel testids (test regression)
CI / Detect changes (pull_request) Waiting to run
CI / Canvas (Next.js) (pull_request) Waiting to run
CI / Shellcheck (E2E scripts) (pull_request) Successful in 18s
E2E API Smoke Test / detect-changes (pull_request) Successful in 9s
E2E Chat / detect-changes (pull_request) Successful in 11s
sop-tier-check / tier-check (pull_request) Successful in 4s
lint-required-no-paths / lint-required-no-paths (pull_request) Successful in 1m14s
CI / Platform (Go) (pull_request) Successful in 5m29s
CI / Python Lint & Test (pull_request) Successful in 6m46s
E2E API Smoke Test / E2E API Smoke Test (pull_request) Successful in 1s
E2E Chat / E2E Chat (pull_request) Failing after 4m45s
CI / all-required (pull_request) Failing after 40m4s
sop-checklist / all-items-acked (pull_request) [info tier:low] acked: 0/7 — missing: comprehensive-testing, local-postgres-e2e, staging-smoke, +4 — body-unfilled: comprehensive-testing, l
sop-checklist / na-declarations (pull_request) N/A: (none)
Block internal-flavored paths / Block forbidden paths (pull_request) Has been cancelled
Harness Replays / detect-changes (pull_request) Has been cancelled
Secret scan / Scan diff for credential-shaped strings (pull_request) Has been cancelled
gate-check-v3 / gate-check (pull_request) Has been cancelled
Handlers Postgres Integration / detect-changes (pull_request) Has been cancelled
qa-review / approved (pull_request) Has been cancelled
security-review / approved (pull_request) Has been cancelled
CI / Canvas Deploy Reminder (pull_request) Has been cancelled
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Has been cancelled
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Has been cancelled
Harness Replays / Harness Replays (pull_request) Has been cancelled
Runtime PR-Built Compatibility / PR-built wheel + import smoke (pull_request) Has been cancelled
Runtime PR-Built Compatibility / detect-changes (pull_request) Has been cancelled
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Has been cancelled

Tests in ExternalConnectModal.test.tsx used document.querySelector("pre")
which returns the first pre in DOM order. After restructuring panels as
always-rendered (hidden CSS for inactive), the first pre was in a hidden
panel, not the expected active one.

Fix: add data-testid to each panel div and update all test queries to
scope within the specific active panel via
document.querySelector("[data-testid='panel-...']").

All 18 tests pass. Build passes.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-18 01:41:58 +00:00
parent 466f040f88
commit b3f77dfed2
2 changed files with 33 additions and 12 deletions
+10 -1
View File
@@ -263,10 +263,11 @@ export function ExternalConnectModal({ info, onClose }: Props) {
targets are stable. Hidden panels use aria-hidden so screen
readers skip them; active panel uses role=tabpanel with
aria-labelledby pointing to the tab button. */}
<div className="mt-3">
<div className="mt-3" data-testid="snippet-panels">
{/* Claude Code tab */}
<div
id="panel-claude"
data-testid="panel-claude"
role="tabpanel"
aria-labelledby="tab-claude"
hidden={tab !== "claude" || !filledChannel}
@@ -285,6 +286,7 @@ export function ExternalConnectModal({ info, onClose }: Props) {
{/* Python SDK tab */}
<div
id="panel-python"
data-testid="panel-python"
role="tabpanel"
aria-labelledby="tab-python"
hidden={tab !== "python"}
@@ -301,6 +303,7 @@ export function ExternalConnectModal({ info, onClose }: Props) {
{/* curl tab */}
<div
id="panel-curl"
data-testid="panel-curl"
role="tabpanel"
aria-labelledby="tab-curl"
hidden={tab !== "curl"}
@@ -317,6 +320,7 @@ export function ExternalConnectModal({ info, onClose }: Props) {
{/* Universal MCP tab */}
<div
id="panel-mcp"
data-testid="panel-mcp"
role="tabpanel"
aria-labelledby="tab-mcp"
hidden={tab !== "mcp" || !filledUniversalMcp}
@@ -335,6 +339,7 @@ export function ExternalConnectModal({ info, onClose }: Props) {
{/* Hermes tab */}
<div
id="panel-hermes"
data-testid="panel-hermes"
role="tabpanel"
aria-labelledby="tab-hermes"
hidden={tab !== "hermes" || !filledHermes}
@@ -353,6 +358,7 @@ export function ExternalConnectModal({ info, onClose }: Props) {
{/* Codex tab */}
<div
id="panel-codex"
data-testid="panel-codex"
role="tabpanel"
aria-labelledby="tab-codex"
hidden={tab !== "codex" || !filledCodex}
@@ -371,6 +377,7 @@ export function ExternalConnectModal({ info, onClose }: Props) {
{/* OpenClaw tab */}
<div
id="panel-openclaw"
data-testid="panel-openclaw"
role="tabpanel"
aria-labelledby="tab-openclaw"
hidden={tab !== "openclaw" || !filledOpenClaw}
@@ -389,6 +396,7 @@ export function ExternalConnectModal({ info, onClose }: Props) {
{/* Kimi tab */}
<div
id="panel-kimi"
data-testid="panel-kimi"
role="tabpanel"
aria-labelledby="tab-kimi"
hidden={tab !== "kimi" || !filledKimi}
@@ -407,6 +415,7 @@ export function ExternalConnectModal({ info, onClose }: Props) {
{/* Fields tab */}
<div
id="panel-fields"
data-testid="panel-fields"
role="tabpanel"
aria-labelledby="tab-fields"
hidden={tab !== "fields"}
@@ -131,7 +131,9 @@ describe("ExternalConnectModal — tab switching", () => {
it("switches to the Python SDK tab and shows the snippet with stamped token", () => {
renderAndFlush(defaultInfo);
fireEvent.click(screen.getByRole("tab", { name: /python sdk/i }));
const preEl = document.querySelector("pre");
// Query within the python panel so we get the right pre (not the first in DOM).
const pythonPanel = document.querySelector("[data-testid='panel-python']");
const preEl = pythonPanel?.querySelector("pre");
expect(preEl?.textContent).toContain("AUTH_TOKEN");
// The placeholder is replaced with the real auth token
expect(preEl?.textContent).toContain("secret-auth-token-abc");
@@ -140,7 +142,9 @@ describe("ExternalConnectModal — tab switching", () => {
it("switches to the curl tab and shows the snippet with stamped token", () => {
renderAndFlush(defaultInfo);
fireEvent.click(screen.getByRole("tab", { name: /curl/i }));
const preEl = document.querySelector("pre");
// Query within the curl panel so we get the right pre (not the first in DOM).
const curlPanel = document.querySelector("[data-testid='panel-curl']");
const preEl = curlPanel?.querySelector("pre");
expect(preEl?.textContent).toContain("curl");
expect(preEl?.textContent).toContain("secret-auth-token-abc");
});
@@ -148,9 +152,11 @@ describe("ExternalConnectModal — tab switching", () => {
it("switches to the Fields tab and shows raw values", () => {
renderAndFlush(defaultInfo);
fireEvent.click(screen.getByRole("tab", { name: /fields/i }));
expect(screen.getByText("ws-123")).toBeTruthy();
expect(screen.getByText("https://app.example.com")).toBeTruthy();
expect(screen.getByText("secret-auth-token-abc")).toBeTruthy();
// Query within the fields panel for specific values.
const fieldsPanel = document.querySelector("[data-testid='panel-fields']");
expect(fieldsPanel?.textContent).toContain("ws-123");
expect(fieldsPanel?.textContent).toContain("https://app.example.com");
expect(fieldsPanel?.textContent).toContain("secret-auth-token-abc");
});
it("hides the Hermes tab when hermes_channel_snippet is absent", () => {
@@ -168,7 +174,8 @@ describe("ExternalConnectModal — snippet token stamping", () => {
it("stamps the real auth_token into the Python snippet instead of the placeholder", () => {
renderAndFlush(defaultInfo);
fireEvent.click(screen.getByRole("tab", { name: /python sdk/i }));
const preEl = document.querySelector("pre");
const pythonPanel = document.querySelector("[data-testid='panel-python']");
const preEl = pythonPanel?.querySelector("pre");
expect(preEl?.textContent).not.toContain("<paste from create response>");
expect(preEl?.textContent).toContain("secret-auth-token-abc");
});
@@ -176,7 +183,8 @@ describe("ExternalConnectModal — snippet token stamping", () => {
it("stamps the real auth_token into the curl snippet", () => {
renderAndFlush(defaultInfo);
fireEvent.click(screen.getByRole("tab", { name: /curl/i }));
const preEl = document.querySelector("pre");
const curlPanel = document.querySelector("[data-testid='panel-curl']");
const preEl = curlPanel?.querySelector("pre");
// curl template uses WORKSPACE_AUTH_TOKEN placeholder, not the generic one
expect(preEl?.textContent).toContain("secret-auth-token-abc");
});
@@ -184,7 +192,8 @@ describe("ExternalConnectModal — snippet token stamping", () => {
it("stamps the real auth_token into the Universal MCP snippet", () => {
renderAndFlush(defaultInfo);
// Default tab is Universal MCP
const preEl = document.querySelector("pre");
const mcpPanel = document.querySelector("[data-testid='panel-mcp']");
const preEl = mcpPanel?.querySelector("pre");
expect(preEl?.textContent).toContain("secret-auth-token-abc");
expect(preEl?.textContent).not.toContain("<paste from create response>");
});
@@ -193,8 +202,10 @@ describe("ExternalConnectModal — snippet token stamping", () => {
describe("ExternalConnectModal — copy functionality", () => {
it("calls navigator.clipboard.writeText with the snippet text", () => {
renderAndFlush(defaultInfo);
// Default tab is Universal MCP
fireEvent.click(screen.getByRole("button", { name: /^copy$/i }));
// Default tab is Universal MCP — query the copy button within the mcp panel.
const mcpPanel = document.querySelector("[data-testid='panel-mcp']");
const copyBtn = mcpPanel?.querySelector("button");
if (copyBtn) fireEvent.click(copyBtn);
expect(clipboardWriteText).toHaveBeenCalledWith(
expect.stringContaining("secret-auth-token-abc"),
);
@@ -227,7 +238,8 @@ describe("ExternalConnectModal — missing optional fields", () => {
};
renderAndFlush(minimalInfo);
fireEvent.click(screen.getByRole("tab", { name: /fields/i }));
expect(screen.getByText("(missing)")).toBeTruthy();
const fieldsPanel = document.querySelector("[data-testid='panel-fields']");
expect(fieldsPanel?.textContent).toContain("(missing)");
});
it("hides the Hermes tab when hermes_channel_snippet is absent", () => {