7

End Goal for serializing FirefoxDriver (my question here) = making WebDriver faster!!

Below is a link that describes how to serialize an object. But it requires you implement from ISerializable for the object you are serializing. What I'd like to do is serialize an object that I did not define--an object based on a class in a 3rd party assembly (from a project reference) that is not implementing ISerializable. Is that possible? How can this be done?

http://www.switchonthecode.com/tutorials/csharp-tutorial-serialize-objects-to-a-file

Property (IWebDriver = interface type):

private IWebDriver driver;

Object Instance (FireFoxDriver is a class type):

driver = new FirefoxDriver(firefoxProfile);

================

3/21/2012 update after answer posted

Why would this throw an error? It doesn't like this line:

serializedObject.DriverInstance = (FirefoxDriver)driver;

...

Error:

Cannot implicitly convert type 'OpenQA.Selenium.IWebDriver' to 'OpenQA.Selenium.Firefox.FirefoxDriver'. An explicit conversion exists (are you missing a cast?)

Here is the code:

    FirefoxDriverSerialized serializedObject = new FirefoxDriverSerialized();
    Serializer serializer = new Serializer();
    serializedObject = serializer.DeSerializeObject(@"C:\firefoxDriver.qa");
    driver = serializedObject.DriverInstance;

    if (driver == null)
    {
        driver = new FirefoxDriver(firefoxProfile);
        serializedObject.DriverInstance = (FirefoxDriverSerialized)driver;
        serializer.SerializeObject(@"C:\firefoxDriver.qa", serializedObject);
    }

Here are the two Serializer classes I built:

public class Serializer
{
   public Serializer()
   {
   }

   public void SerializeObject(string filename, FirefoxDriverSerialized objectToSerialize)
   {
      Stream stream = File.Open(filename, FileMode.Create);
      BinaryFormatter bFormatter = new BinaryFormatter();
      bFormatter.Serialize(stream, objectToSerialize);
      stream.Close();
   }

   public FirefoxDriverSerialized DeSerializeObject(string filename)
   {
      FirefoxDriverSerialized objectToSerialize;
      Stream stream = File.Open(filename, FileMode.Open);
      BinaryFormatter bFormatter = new BinaryFormatter();
      objectToSerialize = (FirefoxDriverSerialized)bFormatter.Deserialize(stream);
      stream.Close();
      return objectToSerialize;
   }
}

[Serializable()]
public class FirefoxDriverSerialized : FirefoxDriver, ISerializable
{
    private FirefoxDriver driverInstance;
    public FirefoxDriver DriverInstance
    {
        get { return this.driverInstance; }
        set { this.driverInstance = value; }
    }

    public FirefoxDriverSerialized()
    {
    }

    public FirefoxDriverSerialized(SerializationInfo info, StreamingContext ctxt)
    {
        this.driverInstance = (FirefoxDriver)info.GetValue("DriverInstance", typeof(FirefoxDriver));
    }

    public void GetObjectData(SerializationInfo info, StreamingContext ctxt)
    {
        info.AddValue("DriverInstance", this.driverInstance);
    }
}

=================

3/23/2012 update #2 - fixed serialization/de-serialization, but having another issue

This fixed the calling code. Because we're deleting the *.qa file when we call the WebDriver.Quit() because that's when we chose to close the browser. This will kill off our cached driver as well. So if we start with a new browser window, we'll hit the catch block and create a new instance and save it to our *.qa file (in the serialized form).

    FirefoxDriverSerialized serializedObject = new FirefoxDriverSerialized();
    Serializer serializer = new Serializer();

    try
    {
        serializedObject = serializer.DeSerializeObject(@"C:\firefoxDriver.qa");
        driver = serializedObject.DriverInstance;
    }
    catch
    {
        driver = new FirefoxDriver(firefoxProfile);
        serializedObject = new FirefoxDriverSerialized();
        serializedObject.DriverInstance = (FirefoxDriver)driver;
        serializer.SerializeObject(@"C:\firefoxDriver.qa", serializedObject);
    }

However, still getting this exception:

Acu.QA.Main.Test_0055_GiftCertificate_UserCheckout:
SetUp : System.Runtime.Serialization.SerializationException : Type 'OpenQA.Selenium.Firefox.FirefoxDriver' in Assembly 'WebDriver, Version=2.16.0.0, Culture=neutral, PublicKeyToken=1c2bd1631853048f' is not marked as serializable.
TearDown : System.NullReferenceException : Object reference not set to an instance of an object.

