Init
This commit is contained in:
169
Prefab.Tests/Playwright/ProductListingPlaywrightShould.cs
Normal file
169
Prefab.Tests/Playwright/ProductListingPlaywrightShould.cs
Normal file
@@ -0,0 +1,169 @@
|
||||
using Microsoft.Playwright;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Prefab.Catalog.Data;
|
||||
using Prefab.Tests.Infrastructure;
|
||||
using Shouldly;
|
||||
|
||||
namespace Prefab.Tests.EndToEnd;
|
||||
|
||||
[Collection("Prefab.Ephemeral")]
|
||||
public sealed class ProductListingPlaywrightShould(PrefabCompositeFixture_Ephemeral fixture)
|
||||
{
|
||||
[Fact]
|
||||
public async Task NavigateViaMegaMenuToLeafCategoryListing()
|
||||
{
|
||||
using var scope = fixture.CreateWebScope();
|
||||
|
||||
var seeder = scope.ServiceProvider.GetRequiredService<Seeder>();
|
||||
await seeder.Execute(scope.ServiceProvider, TestContext.Current.CancellationToken);
|
||||
|
||||
var catalogDb = scope.ServiceProvider.GetRequiredService<IModuleDbReadOnly>();
|
||||
var expectedSlugs = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"ceiling-supports",
|
||||
"boxes-and-covers",
|
||||
"prefab-assemblies",
|
||||
"rod-strut-hardware"
|
||||
};
|
||||
|
||||
var catalogReady = false;
|
||||
var maxAttempts = 120; // ~60 seconds total with 500ms delay
|
||||
for (var attempt = 1; attempt <= maxAttempts && !catalogReady; attempt++)
|
||||
{
|
||||
var slugs = await catalogDb.Categories
|
||||
.Where(category => category.Slug != null && expectedSlugs.Contains(category.Slug))
|
||||
.Select(category => category.Slug!)
|
||||
.ToListAsync(TestContext.Current.CancellationToken);
|
||||
|
||||
if (slugs.Count == expectedSlugs.Count)
|
||||
{
|
||||
catalogReady = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (attempt % 20 == 0)
|
||||
{
|
||||
Console.WriteLine($"[Playwright] catalog readiness attempt {attempt}: found [{string.Join(", ", slugs)}]");
|
||||
}
|
||||
|
||||
await Task.Delay(500, TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
catalogReady.ShouldBeTrue("Catalog seed should populate root categories before UI navigation.");
|
||||
|
||||
using var playwright = await Playwright.CreateAsync();
|
||||
await using var browser = await playwright.Chromium.LaunchAsync(new BrowserTypeLaunchOptions { Headless = true });
|
||||
|
||||
var context = await browser.NewContextAsync(new BrowserNewContextOptions
|
||||
{
|
||||
BaseURL = fixture.PrefabBaseAddress.ToString().TrimEnd('/')
|
||||
});
|
||||
|
||||
var page = await context.NewPageAsync();
|
||||
|
||||
await page.GotoAsync("/");
|
||||
await page.WaitForLoadStateAsync(LoadState.NetworkIdle);
|
||||
await page.WaitForFunctionAsync("window.Blazor && window.Blazor._internal && window.Blazor._internal.navigationManager");
|
||||
|
||||
var menuItem = page.Locator("li.main-nav__item--with--menu").First;
|
||||
await menuItem.WaitForAsync(new LocatorWaitForOptions
|
||||
{
|
||||
State = WaitForSelectorState.Visible,
|
||||
Timeout = 60_000
|
||||
});
|
||||
var interceptDebug = await menuItem.EvaluateAsync<string?>(
|
||||
@"element => {
|
||||
if (!element) { return null; }
|
||||
const rect = element.getBoundingClientRect();
|
||||
const x = rect.left + rect.width / 2;
|
||||
const y = rect.top + rect.height / 2;
|
||||
const stack = document.elementsFromPoint(x, y);
|
||||
return stack.map(node => `${node.tagName.toLowerCase()}#${node.id}.${node.className}`).join(' > ');
|
||||
}");
|
||||
Console.WriteLine($"[Playwright] elementsFromPoint at nav center: {interceptDebug}");
|
||||
await menuItem.HoverAsync();
|
||||
var menuPanel = page.Locator("#productsMenuPanel");
|
||||
await menuPanel.WaitForAsync(new LocatorWaitForOptions
|
||||
{
|
||||
State = WaitForSelectorState.Attached,
|
||||
Timeout = 60_000
|
||||
});
|
||||
var boundingBox = await menuItem.BoundingBoxAsync();
|
||||
if (boundingBox is null)
|
||||
{
|
||||
throw new InvalidOperationException("Navigation menu item bounding box could not be determined.");
|
||||
}
|
||||
|
||||
var menuReady = false;
|
||||
for (var attempt = 1; attempt <= 10 && !menuReady; attempt++)
|
||||
{
|
||||
var responseTask = page.WaitForResponseAsync(
|
||||
resp => resp.Url.Contains("/bff/nav-menu/"),
|
||||
new PageWaitForResponseOptions { Timeout = 5000 });
|
||||
|
||||
await page.Mouse.MoveAsync(
|
||||
boundingBox.X + boundingBox.Width / 2,
|
||||
boundingBox.Y + boundingBox.Height / 2);
|
||||
await menuItem.DispatchEventAsync("mouseenter");
|
||||
await page.WaitForTimeoutAsync(350);
|
||||
|
||||
IResponse? navResponse = null;
|
||||
try
|
||||
{
|
||||
navResponse = await responseTask;
|
||||
}
|
||||
catch (TimeoutException)
|
||||
{
|
||||
// No navigation menu response observed within the window; proceed to inspect DOM.
|
||||
}
|
||||
|
||||
menuReady = await page.EvaluateAsync<bool>(
|
||||
"selector => document.querySelectorAll(selector).length > 0",
|
||||
"#productsMenuPanel .megamenu__links--root li");
|
||||
|
||||
if (!menuReady)
|
||||
{
|
||||
var panelText = await menuPanel.InnerTextAsync();
|
||||
Console.WriteLine($"[Playwright] nav menu attempt {attempt} panel text: {panelText}");
|
||||
if (navResponse is not null)
|
||||
{
|
||||
var payload = await navResponse.TextAsync();
|
||||
Console.WriteLine($"[Playwright] nav menu attempt {attempt} response: {payload}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"[Playwright] nav menu attempt {attempt} response: timed out waiting for /bff/nav-menu.");
|
||||
}
|
||||
|
||||
await menuItem.DispatchEventAsync("mouseleave");
|
||||
await page.WaitForTimeoutAsync(900);
|
||||
}
|
||||
}
|
||||
|
||||
menuReady.ShouldBeTrue("Mega menu should load category links within retry window.");
|
||||
|
||||
var ceilingLink = menuPanel
|
||||
.GetByRole(AriaRole.Link, new() { Name = "Ceiling Supports" })
|
||||
.First;
|
||||
|
||||
await Microsoft.Playwright.Assertions.Expect(ceilingLink)
|
||||
.ToBeVisibleAsync(new LocatorAssertionsToBeVisibleOptions { Timeout = 60000 });
|
||||
await Microsoft.Playwright.Assertions.Expect(menuPanel)
|
||||
.ToHaveAttributeAsync("aria-hidden", "false");
|
||||
await ceilingLink.ClickAsync();
|
||||
|
||||
await page.WaitForURLAsync("**/catalog/products?category-slug=ceiling-supports");
|
||||
await page.WaitForSelectorAsync(".products-view .product-card");
|
||||
|
||||
var productNamesLocator = page.Locator(".product-card__name");
|
||||
await Microsoft.Playwright.Assertions.Expect(productNamesLocator).ToHaveCountAsync(2);
|
||||
|
||||
var productNames = (await productNamesLocator.AllInnerTextsAsync())
|
||||
.Select(name => name.Trim())
|
||||
.ToList();
|
||||
productNames.ShouldContain("Ceiling T-Bar Box Assembly");
|
||||
productNames.ShouldContain("Fan Hanger Assembly");
|
||||
|
||||
await context.CloseAsync();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user