Skip to content

The Daily Design Workshop

If prompted right now, could you explain the design of the software that you’re working on, and the change that you’re making to it? What about the other members of your team? For our team, the answer was no, and a debate over the cause of that failure ultimately led us to implementing daily design workshops which have not only solved the problem, but produced several other unintended benefits.

“What is the cause?” we immediately debated as it became clear that the team couldn’t explain what they were actively doing, even what they had been doing over the course of a week. I argued that there was a failure of commination skills rather than a failure in understanding. The code changes themselves were generally correct, and consistent with the design agreed upon during sprint planning. Others argued that truly understand an idea is synonymous with being able to communicate that idea, a belief I find troublesome after years of studying language. Code itself is a form of communication, but in a collaborative environment it can be difficult to parse out who understands what from the software alone.  Each team member must be able to articulate their understanding.

And so, with one party concerned that the team did not understand the software that they were building, and myself concerned that the team lacked the communication skills to adequately convey their knowledge, we decided upon a strategy to address both concerns.

Each morning, after our daily standup, one team member is tasks with explaining the design for the story at a whiteboard, or for the test automation for that story. The rest of the team is responsible for asking questions until any ambiguities are resolved, and a mutual understanding is achieved. The exercise usually takes about fifteen minutes.

In this way, each team member gains practice discussing and diagraming software design. They’re improving their communication skills through practice, and by observing the techniques of other team members.

We are also ensuring a mutual understanding of the software design with daily discussion and reinforcement of that design. Discoveries made during coding, and modifications to the original plan are raised daily and shared with the entire team within the context of the overall design.

When we discover a challenging problem or an unknown behavior in the software that needs to be discussed with the entire team, this topic is raised during the design workshop instead of the standard review of the story. We gain a mutual awareness of issues, and collective problem solving.

We have also been surprised to see that the side effect of this is faster, more efficient stand-ups. Team members no longer use the stand-up as  an opportunity to share difficulties or discoveries because the design workshop is a better forum for those topics.

Ultimately, we’ve learned that we had both problems of communication and understanding specific to certain individuals and areas of the software. Fortunately, we have a working plan for addressing both issues, and we’ve observed steady progress toward resolving these difficulties.

Developing a Team for Re-engineering

For six months now, I’ve been building a team to take on the re-engineering of a legacy code base for scalability, performance, and to provide flexibility in the core processing components to accommodate differences in global regulatory requirements. To take on this project, I have a smart, but largely inexperienced team. I am finding my usual strategy for training, and professionalizing new developers insufficient, so I’ve decided to try something new. This is the beginning of my experiment.

Here’s what I’ve tried so far: TDD katas running the standard exercises, String Calculator, Bowling Game, etc. I’ve recommended reading Working Effectively with Legacy Code, Clean Code, and The Pragmatic Programmer.   I promote stewardship of the code. Even my most junior team member is expected to take responsibility for tasks, review the code of other team members, and make decisions about how to implement and test features. It’s a safe team to make decisions, and to make mistakes.

In a lot of ways, this approach is working. The team is increasingly effective and independent. We’re delivering code, and testing thoroughly in sprint. But, in many ways, we’re falling short of our goals. Overall confidence on the team is low. The quantity and severity of changes required following code reviews, or as a result of defects discovered during test is driving an impulse to seek approval before committing to low-level design decisions. Knowledge silos build up extremely quickly as an individual (or pair) learn how to interact with the legacy code or framework, then take all similar subsequent tasks to accelerate completion of a story, or sprint. We’re also failing sprints due to a combination of poor estimation, and defects discovered during testing.

I believe that the root of our problem lies in our attempt to work both on a new type of problem, and on that problem in an extremely complex code base. For example, the team recently introduced MyBatis to handle new database interactions instead of continuing the use of a custom DAO framework oriented around stored procedures. For everyone on the team, MyBatis is a new tool, but they’re also trying to integrate this with the WebLogic data sources, to replicate behavior in code, such as transactional updates and audit trails, which have typically been provided by the stored procedures, and to do all of this with complex objects.

There’s too much newness here. As a result, the team has struggled to break big problems down into smaller components. They’re not sure if the failure of their MyBatis DAO is related to the MyBatis configuration, the JNDI lookup of the data source, and since both tools are new, they didn’t know how to configure one at a time and test.

I need experience that I don’t have, so I’ve decided to try and produce it artificially. Starting next sprint, I’m replacing my TDD katas with targeted exercises to practice simplified versions of problems that we’ll be facing in the future. I’m going to take my real code, pare it down, and then present a piece of the upcoming problem as a TDD exercise. I might ask the team to replace a stored procedure with a MyBatis DAO, but the procedure itself will be simple and clear, the objects small, and I’ll provide explicit connection details rather than a JNDI lookup on a WebLogic server. We’ll still test first, but we may need to first refactor for testability, and we’ll still throw away our code at the end of the exercise.

