Migrating all validation to use HTMX and endpoints instead of WASM/Websocket connections
This commit is contained in:
@@ -0,0 +1,648 @@
|
||||
using Microsoft.Playwright;
|
||||
|
||||
namespace Enciphered.Blazor.UIComponents.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for htmx-powered server-side validation on the Forms Demo page.
|
||||
/// Covers: inline field validation on blur, full-form submission validation,
|
||||
/// success message display, error styling, and form reset clearing errors.
|
||||
/// </summary>
|
||||
[TestFixture]
|
||||
public class ValidationTests : PlaywrightTestBase
|
||||
{
|
||||
// ── Helpers ──────────────────────────────────────────────────────────────
|
||||
|
||||
private async Task GoToFormsAsync()
|
||||
{
|
||||
await Page.GotoAsync($"{BaseUrl}/forms", new PageGotoOptions { WaitUntil = WaitUntilState.NetworkIdle });
|
||||
await Page.WaitForSelectorAsync("[data-testid='btn-submit']", new PageWaitForSelectorOptions { Timeout = 10_000 });
|
||||
}
|
||||
|
||||
private ILocator Input(string testId) => Page.Locator($"[data-testid='{testId}']");
|
||||
private ILocator Btn(string testId) => Page.Locator($"[data-testid='{testId}']");
|
||||
|
||||
private ILocator FieldError(string fieldName) =>
|
||||
Page.Locator($"[data-field-error='{fieldName}']");
|
||||
|
||||
/// <summary>
|
||||
/// Focus an input, clear it, then blur to trigger htmx validation.
|
||||
/// </summary>
|
||||
private async Task BlurEmptyField(string testId)
|
||||
{
|
||||
var input = Input(testId);
|
||||
await input.ClickAsync();
|
||||
await input.FillAsync("");
|
||||
await input.BlurAsync();
|
||||
// Wait for htmx round-trip
|
||||
await Page.WaitForTimeoutAsync(500);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fill a field and blur it.
|
||||
/// </summary>
|
||||
private async Task FillAndBlur(string testId, string value)
|
||||
{
|
||||
var input = Input(testId);
|
||||
await input.ClickAsync();
|
||||
await input.FillAsync(value);
|
||||
await input.BlurAsync();
|
||||
await Page.WaitForTimeoutAsync(500);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fill all required fields with valid data.
|
||||
/// </summary>
|
||||
private async Task FillAllValidFields()
|
||||
{
|
||||
await Input("input-name").FillAsync("Alice Johnson");
|
||||
await Input("input-email").FillAsync("alice@example.com");
|
||||
await Input("input-password").FillAsync("secure123");
|
||||
await Input("input-confirmation").FillAsync("CONFIRM");
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
// Inline field validation — blur triggers
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
|
||||
[Test]
|
||||
public async Task Name_Blank_Shows_Required_Error_On_Blur()
|
||||
{
|
||||
await GoToFormsAsync();
|
||||
await BlurEmptyField("input-name");
|
||||
|
||||
var error = FieldError("name");
|
||||
await Expect(error).ToBeVisibleAsync();
|
||||
await Expect(error).ToHaveTextAsync("Name is required.");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Name_Too_Short_Shows_Length_Error_On_Blur()
|
||||
{
|
||||
await GoToFormsAsync();
|
||||
await FillAndBlur("input-name", "A");
|
||||
|
||||
var error = FieldError("name");
|
||||
await Expect(error).ToBeVisibleAsync();
|
||||
await Expect(error).ToHaveTextAsync("Name must be at least 2 characters.");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Name_Valid_Clears_Error_On_Blur()
|
||||
{
|
||||
await GoToFormsAsync();
|
||||
|
||||
// Trigger an error first
|
||||
await BlurEmptyField("input-name");
|
||||
await Expect(FieldError("name")).ToBeVisibleAsync();
|
||||
|
||||
// Now fix it
|
||||
await FillAndBlur("input-name", "Alice");
|
||||
|
||||
var error = FieldError("name");
|
||||
await Expect(error).ToBeHiddenAsync();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Email_Blank_Shows_Required_Error_On_Blur()
|
||||
{
|
||||
await GoToFormsAsync();
|
||||
await BlurEmptyField("input-email");
|
||||
|
||||
var error = FieldError("email");
|
||||
await Expect(error).ToBeVisibleAsync();
|
||||
await Expect(error).ToHaveTextAsync("Email is required.");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Email_Invalid_Shows_Format_Error_On_Blur()
|
||||
{
|
||||
await GoToFormsAsync();
|
||||
await FillAndBlur("input-email", "notanemail");
|
||||
|
||||
var error = FieldError("email");
|
||||
await Expect(error).ToBeVisibleAsync();
|
||||
await Expect(error).ToHaveTextAsync("Please enter a valid email address.");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Email_Valid_Clears_Error_On_Blur()
|
||||
{
|
||||
await GoToFormsAsync();
|
||||
await BlurEmptyField("input-email");
|
||||
await Expect(FieldError("email")).ToBeVisibleAsync();
|
||||
|
||||
await FillAndBlur("input-email", "alice@example.com");
|
||||
await Expect(FieldError("email")).ToBeHiddenAsync();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Password_Blank_Shows_Required_Error_On_Blur()
|
||||
{
|
||||
await GoToFormsAsync();
|
||||
await BlurEmptyField("input-password");
|
||||
|
||||
var error = FieldError("password");
|
||||
await Expect(error).ToBeVisibleAsync();
|
||||
await Expect(error).ToHaveTextAsync("Password is required.");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Password_Too_Short_Shows_Length_Error_On_Blur()
|
||||
{
|
||||
await GoToFormsAsync();
|
||||
await FillAndBlur("input-password", "abc");
|
||||
|
||||
var error = FieldError("password");
|
||||
await Expect(error).ToBeVisibleAsync();
|
||||
await Expect(error).ToHaveTextAsync("Password must be at least 6 characters.");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Password_Valid_Clears_Error_On_Blur()
|
||||
{
|
||||
await GoToFormsAsync();
|
||||
await BlurEmptyField("input-password");
|
||||
await Expect(FieldError("password")).ToBeVisibleAsync();
|
||||
|
||||
await FillAndBlur("input-password", "secure123");
|
||||
await Expect(FieldError("password")).ToBeHiddenAsync();
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
// Input gets destructive border styling on error
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
|
||||
[Test]
|
||||
public async Task Input_Gets_Destructive_Border_On_Error()
|
||||
{
|
||||
await GoToFormsAsync();
|
||||
await BlurEmptyField("input-name");
|
||||
|
||||
var cls = await Input("input-name").GetAttributeAsync("class");
|
||||
Assert.That(cls, Does.Contain("border-destructive"), "Input should have destructive border on error");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Input_Loses_Destructive_Border_When_Valid()
|
||||
{
|
||||
await GoToFormsAsync();
|
||||
await BlurEmptyField("input-name");
|
||||
|
||||
// Verify error styling is present
|
||||
var clsBefore = await Input("input-name").GetAttributeAsync("class");
|
||||
Assert.That(clsBefore, Does.Contain("border-destructive"));
|
||||
|
||||
// Fix the field
|
||||
await FillAndBlur("input-name", "Alice");
|
||||
|
||||
var clsAfter = await Input("input-name").GetAttributeAsync("class");
|
||||
Assert.That(clsAfter, Does.Not.Contain("border-destructive"), "Input should lose destructive border when valid");
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
// Full form submission validation
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
|
||||
[Test]
|
||||
public async Task Empty_Submit_Shows_All_Required_Errors()
|
||||
{
|
||||
await GoToFormsAsync();
|
||||
|
||||
await Btn("btn-submit").ClickAsync();
|
||||
await Page.WaitForTimeoutAsync(500);
|
||||
|
||||
// Name, email, and password should show errors
|
||||
await Expect(FieldError("name")).ToBeVisibleAsync();
|
||||
await Expect(FieldError("email")).ToBeVisibleAsync();
|
||||
await Expect(FieldError("password")).ToBeVisibleAsync();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Valid_Submit_Shows_Success_Message()
|
||||
{
|
||||
await GoToFormsAsync();
|
||||
|
||||
await FillAllValidFields();
|
||||
|
||||
await Btn("btn-submit").ClickAsync();
|
||||
await Page.WaitForTimeoutAsync(500);
|
||||
|
||||
var success = Page.Locator("[data-testid='success-message']");
|
||||
await Expect(success).ToBeVisibleAsync();
|
||||
await Expect(success).ToContainTextAsync("Form submitted successfully");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Valid_Submit_Clears_All_Errors()
|
||||
{
|
||||
await GoToFormsAsync();
|
||||
|
||||
// First trigger errors
|
||||
await Btn("btn-submit").ClickAsync();
|
||||
await Page.WaitForTimeoutAsync(500);
|
||||
await Expect(FieldError("name")).ToBeVisibleAsync();
|
||||
|
||||
// Now fill valid data and resubmit
|
||||
await FillAllValidFields();
|
||||
|
||||
await Btn("btn-submit").ClickAsync();
|
||||
await Page.WaitForTimeoutAsync(500);
|
||||
|
||||
await Expect(FieldError("name")).ToBeHiddenAsync();
|
||||
await Expect(FieldError("email")).ToBeHiddenAsync();
|
||||
await Expect(FieldError("password")).ToBeHiddenAsync();
|
||||
await Expect(FieldError("confirmation")).ToBeHiddenAsync();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Partial_Submit_Shows_Only_Invalid_Errors()
|
||||
{
|
||||
await GoToFormsAsync();
|
||||
|
||||
// Fill name only
|
||||
await Input("input-name").FillAsync("Alice");
|
||||
|
||||
await Btn("btn-submit").ClickAsync();
|
||||
await Page.WaitForTimeoutAsync(500);
|
||||
|
||||
// Name should be clear, email and password should show errors
|
||||
await Expect(FieldError("name")).ToBeHiddenAsync();
|
||||
await Expect(FieldError("email")).ToBeVisibleAsync();
|
||||
await Expect(FieldError("password")).ToBeVisibleAsync();
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
// Reset clears validation state
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
|
||||
[Test]
|
||||
public async Task Reset_Clears_Validation_Errors()
|
||||
{
|
||||
await GoToFormsAsync();
|
||||
|
||||
// Trigger errors
|
||||
await Btn("btn-submit").ClickAsync();
|
||||
await Page.WaitForTimeoutAsync(500);
|
||||
await Expect(FieldError("name")).ToBeVisibleAsync();
|
||||
|
||||
// Reset the form
|
||||
await Btn("btn-reset").ClickAsync();
|
||||
await Page.WaitForTimeoutAsync(300);
|
||||
|
||||
await Expect(FieldError("name")).ToBeHiddenAsync();
|
||||
await Expect(FieldError("email")).ToBeHiddenAsync();
|
||||
await Expect(FieldError("password")).ToBeHiddenAsync();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Reset_Clears_Success_Message()
|
||||
{
|
||||
await GoToFormsAsync();
|
||||
|
||||
// Submit valid form
|
||||
await FillAllValidFields();
|
||||
await Btn("btn-submit").ClickAsync();
|
||||
await Page.WaitForTimeoutAsync(500);
|
||||
await Expect(Page.Locator("[data-testid='success-message']")).ToBeVisibleAsync();
|
||||
|
||||
// Reset
|
||||
await Btn("btn-reset").ClickAsync();
|
||||
await Page.WaitForTimeoutAsync(300);
|
||||
|
||||
var result = Page.Locator("#form-result");
|
||||
await Expect(result).ToBeHiddenAsync();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Reset_Removes_Destructive_Border_Styling()
|
||||
{
|
||||
await GoToFormsAsync();
|
||||
|
||||
// Trigger error on name
|
||||
await BlurEmptyField("input-name");
|
||||
var clsBefore = await Input("input-name").GetAttributeAsync("class");
|
||||
Assert.That(clsBefore, Does.Contain("border-destructive"));
|
||||
|
||||
// Reset
|
||||
await Btn("btn-reset").ClickAsync();
|
||||
await Page.WaitForTimeoutAsync(300);
|
||||
|
||||
var clsAfter = await Input("input-name").GetAttributeAsync("class");
|
||||
Assert.That(clsAfter, Does.Not.Contain("border-destructive"));
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
// Error elements exist for htmx targeting
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
|
||||
[Test]
|
||||
public async Task FormField_Renders_Hidden_Error_Placeholder()
|
||||
{
|
||||
await GoToFormsAsync();
|
||||
|
||||
// Each form field should have a hidden [data-field-error] element
|
||||
var nameError = FieldError("name");
|
||||
await Expect(nameError).ToHaveCountAsync(1);
|
||||
await Expect(nameError).ToBeHiddenAsync();
|
||||
|
||||
var emailError = FieldError("email");
|
||||
await Expect(emailError).ToHaveCountAsync(1);
|
||||
await Expect(emailError).ToBeHiddenAsync();
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
// Custom validator — confirmation field must equal "CONFIRM"
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
|
||||
[Test]
|
||||
public async Task Confirmation_Blank_Shows_Required_Error_On_Blur()
|
||||
{
|
||||
await GoToFormsAsync();
|
||||
await BlurEmptyField("input-confirmation");
|
||||
|
||||
var error = FieldError("confirmation");
|
||||
await Expect(error).ToBeVisibleAsync();
|
||||
await Expect(error).ToHaveTextAsync("Confirmation is required.");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Confirmation_Wrong_Value_Shows_Custom_Error_On_Blur()
|
||||
{
|
||||
await GoToFormsAsync();
|
||||
await FillAndBlur("input-confirmation", "nope");
|
||||
|
||||
var error = FieldError("confirmation");
|
||||
await Expect(error).ToBeVisibleAsync();
|
||||
await Expect(error).ToHaveTextAsync("You must type CONFIRM to proceed.");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Confirmation_Correct_Value_Clears_Error_On_Blur()
|
||||
{
|
||||
await GoToFormsAsync();
|
||||
|
||||
// Trigger error first
|
||||
await FillAndBlur("input-confirmation", "wrong");
|
||||
await Expect(FieldError("confirmation")).ToBeVisibleAsync();
|
||||
|
||||
// Now fix it
|
||||
await FillAndBlur("input-confirmation", "CONFIRM");
|
||||
await Expect(FieldError("confirmation")).ToBeHiddenAsync();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Confirmation_Error_Shows_On_Submit()
|
||||
{
|
||||
await GoToFormsAsync();
|
||||
|
||||
// Fill everything except confirmation
|
||||
await Input("input-name").FillAsync("Alice Johnson");
|
||||
await Input("input-email").FillAsync("alice@example.com");
|
||||
await Input("input-password").FillAsync("secure123");
|
||||
|
||||
await Btn("btn-submit").ClickAsync();
|
||||
await Page.WaitForTimeoutAsync(500);
|
||||
|
||||
await Expect(FieldError("confirmation")).ToBeVisibleAsync();
|
||||
await Expect(FieldError("confirmation")).ToHaveTextAsync("Confirmation is required.");
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
// Date/Time/DateTime validation
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
|
||||
private async Task SetHiddenInputValue(string inputId, string value)
|
||||
{
|
||||
await Page.EvaluateAsync($@"
|
||||
const el = document.getElementById('{inputId}');
|
||||
if (el) {{
|
||||
el.value = '{value}';
|
||||
el.dispatchEvent(new Event('change', {{ bubbles: true }}));
|
||||
}}
|
||||
");
|
||||
}
|
||||
|
||||
private ILocator PopoverPanelFor(string triggerId) =>
|
||||
Page.Locator($"[data-testid='{triggerId}']")
|
||||
.Locator("xpath=ancestor::*[@data-popover]")
|
||||
.Locator("[data-popover-panel]");
|
||||
|
||||
private ILocator PopoverBackdropFor(string triggerId) =>
|
||||
Page.Locator($"[data-testid='{triggerId}']")
|
||||
.Locator("xpath=ancestor::*[@data-popover]")
|
||||
.Locator("[data-popover-backdrop]");
|
||||
|
||||
private async Task SelectDateAsync(string triggerId, DateOnly target)
|
||||
{
|
||||
await Page.Locator($"[data-testid='{triggerId}']").ClickAsync();
|
||||
await Page.WaitForTimeoutAsync(300);
|
||||
|
||||
var panel = PopoverPanelFor(triggerId);
|
||||
|
||||
var yearButton = panel.Locator("[data-calendar-year]");
|
||||
await yearButton.ClickAsync();
|
||||
await Page.WaitForTimeoutAsync(150);
|
||||
|
||||
var yearGrid = panel.Locator(".grid.grid-cols-4");
|
||||
var targetYearBtn = yearGrid.Locator($"button:has-text('{target.Year}')");
|
||||
var attempts = 0;
|
||||
while (await targetYearBtn.CountAsync() == 0 && attempts < 10)
|
||||
{
|
||||
var firstYearText = await yearGrid.Locator("button").First.InnerTextAsync();
|
||||
var firstYear = int.Parse(firstYearText.Trim());
|
||||
if (target.Year < firstYear)
|
||||
await panel.Locator("button[aria-label='Previous month']").ClickAsync();
|
||||
else
|
||||
await panel.Locator("button[aria-label='Next month']").ClickAsync();
|
||||
await Page.WaitForTimeoutAsync(100);
|
||||
yearGrid = panel.Locator(".grid.grid-cols-4");
|
||||
targetYearBtn = yearGrid.Locator($"button:has-text('{target.Year}')");
|
||||
attempts++;
|
||||
}
|
||||
await targetYearBtn.First.ClickAsync();
|
||||
await Page.WaitForTimeoutAsync(150);
|
||||
|
||||
var monthButton = panel.Locator("[data-calendar-month]");
|
||||
await monthButton.ClickAsync();
|
||||
await Page.WaitForTimeoutAsync(150);
|
||||
var monthGrid = panel.Locator(".grid.grid-cols-3");
|
||||
var targetMonthText = new DateOnly(2000, target.Month, 1).ToString("MMM");
|
||||
await monthGrid.Locator($"button:has-text('{targetMonthText}')").First.ClickAsync();
|
||||
await Page.WaitForTimeoutAsync(150);
|
||||
|
||||
var dayGrid = panel.Locator(".grid.grid-cols-7").Last;
|
||||
var dayButton = dayGrid.Locator("button:not([disabled])").Filter(new LocatorFilterOptions { HasTextString = target.Day.ToString() }).First;
|
||||
await dayButton.ClickAsync();
|
||||
await Page.WaitForTimeoutAsync(300);
|
||||
}
|
||||
|
||||
private async Task SelectTimeAsync(string triggerId, int hour, int minute)
|
||||
{
|
||||
await Page.Locator($"[data-testid='{triggerId}']").ClickAsync();
|
||||
await Page.WaitForTimeoutAsync(300);
|
||||
|
||||
var panel = PopoverPanelFor(triggerId);
|
||||
|
||||
var isPm = hour >= 12;
|
||||
var hour12 = hour % 12;
|
||||
if (hour12 == 0) hour12 = 12;
|
||||
|
||||
var hourColumn = panel.Locator(".scrollbar-thin").First;
|
||||
await hourColumn.Locator($"button:has-text('{hour12:D2}')").First.ClickAsync();
|
||||
await Page.WaitForTimeoutAsync(50);
|
||||
|
||||
var minuteColumn = panel.Locator(".scrollbar-thin").Nth(1);
|
||||
await minuteColumn.Locator($"button:has-text('{minute:D2}')").First.ClickAsync();
|
||||
await Page.WaitForTimeoutAsync(50);
|
||||
|
||||
var periodText = isPm ? "PM" : "AM";
|
||||
await panel.Locator($"button:has-text('{periodText}')").First.ClickAsync();
|
||||
await Page.WaitForTimeoutAsync(50);
|
||||
|
||||
var backdrop = PopoverBackdropFor(triggerId);
|
||||
await backdrop.ClickAsync(new LocatorClickOptions { Force = true });
|
||||
await Page.WaitForTimeoutAsync(200);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task BirthDate_Selected_Via_Popover_Submits_Successfully()
|
||||
{
|
||||
await GoToFormsAsync();
|
||||
|
||||
await FillAllValidFields();
|
||||
await SelectDateAsync("trigger-birthdate", new DateOnly(2000, 6, 15));
|
||||
|
||||
await Btn("btn-submit").ClickAsync();
|
||||
await Page.WaitForTimeoutAsync(500);
|
||||
|
||||
var success = Page.Locator("[data-testid='success-message']");
|
||||
await Expect(success).ToBeVisibleAsync();
|
||||
await Expect(FieldError("birthdate")).ToBeHiddenAsync();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task PreferredTime_Selected_Via_Popover_Submits_Successfully()
|
||||
{
|
||||
await GoToFormsAsync();
|
||||
|
||||
await FillAllValidFields();
|
||||
await SelectTimeAsync("trigger-preferredtime", 14, 30);
|
||||
|
||||
await Btn("btn-submit").ClickAsync();
|
||||
await Page.WaitForTimeoutAsync(500);
|
||||
|
||||
var success = Page.Locator("[data-testid='success-message']");
|
||||
await Expect(success).ToBeVisibleAsync();
|
||||
await Expect(FieldError("preferredtime")).ToBeHiddenAsync();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Appointment_Date_And_Time_Selected_Via_Popover_Submits_Successfully()
|
||||
{
|
||||
await GoToFormsAsync();
|
||||
|
||||
await FillAllValidFields();
|
||||
await SelectDateAsync("trigger-appointment-date", new DateOnly(2025, 12, 25));
|
||||
await SelectTimeAsync("trigger-appointment-time", 10, 30);
|
||||
|
||||
await Btn("btn-submit").ClickAsync();
|
||||
await Page.WaitForTimeoutAsync(500);
|
||||
|
||||
var success = Page.Locator("[data-testid='success-message']");
|
||||
await Expect(success).ToBeVisibleAsync();
|
||||
await Expect(FieldError("appointment")).ToBeHiddenAsync();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task BirthDate_Hidden_Input_Gets_Correct_Value_After_Selection()
|
||||
{
|
||||
await GoToFormsAsync();
|
||||
|
||||
await SelectDateAsync("trigger-birthdate", new DateOnly(1995, 3, 20));
|
||||
|
||||
var value = await Page.Locator("#birthdate").InputValueAsync();
|
||||
Assert.That(value, Is.EqualTo("1995-03-20"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task PreferredTime_Hidden_Input_Gets_Correct_Value_After_Selection()
|
||||
{
|
||||
await GoToFormsAsync();
|
||||
|
||||
await SelectTimeAsync("trigger-preferredtime", 9, 15);
|
||||
|
||||
var value = await Page.Locator("#preferredtime").InputValueAsync();
|
||||
Assert.That(value, Is.EqualTo("09:15"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Submit_With_Valid_Date_Time_DateTime_Via_Hidden_Input_Succeeds()
|
||||
{
|
||||
await GoToFormsAsync();
|
||||
|
||||
await FillAllValidFields();
|
||||
await SetHiddenInputValue("birthdate", "2000-06-15");
|
||||
await SetHiddenInputValue("preferredtime", "14:30");
|
||||
await SetHiddenInputValue("appointment", "2025-12-25T10:30");
|
||||
|
||||
await Btn("btn-submit").ClickAsync();
|
||||
await Page.WaitForTimeoutAsync(500);
|
||||
|
||||
var success = Page.Locator("[data-testid='success-message']");
|
||||
await Expect(success).ToBeVisibleAsync();
|
||||
await Expect(success).ToContainTextAsync("Form submitted successfully");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Submit_With_Empty_Optional_DateTime_Fields_Succeeds()
|
||||
{
|
||||
await GoToFormsAsync();
|
||||
|
||||
await FillAllValidFields();
|
||||
|
||||
await Btn("btn-submit").ClickAsync();
|
||||
await Page.WaitForTimeoutAsync(500);
|
||||
|
||||
var success = Page.Locator("[data-testid='success-message']");
|
||||
await Expect(success).ToBeVisibleAsync();
|
||||
|
||||
await Expect(FieldError("birthdate")).ToBeHiddenAsync();
|
||||
await Expect(FieldError("preferredtime")).ToBeHiddenAsync();
|
||||
await Expect(FieldError("appointment")).ToBeHiddenAsync();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Submit_All_Fields_Including_DateTime_Shows_Success()
|
||||
{
|
||||
await GoToFormsAsync();
|
||||
|
||||
await FillAllValidFields();
|
||||
await SelectDateAsync("trigger-birthdate", new DateOnly(1990, 1, 15));
|
||||
await SelectTimeAsync("trigger-preferredtime", 16, 0);
|
||||
await SelectDateAsync("trigger-appointment-date", new DateOnly(2026, 6, 1));
|
||||
await SelectTimeAsync("trigger-appointment-time", 9, 0);
|
||||
|
||||
await Btn("btn-submit").ClickAsync();
|
||||
await Page.WaitForTimeoutAsync(500);
|
||||
|
||||
var success = Page.Locator("[data-testid='success-message']");
|
||||
await Expect(success).ToBeVisibleAsync();
|
||||
await Expect(success).ToContainTextAsync("Form submitted successfully");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Reset_Clears_DateTime_Error_Placeholders()
|
||||
{
|
||||
await GoToFormsAsync();
|
||||
|
||||
await Btn("btn-submit").ClickAsync();
|
||||
await Page.WaitForTimeoutAsync(500);
|
||||
|
||||
await Btn("btn-reset").ClickAsync();
|
||||
await Page.WaitForTimeoutAsync(300);
|
||||
|
||||
await Expect(FieldError("birthdate")).ToBeHiddenAsync();
|
||||
await Expect(FieldError("preferredtime")).ToBeHiddenAsync();
|
||||
await Expect(FieldError("appointment")).ToBeHiddenAsync();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user