diff --git a/tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Cluster/CliRunner.Helpers.cs b/tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Cluster/CliRunner.Helpers.cs
index d0195447..6ca4ffee 100644
--- a/tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Cluster/CliRunner.Helpers.cs
+++ b/tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Cluster/CliRunner.Helpers.cs
@@ -70,14 +70,34 @@ public static partial class CliRunner
/// (Boolean, Int32, Double, String).
/// Defaults to Double.
///
+ ///
+ /// Optional data source reference (tag path). When provided, maps to
+ /// --data-source on the CLI and sets
+ /// TemplateAttribute.DataSourceReference. The InstanceConfigure page
+ /// populates _bindingDataSourceAttrs by filtering attributes to those
+ /// where DataSourceReference is non-empty, so an attribute that needs
+ /// to appear in the Connection Bindings panel MUST be created with this set.
+ ///
/// The CLI failed.
- public static async Task AddAttributeAsync(int templateId, string name, string dataType = "Double")
+ public static async Task AddAttributeAsync(
+ int templateId, string name, string dataType = "Double",
+ string? dataSourceReference = null)
{
- await RunAsync(
+ var inv = System.Globalization.CultureInfo.InvariantCulture;
+ var args = new List
+ {
"template", "attribute", "add",
- "--template-id", templateId.ToString(System.Globalization.CultureInfo.InvariantCulture),
+ "--template-id", templateId.ToString(inv),
"--name", name,
- "--data-type", dataType);
+ "--data-type", dataType,
+ };
+ if (!string.IsNullOrEmpty(dataSourceReference))
+ {
+ args.Add("--data-source");
+ args.Add(dataSourceReference);
+ }
+
+ await RunAsync([.. args]);
}
///
diff --git a/tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Deployment/InstanceConfigureFixture.cs b/tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Deployment/InstanceConfigureFixture.cs
new file mode 100644
index 00000000..760ec95c
--- /dev/null
+++ b/tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Deployment/InstanceConfigureFixture.cs
@@ -0,0 +1,91 @@
+using System.Text.Json;
+using ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests.Cluster;
+
+namespace ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests.Deployment;
+
+///
+/// fixture for the InstanceConfigure E2E tests. Provisions, on the real
+/// running site-a: a zztest template with a single bindable Double attribute, a zztest
+/// data-connection (so the bindings UI has a connection to assign), a zztest area (for the
+/// area-reassignment test), and one instance created with no area. The instance is NOT deployed —
+/// bindings/overrides/area assignment are pre-deploy configuration operations.
+///
+///
+/// Why the attribute is created with --data-source "Value":
+/// InstanceConfigure.razor.cs populates _bindingDataSourceAttrs by filtering
+/// GetAttributesByTemplateIdAsync to attributes where
+/// !string.IsNullOrEmpty(a.DataSourceReference) (line 581 of InstanceConfigure.razor).
+/// A plain Double attribute created without --data-source has
+/// DataSourceReference = null, so it would not appear in the Connection Bindings panel
+/// and the bindings-UI tests would silently see "No data-sourced attributes". The attribute
+/// DOES appear unconditionally in _overrideAttrs (filtered only on !IsLocked,
+/// line 592), so overrides work with or without a tag path. The fixture therefore sets
+/// dataSourceReference: "Value" when adding the attribute so both panels are exercised.
+///
+///
+public sealed class InstanceConfigureFixture : IAsyncLifetime
+{
+ private const string SiteAIdentifier = "site-a";
+
+ public int SiteAId { get; private set; }
+ public int TemplateId { get; private set; }
+ public int ConnectionId { get; private set; }
+ public int AreaId { get; private set; }
+ public int InstanceId { get; private set; }
+
+ /// The single bindable/overridable attribute name on the fixture template.
+ public string AttributeName => "Value";
+
+ /// The fixture data-connection name (for locating it in the bindings UI dropdown).
+ public string ConnectionName { get; private set; } = string.Empty;
+
+ public bool Available { get; private set; }
+
+ public async Task InitializeAsync()
+ {
+ Available = await ClusterAvailability.IsAvailableAsync();
+ if (!Available)
+ {
+ return;
+ }
+
+ try
+ {
+ SiteAId = await CliRunner.ResolveSiteIdAsync(SiteAIdentifier);
+ TemplateId = await CliRunner.CreateTemplateAsync(CliRunner.UniqueName("cfgtmpl"));
+ // The attribute must have DataSourceReference set (via --data-source) so it appears
+ // in _bindingDataSourceAttrs on the InstanceConfigure page. Without it the Connection
+ // Bindings panel shows "No data-sourced attributes" and binding tests cannot run.
+ // See the class-level XML doc for the full analysis.
+ await CliRunner.AddAttributeAsync(TemplateId, AttributeName, "Double", dataSourceReference: AttributeName);
+ ConnectionName = CliRunner.UniqueName("conn");
+ ConnectionId = await CliRunner.CreateDataConnectionAsync(SiteAId, ConnectionName);
+ AreaId = await CliRunner.CreateAreaAsync(SiteAId, CliRunner.UniqueName("cfgarea"));
+ InstanceId = await CliRunner.CreateInstanceAsync(CliRunner.UniqueName("cfginst"), TemplateId, SiteAId);
+ }
+ catch
+ {
+ await SafeCleanupAsync();
+ Available = false;
+ throw;
+ }
+ }
+
+ public async Task DisposeAsync()
+ {
+ if (!Available)
+ {
+ return;
+ }
+
+ await SafeCleanupAsync();
+ }
+
+ private async Task SafeCleanupAsync()
+ {
+ await CliRunner.DeleteInstanceAsync(InstanceId);
+ await CliRunner.DeleteDataConnectionAsync(ConnectionId);
+ await CliRunner.DeleteAreaAsync(AreaId);
+ await CliRunner.DeleteTemplateAsync(TemplateId);
+ }
+}