The result, I hope, will be that when we encounter the problem in our code, the team will have experience with at least some part of the solution. We’ll reduce our unknowns, and we’ll know how to break the problem into smaller pieces because we will have experienced a small piece already. We will address our complexity as complexity, and avoid getting stuck on simple problems concealed by complex code and interactions.

Testing JMS driven middleware with Cucumber-JVM

I recently created sample code for testing JMS inputs and outputs using Cucumber-JVM.  The full code is available on github at https://github.com/wjpowell/cucumber-jvm-jms-example.  The code relies on a reusable class which I’ve set up to simplify interaction with a JMS queue, use of @BeforeClass and @AfterClass hooks for setup and teardown of test suites, and,  because data loads are so often a part of testing middleware, this loads trade data from an excel sheet using the jXLS library.  I can’t cover all of these features in one blog post, so for now I’m going to focus on the JMS components

The entire JMS utilities class used in the Step Definitions is included below.  For convenience, this class only uses the javax.jms interfaces.  This allows any connection factory to be injected, and used in the same way.  The class has been structured to allow users to simply connect to a JMS server, and to create Message producers and message receivers for interacting with that server.

While most of this code is boilerplate setup and teardown of a JMS server, the createTestListener deserves some attention. This method accepts a collection as a parameter, and adds received messages to that collection. It does so by implementing an anonymous MessageListener, which can be attached to the outbound queue.

package cucumber.examples.java.jms.utilities;
	public <T> MessageListener createTestListener(
			final List<T> receivedCollection) {

		MessageListener testListener = new MessageListener() {

			@SuppressWarnings("unchecked")
			@Override
			public void onMessage(Message received) {
				if (received instanceof ObjectMessage) {
					try {
						receivedCollection.add((T) ((ObjectMessage) received)
								.getObject());
					} catch (JMSException e) {
						e.printStackTrace();
					}
				}
			}
		};
		return testListener;
	}

This message listener is attached to the outbound queue before any messages are sent. The code then loads the messages, and sends them through the input queue, and waits until all expected messages have been received before checking the assertions.

	private void waitUntilAllOutputIsReceived(List<Trade> receivedTrades, int expectedTrades, int timeout) throws InterruptedException {
		int timeoutReached = 0;
		while (receivedTrades.size() != expectedTrades && timeoutReached < timeout) {
	    		Thread.sleep(2000);
	    		timeoutReached += 2000;
	    }
	}

Here is the full class for the JMS utilities.

package cucumber.examples.java.jms.utilities;

public class JmsTestUtilities {
	private Session session;
	private Connection connection;
	private ConnectionFactory connectionFactory;

	public JmsTestUtilities(ConnectionFactory connectionFactory) {
		this.connectionFactory = connectionFactory;
	}

	public Queue createQueue(String queueName) throws JMSException {
		return session.createQueue(queueName);
	}

	public void sendMessage(Serializable object, MessageProducer producer) {
		try {
			// Create a messages
			ObjectMessage message = session.createObjectMessage(object);
			producer.send(message);
		} catch (Exception e) {
			System.out.println("Caught: " + e);
			e.printStackTrace();
		}
	}

	public void closeSession() throws JMSException {
		session.close();
		connection.close();
	}

	public void startServerSession() throws JMSException {
		connection = connectionFactory.createConnection();
		session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
		connection.start();
	}

	public MessageProducer createInputProducer(Queue inputDestination)
			throws JMSException {
		MessageProducer producer = session.createProducer(inputDestination);
		producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);

		return producer;
	}

	public MessageConsumer createOutputReceiver(String outputQueue)
			throws JMSException {
		Queue outputDestination = session.createQueue(outputQueue);
		MessageConsumer outputConsumer = session
				.createConsumer(outputDestination);

		return outputConsumer;
	}

	public void attachListenerToOutputConsumer(MessageListener testListener,
			MessageConsumer outputConsumer) throws JMSException {
		outputConsumer.setMessageListener(testListener);
	}

	public <T> MessageListener createTestListener(
			final List<T> receivedCollection) {

		MessageListener testListener = new MessageListener() {

			@SuppressWarnings("unchecked")
			@Override
			public void onMessage(Message received) {
				if (received instanceof ObjectMessage) {
					try {
						receivedCollection.add((T) ((ObjectMessage) received)
								.getObject());
					} catch (JMSException e) {
						e.printStackTrace();
					}
				}
			}
		};
		return testListener;
	}

	public Session getSession() {
		return session;
	}

}

Starting a Gxt Widgets Gem

I’m getting started on a Gem which provides a collection of PageObject widgets for testing Gxt Applications.  I’m working my way through the examples provided on the Gxt Example Gallery.  The project is up and running on GitHub, and I’m looking for contributors.  I’ll release the first few widgets to RubyGems soon.

