chr
2026-04-08 53e656200368a983e563550e2cc1acbc6d86b729
完善代码
17个文件已修改
7个文件已添加
432 ■■■■■ 已修改文件
Editor/MainWindow.xaml.cs 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
Editor/OpenTapEditor.csproj 15 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Editor/Provider/GridControlProvider.cs 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Editor/SequencePanel.xaml.cs 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Editor/UI/VariableSelector.xaml 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Editor/UI/VariableSelector.xaml.cs 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Editor/UI/VariableTextBox.xaml 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Editor/UI/VariableTextBox.xaml.cs 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Editor/UI/VariablesControl.xaml.cs 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
OpenTap/BasicSteps/SequenceCallStep.cs 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
OpenTap/Engine/Addin/Annotation/TreeDataAttribute.cs 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
OpenTap/Engine/Addin/FileGlobalsContext.cs 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
OpenTap/Engine/Addin/LocalsContext.cs 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
OpenTap/Engine/Addin/SequenceContext.cs 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
OpenTap/Engine/Addin/VariableContainer.cs 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
OpenTap/Engine/Addin/VariableContext.cs 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
OpenTap/Engine/SerializerPlugins/TestStepListSerializer.cs 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
OpenTap/Engine/TestPlan.cs 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
OpenTap/Engine/TestPlanExecution.cs 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
OpenTap/Engine/TestPlanRun.cs 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
OpenTap/Engine/TestStep.cs 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
OpenTap/Engine/TestStepList.cs 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin/AddInPlugin/AddInPlugin.csproj 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin/AddInPlugin/Util/TestPlanRunAddIn.cs 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Editor/MainWindow.xaml.cs
@@ -88,7 +88,7 @@
        };
        child.Click += (sender, e) =>
        {
            var testPlan = new TestPlan();
            var testPlan = new TestPlan() { VisualPath = new Guid().ToString() };
            var tabItem = new TabItemData
            {
                Content = new SequencePanel("New Seq.TapPlan", testPlan)
Editor/OpenTapEditor.csproj
@@ -3,10 +3,12 @@
  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net8.0-windows7.0</TargetFramework>
    <!--<TargetFramework>netstandard2.0</TargetFramework>-->
    <Nullable>enable</Nullable>
    <LangVersion>10.0</LangVersion>
    <ImplicitUsings>enable</ImplicitUsings>
    <UseWPF>true</UseWPF>
    <SupportedOSPlatformVersion>7.0</SupportedOSPlatformVersion>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
@@ -28,11 +30,11 @@
  </ItemGroup>
  <ItemGroup>
    <ProjectReference Include="..\..\..\VisualStudio\UILib\UILib.csproj" />
    <ProjectReference Include="..\..\..\VisualStudio\UtilLib\UtilLib.csproj" />
    <ProjectReference Include="..\OpenTap\BasicSteps\Tap.Plugins.BasicSteps.csproj" />
    <ProjectReference Include="..\opentap\Engine\Tap.Engine.csproj" />
    <ProjectReference Include="..\Plugin\AddInPlugin\AddInPlugin.csproj" />
    <ProjectReference Include="..\UILib\UILib.csproj" />
    <ProjectReference Include="..\UtilLib\UtilLib.csproj" />
  </ItemGroup>
  <ItemGroup>
@@ -42,9 +44,18 @@
  </ItemGroup>
  <ItemGroup>
    <Compile Update="UI\VariableTextBox.xaml.cs">
      <SubType>Code</SubType>
    </Compile>
  </ItemGroup>
  <ItemGroup>
    <None Update="App.config">
      <CopyToOutputDirectory>Never</CopyToOutputDirectory>
    </None>
    <None Update="Packages\不加这个报一堆debug的错.txt">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </None>
  </ItemGroup>
</Project>
Editor/Provider/GridControlProvider.cs
@@ -190,6 +190,8 @@
                return tree;
            }
            if (property.GetCustomAttribute<IgnoreVariable>() is IgnoreVariable iv)
            {
            TextBox tb = new TextBox
            {
                IsReadOnly = !(property.CanWrite && property.SetMethod?.IsPublic == true)
@@ -204,6 +206,17 @@
            return tb;
        }
            VariableTextBox vtb = new VariableTextBox() { Step = (ITestStep)source };
            BindingOperations.SetBinding(vtb, VariableTextBox.ValueProperty,
                new Binding(property.Name)
                {
                    Source = source,
                    UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
                });
            return vtb;
        }
        public static bool FilterMember(IMemberData member)
        {
            if (member.DeclaringType.DescendsTo(resourceTypeData) && member.Name == nameof(Resource.Name))
Editor/SequencePanel.xaml.cs
@@ -1,6 +1,7 @@
using CommunityToolkit.Mvvm.ComponentModel;
using Microsoft.Win32;
using OpenTap;
using OpenTap.Addin;
using System.IO;
using System.Windows;
using System.Windows.Controls;
@@ -110,6 +111,7 @@
            };
            if (sfd.ShowDialog() == true)
            {
                UIStationGlobalsManager.Instance.WriteToLocal(UIStationGlobalsManager.Instance.Datasource);
                Plan.Save(sfd.FileName);
                HasChanged = false;
                TabName = Path.GetFileName(Plan.Path);
@@ -128,6 +130,7 @@
        };
        if (sfd.ShowDialog() == true)
        {
            UIStationGlobalsManager.Instance.WriteToLocal(UIStationGlobalsManager.Instance.Datasource);
            Plan.Save(sfd.FileName);
            HasChanged = false;
            TabName = Path.GetFileName(Plan.Path);
Editor/UI/VariableSelector.xaml
New file
@@ -0,0 +1,23 @@
<Window x:Class="OpenTapEditor.VariableSelector"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:OpenTapEditor"
        mc:Ignorable="d"
        Title="选择变量" Height="450" Width="800">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <local:VariableTree Datasource="{Binding Variables}">
        </local:VariableTree>
        <GroupBox Grid.Row="1" Header="结果">
            <TextBox TextWrapping="Wrap" Text="{Binding Result}" />
        </GroupBox>
    </Grid>
</Window>
Editor/UI/VariableSelector.xaml.cs
New file
@@ -0,0 +1,50 @@
using CommunityToolkit.Mvvm.ComponentModel;
using OpenTap;
using OpenTap.Addin;
using System.Collections.ObjectModel;
using System.Windows;
namespace OpenTapEditor
{
    /// <summary>
    /// VariableSelector.xaml 的交互逻辑
    /// </summary>
    [ObservableObject]
    public partial class VariableSelector : Window
    {
        [ObservableProperty]
        private string result;
        [ObservableProperty]
        private ObservableCollection<TestVariable> variables;
        public VariableSelector()
        {
            DataContext = this;
            Variables = new ObservableCollection<TestVariable>() {
                new TestVariable(){
                    Name = "Locals",
                    Type = TestVariableType.Container
                },
                new TestVariable(){
                    Name = "FileGlobals",
                    Type = TestVariableType.Container
                },
                new TestVariable(){
                    Name = "StationGlobals",
                    Type = TestVariableType.Container,
                    Children = UIStationGlobalsManager.Instance.Datasource
                },
            };
            InitializeComponent();
        }
        public bool? Open(TestPlan plan, TestStepList seq)
        {
            Variables[0].Children = seq.Variables;
            Variables[1].Children = plan.Variables;
            return this.ShowDialog();
        }
    }
}
Editor/UI/VariableTextBox.xaml
New file
@@ -0,0 +1,17 @@
<UserControl x:Class="OpenTapEditor.UI.VariableTextBox"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:local="clr-namespace:OpenTapEditor.UI"
             mc:Ignorable="d"
             d:DesignHeight="450" d:DesignWidth="800">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition Width="Auto"/>
        </Grid.ColumnDefinitions>
        <TextBox x:Name="TbPath" Text="{Binding FilePath,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
        <Button Grid.Column="1" Click="Button_Click" Content="f(x)"/>
    </Grid>
</UserControl>
Editor/UI/VariableTextBox.xaml.cs
New file
@@ -0,0 +1,60 @@
using OpenTap;
using System.Windows;
using System.Windows.Controls;
namespace OpenTapEditor.UI
{
    /// <summary>
    /// FileSelector.xaml 的交互逻辑
    /// </summary>
    public partial class VariableTextBox : UserControl
    {
        public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(
                            "Value",
                            typeof(string),
                            typeof(VariableTextBox),
                            new FrameworkPropertyMetadata());
        public static readonly DependencyProperty StepProperty = DependencyProperty.Register(
                            "Step",
                            typeof(ITestStep),
                            typeof(VariableTextBox),
                            new FrameworkPropertyMetadata());
        public ITestStep Step
        {
            get => (ITestStep)GetValue(StepProperty);
            set
            {
                SetValue(StepProperty, value);
            }
        }
        public string Value
        {
            get => (string)GetValue(ValueProperty);
            set
            {
                SetValue(ValueProperty, value);
            }
        }
        public VariableTextBox()
        {
            DataContext = this;
            InitializeComponent();
        }
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            var win = new VariableSelector()
            {
                Owner = Window.GetWindow(this)
            };
            if (win.Open(Step.Root, new TestStepList()) == true)
            {
                Value = win.Result;
            }
        }
    }
}
Editor/UI/VariablesControl.xaml.cs
@@ -46,6 +46,10 @@
            DataContext = this;
            Variables = new ObservableCollection<TestVariable>() {
                new TestVariable(){
                    Name = "Locals",
                    Type = TestVariableType.Container
                },
                new TestVariable(){
                    Name = "FileGlobals",
                    Type = TestVariableType.Container
                },
@@ -61,9 +65,10 @@
        public void SetPlan(TestPlan plan)
        {
            this.plan = plan;
            Variables[0].Children = plan.Variables;
            Variables[1].Children = plan.Variables;
            Sequences = plan.Sequences;
            SelectedSequence = Sequences[0];
            Variables[0].Children = SelectedSequence.Variables;
        }
        private void Button_Click(object sender, System.Windows.RoutedEventArgs e)
@@ -94,6 +99,7 @@
        partial void OnSelectedSequenceChanged(TestStepList value)
        {
            Variables[0].Children = value.Variables;
            selectedSequenceChanged?.Invoke(this, value);
        }
    }
OpenTap/BasicSteps/SequenceCallStep.cs
@@ -30,6 +30,11 @@
            public Guid Guid2 { get; set; }
        }
        protected override void OnRootChanged(TestPlan plan)
        {
            LoadTestPlan();
        }
        /// <summary>
        /// This is the list of path of test plan loaded and used to prevent recursive TestPlan references.
        /// </summary>