The 3rd line in this code block is throwing the exception:

   public void SerializeObject(string filename, FirefoxDriverSerialized objectToSerialize)
   {
      Stream stream = File.Open(filename, FileMode.Create);
      BinaryFormatter bFormatter = new BinaryFormatter();
      bFormatter.Serialize(stream, objectToSerialize);  // <=== this line 
      stream.Close();
   }

====================

update #3 - 3/24/2012 - simplified FirefoxDriver instance by not passing anything to the constructor. I'll make it more complex later by passing in FirefoxProfile into the constructor. I still get the same exception as update #2.

Calling code:

FirefoxDriverSerialized serializedObject = new FirefoxDriverSerialized();
Serializer serializer = new Serializer();
try
{
    serializedObject = serializer.DeSerializeObject(@"C:\firefoxDriver.qa");
    driver = serializedObject.DriverInstance;
}
catch
{
    //driver = new FirefoxDriver(firefoxProfile);
    driver = new FirefoxDriver();
    serializedObject.DriverInstance = (FirefoxDriver)driver;
    serializer.SerializeObject(@"C:\firefoxDriver.qa", serializedObject);
}

Also had to add ": base()" to my constructor in my serialized object class:

[Serializable()]
public class FirefoxDriverSerialized : FirefoxDriver, ISerializable
{
    private FirefoxDriver driverInstance;
    public FirefoxDriver DriverInstance
    {
        get { return this.driverInstance; }
        set { this.driverInstance = value; }
    }

    public FirefoxDriverSerialized() : base()
    {
    }

    public FirefoxDriverSerialized(SerializationInfo info, StreamingContext ctxt)
    {
        this.driverInstance = (FirefoxDriver)info.GetValue("DriverInstance", typeof(FirefoxDriver));
    }

    public void GetObjectData(SerializationInfo info, StreamingContext ctxt)
    {
        info.AddValue("DriverInstance", this.driverInstance);
    }
}

=============

Update #4 - just documenting stub signatures for OpenQA.Selenium.Firefox.FirefoxDriver class

using OpenQA.Selenium;
using OpenQA.Selenium.Remote;
using System;

namespace OpenQA.Selenium.Firefox
{
    public class FirefoxDriver : RemoteWebDriver, ITakesScreenshot
    {
        // CLASS DATA MEMBERS 

        //public static readonly bool AcceptUntrustedCertificates;
        //public static readonly string BinaryCapabilityName;
        //public static readonly bool DefaultEnableNativeEvents;
        //public static readonly int DefaultPort;
        //public static readonly string ProfileCapabilityName;

        // CONSTRUCTORS

        //public FirefoxDriver();
        //public FirefoxDriver(FirefoxProfile profile);
        //public FirefoxDriver(ICapabilities capabilities);
        //public FirefoxDriver(FirefoxBinary binary, FirefoxProfile profile);
        //public FirefoxDriver(FirefoxBinary binary, FirefoxProfile profile, TimeSpan commandTimeout);

        // PROPERTIES

        protected FirefoxBinary Binary { get; }
        protected FirefoxProfile Profile { get; }

        // METHODS

        //protected override RemoteWebElement CreateElement(string elementId);
        //public Screenshot GetScreenshot();
        //protected void PrepareEnvironment();
        //protected override void StartClient();
        //protected override void StopClient();
    }
}
JustBeingHelpful
  • 18,332
  • 38
  • 160
  • 245
  • I am still in the same issue, "bFormatter.Serialize(stream, objectToSerialize);" throws a SerializationException. You found a way to solve it? – blueomega Jun 13 '12 at 14:15
  • I don't remember that exact issue, but if your object inherits another object, the ancestor objects must implement ISerializable .. I would like to look at this again soon and finish what i started – JustBeingHelpful Jun 16 '12 at 20:06

2 Answers2

5

Create your own custom object that derives from ISerializable and the Object you want to serialize and serialize that custom object. This specific example won't work if the 3rd Party object is Sealed (there are other ways which can still be used with ISerializable).

Updated Per Request

Updated Per Question Update

public class MyFirefoxDriver : FirefoxDriver, ISerializable
{
  public MyFirefoxDriver(<Interface/Class> firefoxProfile)
    :base(firefoxProfile)
  {
  }