The goal of building this library are twofold.  First, I’d like to provide a library for testing the standard Gxt Controls, which can be used directly, or easily extended.  Second, I’d like to provide example code for creating Widget extensions for PageObject.

Testing Gxt sites has proven unusually challenging because of the complexity of the HTML generated by its controls, as such it is particularly well suited as the object of a test library.  It’s a popular library which is difficult to test, so the simplification of testing has a big payoff for those development teams working in the technology.

I have already provided simple, straightforward examples of creating widgets for PageObject.  I hope that examples of the more challenging widgets required for testing Gxt will help those developers working with complex custom controls when they look to define their own test widgets.

Run Coded UI Tests on a VM

I was recently helping a team start testing their application using SpecFlow and Coded UI.  They were also using TeamCity as a continuous integration server.  Setting up SpecFlow to run Coded UI tests is well documented on the Specflow Wiki.  Running SpecFlow tests in Teamcity has been well covered in this blog post by Hamish Graham.  The challenge that we encountered was in running UI Tests on locked computers.

It is not possible within our organization to disable screensavers, which prevent UI tests from running.   To get around this, we set up several VMs using VMware Player. on physical servers and installed TeamCity build agent on them.  The VMs were configured without screen savers.  Port forwarding was set up to allow connection to the build agent, in addition to several ports for the applications to communicate with their servers.

The TeamCity build agent can be set up to run as an interactive process (launch it from the command line rather than as a service), or the Microsoft Test Controller and Test Agent can be used to allow distribution of the test run across several VMs.  Make sure to run the Test Agent as an interactive process, if you’re going to run the tests in this way.  You’ll need additional port forwarding set up for the test controller and agents if you choose to use the Microsoft test controller.

Each VM must be open on it’s machine to keep the desktop session alive.  If the VM is open, the physical machine can be safely locked.  Remoting into the VM Directly will cause the desktop session to be closed on log out, so make sure that any users needing access to the VM running the tests are able to connect to the physical machine.

Gxt PageObject widget example

I have provided an example for adding widgets to page-object below.  In this example, I have created a test widget to interact with the gxt pagination tool on the gxt examples page.

This widget is typical of widgets created in day-to-day use.  It defines several methods which identify and act on elements using the ‘nested element’ methods built into page-object.

Widgets such as this one typically rely on the fact that each object will be in a predictable location relative to a main table or div. This widget relies on a defined table with fixed indexes to find the correct buttons.

class GxtPager < PageObject::Elements::Div

  def first
    first_cell = cell_elements(:class=>"x-toolbar-cell")[0]
    first_cell.button_element(:tag_name=>"button").click
  end

  def previous
    next_cell = cell_elements(:class=>"x-toolbar-cell")[1]
    next_cell.button_element(:tag_name=>"button").click
  end

  def last
    next_cell = cell_elements(:class=>"x-toolbar-cell")[8]
    next_cell.button_element(:tag_name=>"button").click
  end

  def next
    next_cell = cell_elements(:class=>"x-toolbar-cell")[7]
    next_cell.button_element(:tag_name=>"button").click
  end

  def page
    page_cell = cell_elements(:class=>"x-toolbar-cell")[4]
    page_cell.text_field_element.value
  end

  def page=(page_number)
    page_cell = cell_elements(:class=>"x-toolbar-cell")[4]
    page_cell.text_field_element.value=page_number
    page_cell.text_field_element.send_keys :return
  end
end
PageObject.register_widget :gxt_pager, GxtPager, 'table'

Below you’ll see the use of the widget within a page-object. I’ve gotten into the habit of registering widgets at the bottom of the defining class, then requiring that class where it needs to be used.

require_relative 'widgets/gxt_grid'
require_relative 'widgets/gxt_pager'

class GxtSamplePageObject
  include PageObject

  div(:basic_grid, :class => "label_basic_grid")
  div(:local_pagination, :class => "label_paging_grid")
  gxt_table(:gxt_table, :class => "x-grid3")
  gxt_pager(:gxt_pager, :class =>"x-toolbar-ct")
end

Just to make sure everything is covered, I have included the step definitions which use this class below. See previous blog posts or the page-object wiki for the gxt_table code used here.

Given /^I am on the Gxt Examples page$/ do
  @page = GxtSamplePageObject.new(@browser)
  @page.navigate_to "http://gxtexamplegallery.appspot.com/"
end

When /^I have Local Pagination open$/ do
  @page.local_pagination_element.click
end

When /^I click next page$/ do
  @page.gxt_pager_element.next
end

Then /^I should be on page "(\d+)"$/ do |page_expected|
  @page.gxt_pager_element.page.should == page_expected
end

When /^"([^"]*)" should be listed in the grid$/ do |name_expected|
  @page.gxt_table_element[1][0].text.should == name_expected
end

