surfmuggle
surfmuggle

Reputation: 5944

How to convert a json array from ExecuteScript

Scope

I want to do automated web testing with Selenium and execute JavaScript. The string func1 contains my js-function and it is passed to ExecuteScript(func1) it returns an array of objects which look like this {label:'start', time: 121}.

I want to cast the result of ExecuteScript into List<timings>

var result = jsExecutor.ExecuteScript(func1); 
var list = (ReadOnlyCollection<object>)result;
var timings = (List<Timing>)list;

I received the error

 Cannot convert type 'System.Collections.ObjectModel.ReadOnlyCollection<object>' 
 to 'System.Collections.Generic.List<CoreConsoleApp.TestExecutions.Timing>' 

This is func1

string func1= @"var t = window.performance.timing;
  var timings = [];
  timings.push({ label: 'navigationStart', time: t.navigationStart  });
  timings.push({ label: 'PageLoadTime', time: t.loadEventEnd - t.navigationStart  });

return timings;" // result is an array of js-objects

The code below is a snippet of the selenium part

 public struct Timing
 {
   public string label;
   public int time;            
 }

 using (var driver = new FirefoxDriver())
 {
  ...
  var jsExecutor = (IJavaScriptExecutor)driver;
  var result = jsExecutor.ExecuteScript(func1); 
  var list = (ReadOnlyCollection<object>)result;
 }

Questions

The selenium docs state that ExecuteScript attempts to return a List for an array. Func1 should return array of {label: string, time: number} it should be easy to cast the result var list = (ReadOnlyCollection<object>)result into List<string,int> timings = (List<timings>)list;

More Info

Start Firefox var driver = new FirefoxDriver() open a URL driver.Navigate().GoToUrl(url); find a certain button IWebElement button = driver.FindElement(By.Name("btnK")); submit the form button.Submit(); After the submission execute JavaScript ExecuteScript(func1) and write the result to the console

The above all works. But i have trouble to cast the JavaScript into a list of c# objects.

So my workaround is this

var result = jsExecutor.ExecuteScript(func1); 
var list = (ReadOnlyCollection<object>)result;

foreach (object item in list)
{   
    var timing = (Dictionary<string, object>)item;
    foreach(KeyValuePair<string, object> kvp in timing)
    {
       Console.WriteLine("Key = {0}, Value = {1}", kvp.Key, kvp.Value);
    }                    
 }

This puts out:

 Key = label, Value = navigationStart
 Key = time, Value = 1529720672670
 Key = label, Value = PageLoadTime
 Key = time, Value = 1194
 Key = label, Value = DOMContentLoadedTime
 Key = time, Value = 589
 Key = label, Value = ResponseTime

Upvotes: 0

Views: 1470

Answers (3)

surfmuggle
surfmuggle

Reputation: 5944

The problem is that the structure of var result = jsExecutor.ExecuteScript(func1); is different than expected.

The result looks similar to List<object>() i created this program:

var dictionary1= new Dictionary<string, object>();

dictionary1.Add("label", (object)"PageloadTime");
dictionary1.Add("time", (object)"1087");    
var dictionary2= new Dictionary<string, object>()
{ 
    {"label", (object)"DOMContentLoadedTime"},
    {"time", (object)"494"}
};

var list = new List<object>(); // this is the structure of result
list.Add(dictionary1);
list.Add(dictionary2);  
list.Dump();

If you call list.Dump(); it looks like this:

Output of List of dictionaries

As you can see the structure contains n Dictionaries of this type Dictionary<string, object>(); Therefore i tried two nested loops to better understand the nesting of the objects

Object result = jsExecutor.ExecuteScript(func1); // 

var resultCollection = (ReadOnlyCollection<object>)result;       
foreach (Dictionary<string, object> item in resultCollection)
{
   Console.WriteLine("{0} ", item.GetType()); 
   foreach (KeyValuePair<string, object> kvp in item)
   {
     Console.WriteLine("Keys: {0} Values: {1}", kvp.Key, kvp.Value);
   }
}

And finally

// create a structure similar to result
var list = new List<object>(); 
list.Add(dictionary1);
list.Add(dictionary2);  

var timings = new List<Timing>();

foreach (Dictionary<string, object> dict in list)
{       
    Console.WriteLine("Label = {0}  Value ={1} "
              , (string)dict["label"]
              , (string)dict["time"]);
    // create a timing object
    var t = new Timing();
    t.label = (string)dict["label"];
    t.time = (string)dict["time"];
    timings.Add(t);
}   

Since sometimes i got a invalid cast exception for (int)dict["time"] i changed the property time from int to string.

Update

As Steven Chong suggested i changed the function func1 to return a string:

public static string jsGetTiming(){

    // func1 is the javascript function that will get executed
    string func1= @"var t = window.performance.timing; 
    var PageLoadTime =  t.loadEventEnd - t.navigationStart;            
    var ResponseTime = t.responseEnd - t.requestStart;            

    var timings = 'navigationStart=' + t.navigationStart;
        timings += '|PageLoadTime=' + PageLoadTime;        
        timings += '|ResponseTime=' + ResponseTime;            
     return timings;";

   return func1;
} 

To execute the string func1 as a function you can call it like this

 Object result = jsExecutor.ExecuteScript(MyClass.jsGetTiming());
 // result is a string and looks like this
result = 
 navigationStart=1534377023791|PageLoadTime=943  
   |DOMContentLoadedTime=434|ResponseTime=337
   |Response=269|DomainLookup=0
   |LoadEvent=5|UnloadEvent=8
   |DOMContentLoadedEvent=17 

Upvotes: 0

teamchong
teamchong

Reputation: 1396

try serialize your data before return.

string func1= @"var t = window.performance.timing;
var timings = [];
timings.push({ label: 'navigationStart', time: t.navigationStart  });
timings.push({ label: 'PageLoadTime', time: t.loadEventEnd - t.navigationStart  });

return JSON.stringify(timings);" // result is string

and access your data in c# using Json.NET

using Newtonsoft.Json.Linq;

string result = Convert.ToString(jsExecutor.ExecuteScript(func1));
Console.Write("result = " + result);
List<Timing> list = JToken.Parse(result).ToObject<List<Timing>>();
Console.Write("result = " + JToken.FromObject(list));

// or access using dynamic
dynamic dynamicList = JToken.Parse(jsExecutor.ExecuteScript(func1)); 
for (var i = 0; i < dynamicList.Count; i++) {
   Console.Write(dynamicList[i]);
}

Upvotes: 1

bit
bit

Reputation: 4487

You need to deserialize the result into the required List<Timings>.

  1. Refer the package JSON.Net from here
  2. Deserialize the result (assuming to be a string) as follows:

    List<Timing> timings = JsonConvert.DeserializeObject<List<Timing>>(result);

Here is some basic help on serialization: https://www.newtonsoft.com/json/help/html/SerializingJSON.htm

Upvotes: 2

Related Questions