diff --git a/tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Deployment/InstanceConfigureTests.cs b/tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Deployment/InstanceConfigureTests.cs
new file mode 100644
index 00000000..ab0a61f9
--- /dev/null
+++ b/tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Deployment/InstanceConfigureTests.cs
@@ -0,0 +1,71 @@
+using System.Text.Json;
+using Microsoft.Playwright;
+using ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests.Cluster;
+
+namespace ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests.Deployment;
+
+///
+/// E2E round-trip for the InstanceConfigure page's Connection Bindings panel. The
+/// provisions a zztest template whose single
+/// bindable Double attribute carries a DataSourceReference (so it appears
+/// in the bindings panel), a zztest data-connection on site-a, a zztest area, and a
+/// non-deployed instance. This fact drives the page's bulk-assign UI to bind every
+/// data-sourced attribute to the fixture connection, saves, and then verifies the bind
+/// actually persisted via a CLI instance get read-back — not just the toast.
+///
+///
+/// Selector note: the bulk select (data-test='binding-bulk-select') is bound to
+/// _bulkConnectionId (an int), and its option VALUES are connection ids while the
+/// option TEXT is "{name} ({protocol})". Selecting by VALUE = the connection id is
+/// the robust choice (it doesn't depend on the connection's protocol suffix in the label).
+/// The bulk row only renders when there is at least one data-sourced attribute AND at
+/// least one site connection — both guaranteed by the fixture — so it is always present
+/// here.
+///
+///
+[Collection("Playwright")]
+public sealed class InstanceConfigureTests : IClassFixture
+{
+ private readonly PlaywrightFixture _fixture;
+ private readonly InstanceConfigureFixture _cfg;
+
+ public InstanceConfigureTests(PlaywrightFixture fixture, InstanceConfigureFixture cfg)
+ {
+ _fixture = fixture;
+ _cfg = cfg;
+ }
+
+ [SkippableFact]
+ public async Task BindAllAttributes_SavesAndPersists()
+ {
+ Skip.IfNot(_cfg.Available, ClusterAvailability.SkipReason);
+
+ var page = await _fixture.NewAuthenticatedPageAsync("multi-role", "password");
+ await page.GotoAsync($"{PlaywrightFixture.BaseUrl}/deployment/instances/{_cfg.InstanceId}/configure");
+
+ // This is a Blazor Server page: it renders a LoadingSpinner while OnInitializedAsync
+ // loads the template attributes + site connections, then re-renders the bindings
+ // panel (the bulk select renders only once both lists are non-empty). Settle the
+ // initial load (NetworkIdle) and web-first wait for the bulk select before driving it,
+ // so the interaction never races the post-load re-render.
+ await page.WaitForLoadStateAsync(LoadState.NetworkIdle);
+ var bulkSelect = page.Locator("[data-test='binding-bulk-select']");
+ await Assertions.Expect(bulkSelect).ToBeVisibleAsync(new() { Timeout = 15_000 });
+
+ // Bulk-assign every bindable attribute to the fixture connection, then Apply + Save.
+ // Select by VALUE (the connection id) — most robust, since the select binds _bulkConnectionId.
+ await bulkSelect.SelectOptionAsync(new SelectOptionValue { Value = _cfg.ConnectionId.ToString() });
+ await page.GetByRole(AriaRole.Button, new() { Name = "Apply" }).ClickAsync();
+ await page.GetByRole(AriaRole.Button, new() { Name = "Save Bindings" }).ClickAsync();
+
+ await Assertions.Expect(page.Locator(".toast")).ToHaveCountAsync(1, new() { Timeout = 15_000 });
+
+ // Verify persistence via CLI read-back (not just the toast).
+ using var doc = await CliRunner.GetInstanceDocumentAsync(_cfg.InstanceId);
+ var bindings = doc.RootElement.GetProperty("connectionBindings");
+ var bound = bindings.EnumerateArray().Any(b =>
+ b.GetProperty("attributeName").GetString() == _cfg.AttributeName
+ && b.GetProperty("dataConnectionId").GetInt32() == _cfg.ConnectionId);
+ Assert.True(bound, "Expected the Value attribute to be bound to the fixture connection after Save Bindings.");
+ }
+}