@@ -87,7 +92,6 @@
                {
                    realPath = this.GetParent<TestPlan>()?.Path;
                }
                LoadTestPlan();
            }
        }
@@ -107,7 +111,7 @@
            }
        }
        [Display("共享变量")]
        //[Display("共享变量")]
        public bool ShareGlobals { get; set; }
        [Browsable(false)]
@@ -268,25 +272,30 @@
                var xml = plan.SerializeToString();
                var listeners = OpenTap.Log.GetListeners();
                var resultSetting = ResultSettings.Current;
                var fileGlobalsContext = PlanRun.fileGlobalsContext;
                using (Session.Create())
                {
                    SequenceContext.SetLocals(new VariableContext(null));
                    var plan2 = Utils.DeserializeFromString<TestPlan>(xml);
                    plan2.PrintTestPlanRunSummary = false;
                    plan2.VisualPath = plan.Path;
                    foreach (var listener in listeners)
                    {
                        OpenTap.Log.AddListener(listener);
                    }
                    TestPlanRun subRun;
                    if (ShareGlobals)
                    {
                        subRun = plan2.Execute(SequenceName, resultSetting, null, null,
                            new VariableContainer(GetMergeContainer(), this.PlanRun.FileGlobalsRuntime, this.PlanRun.StationGlobalsRuntime));
                    }
                    else
                    {
                        subRun = plan2.Execute(SequenceName, resultSetting, null, null, new VariableContainer(GetMergeContainer(), null,
                            this.PlanRun.StationGlobalsRuntime));
                    }
                           new VariableContainer(GetMergeContainer(), fileGlobalsContext, this.PlanRun.StationGlobalsRuntime));
                    //if (ShareGlobals)
                    //{
                    //    subRun = plan2.Execute(SequenceName, resultSetting, null, null,
                    //        new VariableContainer(GetMergeContainer(), fileGlobalsContext, this.PlanRun.StationGlobalsRuntime));
                    //}
                    //else
                    //{
                    //    subRun = plan2.Execute(SequenceName, resultSetting, null, null, new VariableContainer(GetMergeContainer(), null,
                    //        this.PlanRun.StationGlobalsRuntime));
                    //}
                    UpgradeVerdict(subRun.Verdict);
                }
            }
