Monday, December 2, 2013

Rails, Ajax Requests, and Playing Nicely with respond_to do |format|

So this morning I'm working on making a highly interactive dialog for +Rentables. The dialog is initially loaded via ajax using the very familiar respond_to block where render_ajax_dialog is a helper specific to my project.
respond_to do |format|
  format.html {}
  format.js { render_ajax_dialog }
end
Now when the user clicks certain buttons on the dialog, I want to refresh the dialog from the server (i.e. Ajax) without reloading the underlying page and without the updating controller having to know anything about the dialog - basically the blind interacting with the blind.
Pop quiz: How do the blind interact in the stateless hyper text transfer protocol world?
My solution is to pass a parameter to the updating controller (:redisplay) with the URL to the action which renders the dialog. It seems a bit messy, right? Maybe someone reading this has a better idea.
class DialogLoadingController < ApplicationController
  def show
    # load some foo

    respond_to do |format|
      format.html
      format.js { render_ajax_dialog }
    end
  end
end


class UpdatingThingsController < ApplicationController
  def update
    # update some foo

    respond_to do |format|
      format.html { redirect_to pop_return_to_or(foo_path) }
      format.js { 
        if (redisplay = params[:redisplay])
          show_dialog_url = ensure_format_js(redisplay)
          render :js => "$.ajax({url: '#{show_dialog_url}', cache: false});"
        end
      }
    end
  end


  private

  # In order for +respond_to+ to pick JS we need to ensure the URL
  # includes '.js'. The first call will not but subsequent calls will.
  #   ensure_format_js('/path/to/foo?are_you_awesome=true')
  #   # => /path/to/foo.js?are_you_awesome=true
  #
  #   ensure_format_js('/path/to/foo.js?are_you_awesome=true')
  #   # => /path/to/foo.js?are_you_awesome=true
  #
  # @param [String] url the redisplay URL
  # @return [String] Url with format .js specified
  def ensure_format_js(url)
    return nil unless url

    # split off query string, if any
    url = url.split '?'

    # check if JS specified
    if url.first.match(/.*\.js$/)
      # JS specified, re-join URL and query
      url.join('?')
    else
      # add JS and re-join
      url.first + '.js?' + url.last
    end
  end
end

Wednesday, November 20, 2013

Using Ruby to Delete Unused Files in a Rails Project

I have amassed a rather large set of icons for a RoR project I'm working. Initially I just checked all these files into the repository and everything was great. Gradually the list of icons (most of them not used) got longer and longer. Now I'm to the point where running rake assets:precompile in my production environment takes forever. It's time to defriend all those unused icons.

The first step is to get a list of all the icons I actually use. Lucky for me, every icon is conveniently defined in a CSS file.

def referenced_icons(subdir)
  path_to_css = File.join(Dir.pwd, 'app/assets/stylesheets/all/icons.css.scss')

  # Open the CSS file and scan for lines referencing PNG 
  # files and get just the filename
  used_icons = File.open(path_to_css)  do |f| 
    f.grep(/png/).map{|x| x[Regexp.new("(?<=#{subdir}\/).*.png")]}
  end

  # Beat the list into a smaller package
  used_icons.compact.uniq.sort
end

Great! I can get a list of all the PNG files I actually use. Now who am I going to defriend.

def not_my_friends(subdir)
  used_icons = referenced_icons(subdir)
  path_to_pngs = File.join(Dir.pwd, "app/assets/images/icons/#{subdir}/*.png")
  
  # Reject files not in the used_icons list
  Dir.glob(path_to_pngs).reject do |f|
    b = Pathname(f).basename.to_s
    used_icons.include?(b)
  end
end

And now for the defriending:

not_my_friends('some_dir').each{|x| File.delete(x)}

It feels good to lighten the load (by about 2400 unused icons!). Now back to work!

Tuesday, March 16, 2010

Using Reflection to Automatically Parse Command Line Arguments

It's been a while since I wrote a real command line application (long before C# got reflection) and haven't had to parse command line arguments in quite some time. Here is my new ParseArgs() function using reflection. The basic assumption is that arguments are being used to set static Properties of the entry point class.

