How to show user prompts from within the plugin code?

OpenTAP provides the ability to provide user prompts from within the plugin code.
This can be asking a simple yes or no question or it can be asking for part details like a serial number etc.

For simple prompts


Use the built-in DialogStep which is part of the OpenTAP SDK.
You can provide a title, message, and a pre-defined choice of responses(Ok/Cancel and Yes/No)

For prompts showing custom inputs


Use the UserInput class provided by the OpenTAP SDK. When using the TAP editor, it shows up as a dialog, and when using the CLI, it automatically comes up as a user prompt.

The following code contains 2 examples.

  1. Prompt that asks the user to reset a DUT and confirm
  2. Prompt which asks the user to enter a DUT serial number.
public enum WaitForInputResult
    {
        // The number assigned determines the order in which the buttons are shown in the dialog.
        Cancel = 2, Ok = 1
    }

    // This describes a dialog that asks the user to reset the DUT.
    class ResetDutDialog
    {
        // Name is handled specially to create the title of the dialog window.
        public string Name { get { return "DUT Reset"; } }

        [Layout(LayoutMode.FullRow)] // Set the layout of the property to fill the entire row.
        [Browsable(true)] // Show it event though it is read-only.
        public string Message { get { return "Please reset the DUT. Click OK to confirm."; } }

        [Layout(LayoutMode.FloatBottom | LayoutMode.FullRow)] // Show the button selection at the bottom of the window.
        [Submit] // When the button is clicked the result is 'submitted', so the dialog is closed.
        public WaitForInputResult Response { get; set; }
    }

    // This describes a dialog that asks the user to enter the serial number.
    class EnterSNDialog
    {
        // Name is handled specially to create the title of the dialog window.
        public string Name { get { return "Please enter serialnumber"; } }
        
        // Serial number to be entered by the user.
        [Display("Serial Number")]
        public string SerialNumber { get; set; }

    }

    // This example shows how to use UserInput to get user input. 
    // In TAP GUI it appears as a dialog box. In TAP CLI the user is prompted.
    [Display("User Input Example", Groups: new[] { "Examples", "Plugin Development", "GUI" },
        Description: "This example shows how to use UserInput.")]
    public class UserInputExample : TestStep
    {
        [Display("Use Timeout", "Enabling this will make the dialog close after an amount of time.", Group: "Timeout", Order: 1)]
        public bool UseTimeout { get; set; }

        [EnabledIf("UseTimeout", true)]
        [Unit("s")]
        [Display("Timeout", "The dialog closes after this time.", Group: "Timeout", Order: 2)]
        public double Timeout { get; set; }

        public UserInputExample()
        {
            UseTimeout = true;
            Timeout = 5;
            Rules.Add(() => Timeout > 0, "Timeout must > 0s.", "Timeout");
        }

        public override void Run()
        {
            try
            {
                var timeout = UseTimeout ? TimeSpan.FromSeconds(Timeout) : TimeSpan.Zero;

                // Dialog/prompt where user has option to select "OK" or "Cancel".
                // The message/query is set by the Message property. Selection options are defined by WaitForInputResult.
                var dialog = new ResetDutDialog();
                UserInput.Request(dialog, timeout);
                
                // Response from the user.
                if (dialog.Response == WaitForInputResult.Cancel)
                {
                    Log.Info("User clicked Cancel.");
                    return;
                }
                Log.Info("User clicked OK. Now we prompt for DUT S/N.");

                var snDialog = new EnterSNDialog();
                // Dialog/prmpt where user has option to enter a string as DUT S/N
                UserInput.Request(snDialog, timeout);
                
                Log.Info("DUT S/N: {0}", snDialog.SerialNumber);
            }
            catch (TimeoutException)
            {
                Log.Info("User did not click. Is he sleeping?");
            }
        }
    }
5 Likes

Is it possible to present a picture/image in the UserInput dialog/prompt?
This doesnt work:
[Layout(LayoutMode.FullRow, 5)]
[Browsable(true)]
public Image Picture { get; private set; }

If not possible I think it should be a new feature.
Also, be able to control font size and also control dialog background color (e.g. provide a dialog/prompt with a red background) would be very useful.

2 Likes

It might even be nice to be able to call a WPF window or other window type directly from a teststep. This would require changes to opentap as well. Right now you have basically no control over the layout of the UserInput dialog, and it usually ends up looking pretty rough

1 Like