OpenTap/Engine/Addin/Annotation/TreeDataAttribute.cs
@@ -11,4 +11,8 @@
    public class TreeDataAttribute : Attribute, IAnnotation
    {
    }
    public class IgnoreVariable : Attribute, IAnnotation
    {
    }
}
OpenTap/Engine/Addin/FileGlobalsContext.cs
New file
@@ -0,0 +1,26 @@
using System.Collections.Concurrent;
namespace OpenTap.Addin
{
    public class FileGlobalsContext
    {
        ConcurrentDictionary<string, VariableContext> map = new ConcurrentDictionary<string, VariableContext>();
        public VariableContext GetFileGlobals(TestPlan plan)
        {
            string path = plan.VisualPath;
            if (!map.ContainsKey(path))
            {
                var runtimeVariablePool = new ConcurrentDictionary<string, RuntimeVariable>();
                foreach (var data in plan.Variables)
                {
                    RuntimeVariable runTime = data.ToRuntime();
                    if (runTime == null) continue;
                    runtimeVariablePool[runTime.Name] = runTime;
                }
                map[path] = new VariableContext(runtimeVariablePool);
            }
            return map[path];
        }
    }
}
OpenTap/Engine/Addin/LocalsContext.cs
New file
@@ -0,0 +1,17 @@
using System.Collections.Concurrent;
namespace OpenTap.Addin
{
    public class LocalsContext
    {
        //public VariableContext GetLocals(TestPlan plan)
        //{
        //    string path = plan.Path;
        //    if (!map.ContainsKey(path))
        //    {
        //    }
        //    return map[path];
        //}
    }
}
OpenTap/Engine/Addin/SequenceContext.cs
New file
@@ -0,0 +1,14 @@
namespace OpenTap.Addin
{
    public class SequenceContext
    {
        static readonly SessionLocal<VariableContext> LocalsSession = new SessionLocal<VariableContext>(null);
        public static VariableContext Locals => LocalsSession.Value;
        public static void SetLocals(VariableContext locals)
        {
            LocalsSession.Value = locals;
        }
    }
}
OpenTap/Engine/Addin/VariableContainer.cs
@@ -3,13 +3,13 @@
    public class VariableContainer
    {
        public VariableContext Parameters;
        public VariableContext FileGlobals;
        public FileGlobalsContext fileGlobalsContext;
        public VariableContext StationGlobals;
        public VariableContainer(VariableContext parameters, VariableContext fileGlobals, VariableContext stationGlobals)
        public VariableContainer(VariableContext parameters, FileGlobalsContext fileGlobalsContext, VariableContext stationGlobals)
        {
            Parameters = parameters;
            FileGlobals = fileGlobals;
            this.fileGlobalsContext = fileGlobalsContext;
            StationGlobals = stationGlobals;
        }
    }
