Playwright start

This commit is contained in:
2026-05-30 10:04:12 -04:00
parent 73f29cea08
commit 1f7a0819fc
108 changed files with 37445 additions and 62 deletions
+32
View File
@@ -0,0 +1,32 @@
# Project Architecture
The IGP Fan Reference is structured as a modular .NET solution, separating concerns between data models, business logic, and UI components.
## Solution Structure
- **IGP**: The main Blazor WebAssembly project. Contains pages, portals, and application-specific logic.
- **Model**: Domain models and data structures. It is a shared library used by the Services and the main app.
- `Model.BuildOrders`: Logic for build order sequences.
- `Model.Entity`: Unit and building definitions.
- `Model.Website`: Models for UI state and navigation.
- **Services**: Contains the core business logic.
- `Services.Immortal`: Game-specific logic (Economy, Timings, Unit stats).
- `Services.Website`: Infrastructure services (Navigation, Storage, Search, Dialogs).
- **Components**: A shared library for reusable Razor components used across different pages.
- **TestAutomation**: E2E tests using Playwright to ensure feature stability.
## Dependency Injection
Services are registered in `Program.cs` and injected into components using `@inject`. Most services are registered as `Scoped`, which in Blazor WASM behaves like a singleton for the duration of the user session.
## State Management
- **Blazored.LocalStorage**: Used to persist user settings and data (e.g., custom build orders, preferences) across sessions.
- **In-Memory Services**: Services like `StorageService` and `ImmortalSelectionService` maintain the active state during the application's runtime.
## UI & Layout
The app uses **MudBlazor** for its component library, providing a consistent Material Design look and feel.
- `App.razor`: The entry point for the Blazor app, handles routing and global portals.
- `PageLayout.razor`: Defines the main layout, including the AppBar, Drawer (for mobile), and main content area.
- **Portals**: Components like `EntityDialogPortal` are placed at the root level to allow global access to common dialogs via services.
+27
View File
@@ -0,0 +1,27 @@
# UI Components and Layout
The application uses a modular component architecture, leveraging the **MudBlazor** library for UI elements.
## Reusable Components (`Components` project)
Shared components are located in the `Components` project, categorized by their function:
- **Display**: Components for visualizing data (e.g., `EntityIcon`, `StatDisplay`).
- **Inputs**: Specialized input fields for game data.
- **Layout**: Reusable layout sections like `FooterComponent`.
- **Navigation**: Components like `SearchButtonComponent` and `NavigationTracker`.
- **Feedback**: UI elements for user interactions like `ConfirmationDialog`.
## Key Layout Files
- **App.razor**: Configures the Blazor router and hosts global "Portals". Portals are components that stay active across all pages (e.g., `EntityDialogPortal`, `ToastPortal`).
- **PageLayout.razor**: The standard layout for all pages. It includes:
- `MudAppBar`: The top navigation bar.
- `MudDrawer`: A side navigation menu (primarily for mobile/small screens).
- `MudMainContent`: The area where page-specific content is rendered.
## Design Patterns
- **Portals**: Used for dialogs and notifications to ensure they are rendered correctly in the DOM hierarchy regardless of which page is active. They listen to events from their corresponding services (e.g., `EntityDialogService`).
- **Data-Driven UI**: Navigation links and unit lists are driven by static data classes in the `Model` project (e.g., `WebsiteData.GetPages()`), making it easy to add new sections.
- **Dark Mode**: The site is designed primarily for dark mode, managed via `MudThemeProvider`.
+54
View File
@@ -0,0 +1,54 @@
# Development Guide
This guide provides instructions for building, running, and testing the IGP Fan Reference project.
## Prerequisites
- [.NET 8.0 or 10.0 SDK](https://dotnet.microsoft.com/download)
- [PowerShell](https://github.com/PowerShell/PowerShell) (for running scripts)
- [Node.js](https://nodejs.org/) (required for Playwright tests)
## Getting Started
1. **Clone the repository**:
```bash
git clone <repository-url>
cd IGP-Fan-Reference
```
2. **Restore dependencies**:
```bash
dotnet restore
```
3. **Run the application**:
Navigate to the `IGP` project folder and run:
```bash
cd IGP
dotnet watch run
```
The application will be available at `https://localhost:5001` (or the port specified in your output).
## Running Tests
The project uses **Playwright** for E2E testing, located in the `TestAutomation` project.
1. **Install Playwright browsers**:
```bash
dotnet build TestAutomation
pwsh TestAutomation/bin/Debug/net8.0/playwright.ps1 install
```
2. **Execute tests**:
```bash
dotnet test TestAutomation
```
## Project Structure and Maintenance
- **Adding a new Unit/Building**: Update the `Entity` models and data providers in the `Model` project.
- **Modifying UI**: Most common UI elements are in the `Components` library or the `IGP/Pages` directory.
- **Business Logic**: Changes to game mechanics or calculations should be made in `Services.Immortal`.
## Deployment
The site is configured as a Static Web App. Deployment settings can be found in `staticwebapp.config.json` and the `.github/workflows` directory for automated CI/CD.
+22
View File
@@ -0,0 +1,22 @@
# IGP Fan Reference - Overview
The **IGP Fan Reference** is a community-driven fan site for the game *IMMORTAL: Gates of Pyre*. It provides various tools and data references to help players understand game mechanics, optimize build orders, and analyze unit statistics.
## Project Goals
- Provide an accurate and up-to-date database of game entities (units, structures, etc.).
- Offer interactive tools like a Build Calculator and Harass Calculator.
- Serve as a central hub for community resources and data analysis.
## Core Tech Stack
- **Frontend**: [Blazor WebAssembly](https://dotnet.microsoft.com/en-us/apps/aspnet/web-apps/blazor) (.NET 8.0/10.0)
- **UI Framework**: [MudBlazor](https://mudblazor.com/)
- **State Management**: [Blazored.LocalStorage](https://github.com/Blazored/LocalStorage)
- **Testing**: [Playwright](https://playwright.dev/dotnet/) for E2E testing.
- **Analytics**: Google Analytics.
## Key Features
- **Database**: Comprehensive list of game entities with detailed stats.
- **Build Calculator**: Tool for planning and optimizing build orders.
- **Harass Calculator**: Calculate the effectiveness of unit harassment.
- **Data Tables**: Tabular views of game data for easy comparison.
- **Memory Tester**: A tool for practicing game-related knowledge.
+49
View File
@@ -0,0 +1,49 @@
# Design Improvement Recommendations
This document outlines suggested improvements for the IGP Fan Reference project to enhance maintainability, performance, and code quality.
## 1. Code Quality and Maintenance
### Centralize and Refactor Configuration
- **Hardcoded Strings and IDs**: Many strings (like storage keys, event names, and navigation paths) are hardcoded across the application. These should be moved to a central `Constants` or `Configuration` class.
- **Data Initialization**: In `WebsiteData.cs`, the page list is hardcoded with magic numbers for IDs. Consider moving this to a JSON configuration file that can be loaded at runtime.
- **Localization**: While the project has a `Localizations.resx` file, some UI strings (like descriptions in `WebsiteData.cs`) are still hardcoded. All user-facing text should be moved to resource files.
### Standardize Event Handling
- The project uses a manual `Action` subscription pattern in many services (e.g., `StorageService`, `TimingService`, `BuildOrderService`).
- **Recommendation**: Consider using standard C# events or a more robust Message Bus pattern (like `IMessenger` from CommunityToolkit.Mvvm) to reduce boilerplate and avoid memory leaks from missing unsubscriptions.
### Fix "TODO"s and Commented-Out Code
- Several files (e.g., `PageLayout.razor`, `TimingService.cs`) contain `TODO` comments or large blocks of commented-out code.
- **Recommendation**: Clean up these areas. If a feature (like Light Mode) is planned, track it in a backlog instead of leaving dead code in the UI.
## 2. Architecture and Design
### Dependency Injection Cleanup
- **Duplicated Service Registration**: In `Program.cs`, `HttpClient` is registered twice.
- **Service Lifetimes**: Most services are registered as `Scoped`. In Blazor WASM, `Scoped` and `Singleton` behave similarly, but using `Singleton` for stateless services (like `KeyService`) or services that truly span the entire session (like `StorageService`) makes the intent clearer.
### Decouple Logic from UI
- **Portals and Services**: The "Portal" pattern used for dialogs is good, but the services (like `EntityDialogService`) are directly coupled to the UI state.
- **Recommendation**: Ensure that game logic services (in `Services.Immortal`) remain pure and don't depend on UI-specific services unless absolutely necessary.
### Improve Type Safety
- Several methods use `int` or `string` for types that could be enums (e.g., `webPageType` in `NavigationService`, `IsPrivate` string in `WebPageModel`).
- **Recommendation**: Replace magic strings and numbers with strongly typed Enums to prevent runtime errors.
## 3. Performance and UX
### Optimize State Management
- `StorageService` calls `NotifyDataChanged()` frequently. If many components subscribe to this, it could lead to unnecessary re-renders.
- **Recommendation**: Implement more granular notifications or use a state management library if the application complexity grows.
### Move CSS to Isolated Files
- `App.razor` contains a large `<style>` block.
- **Recommendation**: Move these styles to `App.razor.css` (CSS isolation) or a global `app.css` to keep the Razor file focused on structure and logic.
## 4. Testing and Validation
### Expand Test Coverage
- **Unit Tests**: While Playwright E2E tests are present, there is a lack of unit tests for the complex calculation logic in `BuildOrderService` and `EconomyService`.
- **Recommendation**: Add a unit testing project (xUnit or NUnit) to verify the core algorithms independently of the UI.
- **CI/CD**: Ensure that tests are automatically run on every Pull Request to catch regressions early.
+33
View File
@@ -0,0 +1,33 @@
# Application Services
The project relies heavily on dependency injection to provide specialized services. These are split into game-logic services (`Immortal`) and infrastructure services (`Website`).
## Game Logic Services (`Services.Immortal`)
- **IBuildOrderService (`BuildOrderService`)**: Manages build order creation, editing, and calculations.
- **IEconomyService (`EconomyService`)**: Handles calculations related to resource generation and expenditure.
- **IEconomyComparisonService (`EconomyComparisonService`)**: Compares different economic scenarios.
- **IEntityFilterService (`EntityFilterService`)**: Provides logic for filtering game entities based on various criteria.
- **IImmortalSelectionService (`ImmortalSelectionService`)**: Manages the current selection of the "Immortal" (faction/hero).
- **ITimingService (`TimingService`)**: Calculates timings for unit production and upgrades.
- **IMemoryTesterService (`MemoryTesterService`)**: Supports the memory tester mini-game.
## Infrastructure Services (`Services.Website`)
- **IStorageService (`StorageService`)**: Wraps LocalStorage to provide persistent state management for the application.
- **INavigationService (`NavigationService`)**: Manages application-level navigation and state.
- **ISearchService (`SearchService`)**: Powers the global search functionality.
- **IEntityDialogService (`EntityDialogService`)**: Controls the display of unit/building detail dialogs via portals.
- **IMyDialogService (`MyDialogService`)**: A wrapper around MudBlazor's dialog service for simplified use.
- **IToastService (`ToastService`)**: Provides a unified way to show notifications (toasts).
- **IDataCollectionService (`DataCollectionService`)**: Handles anonymous usage analytics.
- **IPermissionService (`PermissionService`)**: Manages user permissions or experimental feature toggles.
## Service Registration
All services are registered in `Program.cs`. Example:
```csharp
builder.Services.AddScoped<IBuildOrderService, BuildOrderService>();
builder.Services.AddScoped<IStorageService, StorageService>();
```
In Blazor WebAssembly, `AddScoped` effectively acts as a singleton for the session.
@@ -0,0 +1,67 @@
---
type: Task
status: AI Gen TODO
category: QA
isAgentGenerated: "true"
---
# Test: Accessibility — Keyboard Navigation
## Description
Verify that the Build Calculator and Database pages are navigable and operable using only the keyboard, with proper ARIA roles, focus indicators, and tab order.
## Rationale
The Build Calculator's hotkey viewer is inherently keyboard-centric (Q, W, E, R hotkeys add entities to the build order), but the **filter dropdowns, clear button, timing inputs, and option toggles** must also be reachable and activatable via keyboard (`Tab`, `Enter`, `Space`, arrow keys). A screen-reader user should be able to:
- Tab through the filter selects and timing inputs in logical order.
- Change a select value using arrow keys.
- Activate the "Clear Build Order" button with Enter or Space.
- See a visible focus ring on every interactive element.
No existing test verifies any of these accessibility requirements.
## Playwright Feature
This test uses **`page.evaluate()`** and **`page.accessibility.snapshot()`** (the Accessibility Tree API) to inspect ARIA attributes, plus **keyboard-based interaction** (Tab, Enter, arrow keys) to test the entire flow without a mouse.
### Approach
#### Tab Order Test
```js
// Starting at the top of the Build Calculator page
await page.keyboard.press('Tab'); // First focusable element
const focused1 = page.locator(':focus');
// Assert focused on the Faction select
await expect(focused1).toBeVisible();
await page.keyboard.press('Tab'); // Move to Immortal select
// Assert focus moved correctly
```
#### ARIA Snapshot Test
```js
const snapshot = await page.accessibility.snapshot();
// Verify the hotkey viewer container has role="application" or role="group"
// Verify selects have role="combobox" or role="listbox"
// Verify buttons have accessible names
```
#### Screen-Reader Logic Test
Verify that a filter select's `aria-label` or associated `<label>` text matches the visible label ("Faction", "Immortal"), using `page.locator('select').getAttribute('aria-label')`.
### What to Assert
- Every `<select>` has an associated `<label>` or an `aria-label` attribute.
- Every button in the hotkey viewer has an `aria-label` derived from the hotkey + entity name (e.g., "Q — Acropolis").
- The timeline's interactive items have a `role="listbox"` or `role="grid"`.
- Toast notifications have `role="alert"` or `aria-live="polite"` so screen readers announce them.
- No element has a `tabindex` greater than 0 (custom tab order) unless intentional.
- The "Clear Build Order" button is keyboard-reachable and activatable.
### Tools
Playwright uses the **Chromium Accessibility Tree** via `page.accessibility.snapshot()`, which mirrors what a Chromevox screen reader would expose. This is *not* a substitute for testing with a real screen reader, but it catches the most common ARIA violations at low cost.
@@ -0,0 +1,75 @@
---
type: Task
status: AI Gen TODO
category: QA
isAgentGenerated: "true"
---
# Test: Blazor WASM Hydration Timing and State Reconciliation
## Description
Verify that the Blazor WASM client correctly reconciles state during hydration — the prerendered HTML should match the client-rendered output, and user interactions initiated **before** Blazor finishes loading should either be queued or gracefully ignored, not cause a crash or a stale render.
## Rationale
The app uses Blazor WASM with server-side prerendering. During the hydration gap (between `window.onload` and Blazor attaching its event handlers), the page shows static prerendered HTML. If a user (or test) interacts with a `<select>` or button during this gap, Blazor may:
- Lose the native-DOM change on re-render (the select value reverts).
- Miss the event entirely (no handler is connected yet).
- Throw a JS interop exception after hydration completes.
Our existing tests all navigate with `waitUntil: 'load'` and immediately start clicking. The `debug_initial_state.js` script revealed that Blazor components re-render asynchronously after load, destroying and recreating the DOM elements. This gap is a real source of flakiness.
## Playwright Feature
This test uses **`page.waitForResponse()`** to wait for a specific WASM resource (`dotnet.js` or `dotnet.wasm`) to confirm the .NET runtime has loaded, plus **`page.evaluate()` with polling** to detect when Blazor's component tree is fully hydrated.
### Approach
```js
// Wait until the Blazor runtime signals it's ready
await page.waitForResponse(response =>
response.url().includes('dotnet.wasm') && response.status() === 200
);
// OR poll for a Blazor-specific DOM signal
await page.waitForFunction(() => {
// Blazor sets the `_blazorCircuitId` or similar on the root element
// Note: adjust the selector to match the app's actual signal
return document.querySelector('select')?.classList.contains('blazor-hydrated')
|| document.querySelector('.keyContainer > div') !== null;
});
```
### Test Cases
| Scenario | Interaction Timing | Expected Behavior |
|---|---|---|
| **Early select** | Select faction BEFORE dotnet.wasm loads | Select reverts on hydration, but WASM should apply the correct default filter |
| **Early click** | Click "Clear Build Order" before Blazor ready | Click is ignored or queued; no crash |
| **Early keyboard** | Press Q/W/E before key handler attached | Keys are ignored; no crash; no entity added |
| **Mid-hydration** | Interact during component re-render (DOM destruction phase) | Interaction should be lost but never crash |
| **Late interaction** | Wait for `dotnet.wasm` response, THEN interact | Should work correctly (this is the happy path that already passes) |
### Detecting Hydration Completion
Since Blazor WASM with prerendering doesn't emit a built-in "I'm done" event, the most reliable signal is:
```js
await page.waitForFunction(() => {
// Wait for at least one Blazor-triggered re-render by checking
// the keyContainer has child divs with keyboard buttons
const container = document.querySelector('.keyContainer');
if (!container) return false;
const buttons = container.querySelectorAll(':scope > div');
return buttons.length === 19 || buttons.length > 10;
});
```
### What This Test Catches
- Race conditions between `page.goto('waitUntil: load')` and Blazor hydration.
- DOM elements that are destroyed and re-created during hydration (stale locator references).
- Test flakiness caused by Blazor's async initialization.
- Missing error boundaries when JS interop calls fail during hydration.
@@ -0,0 +1,51 @@
---
type: Task
status: AI Gen TODO
category: QA
isAgentGenerated: "true"
---
# Test: Mobile Responsive Layout
## Description
Verify the Build Calculator and Database pages render correctly on mobile and tablet viewports, with no overlapping elements, truncated text, or unusable controls.
## Rationale
The `HotkeyViewerComponent.razor` contains a `@media (max-width: 1025px)` rule that scales the key container with `transform: scale(0.85)` and hides the background color. This responsive behavior is entirely CSS-driven and has zero functional test coverage. A CSS refactor could easily break this breakpoint without any existing test noticing.
## Playwright Feature
This test uses **`test.use({ viewport })`** with device-specific viewport sizes. Playwright provides built-in device descriptors via `devices['iPhone 13']`, `devices['Pixel 5']`, and `devices['iPad Pro 11']`.
### Implementation
```js
// Use the built-in iPhone 13 descriptor
const iPhone = devices['iPhone 13'];
test.use({ ...iPhone });
test('Build Calculator is usable on mobile', async ({ page }) => {
// Navigate, filter, click Q — same actions as desktop test
// but the assertions now validate mobile layout works
});
```
### What to Assert (Desktop-equivalent on Mobile)
- All 19 hotkey buttons are present and clickable.
- Font size is legible (no single-character truncation on key labels).
- Entity names are readable (not clipped by the `scaler`).
- The filter `<select>` elements are fully visible and functional (not hidden behind the keyboard view).
- The timeline grid scrolls horizontally without breaking the layout.
- No horizontal scrollbar appears on the `<body>` (content fits within viewport).
### Additional Device Targets
| Descriptor | Type | Purpose |
|---|---|---|
| `iPhone 13` | 390×844 | Modern mobile portrait |
| `Pixel 5` | 393×851 | Android mobile portrait |
| `iPad Pro 11` | 834×1194 | Tablet landscape/portrait breakpoints |
| Custom `{ width: 1024, height: 768 }` | 1024×768 | Exact `@media (max-width: 1025px)` boundary test |
@@ -0,0 +1,53 @@
---
type: Task
status: AI Gen TODO
category: QA
isAgentGenerated: "true"
---
# Test: Database Entity Consistency Across Pages
## Description
Verify that entity data (name, health, damage, faction, hotkey) displayed on the Database entity-detail page matches the same entity's data shown in the Build Calculator's hotkey viewer and timeline.
## Rationale
Entity stats are defined in `Model/Entity/Data/EntityData.*.cs` and consumed by both the Database page (full entity detail) and the Build Calculator (hotkey viewer tooltips, timeline entries). If someone edits the source data and changes a name or stat, it must update consistently across all views. No existing test cross-references entity data between the two pages.
## Playwright Feature
This test uses **multiple `page` objects within the same `browserContext`** — one navigating the Database page and another navigating the Build Calculator. Playwright fixtures provide a fresh `page` per test by default, but you can open additional pages manually with `await context.newPage()`.
### Approach
1. Open the Database single-entity page for "Acropolis" (e.g. `pageA.goto('/database/acropolis')`).
2. Extract displayed values: name, faction, health, armor type, hotkey.
3. Open the Build Calculator (`pageB.goto('/build-calculator')`).
4. Select Q'Rath/Orzum filter.
5. Find the Q hotkey button, read its entity name (Acropolis).
6. Click the entity name / button, read the EntityClickView name.
7. Click the entity in the timeline, read the same detail fields.
8. Assert all values match across the three views.
### What This Catches
- A typo in `EntityInfoModel.Name` that differs from the label used in the hotkey viewer.
- A missing `EntityFactionModel` part that causes an entity to disappear from one view.
- A hotkey assignment that renders differently in the Database vs. the Build Calculator.
- Entity requirements or costs that display inconsistently between the tooltip and the entity detail page.
### Example Assertion Flow
```
pageA (Database) pageB (Build Calculator)
───────────────── ────────────────────────
Name: "Acropolis" → Q button shows "Acropolis"
Faction: "Q'Rath" → Only visible with Q'Rath filter
Health: 2300 → EntityClickView shows correct health
Hotkey: "Q" → Entity appears on Q button (group C)
```
### Future Enhancement
Parameterize by entity type — run the same cross-page comparison for army units (`Sipari`, `Xacal`), research (`Greaves of Ahqar`), and immortals (`Orzum`, `Mala`) to validate every available entity.
@@ -0,0 +1,41 @@
---
type: Task
status: AI Gen TODO
category: QA
isAgentGenerated: "true"
---
# Test: Network Resilience and WASM Loading
## Description
Verify the Blazor WASM app handles network failures gracefully — showing loading states, error boundaries, and retry behavior when `dotnet.js`, `.wasm` files, or `.dll` resources fail to load.
## Rationale
The app is a Blazor WASM single-page application that downloads the .NET runtime (`dotnet.js`, `dotnet.wasm`) and assemblies on first load. If any of these requests fail (server down, CDN outage, flaky connection), the entire app breaks. No existing test covers this failure mode; they all assume the dev or production server is reachable.
## Playwright Feature
This test uses **`page.route()`** (route interception) to selectively block or delay specific resource types:
| Test Case | Interception |
|---|---|
| `dotnet.js` fails to load | `page.route('**/dotnet.js', route => route.abort())` |
| `.dll` assembly fails | `page.route('**_framework_**', route => route.abort())` |
| Slow network | `page.route('**', route => route.continue({ delay: 5000 }))` |
| Timeout during WASM init | Combine slow network with reduced navigation timeout |
### What to Assert
- An error boundary element (`.blazor-error-boundary`, `#blazor-error-ui`) becomes visible.
- A user-facing message like "An unhandled error has occurred" or "Retry" appears.
- The `window.dotnet` global is undefined (runtime never loaded).
- The page body is **not** a blank white screen (user sees a styled error, not a crash).
- Clicking a "Retry" button triggers a page reload.
### Variations
- Block only `dotnet.wasm` but allow `dotnet.js` (tests partial-load scenario).
- Block `.pdb` symbol files (tests debug vs. release build quality).
- Simulate offline mode with `context.setOffline(true)` for full app navigation testing.
@@ -0,0 +1,33 @@
---
type: Task
status: AI Gen TODO
category: QA
isAgentGenerated: "true"
---
# Test: Storage Persistence Across Page Reloads
## Description
Verify that the Build Calculator correctly persists and restores user settings (selected faction, immortal, build input delays, attack/travel times) across page reloads using `localStorage`.
## Rationale
The app relies on `Blazored.LocalStorage` via `IStorageService` to persist settings. No existing test covers the round-trip: set a value, reload the page, and assert the value survived. This is a common source of regressions when storage keys, serialization, or service initialization changes.
## Playwright Feature
This test uses **`page.evaluate()`** to directly inspect `localStorage` and **`page.goto()` with a fresh navigation** to verify persistence across a full Blazor WASM lifecycle (including hydration and service re-initialization).
Unlike existing tests that set values and immediately assert in-memory Blazor state, this test:
1. Sets a filter value via the UI (select option).
2. Reads the raw `localStorage` key to confirm Blazor wrote it.
3. Reloads the page (`page.reload()` or a fresh `page.goto`).
4. Asserts the UI reflects the restored value **without** user re-selection.
### Example Assertions
- `localStorage.getItem('SelectedFaction')` equals `DataType.FACTION_QRath` GUID after selecting Q'Rath.
- After reload, the faction `<select>` element shows Q'Rath selected without any `selectOption` call.
- After reload, the hotkey viewer shows Q→Acropolis (proving the filter was restored end-to-end).
@@ -0,0 +1,77 @@
---
type: Task
status: AI Gen TODO
category: QA
isAgentGenerated: "true"
---
# Test: Toast Notification Timing and Dismissal
## Description
Verify that toast notifications appear with correct error/success styling, auto-dismiss after the expected duration (~1.3s), and multiple toasts stack properly when triggered in rapid succession.
## Rationale
The Build Calculator shows toast notifications for errors ("Missing Requirements", "Not Enough Ether") and potentially for success/addition confirmations. The existing toast test (`tests/buildCalculator.spec.js`) only checks whether a toast **appears** within 3 seconds; it does not validate:
- The toast's CSS class (`.error` vs `.success`).
- The exact display duration.
- Stacking behavior when multiple errors are triggered rapidly.
- Whether a toast persists if the condition that triggered it is resolved.
## Playwright Feature
This test uses **`page.clock`** (Playwright's clock API) to control time without waiting real seconds, plus **`page.waitForSelector`** with exact timeout values and **`page.evaluate`** to inspect DOM classes.
### Clock-based Timing Test
```js
// Install fake timers before navigation
await page.clock.install();
await page.clock.fastForward(5000); // Simulate WASM loading delay
await calc.goto();
// Trigger a toast by clicking E (Soul Foundry without Legion Hall)
await calc.hotkeys.clickKey('E');
await page.clock.fastForward(500); // small delay for render
// Assert toast appears with error styling
const toast = page.locator('.toastsContainer .toastContainer');
await expect(toast).toHaveClass(/error/);
// Fast-forward to just before auto-dismiss
await page.clock.fastForward(1200);
await expect(toast).toBeVisible(); // still visible at ~1.2s
// Fast-forward past dismissal
await page.clock.fastForward(200);
await expect(toast).not.toBeVisible(); // gone by ~1.4s
```
### Stacking Test
```js
// Trigger two toasts in rapid succession
calc.filter.selectFaction("Q'Rath");
await calc.hotkeys.clickKey('E'); // Missing Requirements
// Without waiting, trigger another (e.g., click an entity with zero ether)
await calc.hotkeys.clickKey('Q');
await calc.hotkeys.clickKey('E'); // Not Enough Ether
await page.waitForTimeout(300);
// Assert TWO toasts are visible, stacked
const toasts = page.locator('.toastsContainer .toastContainer');
await expect(toasts).toHaveCount(2);
// Assert the first toast has error styling and correct title
await expect(toasts.nth(0).locator('.toastTitle')).toHaveText('Missing Requirements');
await expect(toasts.nth(1).locator('.toastTitle')).toHaveText('Not Enough Ether');
```
### What This Test Catches
- Auto-dismiss timer drift (toast stays too long or disappears too early).
- Success vs. error CSS class mismatch (all toasts get the same styling).
- Stacking order (new toasts should appear above or below older ones).
- Memory leaks (stale toast DOM nodes that never get removed).
- Race conditions between `ToastService.AddToast` and `StateHasChanged`.
@@ -0,0 +1,33 @@
---
type: Task
status: AI Gen TODO
category: QA
isAgentGenerated: "true"
---
# Test: Visual Regression for Build Calculator Layout
## Description
Use Playwright's built-in screenshot comparison to detect unintended layout changes in the Build Calculator and Database pages.
## Rationale
The Build Calculator renders a complex keyboard-style hotkey viewer with absolute-positioned buttons, a timeline grid, and resin/bank/army sub-panels. A CSS change or component refactor could shift elements, overlap buttons, or misalign the grid. Existing functional tests only check text content and presence of elements — they would not catch a button that renders in the wrong position or overlaps another control.
## Playwright Feature
This test uses **`expect(page).toHaveScreenshot()`** with named snapshots. Playwright compares the current render against a baseline screenshot stored in the repo and reports a diff image on failure.
### Key Benefits
- Catches visual regressions a functional test would miss (position, size, color, overflow).
- Snapshots can be taken at specific viewport sizes (desktop/tablet/mobile) using the `viewport` option.
- Animations (toast fade-out) can be suppressed via `page.evaluate` to freeze the UI before capture.
### Implementation Notes
- Baselines should be generated on first run (`--update-snapshots`) and committed to the repo.
- Use `fullPage: true` to capture the entire scrollable page.
- Mask or remove time-dependent elements (timing counters, toast notifications) before capture to avoid false positives.
- Run in a CI pipeline where the rendering environment (OS, fonts, GPU) is stable.