diff --git a/tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Deployment/DebugViewTreeTests.cs b/tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Deployment/DebugViewTreeTests.cs
new file mode 100644
index 00000000..13c5c134
--- /dev/null
+++ b/tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Deployment/DebugViewTreeTests.cs
@@ -0,0 +1,134 @@
+using Microsoft.Playwright;
+using ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests.Cluster;
+using Xunit;
+
+namespace ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests.Deployment;
+
+///
+/// E2E coverage for the tabbed, tree-rendered Debug View
+/// (/deployment/debug-view) shipped by the debugview-tabs-trees feature.
+/// Where proves the controls render and Connect
+/// resolves to a terminal state, this class proves the connected layout: a tab
+/// strip (Attributes / Alarms), each tab a composition .
+///
+///
+/// Seeding mirrors exactly — the Enabled-only
+/// instance <select> (verified against
+/// DebugView.razor::LoadInstancesForSite, i.State == InstanceState.Enabled)
+/// lists only Enabled instances. A freshly-minted instance is NotDeployed
+/// and would never appear, so the test mints an instance via the shared
+/// and drives it to Enabled over the CLI
+/// (instance deploy) before selecting it. The fixture provisions onto the
+/// real, running site-a so the ClusterClient snapshot round-trip resolves
+/// fast (an unknown site has no registered ClusterClient and only times out).
+///
+///
+///
+/// Tolerant terminal-state wait, then the tabs/trees assertions — Connect
+/// awaits a ClusterClient snapshot round-trip; on success the page flips to the
+/// Live badge and renders the tabbed card, on failure it surfaces a
+/// Connect failed: … .toast (see DebugView.razor::Connect).
+/// The test first waits on a TERMINAL state (Live badge OR error toast) within a
+/// generous window so it never hangs, then asserts the Live badge directly
+/// (OBSERVED reliable on this live cluster in ). Only
+/// once connected does the card with the tab strip render
+/// (@if (_connected && _snapshot != null)), so the tabs/trees
+/// assertions are gated behind the Live badge.
+///
+///
+///
+/// Why the tree assertion is robust regardless of seeded alarms — the
+/// always renders its <ul role="tree"> root
+/// (with an EmptyContent "No alarms." / "No attributes." slot when the
+/// forest is empty), so asserting a [role="tree"] inside the active pane
+/// holds whether or not the fixture instance happens to carry a configured alarm
+/// or composition members. The test therefore does NOT depend on heavy alarm
+/// seeding: it proves the Alarms tab switches the visible pane and that pane hosts
+/// a tree, which is the structural contract of the tabs+trees rework.
+///
+///
+[Collection("Playwright")]
+public class DebugViewTreeTests : IClassFixture
+{
+ private readonly PlaywrightFixture _pw;
+ private readonly DeploymentFixture _cluster;
+
+ public DebugViewTreeTests(PlaywrightFixture pw, DeploymentFixture cluster)
+ {
+ _pw = pw;
+ _cluster = cluster;
+ }
+
+ [SkippableFact]
+ public async Task DebugView_Connected_RendersTabbedTrees_AndAlarmsTabSwitches()
+ {
+ Skip.IfNot(_cluster.Available, ClusterAvailability.SkipReason);
+
+ var (instanceId, uniqueName) = await _cluster.CreateInstanceAsync();
+ try
+ {
+ await CliRunner.DeployInstanceAsync(instanceId); // -> Enabled, so it lists in the dropdown
+
+ var page = await _pw.NewAuthenticatedPageAsync();
+ await page.GotoAsync($"{PlaywrightFixture.BaseUrl}/deployment/debug-view");
+ await page.WaitForLoadStateAsync(LoadState.NetworkIdle);
+
+ // Select the seeded site + Enabled instance (same hooks as DebugViewTests).
+ await page.Locator("[data-test='debug-site-select']")
+ .SelectOptionAsync(new SelectOptionValue { Value = _cluster.SiteAId.ToString() });
+ var instanceOption = page.Locator("[data-test='debug-instance-select'] option",
+ new() { HasText = uniqueName });
+ await Assertions.Expect(instanceOption).ToHaveCountAsync(1, new() { Timeout = 10_000 });
+ await page.Locator("[data-test='debug-instance-select']")
+ .SelectOptionAsync(new SelectOptionValue { Value = instanceId.ToString() });
+
+ var connect = page.Locator("button.btn.btn-primary.btn-sm:has-text('Connect')");
+ await Assertions.Expect(connect).ToBeEnabledAsync(new() { Timeout = 10_000 });
+ await connect.ClickAsync();
+
+ // Outcome-tolerant terminal-state floor: Live badge OR an error toast within a
+ // generous window so the click never hangs (mirrors DebugViewTests). Then tighten
+ // to the observed-reliable Live outcome — the tabbed card only renders when
+ // connected, so the tabs/trees assertions below depend on reaching Live.
+ var terminal = page.Locator("span.badge[aria-label='Connection state: Live'], .toast");
+ await Assertions.Expect(terminal.First).ToBeVisibleAsync(new() { Timeout = 25_000 });
+ await Assertions.Expect(page.Locator("span.badge[aria-label='Connection state: Live']"))
+ .ToBeVisibleAsync(new() { Timeout = 2_000 });
+
+ // ── Tab strip renders with both tabs (the tabs+trees rework's structural hooks). ──
+ var attrTab = page.Locator("[data-test='debug-tab-attributes']");
+ var alarmTab = page.Locator("[data-test='debug-tab-alarms']");
+ await Assertions.Expect(attrTab).ToBeVisibleAsync(new() { Timeout = 10_000 });
+ await Assertions.Expect(alarmTab).ToBeVisibleAsync();
+
+ var attrPane = page.Locator("[data-test='debug-pane-attributes']");
+ var alarmPane = page.Locator("[data-test='debug-pane-alarms']");
+
+ // Attributes is the default tab: its pane is visible and hosts a tree;
+ // the Alarms pane is present in the DOM but hidden (d-none) until selected.
+ await Assertions.Expect(attrTab).ToHaveAttributeAsync("aria-selected", "true");
+ await Assertions.Expect(attrPane).ToBeVisibleAsync();
+ await Assertions.Expect(attrPane.Locator("[role='tree']")).ToHaveCountAsync(1);
+ await Assertions.Expect(alarmPane).ToBeHiddenAsync();
+
+ // ── Switch to the Alarms tab and assert the pane swaps + hosts its own tree. ──
+ await alarmTab.ClickAsync();
+ await Assertions.Expect(alarmTab).ToHaveAttributeAsync("aria-selected", "true");
+ await Assertions.Expect(alarmPane).ToBeVisibleAsync(new() { Timeout = 10_000 });
+ await Assertions.Expect(attrPane).ToBeHiddenAsync();
+ // The TreeView always renders its root (EmptyContent slot when
+ // the forest is empty), so this holds whether or not the instance carries alarms.
+ await Assertions.Expect(alarmPane.Locator("[role='tree']")).ToHaveCountAsync(1);
+
+ // Disconnect tears the card down and re-enables the selectors.
+ await page.Locator("button.btn-outline-danger.btn-sm:has-text('Disconnect')").ClickAsync();
+ await Assertions.Expect(page.Locator("span.badge[aria-label='Connection state: Disconnected']"))
+ .ToBeVisibleAsync(new() { Timeout = 10_000 });
+ await Assertions.Expect(page.Locator("[data-test='debug-site-select']")).ToBeEnabledAsync();
+ }
+ finally
+ {
+ await CliRunner.DeleteInstanceAsync(instanceId);
+ }
+ }
+}