OpenTap/Engine/Addin/VariableContext.cs
@@ -1,5 +1,8 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Dynamic;
using System.IO;
using System.Numerics;
namespace OpenTap.Addin
{
@@ -69,5 +72,17 @@
                }
            }
        }
        public static VariableContext FromTestVariables(IEnumerable<TestVariable> variables)
        {
            var runtimeVariablePool = new ConcurrentDictionary<string, RuntimeVariable>();
            foreach (var data in variables)
            {
                RuntimeVariable runTime = data.ToRuntime();
                if (runTime == null) continue;
                runtimeVariablePool[runTime.Name] = runTime;
            }
            return new VariableContext(runtimeVariablePool);
        }
    }
}
OpenTap/Engine/SerializerPlugins/TestStepListSerializer.cs
@@ -2,6 +2,7 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, you can obtain one at http://mozilla.org/MPL/2.0/.
using OpenTap.Addin;
using System;
using System.Xml.Linq;
@@ -26,6 +27,28 @@
            steps.SequenceName = sequenceNameAttr?.Value ?? string.Empty;
            foreach (var subnode in elem.Elements())
            {
                if (subnode.Name == varName)
                {
                    TestVariable v = null;
                    try
                    {
                        if (!Serializer.Deserialize(subnode, x => v = (TestVariable)x))
                        {
                            Serializer.PushError(subnode, "Unable to deserialize test step.");
                            continue; // skip to next step.
                        }
                    }
                    catch (Exception ex)
                    {
                        Serializer.PushError(subnode, "Unable to deserialize test step.", ex);
                        continue;
                    }
                    if (v != null)
                        steps.Variables.Add(v);
                }
                else
                {
                ITestStep result = null;
                try
                {
@@ -44,11 +67,13 @@
                if (result != null)
                    steps.Add(result);
            }
            }
            setResult(steps);
            return true;
        }
        static XName testStepName = "TestStep";
        static XName varName = "Variable";
        /// <summary> Serialization implementation. </summary>
        public override bool Serialize( XElement elem, object target, ITypeData expectedType)
        {
@@ -66,6 +91,18 @@
                    Serializer.Serialize(newelem, item, null, true);
                    elem.Add(newelem);
                }
                var vars = list.Variables;
                if (vars != null && vars.Count > 0)
                {
                    for (int i = 0; i < vars.Count; i++)
                    {
                        var item = vars[i];
                        var newelem = new XElement(varName);
                        Serializer.Serialize(newelem, item, null, true);
                        elem.Add(newelem);
                    }
                }
                return true;
            }
            return false;
