11import { test , expect } from './fixtures'
2+ import type { FrameLocator , Page } from '@playwright/test'
23import fs from 'fs-extra'
34import {
45 openLineageView ,
@@ -8,6 +9,31 @@ import {
89} from './utils'
910import { createPythonInterpreterSettingsSpecifier } from './utils_code_server'
1011
12+ /**
13+ * Find the iframe that hosts the lineage UI (the one containing the
14+ * Settings cog button). Returns null if it can't be located.
15+ */
16+ async function findLineageFrame ( page : Page ) : Promise < FrameLocator | null > {
17+ const iframes = page . locator ( 'iframe' )
18+ const iframeCount = await iframes . count ( )
19+
20+ for ( let i = 0 ; i < iframeCount ; i ++ ) {
21+ const contentFrame = iframes . nth ( i ) . contentFrame ( )
22+ if ( ! contentFrame ) continue
23+ const activeFrame = contentFrame . locator ( '#active-frame' ) . contentFrame ( )
24+ if ( ! activeFrame ) continue
25+ try {
26+ await activeFrame
27+ . getByRole ( 'button' , { name : 'Settings' } )
28+ . waitFor ( { timeout : 1000 } )
29+ return activeFrame
30+ } catch {
31+ continue
32+ }
33+ }
34+ return null
35+ }
36+
1137test ( 'Settings button is visible in the lineage view' , async ( {
1238 page,
1339 sharedCodeServer,
@@ -35,30 +61,64 @@ test('Settings button is visible in the lineage view', async ({
3561 // Open lineage
3662 await openLineageView ( page )
3763
38- const iframes = page . locator ( 'iframe' )
39- const iframeCount = await iframes . count ( )
40- let settingsCount = 0
64+ const lineageFrame = await findLineageFrame ( page )
65+ expect ( lineageFrame ) . not . toBeNull ( )
66+ } )
4167
42- for ( let i = 0 ; i < iframeCount ; i ++ ) {
43- const iframe = iframes . nth ( i )
44- const contentFrame = iframe . contentFrame ( )
45- if ( contentFrame ) {
46- const activeFrame = contentFrame . locator ( '#active-frame' ) . contentFrame ( )
47- if ( activeFrame ) {
48- try {
49- await activeFrame
50- . getByRole ( 'button' , {
51- name : 'Settings' ,
52- } )
53- . waitFor ( { timeout : 1000 } )
54- settingsCount ++
55- } catch {
56- // Continue to next iframe if this one doesn't have the error
57- continue
58- }
59- }
60- }
61- }
68+ test ( 'Only Direct Neighbors toggle filters the lineage graph' , async ( {
69+ page,
70+ sharedCodeServer,
71+ tempDir,
72+ } ) => {
73+ await fs . copy ( SUSHI_SOURCE_PATH , tempDir )
74+ await createPythonInterpreterSettingsSpecifier ( tempDir )
6275
63- expect ( settingsCount ) . toBeGreaterThan ( 0 )
76+ await openServerPage ( page , tempDir , sharedCodeServer )
77+ await page . waitForSelector ( 'text=models' )
78+
79+ await page
80+ . getByRole ( 'treeitem' , { name : 'models' , exact : true } )
81+ . locator ( 'a' )
82+ . click ( )
83+ await page
84+ . getByRole ( 'treeitem' , { name : 'waiters.py' , exact : true } )
85+ . locator ( 'a' )
86+ . click ( )
87+ await waitForLoadedSQLMesh ( page )
88+
89+ await openLineageView ( page )
90+
91+ const lineageFrame = await findLineageFrame ( page )
92+ expect ( lineageFrame ) . not . toBeNull ( )
93+ if ( ! lineageFrame ) return
94+
95+ // Wait for the graph to render at least one node
96+ await lineageFrame . locator ( '.react-flow__node' ) . first ( ) . waitFor ( )
97+ const nodesBefore = await lineageFrame . locator ( '.react-flow__node' ) . count ( )
98+ expect ( nodesBefore ) . toBeGreaterThan ( 0 )
99+
100+ // Open the settings menu and toggle "Only Direct Neighbors"
101+ await lineageFrame . getByRole ( 'button' , { name : 'Settings' } ) . click ( )
102+ const toggle = lineageFrame . getByRole ( 'button' , {
103+ name : 'Only Direct Neighbors' ,
104+ } )
105+ await toggle . waitFor ( )
106+ await toggle . click ( )
107+
108+ // After enabling, the visible node set must be a subset of the original.
109+ // We assert a strict drop only when the original graph had room to shrink
110+ // (i.e. more than the worst-case direct-neighbor count of 1 + parents + children).
111+ await page . waitForTimeout ( 250 ) // let React Flow re-layout
112+ const nodesAfter = await lineageFrame . locator ( '.react-flow__node' ) . count ( )
113+ expect ( nodesAfter ) . toBeLessThanOrEqual ( nodesBefore )
114+ expect ( nodesAfter ) . toBeGreaterThan ( 0 ) // main node is always shown
115+
116+ // Toggle off → graph returns to the full size
117+ await lineageFrame . getByRole ( 'button' , { name : 'Settings' } ) . click ( )
118+ await lineageFrame
119+ . getByRole ( 'button' , { name : 'Only Direct Neighbors' } )
120+ . click ( )
121+ await page . waitForTimeout ( 250 )
122+ const nodesRestored = await lineageFrame . locator ( '.react-flow__node' ) . count ( )
123+ expect ( nodesRestored ) . toBe ( nodesBefore )
64124} )
0 commit comments