DateTime fields in Test Automation GUI broken

Hi everybody, new member here, just started using OpenTap at our company for testing our hardware.

While using the OpenTap software, we started to notice that the DateTime fields when used in GUI seem to be quite broken. They basically do not update the underlying variable after the user inputs a (new) value.
This is not only apparent in test steps, but I have also found the same behavior in one of the examples, called ExampleSettings (which is the only one that uses a DateTime field).

To reproduce:

  • Open the OpenTap Test Automation with the SDK Examples compiled.
  • Go to Settings > Example Settings
  • Change the value of the Creation Time field.
  • Move to another tab and back to the Example Settings. (Or Press OK to close, and reopen the settings again)
  • Observe the Creation Time again shows the initial (default) value, NOT the manually entered value.

When using a DateTime field in our own plugins, we observe that the value entered by the user in the GUI does not get stored in the underlying variable. It is possible to change the underlying variable programmatically, and this will be reflected in the value shown by the GUI, but the value changed in the GUI never gets used.

Is this already known bug, and will it be fixed at some point?

Hi andries, and welcome to the forum!

I saw the issue you opened on GitHub and already responded there, but I will post my answer here as well for discoverability:

This happens because there is no IStringValueAnnotation implementation for DateTime, which the GUI uses to parse object values to and from strings. OpenTAP supports adding annotators for any type you could need, but since OpenTAP already supports Timespan, I think it should support DateTime as well. I have opened a PR with a proposed implementation here

Here is a small example implementation for how you could add support for DateTime yourself:

/// <summary>
/// This class defines how a DateTime value is converted to and from a string
/// </summary>
class DateTimeAnnotation : IStringValueAnnotation
{
    private AnnotationCollection annotation;
    public DateTimeAnnotation(AnnotationCollection annotation)
    {
        this.annotation = annotation;
    }
    public string Value
    {
        get
        {
            if (annotation?.Get<IObjectValueAnnotation>(from: this).Value is DateTime dt)
                return dt.ToString(CultureInfo.CurrentCulture);
            return "";
        }
        set
        {
            if (annotation == null) return;
            if (DateTime.TryParse(value, out var dt))
                annotation.Get<IObjectValueAnnotation>(from: this).Value = dt;
        }
    }
}

/// <summary>
/// This is an annotator. It is invoked by OpenTAP to annotate objects with data when `AnnotationCollection.Annotate` is called.
/// This is mostly used by GUIs to display data, and to define how data is written back into objects. 
/// </summary>
public class MyCustomAnnotator : IAnnotator
{
    public void Annotate(AnnotationCollection annotation)
    {
        // If this member is an instance of DateTime, we should add our DateTimeAnnotation to the annotation collection.
        // In this way, later invocations of 'annotation.Get<IStringValueAnnotation>()' will return this instance of our
        // DateTimeAnnotation, and the GUI will use our implementation to read/write dates.
        var mem = annotation?.Get<IMemberAnnotation>();
        if (mem?.ReflectionInfo?.DescendsTo(typeof(DateTime)) == true)
            annotation.Add(new DateTimeAnnotation(annotation));
    }

    public double Priority => 2;
}

We also have an example for implementing support for IP addresses in the SDK examples project:

1 Like