When /^I click last page$/ do
  @page.gxt_pager_element.last
end

When /^I click on previous page$/ do
  @page.gxt_pager_element.previous
end

When /^I click on first page$/ do
  @page.gxt_pager_element.first
end

When /^type "(\d+)" into the page box$/ do |page|
  @page.gxt_pager_element.page=page
end

Script your UI tests like a power user

Power users know all the shortcuts in an application, they’re never more than a few clicks away from what they want. They keep their hands on they keyboard. User interface tests, on the other hand, tend to click their way through an application, following the main routes.  When practicing BDD, it is important to write tests for these main routes, but it is also important to script the majority of the tests like a power user or you’ll wind up with slow running tests, and worse, applications that do not reward expertise.

Web application tests can be accelerated by streamlining workflows, and by improving navigation around the site.  The main speed loss in Web application testing is navigation between pages, particularly when tests are written to run in isolation.

Windows CodedUI tests lose most of their time searching for controls on complex applications.  Allowing applications to be navigated using keyboard shortcuts can dramatically reduce the run time for tests.

Applications on any platform should allow values to be typed into combo boxes and date selectors.  Tab index should be properly configured.  It should be possible to complete forms quickly with the keyboard.

When UI tests are running slow, look at the underlying code, but also look at the usability of the application.  Slow running tests may indicate a usability problem.  Improving the navigation of the application will speed up your tests, and make your users happy.

Simplifying tests for Gxt and Gwt applications

I have simplified the creation of widgets for the PageObject gem using the module shown below.  When testing with Watir or Selenium and Cheezy’s PageObject gem, this module allows for the easy creation of test widgets, primarily used for interacting with Gwt or Gxt web applications.

For example, a Gxt table consists of multiple div elements, and each row is itself a table element containing a single row.  To interact with the Gxt table, I extend the PageObject::Element::Table class:

class GxtTable < PageObject::Elements::Table

  @protected
  def child_xpath
    ".//descendant::tr"
  end
end

I then register the class with the PageObject gem. The register_widget method accepts a tag which will be used as the accessor, the class which will be added to the Elements module, and a html element tag which will be used as the top-level html element of the widget, or the html element used in the watir or selenium search.

Widgets.register_widget :gxt_table, GxtTable, :div

The GxtTable then behaves as if it were an html element for the purpose of page-object definitions.

class WidgetTestPageObject
  include PageObject

  gxt_table(:a_table, :id => "top_div_id")
  gxt_table :gxt_block_table do |element|
    "block_gxt_table"
  end

  div(:outer_div)
  gxt_table(:a_nested_gxt_table) {|page| page.outer_div_element.gxt_table_element}
end

The code for the WidgetModule is shown below. My next step is to submit this code as a pull request within PageObject.

require 'page-object/elements'
require 'page-object/platforms/selenium_webdriver/page_object'
require 'page-object/platforms/watir_webdriver/page_object'

