Charteris Community Server

Welcome to the Charteris Community
Welcome to Charteris Community Server Sign in | Join | Help
in Search

Edward Wilde

  • Wax - create browser automation tests using Excel and WatIn

    Wax stands for "WatIn and Excel". I developed this tool during my daily 1 hour train journey (1/2 hour each way commuting from London to Microsoft in Reading). Using a test driven development (TDD) approach helped me keep track of where I was. TDD also made it easy for me to just program for half an hour, hibernate the laptop and then pick it up from where I left off 10 hours later.

    Using Wax

    You define browser automation tests using an Excel spreadsheet that has a limited and simple command structure.

    Microsoft Excel - SearchGoogle.xlsx (2).png

    Once you've defined your script in Excel you can test and execute it using the Wax runner application:

    WaxRunner.png

    Wax and your build process

    An alternative way to execute a Wax script is to use the Wax command line runner. Errors are written to the console and the application returns a non-zero exit code if errors are encountered during test execution.

    wax console runner.png

    Below is an MSBuild target that executes a Wax script.

    <Target Name="WaxTests"> <Message Text="Executing Wax tests..." /> <Exec Command="bin\WaxConsole.exe /file:SearchGoogle.xls" WorkingDirectory="$(BuildPath)\..\TestScripts\" /> </Target>

    Benefits of Wax

    • Allows testers with no programming language skills to create and run WatIn style automated browser tests
    • Managers, or anyone for that matter, can run these tests on their PC's, the only software requirement is the installation of Wax.
    • If required Wax can be integrated into your build process using the command line Wax runner.

    Useful Links

     

    del.icio.us Tags: , ,

    Technorati Tags: , ,

  • Windows Workflow on SharePoint - Updating payloads and dealing with DateTime in a ListItem

    1. Updating the workflow payload when a workflow starts automatically

    The payload is the list item associated with the workflow. Most commonly a document list item. When a user associates a workflow with a document library they can choose to start the workflow when a user creates or updates a list item.

    Workflow association page
    Workflow association page

     

    The workflow will be kicked off if a user updates any part of the list item. For example if they update the document, the workflow will start. Additionally if they update any of the list item properties (meta-data) the workflow will also start.

    Problem

    If your workflow programmatically updates the payload this can present a few problems depending on how you update the list item.

    • Unintentionally starting another workflow (if one exists that starts on update)
    • Check-out, update in word, Check-in from word, when the document is already in a workflow, throws an error and puts the workflow into the 'Error Occurred state.

    Solution

    Instead of using SPListItem.Update use SPListItem.SystemUpdate. Calling SystemUpdate saves the list item but does not raise the update events, solving the above issues.

     

    2. How to determine how old a list item is?

    I thought this would be relatively straight forward; just grab the Created date and subtract it from the current date - wrong.

    SharePoint sites can individually specify different time zones in site settings:

    Windows Sharepoint Services Regional Settings

     

    The local date time is stored in the list items meta-data. Therefore if your time zone is set to GMT+2 the date created value will be +2 GMT. The following code shows how to correctly calculate the age of an item by first converting the created date to UTC and then subtracting the current time in UTC.

     

    1 DateTime itemCreatedDate = (DateTime)item["Created"];
    2
    3 itemCreatedDate = SPContext.GetContext(item.Web).RegionalSettings.TimeZone.LocalTimeToUTC(itemCreatedDate);
    4
    5 TimeSpan itemAge = DateTime.UtcNow.Subtract(itemCreatedDate);
    6

     

    del.icio.us Tags: , ,
    Technorati Tags: , ,
  • SharePoint - How to determine if a user has access to your site (SPWeb) or list?

    If you have a user's login in the form DOMAIN\username it's quite trivial:

    bool hasWebPermission = web.DoesUserHavePermissions(login, SPBasePermissions.Open); bool hasListPermission = web.Lists[0].DoesUserHavePermissions(login, SPBasePermissions.Open);

    On the other hand if you only have their email address, you need to first resolve that to a login. Lucky the SPUtility class can help out.

    string userName = "ewilde@somedomain.com"; SPPrincipalInfo principleInfo = SPUtility.ResolvePrincipal(web, userName, SPPrincipalType.All, SPPrincipalSource.All, null, true); bool hasPermissions = web.DoesUserHavePermissions(principleInfo.LoginName, SPBasePermissions.Open);

    On a side note fellow Charteris colleague Peter also has a SharePoint blog well worth checking out.

     

    Technorati Tags: ,

    del.icio.us Tags: ,
  • Windows Workflow on SharePoint - Alert notifications

     

    Creating a custom workflow task email alert

    The default email notification should suffice in most cases. However for us the email didn't meet the following criteria:

    • Degrade MOSS only functionality gracefully for Outlook 2007 client if workflow is hosted in a WSSv3 environment
    • Provide appropriate text & link explaining how to edit the task for non-MOSS environments. The default email assumes you can use the 'Edit this Task' button hence the copy in the email does not make it obvious when linking to the task. Side note: The default email on an Office 2003 client does make this obvious.

     

    Problem: Default email shows 'Edit this task' button - which only works if the workflow is hosted in a MOSS environment.

    Tasks - Please review csharp language sp... has been assigned to you - Message (HTML)

    Clicking on the button when the workflow is hosted on WSSv3 gives an end-user a rather unhelpful error message:

    "An error occurred while retrieving the workflow task details. This could be caused by not having connectivity to the server or because the task no longer exists.

    If this error persists, contact your system administrator."

    Microsoft Office Outlook
    Cracking open fiddler reveals that the office 2007 client is making a call to a web service, workflow.asmx, which is only present on MOSS systems:

    WindowClipping (5)

    Solutions:

    1. Define a custom email body that makes the task editing link inside the email more obvious. This still has the problem of showing the non-functional 'Edit this task' button.

      One caveat with using a custom email body is the rather unintuitive extra API call you need to make in order to get it working; namely: HasCustomEmailBody = true. See the last 3 lines in the sample create task activity below:
      private void CreateTask(object sender, EventArgs e) { try { this.ApproveTaskId = Guid.NewGuid(); this.ApproveTaskProperties.Title = string.Format(Thread.CurrentThread.CurrentCulture, Resources.TaskApproveTitle, this.WorkflowProperties.Item.Name); this.ApproveTaskProperties.AssignedTo = this.formData.Approver; this.ApproveTaskProperties.Description = this.formData.Comment; this.ApproveTaskProperties.ExtendedProperties["Author"] = this.taskLastUpdatedByUserName; if (!this.formData.TaskDueDate.Equals(DateTime.MinValue)) { this.ApproveTaskProperties.DueDate = this.formData.TaskDueDate; } this.ApproveTaskProperties.SendEmailNotification = true; this.ApproveTaskProperties.EmailBody = "My custom email body"; this.ApproveTaskProperties.HasCustomEmailBody = true; } catch (Exception unhandledException) { Logger.LogMessage(string.Format(Thread.CurrentThread.CurrentCulture, Resources.ErrorMessageCreateTask, unhandledException.Message), EventLogEntryType.Warning); throw; } }

    2. Send your own email

    This solution will remove the 'Edit this task' button as we won't be sending the custom SMTP headers which cause this button to appear. Of course the down side of this approach, is that if your workflow does end up in a MOSS environment you won't get the enhanced outlook integration with the email notifications.

    In our case we needed to include a link to the task edit form in the email. Therefore we can't send the email in the CreateTask workflow activity, since the task has not been created we won't have the task ID which forms part of the URL to the edit/display form i.e. http://localhost/Lists/Tasks/DispForm.aspx?ID=21 <- task id

    To overcome this problem we simply hook the OnTaskCreated event using a Microsoft.SharePoint.WorkflowActions.OnTaskCreated activity. At this point we have the task id and can either use a send email activity or SPUtility.SendEmail to fire off the custom notification email sans 'Edit this task' button.

    The code below shows how to extract the URL to the task display form

    /// <summary> /// Gets the task display form Url. /// </summary> /// <param name="web">The web.</param> /// <param name="list">The list.</param> /// <param name="itemId">The item id.</param> /// <returns>Task display form link</returns> [CLSCompliant(false)] public static string GetTaskDisplayFormLink(SPWeb web, SPList list, int itemId) { if (web == null) { throw new ArgumentNullException("web"); } if (list == null) { throw new ArgumentNullException("list"); } SPForm form = list.Forms[PAGETYPE.PAGE_DISPLAYFORM]; return string.Concat(new object[] { web.Url, '/', form.Url, "?ID=", itemId }); }

    Sending an email using SPUtility:

    SPUtility.SendEmail(this.WorkflowProperties.Web, false, false, emailTo, emailSubject, emailBody, false);

     

    Technorati Tags: , ,

    del.icio.us Tags: , ,
  • SharePoint changing a job schedule

    Although you can view the schedules for jobs from the SharePoint administration web site; there is no way to change the frequency which they run.

    Edit Timer Job - Windows Internet Explorer

    Using the command line tool stsadm to change the schedule

    However it's possible to do this using the good old command line tool stsadm.

    stsadm -o setproperty -propertyname job-immediate-alerts -url http://localhost -propertyvalue "every 1 minutes between 0 and 59"

    This will configure the 'Immediate alerts' job to run once every minute; particularly useful if you're trying to test some feature that relies on receiving SharePoint alerts 

     

    Technorati Tags: , ,

    del.icio.us Tags: , ,
  • WatiN - Web Application Testing in .Net - an Introduction

    Download code sample (zipped 190k)

    WatiN is a .Net library that can be used to automate Internet Explorer. It allows developers to create unit tests for web application front-ends. Due to the fact that it's basically just a DLL it easily integrates with nunit and mstest

    Why use WatiN?

    WatiN allows you to emulate real users interacting with your web site by automating IE, and more importantly allows you to assert conditions about the web site.

    A typical example of this might be to test user registration, asserting appropriate error messages are displayed when required fields are not filled out.

    In most cases this type of validation is performed using JavaScript; which is why you need to use a framework such as WatiN that executes the JavaScript contained within your web page. This is where nunitasp or Visual Studio web tests fall down as they both simulate a browser and do not run client side code during test execution.

    In past projects I've used WatiN to test all of the expected user interactions and integrated the tests into our build process. It's quite helpful at giving you an early indication that you might have broken something.

     

     Hello world test

    [TestMethod] public void Search() { // Perform search IE browser = new IE(); browser.GoTo("http://search.live.com/"); browser.TextField("q").TypeText("Hello world"); browser.Button("go").Click(); // Navigate to next page browser.Link(Find.ByText("Next")).Click(); // Now assert that we really are on page 2 Assert.IsTrue(browser.Div("search_header").Text.Contains("Page 2 of ")); }

     

    Locating elements 

    Creating test scripts in most cases involves finding an html element and either causing it to fire an event, set it's value or assert it's expected value.

    In order to perform an action against an element you must first obtain a reference to it. This can be done in 3 different ways:

    1. By the elements id (if it has one)
    2. Regular expression that matches the elements id
    3. Attribute class

    The attribute class is extremely flexible and makes finding elements that do not have an id a breeze. The Find class provides factory methods (returning instances of Attribute) for the most commonly used ways to find an element:

    [TestMethod] public void FindElements() { IE browser = new IE(); browser.GoTo(Path.Combine(Environment.CurrentDirectory, "testpage1.htm")); // Find using the name: <input type="button" name="btnGo".... browser.Button(Find.ByName("btnGo")).Flash(); // Find a label element by the id of the control it's linked to browser.Label(Find.ByFor("firstName")).Flash(); // Find an image by it's source attribute browser.Image(Find.BySrc("princejazzbo.jpg")); // Find an element by it's index in the collection return by the first findby browser.RadioButton(Find.ByName("cardType") && new Index(1)).Flash(); // Alternatively we could locate a radio button by name and value browser.RadioButton(Find.ByName("cardType") && Find.ByValue("MasterCard")).Flash(); }

    In order to determine the best way to find an element it's often necessary to have a peek at the DOM for the page you're testing. Two great tools for this are:

     Firebug - Live Search Hello world

     

    Interacting with JavaScript dialogs

    One of the great things about WatiN is how easy it is to automate interaction the pop-up windows and JavaScript dialogs.

    [TestMethod] public void JSDialog() { using (IE browser = new IE()) { browser.GoTo(Path.Combine(Environment.CurrentDirectory, "testpage1.htm")); // First create an object to handle the js alert AlertDialogHandler dialogHandler = new AlertDialogHandler(); browser.DialogWatcher.Add(dialogHandler); // Need to use ClickNoWait to allow the code to continue execution // and handle the modal dialog that's created as a result of the click browser.Link("linkJSDialog").ClickNoWait(); // Now interact with the dialog dialogHandler.OKButton.Click(); // Remove the handler so that it does not intercept any more dialogs browser.DialogWatcher.Remove(dialogHandler); } }

    If the only thing you're interested in is making sure that a dialog did or did not appear use the SimpleJavaDialogHandler class. This is particularly useful for testing registration forms that use alert dialogs.

    [TestMethod] public void SimpleJavaDialogHandler() { using (IE browser = new IE()) { browser.GoTo(Path.Combine(Environment.CurrentDirectory, "testpage1.htm")); // First create an object to handle the js alert SimpleJavaDialogHandler dialogHandler = new SimpleJavaDialogHandler(); browser.DialogWatcher.Add(dialogHandler); browser.Link("linkJSDialog").Click(); // Now assert that a dialog was shown. Assert.IsTrue(dialogHandler.HasHandledDialog); // Remove the handler so that it does not intercept any more dialogs browser.DialogWatcher.Remove(dialogHandler); } }

    Code sample:

    If you've never tried using WatiN I encourage you to give it a go: WatiN introduction sample code (zipped 190k)

     

    Useful WatiN links:

    http://watin.sourceforge.net/ - WatiN Home page

    http://watin.sourceforge.net/htmlelementmapping.html - HTML to WatiN object mapping table

    http://watintestrecord.sourceforge.net/ - Test recorder for WatiN

  • SharePoint Web Part development - Some lessons learnt.

    Having recently helped out at the end of web part development project, I thought I would share some of the lessons that we learnt.

    • Getting better error messages
    • SharePoint Diagnostic logging
    • WebPart properties, storing complex types
    • WebPart storage mechanism applicable for storing user application data or personalization data?
    • Make sure you dispose SharePoint objects properly

     

    Getting better error messages

    If an unhandled exception is thrown inside of a web part, SharePoint will by default display a rather uninformative generic message page:

     Error - Windows Internet Explorer

    Great for end-users (possibly), not so great for developers. However if you make a few modifications to the web.config for the web application your developing on, you can once again get helpful error messages with stack traces!

    <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <configuration> <configSections> ... </configSections> <SharePoint> <SafeMode MaxControls="200" CallStack="true" DirectFileDependencies="10" TotalFileDependencies="50" AllowPageLevelTrace="false"> <PageParserPaths> </PageParserPaths> </SafeMode>

    CallStack="true" being the important setting here.

     

    Then further on down the file:

    <customErrors mode="Off" />

     

     After making these configuration changes the error message shown like it's supposed to.

     Attempted to divide by zero. - Windows Internet Explorer

     

    SharePoint Diagnostic logging

    You can manage how SharePoint handles diagnostic information from: Central Administration > Operations > Diagnostic Logging. When developing it can be quite useful to set the trace log to verbose, decrease the number of log files, to say 5, and set 'Number of minutes to use a log file' to a couple of minutes. That way when you need to read the log file your not wading through tons of old diagnostic information

    Diagnostic Logging - Windows Internet Explorer

     

    WebPart properties, storing complex types

    It's much easier to use simple types when implementing WebPart properties due to the way that SharePoint persists the WebPart. However there may be some cases where a complex type make more sense. You can make it work using standard XML serialization attributes

    The example below has the appropriate attributes to persist a complex type property and an array property:

     

    [XmlRoot(Namespace = "MyWebPart")] public class MyWebPart : Microsoft.SharePoint.WebPartPages.WebPart { [WebPartStorage(Storage.Personal), WebBrowsable(false)] [XmlElement("ComplexType")] public MyComplexType ComplexType { get { ///; } set { ///; } } [WebPartStorage(Storage.Personal), WebBrowsable(false)] [XmlElement("MyArray", typeof(typeStoredInArray))] public ArrayList MyArray { get { ///; } set { ///; } } } [Serializable] public class MyComplexType { }

     

     

    WebPart storage mechanism applicable for storing user application data or personalization data?

    It seems that using WebPartStorage for storing user application data may not always be appropriate. We found that site administrator's values for a WebPart property marked as Storage.Personal, would become the default values for that property. Site members using the WebPart for the first time would then start with the administrator's value (not ideal).

    If you wanted to store things specific to a user i.e. most recently used xyz, WebPartStorage may not be the way to go.

     

    Make sure you dispose SharePoint objects properly

    If you use the SharePoint object model in many cases SPSite and SPWeb require disposal. There's a very good reference on MSDN: http://msdn2.microsoft.com/en-us/library/ms778813.aspx#sharepointobjmodel__otherobjectsthatrequiredisposal which I've attempted to summarize for my own sanity below:

    Object / Method / Property Requires disposal What to dispose Example usage / Explanation
    new SPSite() Yes returned SPSite  
    SPSite.OpenWeb() Yes returned SPWeb  
    SPSite.RootWeb Yes returned SPWeb  
    SPSiteCollection.Add() Yes returned SPSite  
    new SPGlobalAdmin() Yes returned SPGlobalAdmin  
    SPSite.AllWebs[]
    ↔  SPWebCollection[]
    Yes returned SPWeb using(SPWeb web = site.AllWebs[0])
    { /// }
    SPSite.SelfServiceCreateSite Yes returned SPSite  
    SPSite.LockIssue
    SPSite.Owner
    SPSite.SecondaryContact
    Yes SPSite.RootWeb The properties reference data from the top-level Web site and use the SPSite.RootWeb property
    SPWeb.ParentWeb Yes returned SPWeb  
    SPWeb.Site No?   Think this gets disposed of when you dispose the SPWeb instance
    SPWeb.Webs.Add()
    ↔ SPWebCollection.Add()
    Yes returned SPWeb  
    SPWeb.Webs[]
    ↔ SPWebCollection[]
    Yes returned SPWeb  
    Microsoft.SharePoint.Portal.SiteData.Area.Web Yes returned SPWeb  
    WebPartPage.RootWeb Yes returned SPWeb  
    SPControl.GetContextSite No    
    SPControl.GetContextWeb No    
    Updated: 23/07/07 - Included SPWeb.Site
  • Cross-browser event handling for IE and Firefox

    The other day I needed to brush up cross-browser events handling in JavaScript and couldn't find a concise summary for the types of things I wanted to do, namely:

    • Attach events handlers
    • Raising the events from markup
    • Reference the event object in the event handler
    • Obtain a reference to the element that caused the event to fire
    • Cancel an event to stop event bubbling / propagation

    So I thought I would put together a quick howto:

    Attaching Event Handlers

    if (typeof( window.addEventListener ) != "undefined" ) { // Firefox way document.body.addEventListener("click", myFunction, false); } else { // IE way document.body.attachEvent('onclick', myFunction); }

     

    Calling events from markup

    <a href="#" onclick="j avascript:myFunction(event);return false; ">Call function</a>

     

    Reference the event object inside the event handler

    function myFunction(e) { // e will be null if running under IE, // in the case of firefox e will already be an event object . if( typeof( e ) == "undefined" && typeof( window.event ) != "undefined" ) e = window.event; }

     

    Obtain a reference to the element that caused the event to fire

    function myFunction(e) { var sender = (typeof( window.event ) != "undefined" ) ? e.srcElement : e.target; alert(sender.tagName); }

     

    Cancel event propagation

    function dropButtonClick(e) { if( typeof( e ) == "undefined" && typeof( window.event ) != "undefined" ) e = window.event; // do things.... if (typeof( window.event ) != "undefined" ) { // IE e.cancelBubble=true; } else { // Firefox e.stopPropagation(); } }