-
Notifications
You must be signed in to change notification settings - Fork 1
test(e2e): cover the customer-master in-place post-open fix; update gap baseline #467
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,90 @@ | ||
| import { test, expect } from "./fixtures"; | ||
|
|
||
| /** | ||
| * Regression guard for the 2026-08-22 Customer Master navigation bug: | ||
| * clicking a customer's related post used to call changeDestination("board") | ||
| * and jump the whole workspace away from Customer Master instead of showing | ||
| * the post in place. Discovers a real customer entity with a linked post | ||
| * through the running app's own API rather than a fixed id (AGENTS.md's | ||
| * synthetic-only-artifact rule) -- runs against the live authenticated | ||
| * stack (`make up && make seed`, or an authorized real import). | ||
| */ | ||
|
|
||
| interface RelatedNode { | ||
| node_id: string; | ||
| node_type_code: string; | ||
| label?: string; | ||
| } | ||
|
|
||
| async function findEntityWithRelatedPost( | ||
| request: import("@playwright/test").APIRequestContext, | ||
| baseURL: string, | ||
| accessToken: string, | ||
| ): Promise<{ entityId: string; entityName: string } | null> { | ||
| const master = await request.get(`${baseURL}/api/customer-master`, { | ||
| headers: { Authorization: `Bearer ${accessToken}` }, | ||
| }); | ||
| if (!master.ok()) return null; | ||
| const { corporate_entities: entities } = (await master.json()) as { | ||
| corporate_entities: { corporate_entity_id: string; entity_name: string }[]; | ||
| }; | ||
| for (const entity of entities) { | ||
| const related = await request.get(`${baseURL}/api/corporate-entities/${entity.corporate_entity_id}/related`, { | ||
| headers: { Authorization: `Bearer ${accessToken}` }, | ||
| }); | ||
| if (!related.ok()) continue; | ||
| const { related: nodes } = (await related.json()) as { related: RelatedNode[] }; | ||
| if (nodes.some((node) => node.node_type_code === "node_post")) { | ||
| return { entityId: entity.corporate_entity_id, entityName: entity.entity_name }; | ||
| } | ||
| } | ||
| return null; | ||
| } | ||
|
|
||
| test("opens a customer's related post in place, never navigating away from Customer master", async ({ | ||
| page, | ||
| request, | ||
| }) => { | ||
| const backendBaseURL = process.env.LINEAGEWEAVE_BACKEND_URL ?? "http://localhost:18420"; | ||
| const tokenResponse = await request.post( | ||
| `${process.env.LINEAGEWEAVE_KEYCLOAK_URL ?? "http://localhost:18080"}/realms/lineageweave-demo/protocol/openid-connect/token`, | ||
| { | ||
| form: { | ||
| client_id: "lineageweave-frontend", | ||
| grant_type: "password", | ||
| username: process.env.LINEAGEWEAVE_E2E_USERNAME ?? "demo.analyst", | ||
| password: process.env.LINEAGEWEAVE_E2E_PASSWORD ?? "lineageweave-demo-only", | ||
| }, | ||
| }, | ||
| ); | ||
| test.skip(!tokenResponse.ok(), "Keycloak token endpoint unavailable in this environment"); | ||
| const { access_token: accessToken } = (await tokenResponse.json()) as { access_token: string }; | ||
|
|
||
| const entity = await findEntityWithRelatedPost(request, backendBaseURL, accessToken); | ||
| test.skip(entity === null, "No customer entity with a linked post is visible in this environment"); | ||
| const { entityName } = entity!; | ||
|
|
||
| await page.goto("/"); | ||
| await page.getByRole("button", { name: "Customer master" }).click(); | ||
| await expect(page.getByRole("heading", { name: "Customer master" })).toBeVisible(); | ||
|
|
||
| const entityButton = page.getByRole("button", { name: new RegExp(entityName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")) }); | ||
| await entityButton.click(); | ||
|
Comment on lines
+71
to
+72
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📝 Info: Unanchored entity-name regex can match multiple buttons The RegExp built from the entity name is unanchored, so it matches any button whose accessible name contains that string. If one entity name is a substring of another (parent/child, or generic names), Was this helpful? React with 👍 or 👎 to provide feedback. |
||
|
|
||
| const relatedPostButton = page.getByRole("button", { name: /^Open related post:/ }).first(); | ||
| await expect(relatedPostButton).toBeVisible({ timeout: 15000 }); | ||
| await relatedPostButton.click(); | ||
|
Comment on lines
+74
to
+76
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📝 Info: Related-post button first() can resolve to a hint card
Was this helpful? React with 👍 or 👎 to provide feedback. |
||
|
|
||
| // The fix: this popup renders in place (a fixed right-docked panel), | ||
| // it does not navigate to Board -- Customer master stays mounted and | ||
| // its own entity for this related post is still visible underneath. | ||
| const popup = page.locator(".popup-panel"); | ||
| await expect(popup).toBeVisible({ timeout: 15000 }); | ||
| await expect(page.getByRole("heading", { name: "Customer master" })).toBeVisible(); | ||
| await expect(page.getByRole("heading", { name: "Board", exact: true })).not.toBeVisible(); | ||
|
|
||
| await popup.getByRole("button", { name: /close/i }).click(); | ||
| await expect(popup).not.toBeVisible(); | ||
| await expect(page.getByRole("heading", { name: "Customer master" })).toBeVisible(); | ||
| await expect(entityButton).toBeVisible(); | ||
| }); | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📝 Info: Test API contracts match backend
/api/customer-masterreturnscorporate_entitieswithcorporate_entity_id/entity_name(backend/app/main.py:1157-1172) and/api/corporate-entities/{id}/relatedreturns{ related: [...] }(backend/app/main.py:2192-2196). Thenode_postfilter matchesNODE_POST(lineageweave/knowledge_graph.py:131) and the frontend filter (frontend/src/components/CustomerEntityTree.tsx:63). Close-button label matches/close/i.Was this helpful? React with 👍 or 👎 to provide feedback.