diff --git a/tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Deployment/DebugViewTests.cs b/tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Deployment/DebugViewTests.cs
new file mode 100644
index 00000000..680849b0
--- /dev/null
+++ b/tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Deployment/DebugViewTests.cs
@@ -0,0 +1,132 @@
+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 Debug View page (/deployment/debug-view), which
+/// live-streams a single instance's attribute values and alarm states from its
+/// owning site. Two facts: the controls always render with Connect correctly
+/// gated, and connecting a freshly-deployed instance resolves to a terminal state
+/// without hanging.
+///
+///
+/// The Enabled-only dropdown + deploy precondition (verified against
+/// DebugView.razor::LoadInstancesForSite): the instance <select>
+/// lists ONLY instances whose config state is Enabled
+/// (i.State == InstanceState.Enabled). A freshly-minted instance is
+/// NotDeployed and would never appear, so Fact B first drives the instance
+/// to Enabled over the CLI (instance deploy) before it can be picked
+/// in the dropdown. Both <select>s carry the data-test hooks
+/// (debug-site-select / debug-instance-select, option value = entity
+/// id); Fact A asserts those hooks are served, which doubles as proof the cluster
+/// rebuild that added them actually took.
+///
+///
+///
+/// Tolerant terminal-state assertion (validation-behavior protocol): whether
+/// Connect reaches a Live badge depends on the owning site returning a
+/// snapshot for the instance. Reading DebugView.razor::Connect: it awaits
+/// DebugStreamService.StartStreamAsync (a ClusterClient snapshot round-trip);
+/// on success it flips _connected = true (Live badge + success toast), and on
+/// any exception it surfaces a Connect failed: … error .toast. Either
+/// way _connecting resets, so the click always resolves — it never hangs. The
+/// protocol therefore first waits on a TERMINAL state (Live badge OR an error toast)
+/// within a generous window. OBSERVED reality on this live cluster (4 runs): Connect
+/// RELIABLY reaches Live in ~1s with a Success: Streaming … toast (never
+/// an error), so — as the protocol permits — Fact B is tightened past the tolerant
+/// floor to assert the Live badge, then Disconnect → Disconnected badge + selects
+/// re-enabled. The tolerant OR-wait is kept as a belt-and-braces floor so a one-off
+/// snapshot hiccup degrades to a clear missing-Live assertion failure, not a hang.
+///
+///
+[Collection("Playwright")]
+public class DebugViewTests : IClassFixture
+{
+ private readonly PlaywrightFixture _pw;
+ private readonly DeploymentFixture _cluster;
+
+ public DebugViewTests(PlaywrightFixture pw, DeploymentFixture cluster)
+ {
+ _pw = pw;
+ _cluster = cluster;
+ }
+
+ [SkippableFact]
+ public async Task DebugView_ControlsRender_ConnectGatedOnSelection()
+ {
+ Skip.IfNot(_cluster.Available, ClusterAvailability.SkipReason);
+
+ var page = await _pw.NewAuthenticatedPageAsync();
+ await page.GotoAsync($"{PlaywrightFixture.BaseUrl}/deployment/debug-view");
+ await page.WaitForLoadStateAsync(LoadState.NetworkIdle);
+
+ await Assertions.Expect(page.Locator("h4:has-text('Debug View')")).ToBeVisibleAsync();
+ // The data-test hooks from Task 4 must be served (proves the rebuild took).
+ await Assertions.Expect(page.Locator("[data-test='debug-site-select']")).ToHaveCountAsync(1);
+ await Assertions.Expect(page.Locator("[data-test='debug-instance-select']")).ToHaveCountAsync(1);
+
+ // No site/instance selected -> Connect is disabled.
+ var connect = page.Locator("button.btn.btn-primary.btn-sm:has-text('Connect')");
+ await Assertions.Expect(connect).ToBeDisabledAsync();
+ await Assertions.Expect(page.Locator("span.badge[aria-label='Connection state: Disconnected']"))
+ .ToBeVisibleAsync();
+ }
+
+ [SkippableFact]
+ public async Task DebugView_ConnectEnabledInstance_ResolvesAndDisconnect()
+ {
+ 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);
+
+ 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 wait: Connect resolves to Live badge OR an error
+ // toast within a generous window rather than hanging. The OR-form is the tolerant
+ // floor required by the validation-behavior protocol.
+ //
+ // OBSERVED on this live cluster (4 runs): Connect RELIABLY reaches Live — the site's
+ // snapshot round-trip succeeds for a freshly-deployed zztest instance in ~1s and the
+ // page shows the Live badge plus a "Success: Streaming " toast every time
+ // (never an error toast). Because Live is reliable, the protocol permits tightening:
+ // after the tolerant wait below resolves, this fact asserts the Live badge directly,
+ // then Disconnect → Disconnected badge + selects re-enabled. The OR-locator stays as a
+ // belt-and-braces floor so a one-off snapshot hiccup degrades to a clear assertion
+ // failure on the missing Live badge rather than a 25s hang.
+ var terminal = page.Locator("span.badge[aria-label='Connection state: Live'], .toast");
+ await Assertions.Expect(terminal.First).ToBeVisibleAsync(new() { Timeout = 25_000 });
+
+ // Tightened to the observed-reliable Live outcome.
+ await Assertions.Expect(page.Locator("span.badge[aria-label='Connection state: Live']"))
+ .ToBeVisibleAsync(new() { Timeout = 25_000 });
+
+ 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);
+ }
+ }
+}