OpenTap/Engine/TestPlan.cs
@@ -480,12 +480,27 @@
        }
        #endregion
        private string path;
        /// <summary>
        /// Gets where this plan was last saved or loaded from. It might be null.
        /// </summary>
        [XmlIgnore]
        [AnnotationIgnore]
        public string Path { get; internal set; }
        public string Path
        {
            get => path; internal set
            {
                path = value;
                VisualPath = value;
            }
        }
        /// <summary>
        /// 虚拟路径
        /// </summary>
        [XmlIgnore]
        [AnnotationIgnore]
        public string VisualPath { get; set; }
        /// <summary> The directory where the test plan is stored.</summary>
        [MetaData(macroName: "TestPlanDir")]
OpenTap/Engine/TestPlanExecution.cs
@@ -568,9 +568,12 @@
                resultListeners = resultListeners.Concat(new IResultListener[] { summaryListener });
            resultListeners = resultListeners.Where(r => r is IEnabledResource ? ((IEnabledResource)r).IsEnabled : true);
            IList<ITestStep> steps;
            TestStepList list;
            if (stepsOverride == null)
            {
                steps = GetStepsByName(sequenceName);
                list = GetStepsByName(sequenceName);
                steps = list;
            }
            else
            {
@@ -579,6 +582,7 @@
                {
                    throw new ArgumentException("No Name");
                }
                list = tempStep;
                // Remove steps that are already included via their parent steps.
                foreach (var step in stepsOverride)
@@ -669,6 +673,7 @@
                    execStage.Parameters.AddRange(PluginManager.GetPluginVersions(new List<object> { r }));
                };
            }
            execStage.LocalsRuntime = VariableContext.FromTestVariables(list.Variables);
            if (metaDataParameters != null)
OpenTap/Engine/TestPlanRun.cs
@@ -49,15 +49,18 @@
        static readonly TraceSource log = Log.CreateSource("TestPlan");
        static readonly TraceSource resultLog = Log.CreateSource("Resources");
        public VariableContext LocalsRuntime { get; internal set; }
        /// <summary>
        /// FileGlobals运行时
        /// </summary>
        public VariableContext ParametersRuntime { get; private set; }
        public FileGlobalsContext fileGlobalsContext { get; private set; }
        /// <summary>
        /// FileGlobals运行时
        /// </summary>
        public VariableContext FileGlobalsRuntime { get; private set; }
        public VariableContext FileGlobalsRuntime { get => fileGlobalsContext?.GetFileGlobals(plan); }
        /// <summary>
        /// StationGlobals运行时