public static void ParseArgs (string [] args)
{
    // Arguments are assumed to come in pairs, e.g.:
    //    program /arg1 arg_value1 /arg2 arg_value2
    Validate.True ("Number of arguments is even",
        0 == (args.Length & 1));

    MethodInfo method_info;
    string convert_method;
    object [] parameters = new object [1];
    Type [] string_type = { typeof (String) };

    // Get the type for where ever this is being called
    //   There must be a better way to do this
    Type this_type = new StackTrace ().GetFrame (0).GetMethod ().ReflectedType;

    try
    {
        // Loop through each argument pair (i += 2), assumed format is:
        //    program /arg1 arg_value1 /arg2 arg_value2
        // Other formats are not supported.  arg1, arg2, etc are assumed
        // to be static properties for this_type.
        for (int i = 0; i < args.Length; i += 2)
        {
            // Trim the leading slash on the property name
            Validate.True ("Arguments start with a slash(/)",
                args [i] [0].Equals ('/'));
            string prop_name = args [i].TrimStart ('/');

            // Get the property associated with this argument. We don't
            // have an instance, so only static properties are appropriate.
            PropertyInfo pi = this_type.GetProperty (prop_name, 
                BindingFlags.IgnoreCase | 
                BindingFlags.Static | 
                BindingFlags.SetProperty | 
                BindingFlags.Public);

            // If there is no match then just continue
            if (null == pi)
                continue;

            // Get the Setter mthod for this property
            method_info = pi.GetSetMethod ();

            // Conversion is based on the Convert class.

            // Here we try to assemble the name for the appropriate
            // method of Convert. Methods are of the form "To____(String)"
            // where "____" is the Type we are trying to convert the
            // string two.
            convert_method = "To" + pi.PropertyType.Name;

            // Get the Convert method
            MethodInfo m = typeof (Convert).GetMethod (
                convert_method, string_type);

            // We will pass the current arg_value to the Convert method 
            parameters [0] = args [i + 1];

            // Invoke the convert method.  The result will be passed to
            // the property's Setter method.
            parameters [0] = m.Invoke (typeof (Convert), parameters);

            // Invoke the setter method on this_type
            method_info.Invoke (this_type, parameters);
        }
    }
    catch (Exception ex)
    {
        new LoggedException (ex, "Failed to parse command line arguments").Log ();
    }
}

Wednesday, March 10, 2010

Automated Retries

I'm still working on building up an automated GUI testing framework. It turns out that many GUI interaction are not very reliable for a number of reasons (the time it takes a for a dialog to pop-up can vary from session to session, for example).

To solve this my framework will retry some operations until successful or some limit is reached. Of course I don't want to write the same retry logic over and over again, so I came up with this little library function that makes use of lambdas to keep it generic.

public class AutomatedRetry
{
    public uint RETRY_FACTOR = 50;

    public bool Try (Func<bool> action, uint limit)
    {
        for (uint tries = 0; tries < limit; tries++)
        {
            if (action ())
                return true;
            else
            {
                uint time = tries * RETRY_FACTOR;
                Log ("  retry in {0} ms", time);

                Thread.Sleep ((int)time);
            }
        }

        return false;
    }

    ...
}

The function Try() takes a delegate that returns true when successful and false otherwise. It will keep calling action until it returns true or the limit is reached.

Here is an example of it being used:

public AutomationElement FindElementByName (
    AutomationElement root, string name)
{
    AutomationElement elm = null;

    Func<bool> action = () =>
    {
        elm = FindElementByName_ (root, name);

        return null != elm;
    };

    AutomatedRetry trier = new AutomatedRetry ();
    trier.Try (action, RETRY_LIMIT);

    return elm;
}

I make a delegate (action) that calls the method that is going to get retried and tests the result to see if it was successful or not. In this case we are trying to find a UI Element by it's name. FindElementByName_() will return the element if successful or null if not. To use the AutomatedRetry we simple call Try() with the action delegate and the number of times to retry. Pretty cool, huh?

Monday, February 22, 2010

Improving GUI Test Maintenance with a Data Driven Framework

One the problems with automated GUI testing is maintenance. Dialog titles change, buttons move, tabs change, etc. These changes generally break automated tests. To make tests maintenance easier I have been working on a test framework that includes JSON files which define UI elements. This almost certainly will not be the final definition, but I thought I'd throw it out there and see if I get comments back on the structure.

Here is an example of a definition for the Page Setup dialog in Notepad.