There is an example here mostly created by @kaushik.santhanam that people have been happy with. It showing both using an image and a fully custom window:

        private class WpfDialogHost
        {
            // NOTE:
            // This is a temporary workaround to allow the teststep itself to display
            // a dialog while running within a server process.
            // Ultimately this has to be replaced completely by a dialog service that
            // will be presenting the interactive part and the teststep only issuing
            // the calls and handling the results.

            private static bool IsDone { get; set; }
            private static System.Windows.Application WpfApp = null;
            public static BarcodeScannerDialog StepReference { get; set; }
            public static bool? DialogResult { get; set; }

            public static ScannerDialog Dialog { get; private set; }

            [STAThread]
            public static void ShowDialog()
            {
                // Create the Application context on the first go
                if (WpfApp == null)
                {
                    Thread thread = new Thread(new ThreadStart(() =>
                    {
                        WpfApp = new System.Windows.Application();
                        WpfApp.ShutdownMode = ShutdownMode.OnExplicitShutdown;
                        WpfApp.Run();
                    }));
                    thread.SetApartmentState(ApartmentState.STA);
                    thread.IsBackground = true; // Make sure we do not produce a zombie thread: A Background thread is automatically terminated on process shutdown (and other than that identical to a foreground thread).
                    thread.Start();

                    while (WpfApp == null)
                        System.Threading.Thread.Sleep(150);
                }

                // Use the Application context to create display the dialog
                IsDone = false;
                WpfApp.Dispatcher.Invoke(() =>
                {
                    Dialog = StepReference.CreateDialog();
                    DialogResult = Dialog.ShowDialog();
                    IsDone = true;
                });

                while (!IsDone)
                    System.Threading.Thread.Sleep(150);
            }
        }
    }
