diff --git a/.changelogs/NEXT.md b/.changelogs/NEXT.md index 2df4113e..b5dcf6ff 100644 --- a/.changelogs/NEXT.md +++ b/.changelogs/NEXT.md @@ -2,11 +2,6 @@ ## Added -- 9 new Python unit test files covering all v3.3–v3.8 ops modules: animation, audio, material_graph, datatable, struct_enum, input_system, mesh, pcg, statetree (327 tests total) -- Phase 7 smoke tests in `test-comprehensive-mcp.js` covering all 47 new tools from v3.3–v3.8 -- `testToolStrict()` helper in comprehensive MCP test to reduce error-check boilerplate - ## Fixed -- PCG plugin crash on `restart_listener()`: `unreal.PCGGraphInterface` type annotation in `pcg.py` was evaluated at import time, causing `AttributeError` that killed the listener. Fixed with `from __future__ import annotations` + `hasattr` import guard (mirrors Niagara pattern) -- PCG tools now registered conditionally — gracefully skipped when PCG plugin is absent, matching Niagara's optional registration pattern +## Changed diff --git a/.changelogs/v3.8.0.md b/.changelogs/v3.8.0.md new file mode 100644 index 00000000..ee016157 --- /dev/null +++ b/.changelogs/v3.8.0.md @@ -0,0 +1,32 @@ +# Release v3.8.0 + +Released: 2026-04-02 + +## Features + +- **Niagara VFX System** (v3.2) — 8 new MCP tools: create systems, add emitters/modules, configure renderers, compile, spawn actors, set parameters, list templates. Rewritten for UE 5.7 Python API +- **Performance Profiling** (v3.3) — 3 new MCP tools: `perf_rendering_stats`, `perf_gpu_stats`, `perf_scene_breakdown` for scene analysis and rendering cost inspection +- **Animation Blueprint System** (v3.4) — 8 new MCP tools: create ABPs, state machines, states, transitions, variables, montages, layer linking, and metadata inspection +- **Advanced Materials & Audio** (v3.5) — 10 new MCP tools: material graph editing (add/connect expressions, set properties, create functions, inspect graph), MetaSound asset creation and node routing +- **Data & Procedural Systems** (v3.6) — 12 new MCP tools: DataTable CRUD (create/add/get/update/delete rows), custom struct and enum creation, Enhanced Input System mapping +- **PCG & StateTree AI** (v3.7) — 14 new MCP tools: PCG graph creation and node management (195+ node types), StateTree asset authoring with states, transitions, tasks, evaluators, and bindings +- **Mesh & LOD Management** (v3.8) — 5 new MCP tools: mesh metadata inspection, FBX LOD import, LOD screen-size thresholds, auto LOD generation, per-LOD instance breakdown + +## Testing + +- 310 Python unit tests across all ops modules (up from 51 in v3.1.0) +- Phase 7 smoke tests in `test-comprehensive-mcp.js` covering all 52 new tools from v3.3–v3.8 +- `testToolStrict()` helper reduces error-check boilerplate in comprehensive E2E tests + +## Bug Fixes + +- PCG plugin crash on `restart_listener()`: `unreal.PCGGraphInterface` type annotation evaluated at import time caused `AttributeError`. Fixed with `from __future__ import annotations` + `hasattr` import guard +- PCG tools registered conditionally — gracefully skipped when PCG plugin absent (mirrors Niagara pattern) +- `test_performance` no longer overwrites shared `unreal` mock across test modules +- Restored closing triple-quote in `ops/__init__.py` docstring +- Niagara `set_parameter` complexity reduced — extracted typed dispatch helpers to satisfy linter +- E2E log scanner no longer flags expected error-handling test failures as CI failures + +## Full Changelog + +**Full Diff**: https://github.com/atomantic/UEMCP/compare/v3.1.0...v3.8.0 diff --git a/Demo/Content/Calibration.umap b/Demo/Content/Calibration.umap index 7728dd74..030f1837 100644 Binary files a/Demo/Content/Calibration.umap and b/Demo/Content/Calibration.umap differ diff --git a/PLAN.md b/PLAN.md index 3013446d..f54ad17d 100644 --- a/PLAN.md +++ b/PLAN.md @@ -12,7 +12,7 @@ - [x] Update dev dependencies (TypeScript, ESLint plugins, tsx, ts-jest) - [x] Bump version to 2.1.0 -## Current Focus: v3.0.0 — Blueprint Graph Editing & Game Logic +## v3.0.0 — Blueprint Graph Editing & Game Logic ✅ The #1 gap vs competitors: we can create/compile/document Blueprints but cannot edit the visual graph. This transforms AI from "scene builder" to "game logic builder." @@ -53,124 +53,124 @@ The #1 gap vs competitors: we can create/compile/document Blueprints but cannot - [x] `blueprint_modify_component` — Set any component property via reflection - [x] `blueprint_set_variable_default` — Set default values on CDO -## v3.1.0 — UMG Widget System +## v3.1.0 — UMG Widget System ✅ Full UI building capability — essential for any real game. -- [ ] `widget_create` — Create Widget Blueprint with parent class -- [ ] `widget_add_component` — Add UI components (TextBlock, Button, Image, Slider, Checkbox, ProgressBar, etc.) -- [ ] `widget_set_layout` — Position, size, anchors, z-order, alignment -- [ ] `widget_set_property` — Set component properties (text, color, font, opacity) -- [ ] `widget_bind_event` — Bind events (OnClicked, OnHovered, OnValueChanged, input events) -- [ ] `widget_set_binding` — Property bindings for dynamic data -- [ ] `widget_get_metadata` — Comprehensive widget inspection (components, layout, hierarchy, bindings) -- [ ] `widget_screenshot` — Capture widget preview for visual verification +- [x] `widget_create` — Create Widget Blueprint with parent class +- [x] `widget_add_component` — Add UI components (TextBlock, Button, Image, Slider, Checkbox, ProgressBar, etc.) +- [x] `widget_set_layout` — Position, size, anchors, z-order, alignment +- [x] `widget_set_property` — Set component properties (text, color, font, opacity) +- [x] `widget_bind_event` — Bind events (OnClicked, OnHovered, OnValueChanged, input events) +- [x] `widget_set_binding` — Property bindings for dynamic data +- [x] `widget_get_metadata` — Comprehensive widget inspection (components, layout, hierarchy, bindings) +- [x] `widget_screenshot` — Capture widget preview for visual verification -## v3.2.0 — Niagara VFX System +## v3.2.0 — Niagara VFX System ✅ Visual effects are a common AI-assisted task. -- [ ] `niagara_create_system` — Create Niagara systems (with templates) -- [ ] `niagara_add_emitter` — Add emitters to systems -- [ ] `niagara_add_module` — Add modules (spawn, update, render) to emitters -- [ ] `niagara_configure_module` — Set module parameters (float, vector, curve, enum) -- [ ] `niagara_set_renderer` — Configure sprite/mesh/ribbon renderers -- [ ] `niagara_compile` — Compile and save systems -- [ ] `niagara_spawn` — Create VFX actors in the world -- [ ] `niagara_get_metadata` — Inspect system structure +- [x] `niagara_create_system` — Create Niagara systems (with templates) +- [x] `niagara_add_emitter` — Add emitters to systems +- [x] `niagara_add_module` — Add modules (spawn, update, render) to emitters +- [x] `niagara_configure_module` — Set module parameters (float, vector, curve, enum) +- [x] `niagara_set_renderer` — Configure sprite/mesh/ribbon renderers +- [x] `niagara_compile` — Compile and save systems +- [x] `niagara_spawn` — Create VFX actors in the world +- [x] `niagara_get_metadata` — Inspect system structure -## v3.3.0 — Performance Profiling & Console +## v3.3.0 — Performance Profiling & Console ✅ Low effort, high utility. -- [ ] `perf_rendering_stats` — Draw calls, VRAM usage, instance breakdown -- [ ] `perf_gpu_stats` — GPU timing and memory -- [ ] `perf_scene_breakdown` — Per-mesh rendering costs, LOD breakdown -- [ ] `console_command` — Execute any UE console command directly +- [x] `perf_rendering_stats` — Draw calls, VRAM usage, instance breakdown +- [x] `perf_gpu_stats` — GPU timing and memory +- [x] `perf_scene_breakdown` — Per-mesh rendering costs, LOD breakdown +- [x] `console_command` — Execute any UE console command directly -## v3.4.0 — Animation Blueprint System +## v3.4.0 — Animation Blueprint System ✅ Required for character-driven games. -- [ ] `anim_create_blueprint` — Create Animation Blueprint with skeleton reference -- [ ] `anim_create_state_machine` — Build state machines programmatically -- [ ] `anim_add_state` — Add states with animation references -- [ ] `anim_add_transition` — Connect states with transition rules -- [ ] `anim_add_variable` — Add typed variables to ABP -- [ ] `anim_get_metadata` — Inspect states, variables, montages -- [ ] `anim_create_montage` — Create animation montages -- [ ] `anim_link_layer` — Animation layer stacking +- [x] `anim_create_blueprint` — Create Animation Blueprint with skeleton reference +- [x] `anim_create_state_machine` — Build state machines programmatically +- [x] `anim_add_state` — Add states with animation references +- [x] `anim_add_transition` — Connect states with transition rules +- [x] `anim_add_variable` — Add typed variables to ABP +- [x] `anim_get_metadata` — Inspect states, variables, montages +- [x] `anim_create_montage` — Create animation montages +- [x] `anim_link_layer` — Animation layer stacking -## v3.5.0 — Advanced Materials & Audio +## v3.5.0 — Advanced Materials & Audio ✅ ### Material Graph Editing -- [ ] `material_add_expression` — Add expression nodes (texture sample, math, parameters, custom HLSL) -- [ ] `material_connect_expressions` — Link expression inputs/outputs -- [ ] `material_set_expression_property` — Configure expression settings -- [ ] `material_create_function` — Reusable material node graphs -- [ ] `material_get_graph` — Inspect material expression structure and connections +- [x] `material_add_expression` — Add expression nodes (texture sample, math, parameters, custom HLSL) +- [x] `material_connect_expressions` — Link expression inputs/outputs +- [x] `material_set_expression_property` — Configure expression settings +- [x] `material_create_function` — Reusable material node graphs +- [x] `material_get_graph` — Inspect material expression structure and connections ### MetaSound/Audio -- [ ] `audio_import` — Import WAV, MP3, OGG, FLAC, AIFF -- [ ] `audio_create_metasound` — Create MetaSound source/patch assets -- [ ] `audio_add_node` — Add audio nodes (oscillators, filters, envelopes) -- [ ] `audio_connect_nodes` — Audio routing between nodes -- [ ] `audio_set_parameter` — Configure audio parameters +- [x] `audio_import` — Import WAV, MP3, OGG, FLAC, AIFF +- [x] `audio_create_metasound` — Create MetaSound source/patch assets +- [x] `audio_add_node` — Add audio nodes (oscillators, filters, envelopes) +- [x] `audio_connect_nodes` — Audio routing between nodes +- [x] `audio_set_parameter` — Configure audio parameters -## v3.6.0 — Data & Procedural Systems +## v3.6.0 — Data & Procedural Systems ✅ ### DataTable CRUD -- [ ] `datatable_create` — Create DataTables with struct definition -- [ ] `datatable_add_rows` — Add rows with property mapping -- [ ] `datatable_get_rows` — Query rows by name -- [ ] `datatable_update_row` — Modify existing rows -- [ ] `datatable_delete_row` — Remove rows +- [x] `datatable_create` — Create DataTables with struct definition +- [x] `datatable_add_rows` — Add rows with property mapping +- [x] `datatable_get_rows` — Query rows by name +- [x] `datatable_update_row` — Modify existing rows +- [x] `datatable_delete_row` — Remove rows ### Struct & Enum Creation -- [ ] `struct_create` — Create custom UE structs with typed properties -- [ ] `struct_update` — Modify existing structs -- [ ] `enum_create` — Create enum definitions -- [ ] `enum_get_values` — List enum values +- [x] `struct_create` — Create custom UE structs with typed properties +- [x] `struct_update` — Modify existing structs +- [x] `enum_create` — Create enum definitions +- [x] `enum_get_values` — List enum values ### Enhanced Input System -- [ ] `input_create_mapping` — Create input mappings with modifier support -- [ ] `input_list_actions` — List available input actions -- [ ] `input_get_metadata` — Input system introspection +- [x] `input_create_mapping` — Create input mappings with modifier support +- [x] `input_list_actions` — List available input actions +- [x] `input_get_metadata` — Input system introspection -## v3.7.0 — PCG & StateTree AI +## v3.7.0 — PCG & StateTree AI ✅ ### Procedural Content Generation -- [ ] `pcg_create_graph` — Create PCG graphs (with built-in templates) -- [ ] `pcg_add_node` — Add nodes from 195+ available types -- [ ] `pcg_connect_nodes` — Wire node connections -- [ ] `pcg_set_node_property` — Configure node settings -- [ ] `pcg_search_palette` — Discover available node types -- [ ] `pcg_spawn_actor` — Create PCG component actors -- [ ] `pcg_execute` — Run procedural generation +- [x] `pcg_create_graph` — Create PCG graphs (with built-in templates) +- [x] `pcg_add_node` — Add nodes from 195+ available types +- [x] `pcg_connect_nodes` — Wire node connections +- [x] `pcg_set_node_property` — Configure node settings +- [x] `pcg_search_palette` — Discover available node types +- [x] `pcg_spawn_actor` — Create PCG component actors +- [x] `pcg_execute` — Run procedural generation ### StateTree AI -- [ ] `statetree_create` — Create StateTree assets with schema -- [ ] `statetree_add_state` — Add execution states -- [ ] `statetree_add_transition` — State transition logic -- [ ] `statetree_add_task` — Task execution nodes -- [ ] `statetree_add_evaluator` — Global evaluators -- [ ] `statetree_add_binding` — Property and target bindings -- [ ] `statetree_get_metadata` — Full structure inspection - -## v3.8.0 — Mesh & LOD Management - -- [ ] `mesh_get_metadata` — LOD count, vertices, triangles, bounds, materials, Nanite support -- [ ] `mesh_import_lod` — Import FBX into LOD slots -- [ ] `mesh_set_lod_screen_size` — Set LOD transition thresholds -- [ ] `mesh_auto_generate_lods` — Built-in mesh reduction -- [ ] `mesh_get_instance_breakdown` — Rendering cost per LOD +- [x] `statetree_create` — Create StateTree assets with schema +- [x] `statetree_add_state` — Add execution states +- [x] `statetree_add_transition` — State transition logic +- [x] `statetree_add_task` — Task execution nodes +- [x] `statetree_add_evaluator` — Global evaluators +- [x] `statetree_add_binding` — Property and target bindings +- [x] `statetree_get_metadata` — Full structure inspection + +## v3.8.0 — Mesh & LOD Management ✅ + +- [x] `mesh_get_metadata` — LOD count, vertices, triangles, bounds, materials, Nanite support +- [x] `mesh_import_lod` — Import FBX into LOD slots +- [x] `mesh_set_lod_screen_size` — Set LOD transition thresholds +- [x] `mesh_auto_generate_lods` — Built-in mesh reduction +- [x] `mesh_get_instance_breakdown` — Rendering cost per LOD ## Backlog diff --git a/package.json b/package.json index 8f5faa77..89739d69 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "uemcp", - "version": "3.1.0", + "version": "3.8.0", "description": "Unreal Engine Model Context Protocol - AI Assistant Integration", "keywords": [ "unreal-engine", diff --git a/plugin/Content/Python/ops/niagara.py b/plugin/Content/Python/ops/niagara.py index 9a89229b..3ed9ac58 100644 --- a/plugin/Content/Python/ops/niagara.py +++ b/plugin/Content/Python/ops/niagara.py @@ -388,6 +388,97 @@ def compile( } +def _apply_niagara_parameter(nc: Any, parameter_name: str, value: Any, value_type: str) -> None: + """Dispatch a typed Niagara user-parameter set onto a NiagaraComponent.""" + if value_type == "float": + nc.set_variable_float(parameter_name, float(value)) + elif value_type == "int": + nc.set_variable_int(parameter_name, int(value)) + elif value_type == "bool": + _apply_niagara_bool(nc, parameter_name, value) + elif value_type == "vector": + _apply_niagara_vector(nc, parameter_name, value) + elif value_type == "color": + _apply_niagara_color(nc, parameter_name, value) + else: + raise ProcessingError( + f"Unsupported Niagara parameter type '{value_type}' for parameter '{parameter_name}'", + operation="niagara_set_parameter", + details={"parameter_name": parameter_name, "value_type": value_type, "value": value}, + ) + + +def _apply_niagara_bool(nc: Any, parameter_name: str, value: Any) -> None: + if isinstance(value, bool): + bool_val = value + elif isinstance(value, (int, float)): + bool_val = value != 0 + else: + raise ProcessingError( + f"Bool value must be a boolean or number, got {type(value).__name__}: {value!r}", + operation="niagara_set_parameter", + details={"value": value, "value_type": type(value).__name__}, + ) + nc.set_variable_bool(parameter_name, bool_val) + + +def _apply_niagara_vector(nc: Any, parameter_name: str, value: Any) -> None: + if isinstance(value, (list, tuple)) and len(value) == 3: + nc.set_variable_vec3(parameter_name, create_vector(value)) + elif isinstance(value, dict): + for key in ("x", "y", "z"): + if key not in value: + raise ProcessingError( + f"Vector dict missing required key '{key}': need {{x, y, z}}", + operation="niagara_set_parameter", + details={"value": value, "missing_key": key}, + ) + nc.set_variable_vec3(parameter_name, create_vector([value["x"], value["y"], value["z"]])) + else: + raise ProcessingError( + "Vector value must be [x, y, z] or {x, y, z}", + operation="niagara_set_parameter", + details={"value": value}, + ) + + +def _apply_niagara_color(nc: Any, parameter_name: str, value: Any) -> None: + if isinstance(value, (list, tuple)): + if len(value) not in (3, 4): + raise ProcessingError( + "Color value must have 3 or 4 components (RGB or RGBA)", + operation="niagara_set_parameter", + details={"value": value}, + ) + color = unreal.LinearColor( + r=float(value[0]), + g=float(value[1]), + b=float(value[2]), + a=float(value[3]) if len(value) > 3 else 1.0, + ) + elif isinstance(value, dict): + for key in ("r", "g", "b"): + if key not in value: + raise ProcessingError( + f"Color dict missing required key '{key}': need {{r, g, b[, a]}}", + operation="niagara_set_parameter", + details={"value": value, "missing_key": key}, + ) + color = unreal.LinearColor( + r=float(value["r"]), + g=float(value["g"]), + b=float(value["b"]), + a=float(value.get("a", 1.0)), + ) + else: + raise ProcessingError( + "Color value must be [r, g, b] or [r, g, b, a] or {r, g, b[, a]}", + operation="niagara_set_parameter", + details={"value": value}, + ) + nc.set_variable_linear_color(parameter_name, color) + + @validate_inputs( { "actor_name": [RequiredRule(), TypeRule(str)], @@ -449,76 +540,7 @@ def set_parameter( details={"actor_name": actor_name, "actor_class": type(target).__name__}, ) - # Apply the parameter using the typed setter methods on NiagaraComponent - if value_type == "float": - nc.set_variable_float(parameter_name, float(value)) - elif value_type == "int": - nc.set_variable_int(parameter_name, int(value)) - elif value_type == "bool": - if isinstance(value, bool): - bool_val = value - elif isinstance(value, (int, float)): - bool_val = value != 0 - else: - raise ProcessingError( - f"Bool value must be a boolean or number, got {type(value).__name__}: {value!r}", - operation="niagara_set_parameter", - details={"value": value, "value_type": type(value).__name__}, - ) - nc.set_variable_bool(parameter_name, bool_val) - elif value_type == "vector": - if isinstance(value, (list, tuple)) and len(value) == 3: - nc.set_variable_vec3(parameter_name, create_vector(value)) - elif isinstance(value, dict): - for key in ("x", "y", "z"): - if key not in value: - raise ProcessingError( - f"Vector dict missing required key '{key}': need {{x, y, z}}", - operation="niagara_set_parameter", - details={"value": value, "missing_key": key}, - ) - nc.set_variable_vec3(parameter_name, create_vector([value["x"], value["y"], value["z"]])) - else: - raise ProcessingError( - "Vector value must be [x, y, z] or {x, y, z}", - operation="niagara_set_parameter", - details={"value": value}, - ) - elif value_type == "color": - if isinstance(value, (list, tuple)): - if len(value) not in (3, 4): - raise ProcessingError( - "Color value must have 3 or 4 components (RGB or RGBA)", - operation="niagara_set_parameter", - details={"value": value}, - ) - color = unreal.LinearColor( - r=float(value[0]), - g=float(value[1]), - b=float(value[2]), - a=float(value[3]) if len(value) > 3 else 1.0, - ) - elif isinstance(value, dict): - for key in ("r", "g", "b"): - if key not in value: - raise ProcessingError( - f"Color dict missing required key '{key}': need {{r, g, b[, a]}}", - operation="niagara_set_parameter", - details={"value": value, "missing_key": key}, - ) - color = unreal.LinearColor( - r=float(value["r"]), - g=float(value["g"]), - b=float(value["b"]), - a=float(value.get("a", 1.0)), - ) - else: - raise ProcessingError( - "Color value must be [r, g, b, a] or {r, g, b, a}", - operation="niagara_set_parameter", - details={"value": value}, - ) - nc.set_variable_linear_color(parameter_name, color) + _apply_niagara_parameter(nc, parameter_name, value, value_type) log_debug(f"Set parameter '{parameter_name}'={value} ({value_type}) on {actor_name}") diff --git a/plugin/Content/Python/version.py b/plugin/Content/Python/version.py index fa7cead1..484e981b 100644 --- a/plugin/Content/Python/version.py +++ b/plugin/Content/Python/version.py @@ -6,12 +6,12 @@ """ # UEMCP Plugin Version -VERSION = "3.1.0" +VERSION = "3.8.0" # Version info for programmatic access VERSION_INFO = { "major": 3, - "minor": 1, + "minor": 8, "patch": 0, "prerelease": None, # e.g., "beta", "alpha", "rc1" } diff --git a/plugin/UEMCP.uplugin b/plugin/UEMCP.uplugin index 01b27e3b..39dbda59 100644 --- a/plugin/UEMCP.uplugin +++ b/plugin/UEMCP.uplugin @@ -1,7 +1,7 @@ { "FileVersion": 3, - "Version": 0, - "VersionName": "1.1.0", + "Version": 3, + "VersionName": "3.8.0", "FriendlyName": "Unreal Engine Model Context Protocol", "Description": "MCP integration for AI-assisted Unreal Engine development", "Category": "Developer Tools", diff --git a/server/package-lock.json b/server/package-lock.json index 3f3bc312..c00894a6 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -1,12 +1,12 @@ { "name": "uemcp-server", - "version": "3.1.0", + "version": "3.8.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "uemcp-server", - "version": "3.1.0", + "version": "3.8.0", "license": "MIT", "dependencies": { "@modelcontextprotocol/sdk": "^1.28.0" diff --git a/server/package.json b/server/package.json index 41914187..f3490368 100644 --- a/server/package.json +++ b/server/package.json @@ -1,6 +1,6 @@ { "name": "uemcp-server", - "version": "3.1.0", + "version": "3.8.0", "description": "Unreal Engine Model Context Protocol Server", "engines": { "node": ">=20.19.0" diff --git a/test-e2e.js b/test-e2e.js index c32141f5..770cb4b5 100755 --- a/test-e2e.js +++ b/test-e2e.js @@ -348,6 +348,7 @@ result = {"success": True, "message": "Test folder structure cleaned"} const logLines = logContent.split('\n').filter(line => line.trim()); const errorLines = []; + let suppressedCount = 0; const criticalPatterns = [ /Error:/i, /Fatal error/i, @@ -355,9 +356,30 @@ result = {"success": True, "message": "Test folder structure cleaned"} /=== Critical error: ===/i, /Assertion failed/i, ]; - + // Expected errors produced intentionally by error-handling test cases. + // Each pattern allows up to maxSuppress occurrences; further hits are treated + // as real errors so regressions producing the same messages still surface in CI. + const expectedTestErrorPatterns = [ + { regex: /LogPython: Error: UEMCP: actor operation failed: Socket .* not found/, maxSuppress: 5 }, + { regex: /LogPython: Error: UEMCP: actor operation failed: Actor .* not found in level/, maxSuppress: 3 }, + ]; + const suppressionCounts = new Map(); + // Scan for errors - for (const line of logLines) { + lineLoop: for (const line of logLines) { + for (const { regex, maxSuppress } of expectedTestErrorPatterns) { + if (regex.test(line)) { + const key = regex.toString(); + const count = suppressionCounts.get(key) || 0; + if (count < maxSuppress) { + suppressionCounts.set(key, count + 1); + suppressedCount++; + continue lineLoop; + } + // maxSuppress reached — fall through and treat as a real error + break; + } + } for (const pattern of criticalPatterns) { if (pattern.test(line)) { errorLines.push(line); @@ -365,19 +387,23 @@ result = {"success": True, "message": "Test folder structure cleaned"} } } } - + + if (suppressedCount > 0) { + this.log(`Suppressed ${suppressedCount} expected error-handling test error(s) from UE log scan`, 'info'); + } + if (errorLines.length > 0) { this.log(`❌ Found ${errorLines.length} errors in UE logs:`, 'error'); - + // Show first 10 errors errorLines.slice(0, 10).forEach(line => { console.log(` ${line.trim()}`); }); - + if (errorLines.length > 10) { console.log(` ... and ${errorLines.length - 10} more errors`); } - + this.results.e2e.ueLogErrors = errorLines.length; } else { this.log('✅ No errors found in UE logs', 'success');