"Notepad Page Setup": {
   Name: "Page Setup",
   Keystrokes: "%(fu)",
   SelectionLists: {
      "Sizes": {
         AutomationId: "1137",
         SelectionItems: {
            "Ledger": {
               Name: "11x17",
            },
            "A5": {
               Name: "A5",
            },
            "Letter": {
               Name: "Letter",
            }
         }
      }
   },
   Fields: {
      "Header": {
         AutomationId: "30"
      },
      "Footer": {
         AutomationId: "31"
      }
   }
   Buttons: {
      "Ok": {
         AutomationId: "1"
      }
   }
}
You will need UISpy to find the Name and/or AutomationId for UI elements.

You can think of this structure as a hash table with key => value pairs. The main element is "Notepad Page Setup". This is the key we will use to reference the Page Setup dialog in our test code. The value, in this case, is a bunch more key => value pairs that define the GUI element. BTW this is not a complete definition of the Page Setup dialog.

As an example I'll explain the paper size drop down on the Page Setup dialog (launch Notepad and UISpy if you want to follow along). The paper size drop down is a Selection List and in our definition we have called it "Sizes". It's AutomationId is "1137" (from UISpy).

The "Sizes" list is made of many Selection Items. I have only include a few here: "Ledger", "A5", and "Letter". These are the names that will be used in the test code to refer to these elements. Notice the hash key ("Ledger") does not have to match the Name of the element name ("11x17").

Here are some general definitions:
Name:
The name of the UI element (from UISpy).  The Name or the AutomationId can be used to find UI elements.
AutomationId:
The AutomationId for the UI element (from UISpy)
Keystroke:
Key stroke pattern to open the UI element
SelectionLists:
Array of Selection List defintions. Selection Lists are UI element that allow the user to chose one or more items from a list, e.g. ComboBox, ListBox, and even a DataGridView.
SelectionItem:
The definition for an item in a SelectionList
Fields:
Test fields
Buttons:
Generally buttons, but could be other things the user might click
The code that uses this JSON definition might look something like this:

[Test]
public void SetPageSize()
{
   // Assume Notepad has already been opened
   ...
   // Open the Page Setup Dialog
   UiItem ui = UiItem.Open("Notepad Page Setup");
   ui.SelectionLists["Sizes"].SelectionItems["Ledger"].Select();
   ui.Buttons["Ok"].Click();
   ...
}

Now if the title of the dialog changes or the keystrokes it launch it or the name of a paper size changes we don't have to modify the test code.  Instead we just update the JSON definition and any tests using that definition will automatically get the new definition.  All the test code remains unchanged 8]

Thursday, February 11, 2010

Sitemap for Blogger When Using FeedBurner

So I signed up with FeedBuner for CoderDoWhat.com and it broke (sort of) the sitemap used by Web Master Tools. Normally in WMT I would use http://blog.coderdowhat.com/atom.xml for the sitemap, but when you signup for and configure FeedBurner, requests for atom.xml get redirected to http://feeds.feedburner.com/coderdowhat which is not what I want to use as my sitemap.

As an alternative to atom.xml you can use http://blog.yourdomain.com/feeds/posts/full as your sitemap URL in WMT. This is not redirected and works perfect.

Monday, February 8, 2010

Implementing Join() for Arrays in C# Using Extension Methods

Microsoft seems pretty good at borrowing good ideas from others, so why is it that .NET still doesn't include a Join() method for IEnumerables? Luckily it's easy to add this functionality using extension methods. Here is a fairly simple implementation.
using System;
using System.Collections;
using System.Text;
using System.Collections.Generic;

namespace Helpers
{
    public static class CollectionHelper
    {
        public static string Join(this IEnumerable list, string separator)
        {
            string text = "";

            if (null == list)
                return text;

            foreach (object item in list)
            {
                text += String.Format(
                    "{0}{1}", item.ToString(), separator);
            }

            int idx = text.LastIndexOf(separator);
            if (-1 < idx)
            {
                text = text.Remove(text.LastIndexOf(separator));
            }

            return text;
        }
    }
}
You can include the Join() method by adding a using Helpers.CollectionHelpers statement to your source file. It looks something like this.
using System;
using System.Collections;

namespace MyAppSpace
{
    using Helpers.CollectionHelper;

    public partial class SomeClass
    {
        ...

        ...

        public string ProcessData(IList<double> data)
        {
            List<double> new_data = new List<double>();

            double value = 0;
            foreach (double d in data)
            {
                value += d
                new_data.Add(value);
            }

            return new_data.Join(','); // <= Extension method
        }
    }
}