module Widgets

  def self.register_widget(widget_tag, widget_class, base_element_tag)
    if widget_class.ancestors.include? PageObject::Elements::Element
      define_accessors(PageObject::Accessors, widget_tag)
      define_nested_elements(PageObject::Elements::Element, widget_tag)
      define_locators(PageObject, widget_tag)
      define_selenium_accessors(PageObject::Platforms::SeleniumWebDriver::PageObject, widget_tag, widget_class, base_element_tag)
      define_watir_accessors(PageObject::Platforms::WatirWebDriver::PageObject, widget_tag, widget_class, base_element_tag)
    end
  end

  @private

  def self.define_accessors(base, widget_tag)
    accessors_module = Module.new do
      class_eval "def #{widget_tag}(name, identifier={}, &block)
          identifier={:index=>0} if identifier.empty?
          define_method(\"\#{name}_element\") do
            return call_block(&block) if block_given?
            platform.#{widget_tag}_for(identifier.clone)
          end
          define_method(\"\#{name}?\") do
            return call_block(&block).exists? if block_given?
            platform.#{widget_tag}_for(identifier.clone).exists?
          end
        end"
    end

    base.send(:include, accessors_module)
  end

  def self.define_watir_accessors(base, widget_tag, widget_class, base_element_tag)
    base.send(:define_method, "#{widget_tag}_for") do |identifier|
      find_watir_element("#{base_element_tag}(identifier)", widget_class, identifier, base_element_tag)
    end

    base.send(:define_method, "#{widget_tag}s_for") do |identifier|
      find_watir_elements("#{base_element_tag}(identifier)", widget_class, identifier, base_element_tag)
    end

  end

  def self.define_selenium_accessors(base, widget_tag, widget_class, base_element_tag)
    base.send(:define_method, "#{widget_tag}_for") do |identifier|
      find_selenium_element(identifier, widget_class, base_element_tag)
    end

    base.send(:define_method, "#{widget_tag}s_for") do |identifier|
      find_selenium_elements(identifier, widget_class, base_element_tag)
    end
  end

  def self.define_nested_elements(base, widget_tag)
    base.send(:define_method, "#{widget_tag}_element") do |identifier={}|
      identifier={:index => 0} if identifier.empty?
      @platform.send("#{widget_tag}_for", identifier.clone)
    end

    base.send(:define_method, "#{widget_tag}_elements") do |identifier={}|
      identifier={:index => 0} if identifier.empty?
      @platform.send("#{widget_tag}s_for", identifier.clone)
    end
  end


  def self.define_locators(base, widget_tag)
    base.send(:define_method, "#{widget_tag}_element") do |identifier={}|
      identifier={:index => 0} if identifier.empty?
      platform.send("#{widget_tag}_for", identifier.clone)
    end

    base.send(:define_method, "#{widget_tag}_elements") do |identifier={}|
      platform.send("#{widget_tag}s_for", identifier.clone)
    end
  end
end

Extending PageObjects

When testing web applications in Ruby with Cucumber, I typically use the PageObject gem to simplify the creation and maintenance of my Page Objects.  While these are useful tools for working with simple web pages, I often find it necessary to build test widgets to interact with web application widgets or GXT controls.

In order to simplify the use of test widgets, and maintain a consistent pattern in my Page Object classes, I create widgets which extend the capabilities of the PageObject gem.  In order to avoid forking PageObject, and ultimately getting out of sync with updates, I write the extension in several modules, and use Ruby’s ‘send’ method to include the widgets in the appropriate modules of PageObject.

The example below shows a simple extension of PageObject for working with a table in a GXT application.  The tables created by GXT were not typical html tables, rather, each row is itself a table element containing a single row, the tables are organized within divs, and the topmost element of the control is itself a div.  This structure can make GXT applications difficult to test, since the methods for interacting with standard elements do not apply.

The code below extends the PageObject::Elements::Table class to override the child_xpath method which PageObject uses to collect rows into an array.  The Table class gets the immediate child rows, the GxtTable extension gets all descendant rows.  This change aggregates all the rows from each table element in the GXT Table into a single object and allows us to interact with it using the methods on the Table class.  By creating classes such as this, I am able to create page object classes which treat controls as isolated objects regardless of how complex the html used to create them actually is.

Although I’ve been happy with this solution as a template, I believe that I can simplify the creation of Element classes by dynamically creating the methods which need to be included.  To that end, in the coming weeks, I’ll be working on modifying this solution so that Widget Elements can be added to PageObject by defining and registering each class.

The following line are placed in the env.rb file to add the extension to PageObject on load.


PageObject.send(:include,Widgets)

The code below is the modules and the class created so that the GxtTable class behaves as any other PageObject Element.

require 'watir-webdriver/extensions/alerts'
require 'page-object/elements'
require 'page-object/platforms/selenium_webdriver/page_object'
require 'page-object/platforms/watir_webdriver/page_object'

module Widgets
  def self.included(base)
        base::Elements.send(:include, WidgetElements)
        base.send(:include, WidgetElementLocators)
        base::Platforms::SeleniumWebDriver::PageObject.send(:include, WidgetSeleniumPageObject)
        base::Platforms::WatirWebDriver::PageObject.send(:include, WidgetWatirPageObject)
        base::Accessors.send(:include, WidgetAccessors)
        base::Elements::Element.send(:include, WidgetNestedElements)
  end
end

module WidgetElements
  class GxtTable < PageObject::Elements::Table     protected     def child_xpath       ".//descendant::tr"     end   end   ::PageObject::Elements.tag_to_class[:gxt_table] = ::Widgets::WidgetElements::GxtTable end module WidgetAccessors   #   # adds two methods - one to retrieve the table element, and another to   # check the table's existence.  The existence method does not work   # on Selenium so it should not be called.   #   # @example   #   gxt_table(:cart, :id => 'shopping_cart')
  #   # will generate a 'cart_element' and 'cart?' method
  #
  # @param [Symbol] the name used for the generated methods
  # @param [Hash] identifier how we find a table.  You can use a multiple paramaters
  #   by combining of any of the following except xpath.  The valid keys are:
  #   * :class => Watir and Selenium
  #   * :id => Watir and Selenium
  #   * :index => Watir and Selenium
  #   * :name => Watir and Selenium
  #   * :xpath => Watir and Selenium
  # @param optional block to be invoked when element method is called
  #
  def gxt_table(name, identifier={:index => 0}, &block)
    define_method("#{name}_element") do
      return call_block(&block) if block_given?
      platform.gxt_table_for(identifier.clone)
    end
    define_method("#{name}?") do
      return call_block(&block).exists? if block_given?
      platform.gxt_table_for(identifier.clone).exists?
    end
    alias_method "#{name}_table".to_sym, "#{name}_element".to_sym
  end
end

module WidgetElementLocators
  #
  # Finds a table
  #
  # @param [Hash] identifier how we find a table.  You can use a multiple paramaters
  #   by combining of any of the following except xpath.  It defaults to {:index => 0}
  #   which will return the first table.  The valid keys are:
  #   * :class => Watir and Selenium
  #   * :id => Watir and Selenium
  #   * :index => Watir and Selenium
  #   * :name => Watir and Selenium
  #   * :xpath => Watir and Selenium
  #
  def gxt_table_element(identifier={:index => 0})
    platform.gxt_table_for(identifier.clone)
  end

  #
  # Finds all tables that match the provided identifier
  #
  # @param [Hash] identifier how we find a table.  You can use a multiple paramaters
  #   by combining of any of the following except xpath.  It defaults to an empty Hash
  #   which will return all tables.  The valid keys are:
  #   * :class => Watir and Selenium
  #   * :id => Watir and Selenium
  #   * :index => Watir and Selenium
  #   * :name => Watir and Selenium
  #   * :xpath => Watir and Selenium
  #
  def gxt_table_elements(identifier={})
    platform.gxt_tables_for(identifier.clone)
  end
end

module WidgetNestedElements
  def gxt_table_element(identifier={:index => 0})
    @platform.gxt_table_for(identifier)
  end

  def gxt_table_elements(identifier={:index => 0})
    @platform.gxt_tables_for(identifier)
  end
end

module WidgetSeleniumPageObject
  #
  # platform method to retrieve a table element
  # See WidgetAccessors#table
  #
  def gxt_table_for(identifier)
    find_selenium_element(identifier, PageObject::Elements::GxtTable, 'div')
  end

  #
  # platform method to retrieve all table elements
  #
  def gxt_tables_for(identifier)
    find_selenium_elements(identifier, PageObject::Elements::GxtTable, 'div')
  end
end

module WidgetWatirPageObject
  #
  # platform method to retrieve a table element
  # See WidgetAccessors#table
  #
  def gxt_table_for(identifier)
    find_watir_element("div(identifier)", PageObject::Elements::GxtTable, identifier, 'div')
  end

  #
  # platform method to retrieve an array of table elements
  #
  def gxt_tables_for(identifier)
    find_watir_elements("div(identifier)", PageObject::Elements::GxtTable, identifier, 'div')
  end
end

A Testable Custom Tab Control

In adding some user interface tests to a .NET windows application, I stumbled upon a custom TabControl whose children were not accessible to the Microsoft CodedUI libraries. Inspect.exe (a tool for browsing the control tree of windows application) showed TabItems without content.

I was able to restore testability to the application with a minor change to the custom TabControl, and by implementing custom AutomationPeers.  The complete source code is shown below, but first, a look at how the problem was created, and an explanation of the solution.

The problematic TabControl came from this post on StackOverflow: Stop TabControl From Recreating It’s Children. The TabControlEx class prevents the TabControl form unloading it’s children from memory when the user switches between TabItems.    To accomplish this, the TabControl has been changed from a type of ContentPresenter to a type of Panel.  The standard TabControl copies the TabItem content into it’s own SelectedContent dependency property, which then makes the controls within the TabItem visible and accessible.  TabControl is a ContentPresenters whose children are replaced when the user switches between tabs.  TabControlEx instead adds TabItems to the ‘Children’ collection of the Panel.  When the user switches between tabs, the selected tab is made visible, and the other tabs are collapsed.

The TabItemAutomationPeer which provides automation accessibility for the contents of TabItems loads it’s child controls by referring to the TabControl.  Specifically, GetChildrenCore method casts the parent of the TabItem as a TabControl, and returns the children of the TabControl.SelectedContentPresenter property.  The SelectedContentPresenter property calls GetTemplateChild to return the current state of the TabControl ContentPresenter using a hard coded TemplatePart Name.  When the TabItemAutomationPeer attempts to return its children from the TabControlEx, none are found.  As a result, the TabItems are visible within the control tree, but they do not appear to have any contents.

In order to return testability to this control class, I have added a new SelectedContentPresenter method which uses methods already provided on the class to return the ContentPresenter for the selected TabItem.  This method replicates the interaction between the TabControl and the TabItemAutomationPeer, making use of the collection of ContentPresenters contained in the TabControlEx.

I have also overridden the TabControlAutomationPeer, and the TabItemAutomationPeer classes.  The TabItemAutomationPeer was only modified to refer to the TabControlEx class and use the SelectedContentPresenter method on that class.  The TabControlAutomationPeer was only modified to make use of the CustomTabItemAutomationPeer class, and the TabControlEx class was also modified to load the CustomTabControlAutomationPeer by overriding the OnCreateAutomationPeer event.

// Extended TabControl which saves the displayed item so you don't get the performance hit of
// unloading and reloading the VisualTree when switching tabs

// Obtained from http://eric.burke.name/dotnetmania/2009/04/26/22.09.28
// and made a some modifications so it reuses a TabItem's ContentPresenter when doing drag/drop operations

[TemplatePart(Name = "PART_ItemsHolder", Type = typeof(Panel))]
public class TabControlEx : System.Windows.Controls.TabControl
{
    // Holds all items, but only marks the current tab's item as visible
    private Panel _itemsHolder = null;

    // Temporaily holds deleted item in case this was a drag/drop operation
    private object _deletedObject = null;

    public TabControlEx()
        : base()
    {
        // this is necessary so that we get the initial databound selected item
        this.ItemContainerGenerator.StatusChanged += ItemContainerGenerator_StatusChanged;
    }

    /// <summary>
    ///  Overrides OnCreateAutomationPeer to use CustomTabControlAutomationPeer..
    /// </summary>
    protected override AutomationPeer OnCreateAutomationPeer()
    {
        return new CustomTabControlAutomationPeer(this);
    }

    /// <summary>
    /// if containers are done, generate the selected item
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    void ItemContainerGenerator_StatusChanged(object sender, EventArgs e)
    {
        if (this.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
        {
            this.ItemContainerGenerator.StatusChanged -= ItemContainerGenerator_StatusChanged;
            UpdateSelectedItem();
        }
    }

    /// <summary>
    /// get the ItemsHolder and generate any children
    /// </summary>
    public override void OnApplyTemplate()
    {
        base.OnApplyTemplate();
        _itemsHolder = GetTemplateChild("PART_ItemsHolder") as Panel;
        UpdateSelectedItem();
    }

    /// <summary>
    /// when the items change we remove any generated panel children and add any new ones as necessary
    /// </summary>
    /// <param name="e"></param>
    protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
    {
        base.OnItemsChanged(e);

        if (_itemsHolder == null)
        {
            return;
        }

        switch (e.Action)
        {
            case NotifyCollectionChangedAction.Reset:
                _itemsHolder.Children.Clear();

                if (base.Items.Count > 0)
                {
                    base.SelectedItem = base.Items[0];
                    UpdateSelectedItem();
                }

                break;

            case NotifyCollectionChangedAction.Add:
            case NotifyCollectionChangedAction.Remove:

                // Search for recently deleted items caused by a Drag/Drop operation
                if (e.NewItems != null && _deletedObject != null)
                {
                    foreach (var item in e.NewItems)
                    {
                        if (_deletedObject == item)
                        {
                            // If the new item is the same as the recently deleted one (i.e. a drag/drop event)
                            // then cancel the deletion and reuse the ContentPresenter so it doesn't have to be
                            // redrawn. We do need to link the presenter to the new item though (using the Tag)
                            ContentPresenter cp = FindChildContentPresenter(_deletedObject);
                            if (cp != null)
                            {
                                int index = _itemsHolder.Children.IndexOf(cp);

                                (_itemsHolder.Children[index] as ContentPresenter).Tag =
                                    (item is TabItem) ? item : (this.ItemContainerGenerator.ContainerFromItem(item));
                            }
                            _deletedObject = null;
                        }
                    }
                }

                if (e.OldItems != null)
                {
                    foreach (var item in e.OldItems)
                    {

                        _deletedObject = item;

                        // We want to run this at a slightly later priority in case this
                        // is a drag/drop operation so that we can reuse the template
                        this.Dispatcher.BeginInvoke(DispatcherPriority.DataBind,
                            new Action(delegate()
                        {
                            if (_deletedObject != null)
                            {
                                ContentPresenter cp = FindChildContentPresenter(_deletedObject);
                                if (cp != null)
                                {
                                    this._itemsHolder.Children.Remove(cp);
                                }
                            }
                        }
                        ));
                    }
                }

                UpdateSelectedItem();
                break;

            case NotifyCollectionChangedAction.Replace:
                throw new NotImplementedException("Replace not implemented yet");
        }
    }

    /// <summary>
    /// update the visible child in the ItemsHolder
    /// </summary>
    /// <param name="e"></param>
    protected override void OnSelectionChanged(SelectionChangedEventArgs e)
    {
        base.OnSelectionChanged(e);
        UpdateSelectedItem();
    }

    /// <summary>
    /// generate a ContentPresenter for the selected item
    /// </summary>
    void UpdateSelectedItem()
    {
        if (_itemsHolder == null)
        {
            return;
        }

        // generate a ContentPresenter if necessary
        TabItem item = GetSelectedTabItem();
        if (item != null)
        {
            CreateChildContentPresenter(item);
        }

        // show the right child
        foreach (ContentPresenter child in _itemsHolder.Children)
        {
            child.Visibility = ((child.Tag as TabItem).IsSelected) ? Visibility.Visible : Visibility.Collapsed;
        }
    }

    /// <summary>
    /// create the child ContentPresenter for the given item (could be data or a TabItem)
    /// </summary>
    /// <param name="item"></param>
    /// <returns></returns>
    ContentPresenter CreateChildContentPresenter(object item)
    {
        if (item == null)
        {
            return null;
        }

        ContentPresenter cp = FindChildContentPresenter(item);

        if (cp != null)
        {
            return cp;
        }

        // the actual child to be added.  cp.Tag is a reference to the TabItem
        cp = new ContentPresenter();
        cp.Content = (item is TabItem) ? (item as TabItem).Content : item;
        cp.ContentTemplate = this.SelectedContentTemplate;
        cp.ContentTemplateSelector = this.SelectedContentTemplateSelector;
        cp.ContentStringFormat = this.SelectedContentStringFormat;
        cp.Visibility = Visibility.Collapsed;
        cp.Tag = (item is TabItem) ? item : (this.ItemContainerGenerator.ContainerFromItem(item));
        _itemsHolder.Children.Add(cp);
        return cp;
    }

    /// <summary>
    /// Provides the content Presenter to the TabItem for UI automation.
    /// </summary>
    /// <returns>ContentPresenter</returns>
    internal ContentPresenter SelectedContentPresenter
    {
        get
        {
            return FindChildContentPresenter(GetSelectedTabItem());
        }
    }

    /// <summary>
    /// Find the CP for the given object.  data could be a TabItem or a piece of data
    /// </summary>
    /// <param name="data"></param>
    /// <returns></returns>
    ContentPresenter FindChildContentPresenter(object data)
    {
        if (data is TabItem)
        {
            data = (data as TabItem).Content;
        }

        if (data == null)
        {
            return null;
        }

        if (_itemsHolder == null)
        {
            return null;
        }

        foreach (ContentPresenter cp in _itemsHolder.Children)
        {
            if (cp.Content == data)
            {
                return cp;
            }
        }

        return null;
    }

    /// <summary>
    /// copied from TabControl; wish it were protected in that class instead of private
    /// </summary>
    /// <returns></returns>
    protected TabItem GetSelectedTabItem()
    {
        object selectedItem = base.SelectedItem;
        if (selectedItem == null)
        {
            return null;
        }

        if (_deletedObject == selectedItem)
        {

        }

        TabItem item = selectedItem as TabItem;
        if (item == null)
        {
            item = base.ItemContainerGenerator.ContainerFromIndex(base.SelectedIndex) as TabItem;
        }
        return item;
    }
}

/// <summary>
/// Extends the TabControlAutomationPeer to use the CustomItemAutomationPeer
/// </summary>
public class CustomTabControlAutomationPeer : TabControlAutomationPeer
{
    /// <summary>
    /// Initializes a new instance of the <see cref="CustomTabControlAutomationPeer"/> class.
    /// </summary>
    public CustomTabControlAutomationPeer(TabControl owner)
        : base(owner)
    {
    }

    /// <summary>
    /// overrides GetClasNamecore to display TabControlEx.
    /// </summary>
    protected override string GetClassNameCore()
    {
        return "TabControlEx";
    }

    /// <summary>
    /// overrides CreateItemAutomationPeer to returnCustomTabItemAutomationPeer
    /// </summary>
    /// <returns>ItemAutomationPeer</returns>
    protected override ItemAutomationPeer CreateItemAutomationPeer(object item)
    {
        return new CustomTabItemAutomationPeer(item, this);
    }
}

/// <summary>
/// Extends the TabItemAutomation peer to add child Automation peers from the TabControlEx
/// </summary>
public class CustomTabItemAutomationPeer :TabItemAutomationPeer
{
    /// <summary>
    /// Initializes a new instance of the <see cref="CustomTabItemAutomationPeer"/> class.
    /// </summary>
    public CustomTabItemAutomationPeer(object owner, TabControlAutomationPeer tabControlAutomationPeer)
        : base(owner, tabControlAutomationPeer)
    {
    }

    /// <summary>
    /// overrides GetChildrenCore to return return children from the content presenters in a TabControlEx.
    /// </summary>
    protected override List<AutomationPeer> GetChildrenCore()
    {
        List<AutomationPeer> childrenCore = base.GetChildrenCore();

        TabControlEx owner = base.ItemsControlAutomationPeer.Owner as TabControlEx;
        if (owner == null)
        {
            return childrenCore;
        }
        ContentPresenter selectedContentPresenter = owner.SelectedContentPresenter;
        if (selectedContentPresenter == null)
        {
            return childrenCore;
        }
        List<AutomationPeer> children = new FrameworkElementAutomationPeer(selectedContentPresenter).GetChildren();
        if (children == null)
        {
            return childrenCore;
        }
        if (childrenCore == null)
        {
            return children;
        }
        childrenCore.AddRange(children);

        return childrenCore;
    }
}