using System.Diagnostics; using System.Text.Json; using System.Runtime.InteropServices.WindowsRuntime; using Microsoft.UI.Xaml.Automation.Peers; using Microsoft.UI.Xaml.Automation.Provider; using Microsoft.UI.Xaml.Controls.Primitives; using XFEEDToolBox.Controls; using XFEEDToolBox.Core.Engineering; using XFEEDToolBox.Core.Outfitting; using XFEEDToolBox.Views; namespace XFEEDToolBox; public partial class App { private static async Task RunRefinementProbeAsync(string reportPath) { var directory=Path.GetDirectoryName(Path.GetFullPath(reportPath))!;Directory.CreateDirectory(directory); var initialSystem=Runtime.Player.Navigation.CurrentSystemAddress;var initialJump=Runtime.Player.Navigation.LastJumpAt; object? editor=null,progress=null,list=null,profile=null,logs=null,shield=null,interfaceChanges=null;string? failure=null; static void Require(bool condition,string message){if(!condition)throw new InvalidOperationException(message);} static void Invoke(Button button)=>((IInvokeProvider)new ButtonAutomationPeer(button).GetPattern(PatternInterface.Invoke)).Invoke(); async Task Open(Type type){AppShellPage.Current!.Navigate(type);await Task.Delay(250);var page=AppShellPage.Current.CurrentPage!;page.UpdateLayout();return page;} try { MainWindow.AppWindow.Move(new(-30000,-30000));MainWindow.AppWindow.Resize(new(1900,1160));MainWindow.AppWindow.Show(false); var page=(RefitPage)await Open(typeof(RefitPage));ViewModel.RebuildRefitCommand.Execute(null);await Task.Delay(300); var slots=(GridView)page.FindName("SlotList");var sample=ViewModel.RefitVisibleSlots.First(s=>s.Module?.Engineering is not null); slots.SelectedItem=sample;await Task.Delay(200); var wait=Stopwatch.StartNew();while(Descendants(slots).OfType().Count(i=>i.IsRealGameIcon)().Select(i=>new{i.Item,i.ResourceHash,i.IsRealGameIcon,i.Failure}).ToArray(); var container=(GridViewItem)slots.ContainerFromItem(sample);var presenter=Descendants(container).OfType().Single(); var ring=(ProgressRing)page.FindName("CurrentEngineeringProgress");var actualProgress=sample.Module!.Engineering!.Quality!.Value*100; editor=new{cards=slots.Items.Count,icons,realIcons=icons.Count(i=>i.IsRealGameIcon),squareCorners=container.CornerRadius.TopLeft==0&&presenter.CornerRadius.TopLeft==0, noWholeShipTotals=page.FindName("RefitTotals") is null&&!Descendants(page).OfType().Any(t=>t.Text.Contains("整船材料总表")),currentRing=ring.Value,actualProgress,currentRingReadOnly=!ring.IsHitTestVisible, slotSize=sample.SlotSize,currentGrade=sample.CurrentGrade,cardName=sample.CardName}; Require(icons.Length>0&&icons.All(i=>i.IsRealGameIcon)&&container.CornerRadius.TopLeft==0,"Real module icons or square card style failed."); Require(Math.Abs(ring.Value-actualProgress)<.01&&!ring.IsHitTestVisible&&!ring.IsIndeterminate,"Current progress must be a read-only ring."); var rating=(XFEEDToolBox.Controls.EngineeringGradePicker)page.FindName("EngineeringGrade");rating.Value=5;await Task.Delay(100); Require(ViewModel.RefitGrade==5&&Math.Abs(ring.Value-actualProgress)<.01,"Target grade changed current progress."); await CapturePageAsync(page,Path.Combine(directory,"ship-editor.png")); var before=ViewModel.UpgradeEntries.Count; Invoke((Button)page.FindName("AddShipToListButton")); wait.Restart();while((ViewModel.RefitBusy||ViewModel.UpgradeBusy||!((Button)AppShellPage.Current!.FindName("UpgradeListButton")).Flyout.IsOpen)&&wait.Elapsede.SourceKey==sample.InstanceKey&&e.Target.Grade==5); ((FrameworkElement)listFlyout.Content).UpdateLayout(); var compactRows=Descendants(listFlyout.Content).OfType().Where(g=>g.Tag as string=="compact-material-row").ToArray(); Require(compactRows.Length>0&&compactRows.All(g=>g.ActualHeight is >0 and <=56),"Global list preview rows must use the compact layout."); list=new{before,after=ViewModel.UpgradeEntries.Count,ownsEntry,flyoutOpened=listFlyout.IsOpen,stayedOnEditor=shell.CurrentPage is RefitPage,materialRows=ViewModel.UpgradeNeeds.Count, flyoutRows=Descendants(listFlyout.Content).OfType().FirstOrDefault()?.Items.Count,compactRowHeight=compactRows.Max(g=>g.ActualHeight),previewWidth=((FrameworkElement)listFlyout.Content).ActualWidth,summary=ViewModel.UpgradeSummary,result=ViewModel.RefitResult}; Require(ownsEntry&&listFlyout.IsOpen&&shell.CurrentPage is RefitPage&&ViewModel.UpgradeNeeds.Count>0,"Adding ship targets must open global materials Flyout."); await CaptureRefinementElementAsync((FrameworkElement)listFlyout.Content,Path.Combine(directory,"materials-flyout.png"));listFlyout.Hide(); var original=ViewModel.RefitDocument!; var fixture=original with{EdsyHash="ui-probe",Slots=original.Slots.Select(s=>s.Slot==sample.Slot?s with{InstanceKey="",Origin="界面验收夹具",Target=null,Module=s.Module! with{Engineering=s.Module.Engineering! with{Level=3,Quality=.65}}}:s with{Target=null}).ToArray()}; ViewModel.LoadRefitProbeDocument(fixture);ViewModel.RefitSlot=ViewModel.RefitVisibleSlots.Single(s=>s.Slot==sample.Slot);await Task.Delay(120);rating.Value=5;await Task.Delay(100); var module=ViewModel.RefitSlot!.Module!;var engineer=ViewModel.RefitEngineer!.Record; var plan=EngineeringPlanner.Calculate(EngineeringCatalog.Default,module,ViewModel.RefitBlueprint!,5,1,null,engineer,[]); progress=new{currentLevel=module.Engineering!.Level,currentQuality=module.Engineering.Quality,target=ViewModel.RefitGrade,ring=ring.Value,firstRollGrade=plan.Rolls.First().Grade,firstRollQuality=plan.Rolls.First().BeforeQuality,lowerGradesCharged=plan.Rolls.Any(r=>r.Grade<3)}; Require(module.Engineering.Quality==.65&&ring.Value==65&&plan.Rolls.First().Grade==3&&plan.Rolls.First().BeforeQuality==.65&&!plan.Rolls.Any(r=>r.Grade<3),"G3 65% to G5 regression failed in the real controls."); ViewModel.RebuildRefitCommand.Execute(null); var engineeringPage=await Open(typeof(EngineeringPage)); ViewModel.EngineeringType="Multi-cannon"; ViewModel.EngineeringSource=ViewModel.EngineeringSources.First(s=>s.Key.Length==0); ViewModel.EngineeringBlueprint=ViewModel.EngineeringBlueprints.First(b=>b!="Overcharged Weapon"); ViewModel.EngineeringSource=ViewModel.EngineeringSources.First(s=>s.Key==sample.InstanceKey); ViewModel.EngineeringGrade=5;await Task.Delay(100); var engineeringRing=(ProgressRing)engineeringPage.FindName("CurrentEngineeringProgress"); Require(ViewModel.EngineeringBlueprint=="Overcharged Weapon"&&Math.Abs(engineeringRing.Value-actualProgress)<.01,"Owned engineering source retained another module's blueprint."); await CapturePageAsync(engineeringPage,Path.Combine(directory,"module-engineering.png")); var profileButton=(Button)shell.FindName("ProfileButton");var beforePage=shell.CurrentPage;profileButton.Flyout.ShowAt(profileButton);await Task.Delay(180); var profileFlyout=(Flyout)profileButton.Flyout;var profileContent=(FrameworkElement)profileFlyout.Content; var avatarFirst=((StackPanel)profileButton.Content).Children.First() is PersonPicture; await ViewModel.RefreshWalletAsync();await CaptureRefinementElementAsync(profileContent,Path.Combine(directory,"profile-flyout.png")); var profileStayed=ReferenceEquals(beforePage,shell.CurrentPage); Invoke((Button)shell.FindName("ProfileDetailsButton"));await Task.Delay(200); Require(shell.CurrentPage is ProfilePage,"Profile Flyout details must navigate to personal page."); var profilePage=(ProfilePage)shell.CurrentPage!;var map=(GalaxyMapView)profilePage.FindName("ExploredMap"); var walletExpected=false; foreach(var process in System.Diagnostics.Process.GetProcessesByName("EliteDangerous64")) using(process)if(Runtime.Player.LoadGameAt is {} loaded&&loaded.UtcTicks>=process.StartTime.ToUniversalTime().Ticks-TimeSpan.FromSeconds(5).Ticks)walletExpected=true; profile=new{avatarFirst,profileStayed,profileFlyoutClosed=!profileFlyout.IsOpen,personalPage=shell.CurrentPage is ProfilePage,commanderIdBound=ViewModel.ProfileCommanderId==Runtime.Player.CommanderId, arx=ViewModel.ProfileArxText,wallet=ViewModel.WalletSnapshot,walletExpected,explored=map.Systems?.Count,recordedVisits=Runtime.Player.Profile.VisitedSystems.Count,statistics=ViewModel.ProfileStatistics.Count,ranks=ViewModel.ProfileRanks.Count}; Require(avatarFirst&&profileStayed&&ViewModel.ProfileCommanderId==Runtime.Player.CommanderId&&map.Systems?.Count==Runtime.Player.Profile.VisitedSystems.Count,"Profile layout or explorer source failed."); Require((walletExpected?ViewModel.WalletSnapshot is not null:ViewModel.WalletSnapshot is null&&ViewModel.ProfileArxText=="未读取")&&ViewModel.ProfileStatistics.Count>0,"Wallet must belong to the fresh live commander session, or explicitly remain unread."); await CapturePageAsync(profilePage,Path.Combine(directory,"profile.png")); await Open(typeof(RefitPage));Invoke((Button)shell.FindName("ProfileFooterButton"));await Task.Delay(120);Require(shell.CurrentPage is ProfilePage,"Bottom profile entry failed."); var logPage=await Open(typeof(LogViewPage));await ViewModel.RefreshGameEventsAsync(); var first=ViewModel.GameEventRows.Select(r=>r.Id).ToArray();await ViewModel.NextGameLogPageCommand.ExecuteAsync(null);var second=ViewModel.GameEventRows.Select(r=>r.Id).ToArray(); ViewModel.GameEventSearch="FSDJump";await Task.Delay(350);await ViewModel.RefreshGameEventsAsync();var searchCorrect=ViewModel.GameEventRows.Count>0&&ViewModel.GameEventRows.All(r=>r.Record.Json.Contains("FSDJump")); ViewModel.GameEventSearch="";await Task.Delay(300);await ViewModel.LatestGameLogCommand.ExecuteAsync(null); logs=new{firstPage=first.Length,secondPage=second.Length,noOverlap=!first.Intersect(second).Any(),searchCorrect,total=Runtime.GameEvents.Query().Count,displayed=ViewModel.GameEventRows.Count}; Require(first.Length==200&&second.Length>0&&!first.Intersect(second).Any()&&searchCorrect,"Full game log paging or search failed."); await CapturePageAsync(logPage,Path.Combine(directory,"game-events.png")); var home=await Open(typeof(MainPage));var toggle=(ToggleSwitch)home.FindName("ShieldRestartToggle");var defaultOff=!toggle.IsOn;toggle.IsOn=true;await Task.Delay(100);var disconnected=ViewModel.ShieldRestartStatus.Contains("验收");toggle.IsOn=false; shield=new{defaultOff,settingBound=ViewModel.ShieldZeroRestartEnabled==false,qaActionsDisconnected=disconnected,actualRestartInvoked=false}; Require(defaultOff&&disconnected,"Shield switch must be off by default and isolated in UI QA."); await CapturePageAsync(home,Path.Combine(directory,"home.png")); interfaceChanges=await RunInterfaceRefinementProbeAsync(directory); } catch(Exception error){failure=error.ToString();} await File.WriteAllTextAsync(reportPath,JsonSerializer.Serialize(new{editor,progress,list,profile,logs,shield,interfaceChanges,failure,uiError=ViewModel.ErrorMessage, gamePositionUnchanged=initialSystem==Runtime.Player.Navigation.CurrentSystemAddress&&initialJump==Runtime.Player.Navigation.LastJumpAt, gameFlightCommandsInvoked=false,gameRestartInvoked=false,walletRequestedWriteAccess=false,walletNativeFunctionsInvoked=false,osInputSent=false,screenUse=false},new JsonSerializerOptions{WriteIndented=true})); MainWindow.Close(); } private static async Task CaptureRefinementElementAsync(FrameworkElement element,string path) { var content=element; var parent=Microsoft.UI.Xaml.Media.VisualTreeHelper.GetParent(element); while(parent is not null) { if(parent is FlyoutPresenter presenter){element=presenter;break;} parent=Microsoft.UI.Xaml.Media.VisualTreeHelper.GetParent(parent); } await Task.Delay(250); element.UpdateLayout();var bitmap=new Microsoft.UI.Xaml.Media.Imaging.RenderTargetBitmap();await bitmap.RenderAsync(element); if(bitmap.PixelWidth==0||bitmap.PixelHeight==0){bitmap=new();await bitmap.RenderAsync(content);} var pixels=await bitmap.GetPixelsAsync(); using var reader=Windows.Storage.Streams.DataReader.FromBuffer(pixels);var bytes=new byte[pixels.Length];reader.ReadBytes(bytes); using var output=File.Open(path,FileMode.Create);var encoder=await Windows.Graphics.Imaging.BitmapEncoder.CreateAsync(Windows.Graphics.Imaging.BitmapEncoder.PngEncoderId,output.AsRandomAccessStream()); encoder.SetPixelData(Windows.Graphics.Imaging.BitmapPixelFormat.Bgra8,Windows.Graphics.Imaging.BitmapAlphaMode.Premultiplied,(uint)bitmap.PixelWidth,(uint)bitmap.PixelHeight,96,96,bytes);await encoder.FlushAsync(); } }