  void GetObjectData(SerializationInfo info, StreamingContext context)
  {
    // Properties needing to be serialized
    info.AddValue("SomeProperty", base.SomeProperty);
  }
}

Update 2

Your new code is confusing me. I think you're looking for..

serializedObject = serializer.DeSerializeObject(@"C:\firefoxDriver.qa");
driver = serializedObject;

This is because FirefoxDriverSerialized is a FireFoxDriver.

Update 3

It is important to note that constructors are not called when an object is deserialized. This means that things that are normally constucted/set in the constructor won't be upon deserialization, which usually results in a NullReferenceException. The way around that is to implement ISerializable and explicity set the objects needed for the class to work (both for GetObjectData and the special deserializer constructor). This can be most difficult if the understanding of the object in question is not simple nor if we don't have the source for it.

It is important to stress that you need to implement both GetObjectData as well as the special constructor when ISerializable is added to a class. The compiler will warn you if GetObjectData is missing, but since it is impossible to enforce the implementation of a constructor, no warnings will be given if the constructor is absent and an exception will be thrown when an attempt is made to deserialize a class without the constructor.

public class MyObject
{
  public MyObject()
  {
    this.SomeOtherObject = new MyObject2();
  }
  public string Name { get; set; }
  public MyObject2 SomeOtherObject { get; set; }
}

public class MyObjectSerializable : MyObject, ISerializable
{
  protected MyObjectSerializable(SerializationInfo si, StreamingContext context) 
  {
    // base() is never called during deserialization
    // so use the special ISerializable constructor to set the value of the object
    // why not add it to the si.AddValue?
    // because, most likely in this question, it is not a [Serializable] object either
    // so we have to treat it differently as well
    this.SomeOtherObject = new MyObject2();
  }

  public override void GetObjectData(SerializationInfo si, StreamingContext context)
  {
    si.AddValue("Name", Name);
  }
}
Erik Philips
  • 53,428
  • 11
  • 128
  • 150
  • can you show me an example of what the class signature would look like in your answer? – JustBeingHelpful Mar 21 '12 at 20:10
  • I'll show you some example code... it's actually of an interface type, not a class type ... not sure which one I should use. See my edit for details. – JustBeingHelpful Mar 21 '12 at 20:12
  • So given my update, would I want to derive from FirefoxDriver then? – JustBeingHelpful Mar 21 '12 at 20:16
  • 1
    I'll take back my original comment as your posted example shows how inheritance helps by providing simple access to the properties from the base object. It's still not neccessary to inherit - but it's a good looking solution. – pmartin Mar 21 '12 at 20:16
  • This is true, a wrapper class could be used. I prefer to encapsulate the object I need instead of writing a wrapper if the class is not sealed. Additionally, my answer only is reference how to use ISerializable, not other methods that do not require implementing ISerializable – Erik Philips Mar 21 '12 at 20:19
  • see my update ... one line doesn't like the type I'm casting it to – JustBeingHelpful Mar 21 '12 at 20:51
  • I added a few things to your code from your last updated answer in my Update #2. See the new exception. I'll have to try a real simple example with this architecture from that article based on the code I have now. I'm still guessing there are nested objects in our FirefoxDriver object that don't inherit ISerializable. I remember running into this with Java years ago. That's a guess though. – JustBeingHelpful Mar 23 '12 at 23:25
  • 1
    Adding ISerializable means you have to take care of saving all the simple properties (string, int, float, double, guid, etc) you need (serialization) and instantiating all the objects the within the object itself (for deserialization). Updated 3 with example. – Erik Philips Mar 24 '12 at 06:53
  • I found one other discrepancy/mistake, so I fixed that in update #3. It sounds like I have to make sure I list all properties. Per your last comment, I will see what the FirefoxDriver class has for properties and put those into my custom class, and make a 4th update. We'll see if that fixes things. – JustBeingHelpful Mar 24 '12 at 16:38
1

It isn't required to inherit from ISerializable. By default, the object must either inherit from ISerializable or be decorated with the SerializableAttribute. Without either of those, your best option is to use a SurrogateSelector. This will allow you to tell the serializer to serialize another object in its place based on how you define your surrogate.

Eric
  • 1,737
  • 1
  • 13
  • 17
  • You seem to know a little more about these serialized objects. Does my answer seem correct to you? I ran into this problem with Java years ago, and this is what I got out of it. I upvoted you so you can share your knowledge on this subject. – JustBeingHelpful Mar 23 '12 at 23:37