@@ -675,20 +678,30 @@
        void RefreshFileGlobals()
        {
            if (variableContainer?.FileGlobals != null)
            //if (variableContainer?.FileGlobals != null)
            //{
            //    FileGlobalsRuntime = variableContainer.FileGlobals;
            //}
            //else
            //{
            //    var runtimeVariablePool = new ConcurrentDictionary<string, RuntimeVariable>();
            //    foreach (var data in plan.Variables)
            //    {
            //        RuntimeVariable runTime = data.ToRuntime();
            //        if (runTime == null) continue;
            //        runtimeVariablePool[runTime.Name] = runTime;
            //    }
            //    this.FileGlobalsRuntime = new VariableContext(runtimeVariablePool);
            //}
            if (variableContainer?.fileGlobalsContext != null)
            {
                FileGlobalsRuntime = variableContainer.FileGlobals;
                fileGlobalsContext = variableContainer.fileGlobalsContext;
            }
            else
            {
                var runtimeVariablePool = new ConcurrentDictionary<string, RuntimeVariable>();
                foreach (var data in plan.Variables)
                {
                    RuntimeVariable runTime = data.ToRuntime();
                    if (runTime == null) continue;
                    runtimeVariablePool[runTime.Name] = runTime;
                }
                this.FileGlobalsRuntime = new VariableContext(runtimeVariablePool);
                fileGlobalsContext = new FileGlobalsContext();
                var _ = fileGlobalsContext.GetFileGlobals(plan);
            }            
        }
OpenTap/Engine/TestStep.cs
@@ -16,6 +16,7 @@
using System.Threading;
using System.Runtime.CompilerServices;
using System.Runtime.ExceptionServices;
using OpenTap.Addin.Annotation;
namespace OpenTap
@@ -34,9 +35,22 @@
    {
        #region Properties
        private TestPlan root;
        protected TestPlan root;
        [XmlIgnore]
        public TestPlan Root { get => root; set => root = value; }
        public TestPlan Root
        {
            get => root;
            set
            {
                root = value;
                OnRootChanged(value);
            }
        }
        protected virtual void OnRootChanged(TestPlan plan)
        {
        }
        public StepResult Result { get; set; }
@@ -97,6 +111,7 @@
                              "replaced with it's current value in some views.", Group: "Common", Order: 20001, Collapsed: true)]
        [Unsweepable]
        [MetaData(Frozen = true)]
        [IgnoreVariable]
        public string Name
        {
            get => name;
@@ -206,6 +221,7 @@
        private string description;
        [Display("描述")]
        [IgnoreVariable]
        public string Description
        {
            get => description;
OpenTap/Engine/TestStepList.cs
@@ -2,6 +2,7 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, you can obtain one at http://mozilla.org/MPL/2.0/.
using OpenTap.Addin;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
@@ -83,6 +84,8 @@
            }
        }
        public ObservableCollection<TestVariable> Variables { get; set; } = new ObservableCollection<TestVariable>();
        /// <summary>
        /// When true, the nesting rules defined by <see cref="AllowAsChildInAttribute"/> and 
        /// <see cref="AllowAnyChildAttribute"/> are checked when trying to insert a step into 
Plugin/AddInPlugin/AddInPlugin.csproj
@@ -45,14 +45,14 @@
  </ItemGroup>
  <ItemGroup>
    <ProjectReference Include="..\..\..\..\VisualStudio\UtilLib\UtilLib.csproj" />
    <ProjectReference Include="..\..\..\ViHelper\ViHelper.csproj" />
    <ProjectReference Include="..\..\OpenTap\Engine\Tap.Engine.csproj" />
    <ProjectReference Include="..\..\UtilLib\UtilLib.csproj" />
    <ProjectReference Include="..\..\ViHelper\ViHelper.csproj" />
  </ItemGroup>
  <ItemGroup>
    <Reference Include="LabViewRunner">
      <HintPath>..\..\ViHelper\Libs\LabViewRunner.dll</HintPath>
      <HintPath>..\..\..\..\..\Users\chrry\Desktop\builds\Untitled Project 1\My Interop Assembly\LabViewRunner.dll</HintPath>
    </Reference>
    <Reference Include="NationalInstruments.LabVIEW.Interop">
      <HintPath>..\..\..\..\..\Program Files\National Instruments\Shared\LabVIEW Run-Time\2020\NationalInstruments.LabVIEW.Interop.dll</HintPath>
Plugin/AddInPlugin/Util/TestPlanRunAddIn.cs
@@ -1,5 +1,6 @@
using DynamicExpresso;
using OpenTap;
using OpenTap.Addin;
using System;
namespace AddInPlugin.Util
@@ -15,6 +16,8 @@
            target.SetVariable("Parameters", run.ParametersRuntime);
            target.SetVariable("Step", step);
            target.SetVariable("Locals", run.LocalsRuntime);
            var context = step?.GetParent<ContextStep>();
            if (context != null)
            {