`
3 Likes

By using this way, how to store the test data into the folder named by the serial number? I know I can use the meta ID to create the folder.

1 Like

Hello @smartkuku and Welcome to the OpenTAP forum!

My suggestion is the following:

Create a ComponentSettings where your Serial Number is defined and make sure to mark it as MetaData, I have added the following to my project:

[Display("MySettings1", Description: "Add a description here")]
public class MySettings1 : ComponentSettings<MySettings1>
{
    [MetaData(true)]
    [Display("Serial", Description: "Serial Number.")]
    public string Serial { get; set; }

    public MySettings1()
    {
        Serial = "12345";
    }
}

In my Engine settings I checked the “Allow Metadata Prompt”, however you can use the steps above to prompt the user for the Serial:

Finally, on my CSV Result listener I used the tag <Serial> as part of the File Path:

And you can see that there is a folder created for each Serial Number that I have tested, along with some results that were generated before I added the <Serial> tag :

image

Let me know if this helps you,

Carlos Montes

1 Like

Hi @carlos.montes ,

Thanks for your responding. I implement this requirement by following your steps and found a possible bug.

Let me first describe the possible bug.

The folder is created by name defined in last run not current run. Here is the procedure, at first time, I run the test plan, and enter the serial “1000”, the folder name is “1000”; run the test plan again, the serial number I entered is 1001, the folder name is still “1000”; run the test plan again, enter serial number “1002”, the folder name is “1001”.

This bug exists when I using the test step to enter the serial number or using the “Allow Metadata Prompt”.

Below is the detail How I implement the code

    [SettingsGroupAttribute("Bench", Profile: true)]
    [Display("DUT Custom Settings", Description: "A collection of different instances of DUT settings.")]
    [XmlInclude(typeof(CustomBenchSettings))]
    public class DUTCustomSetting : ComponentSettings<DUTCustomSetting>
    {
        // No code necessary.
        [MetaData(true)]
        [Display("Serial", Description: "Serial Number.")]
        public string Serial { get; set; }

        public DUTCustomSetting()
        {
            Serial = "";
        }
    }

    [Display("Please enter serial number")]
    class SNInputDialog
    {
        // Serial number to be entered by the user.
        [Display("Serial Number")]
        public string SerialNumber { get; set; }
    }

    // This example shows how to use UserInput to get user input. 
    // In TAP GUI it appears as a dialog box. In TAP CLI the user is prompted.
    [Display("Serial Input", Groups: new[] { "Examples", "Plugin Development", "GUI" },
        Description: "This example shows how to use UserInput.")]
    public class SerialInput : TestStep
    {
        [Display("Use Timeout", "Enabling this will make the dialog close after an amount of time.", Group: "Timeout", Order: 1)]
        public bool UseTimeout { get; set; }

        [EnabledIf("UseTimeout", true)]
        [Unit("s")]
        [Display("Timeout", "The dialog closes after this time.", Group: "Timeout", Order: 2)]
        public double Timeout { get; set; }

        public SerialInput()
        {
            UseTimeout = true;
            Timeout = 1000;
            Rules.Add(() => Timeout > 0, "Timeout must > 0s.", "Timeout");
        }

        public override void Run()
        {
            try
            {
                var timeout = UseTimeout ? TimeSpan.FromSeconds(Timeout) : TimeSpan.Zero;

                // Dialog/prompt where user has option to select "OK" or "Cancel".
                // The message/query is set by the Message property. Selection options are defined by WaitForInputResult.

                var snDialog = new SNInputDialog();
                // Dialog/prompt where user has option to enter a string as DUT S/N
                UserInput.Request(snDialog, timeout);
                DUTCustomSetting.Current.Serial = snDialog.SerialNumber;
                
                Log.Info("DUT S/N: {0}", DUTCustomSetting.Current.Serial);
            }
            catch (TimeoutException)
            {
                Log.Info("User did not click. Are they sleeping?");
            }
        }
    }

Hi @carlos.montes , Can you reproduce it? any suggestion on this issue?

Hello @smartkuku, Yes, I can reproduce it. I am working with R&D on a solution for this. I will let you know as soon as I get a resolution. Thanks for your patience.

Hello @smartkuku,

We have confirmed the bug when using component settings and I have filed an issue with R&D.

In the meantime, there is a work around.

First you need to create a DUT and move the Serial from the Component Settings to the new created DUT, also in order to propagate the serial number change we need to add a private property and also call the OnPropertyChanged() method on the set, here is an example of the DUT I created:

namespace OpenTap.Plugins.MyPlugin1
{
[Display(“MyDut1”, Group: “OpenTap.Plugins.MyPlugin1”, Description: “Add a description here”)]
public class MyDut1 : Dut
{

    private string _Serial;
    [MetaData(true, "Serial")]
    [Display("Serial", Description: "Serial Number.")]
    public string Serial
    {
        get
        {
            return _Serial;
        }
        set
        {
            _Serial = value;
            OnPropertyChanged("Serial");
        }
    }

    /// <summary>
    /// Initializes a new instance of this DUT class.
    /// </summary>
    public MyDut1()
    {
        Name = "MyDUT";
        // ToDo: Set default values for properties / settings.
        Serial = "12345";
    }
}

}

Now, make sure you add at least one test that makes use of the new DUT, I created a step that shows the serial number that the user entered.

[Display("Display Serial Number", Group: "OpenTap.Plugins.MyPlugin1", Description: "Insert description here")]
public class Step : TestStep
{
    #region Settings
    public MyDut1 MyDut1 { get; set; }
    #endregion

    public Step()
    {
        // ToDo: Set default values for properties / settings.
    }

    public override void Run()
    {
        // ToDo: Add test case code.
        Log.Info("Serial: " + MyDut1.Serial);

        // If no verdict is used, the verdict will default to NotSet.
        // You can change the verdict using UpgradeVerdict() as shown below.
        UpgradeVerdict(Verdict.Pass);
    }
}

Again make sure your CSV Result listener has the following File Path string:

Results\<Serial>\<ResultName>-<Date>-<Verdict>.csv

Now make sure the Allow Metadata Prompt is enabled in Engine settings, and after running your test sequence you should get the results in the folder named as the serial number you provided on the metadata prompt.

The bug found using component settings also applies to a custom step that provides the user with a prompt to enter the serial number, so please use the method using the DUT and the Allow metadata prompt from the engine to input data from the user.

Let me know if this helps,

Carlos Montes

1 Like

Hi Carlos,

Yes, it works. Still wondering if you will fix the issue, I plan to write a test step to ask user input Serial Number and meanwhile show operator the DUT picture.

I have another question about the result listener. With I use the in the csv listener file path, the verdict is the whole test plan verdict, not the test step verdict.

For example, I have a test plan to test DUT many parameters, each parameter use a different test step and generate different csv file. If one test fails, the whole test plan fails. This will make all the csv file suffix with Fail. I have debugged the test sequence, when the test plan is not complete, the csv file generated is suffixed with Verdict NotSet, once the test plan complete, all the csv file will be renamed with test plan Verdict.

I think separate the test result is necessary, my DUT may have ten to twenty test steps.

Thanks

1 Like

This feature was added in OpenTAP 9.16! Pictures can be included in user dialogs. There is an SDK example here: opentap/OpenTapPictureExample.cs at main · opentap/opentap · GitHub

2 Likes

Hello @smartkuku, alexander just mentioned that the pictures can now be included in user dialogs.

To use Test Step verdict as part of the file name, I have created a feature request on the CSV Result Listener. I will let you know once we have a resolution on this request.

Thanks,

Carlos Montes

1 Like

Hi,

I have just update the OpenTAP to latest version (9.18), the issue still there. I am wondering how do you store the test result with serial number?

@rolf_madsen do you have any ideas here?

I don’t have any really good suggestions. Normally I’d suggest using ‘PromptUser metadata’, but since it is also wanted to show a picture it gets a bit complicated.

Maybe it could be done by using Input/Output - One step reads gets serial number and assigns to the next step which could be the parent step for the rest of the plan.