import { test, expect, type Page } from '@playwright/test'; async function waitForGraph(page: Page) { await page.waitForSelector('svg g.node', { timeout: 15000 }); } function nodeByLabel(page: Page, label: string) { return page.locator('g.node').filter({ hasText: label }); } async function openSubgraph(page: Page, nodeLabel: string) { await nodeByLabel(page, nodeLabel).click({ button: 'right' }); await expect(page.locator('.ant-dropdown:visible')).toBeVisible({ timeout: 3000 }); await page.waitForTimeout(300); await page.locator('.ant-dropdown-menu-item').filter({ hasText: 'Subgraph' }).click(); await waitForGraph(page); } const breadcrumbLinks = (page: Page) => page.locator('.ant-breadcrumb-link'); test.describe('Subgraph preservation during breadcrumb navigation', () => { test.beforeEach(async ({ page }) => { await page.goto('/'); await waitForGraph(page); }); test('nodes added to a subgraph are preserved after navigating to root and back', async ({ page }) => { // Enter Start's subgraph and add a node await openSubgraph(page, 'Start'); await nodeByLabel(page, 'End').click(); await expect(page.locator('g.node')).toHaveCount(3, { timeout: 5000 }); // Go back to main await breadcrumbLinks(page).first().click(); await expect(breadcrumbLinks(page)).toHaveCount(1, { timeout: 3000 }); await waitForGraph(page); // Re-enter the same subgraph await openSubgraph(page, 'Start'); // The extra node should still be there await expect(page.locator('g.node')).toHaveCount(3, { timeout: 5000 }); }); test('main graph is preserved when navigating into a subgraph and back', async ({ page }) => { // Add a node to the main graph await nodeByLabel(page, 'Start').click(); await expect(page.locator('g.node')).toHaveCount(3, { timeout: 5000 }); // Navigate into End's subgraph await openSubgraph(page, 'End'); // Go back to main await breadcrumbLinks(page).first().click(); await expect(breadcrumbLinks(page)).toHaveCount(1, { timeout: 3000 }); await waitForGraph(page); // Main graph should still have 3 nodes await expect(page.locator('g.node')).toHaveCount(3, { timeout: 5000 }); await expect(nodeByLabel(page, 'Start')).toBeVisible(); await expect(nodeByLabel(page, 'End')).toBeVisible(); }); test('two sibling subgraphs preserve their states independently', async ({ page }) => { // Enter Start's subgraph and add 1 extra node (3 total) await openSubgraph(page, 'Start'); await nodeByLabel(page, 'End').click(); await expect(page.locator('g.node')).toHaveCount(3, { timeout: 5000 }); // Back to main await breadcrumbLinks(page).first().click(); await expect(breadcrumbLinks(page)).toHaveCount(1, { timeout: 3000 }); await waitForGraph(page); // Enter End's subgraph and add 2 extra nodes (4 total) await openSubgraph(page, 'End'); await nodeByLabel(page, 'End').click(); await expect(page.locator('g.node')).toHaveCount(3, { timeout: 5000 }); await nodeByLabel(page, 'End').click(); await expect(page.locator('g.node')).toHaveCount(4, { timeout: 5000 }); // Back to main await breadcrumbLinks(page).first().click(); await expect(breadcrumbLinks(page)).toHaveCount(1, { timeout: 3000 }); await waitForGraph(page); // Re-enter Start's subgraph — should still have 3 nodes await openSubgraph(page, 'Start'); await expect(page.locator('g.node')).toHaveCount(3, { timeout: 5000 }); // Back to main await breadcrumbLinks(page).first().click(); await waitForGraph(page); // Re-enter End's subgraph — should still have 4 nodes await openSubgraph(page, 'End'); await expect(page.locator('g.node')).toHaveCount(4, { timeout: 5000 }); }); test('intermediate level subgraph is preserved when navigating back from deeper nesting', async ({ page }) => { // Enter Start's subgraph (level 1) and add a node await openSubgraph(page, 'Start'); await nodeByLabel(page, 'End').click(); await expect(page.locator('g.node')).toHaveCount(3, { timeout: 5000 }); // Go one level deeper (level 2) await openSubgraph(page, 'Start'); await expect(breadcrumbLinks(page)).toHaveCount(3, { timeout: 3000 }); // Navigate all the way back to root await breadcrumbLinks(page).first().click(); await expect(breadcrumbLinks(page)).toHaveCount(1, { timeout: 3000 }); await waitForGraph(page); // Re-enter Start's subgraph (level 1) — the 3-node state must be intact await openSubgraph(page, 'Start'); await expect(page.locator('g.node')).toHaveCount(3, { timeout: 5000 }); }); test('edges added inside a subgraph are preserved after navigating away and back', async ({ page }) => { // Enter Start's subgraph, create a node by clicking End, then link them await openSubgraph(page, 'Start'); const edgeCountBefore = await page.locator('g.edge').count(); // Ctrl+click Start to select it as a parent for linking await page.keyboard.down('Control'); await nodeByLabel(page, 'Start').click(); await page.keyboard.up('Control'); // Click End without Ctrl to link selected Start → End await nodeByLabel(page, 'End').click(); await expect(page.locator('g.edge')).toHaveCount(edgeCountBefore + 1, { timeout: 5000 }); // Navigate back to main await breadcrumbLinks(page).first().click(); await expect(breadcrumbLinks(page)).toHaveCount(1, { timeout: 3000 }); await waitForGraph(page); // Re-enter the subgraph — extra edge must still be there await openSubgraph(page, 'Start'); await expect(page.locator('g.edge')).toHaveCount(edgeCountBefore + 1, { timeout: 5000 }); }); });