98 lines
4.6 KiB
C#
98 lines
4.6 KiB
C#
using Microsoft.Playwright;
|
|
using ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests.Cluster;
|
|
using Xunit;
|
|
|
|
namespace ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests.Design;
|
|
|
|
[Collection("Playwright")]
|
|
public class SharedScriptCrudTests
|
|
{
|
|
private readonly PlaywrightFixture _pw;
|
|
|
|
public SharedScriptCrudTests(PlaywrightFixture pw)
|
|
{
|
|
_pw = pw;
|
|
}
|
|
|
|
/// <summary>
|
|
/// CLI-create a shared script, then exercise the Central UI list render + UI delete
|
|
/// round-trip. The create/edit form is driven by a Monaco editor (brittle to type),
|
|
/// so authoring happens via the CLI; the UI delete is the behavior under test. A
|
|
/// best-effort CLI delete in finally guarantees no zztest-script-* definition leaks
|
|
/// if the UI path fails mid-way (the happy path already deletes via the UI).
|
|
/// </summary>
|
|
[SkippableFact]
|
|
public async Task CliCreated_Script_DeletesViaCard()
|
|
{
|
|
Skip.IfNot(await ClusterAvailability.IsAvailableAsync(), ClusterAvailability.SkipReason);
|
|
|
|
var name = CliRunner.UniqueName("script");
|
|
int id = await CliRunner.CreateSharedScriptAsync(name);
|
|
|
|
try
|
|
{
|
|
var page = await _pw.NewAuthenticatedPageAsync();
|
|
await page.GotoAsync($"{PlaywrightFixture.BaseUrl}/design/shared-scripts");
|
|
await page.WaitForLoadStateAsync(LoadState.NetworkIdle);
|
|
await Assertions.Expect(page.Locator("h4:has-text('Shared Scripts')")).ToBeVisibleAsync();
|
|
|
|
// Narrow the list so the card locator stays unambiguous even with many scripts.
|
|
await page.Locator("input[placeholder='Filter by name or code…']").FillAsync(name);
|
|
|
|
var card = page.Locator("div.card").Filter(new() { HasText = name });
|
|
await Assertions.Expect(card).ToHaveCountAsync(1, new() { Timeout = 10_000 });
|
|
|
|
// Scope the kebab + delete item to the card's .dropdown for strict-mode safety.
|
|
var dropdown = card.Locator(".dropdown");
|
|
await dropdown.Locator("button[aria-label^='More actions']").ClickAsync();
|
|
await dropdown.Locator(".dropdown-menu button.dropdown-item.text-danger").ClickAsync();
|
|
|
|
await Assertions.Expect(page.Locator(".modal-title:has-text('Delete Shared Script')")).ToBeVisibleAsync();
|
|
await page.Locator(".modal-footer .btn-danger").ClickAsync();
|
|
|
|
// Single web-first assertion on the success toast — toasts auto-dismiss, so we
|
|
// do NOT chase the toast body in a second sequential check.
|
|
await Assertions.Expect(page.Locator(".toast", new() { HasText = "deleted" }))
|
|
.ToHaveCountAsync(1, new() { Timeout = 15_000 });
|
|
|
|
// The card must be gone.
|
|
await Assertions.Expect(page.Locator("div.card").Filter(new() { HasText = name }))
|
|
.ToHaveCountAsync(0, new() { Timeout = 10_000 });
|
|
}
|
|
finally
|
|
{
|
|
// Best-effort teardown: the happy path deletes via the UI, so this finds
|
|
// nothing; it only fires if the UI path failed mid-way.
|
|
await CliRunner.DeleteSharedScriptAsync(id);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The create form renders: heading, the (enabled) Name input, the editor tabs, and
|
|
/// the Monaco editor surface. Asserts render only — does NOT click Save, Check Syntax,
|
|
/// or Test Run (Test Run fires real I/O). Mutates nothing.
|
|
/// </summary>
|
|
[SkippableFact]
|
|
public async Task CreateForm_Renders()
|
|
{
|
|
Skip.IfNot(await ClusterAvailability.IsAvailableAsync(), ClusterAvailability.SkipReason);
|
|
|
|
var page = await _pw.NewAuthenticatedPageAsync();
|
|
|
|
await page.GotoAsync($"{PlaywrightFixture.BaseUrl}/design/shared-scripts/create");
|
|
await page.WaitForLoadStateAsync(LoadState.NetworkIdle);
|
|
await Assertions.Expect(page.Locator("h4:has-text('New Shared Script')")).ToBeVisibleAsync();
|
|
|
|
var nameInput = page.Locator("input[type=text].form-control.form-control-sm");
|
|
await Assertions.Expect(nameInput).ToBeVisibleAsync();
|
|
await Assertions.Expect(nameInput).ToBeEnabledAsync();
|
|
|
|
await Assertions.Expect(page.Locator("button.nav-link:has-text('Code')")).ToBeVisibleAsync();
|
|
await Assertions.Expect(page.Locator("button.nav-link:has-text('Parameters')")).ToBeVisibleAsync();
|
|
await Assertions.Expect(page.Locator("button.nav-link:has-text('Return type')")).ToBeVisibleAsync();
|
|
|
|
// Monaco mounts asynchronously via JS interop — give it a generous timeout.
|
|
await Assertions.Expect(page.Locator(".monaco-editor")).ToBeVisibleAsync(new() { Timeout = 15_000 });
|
|
}
|
|
}
|