Sunday, June 15, 2008

I love toys an gadgets!  They are so much fun.  Most of us geeks are gadget guys too.  I just got a GPS for my car, it's so much fun to play with.  Toys that help me get my job done faster and better are really cool.  They help me pay for the other cool toys like GPS's, HD Camcorders, MP3 players, etc.  One of my favorite toys for work is ReSharper, a super Visual Studio add-on that helps out in many ways.  If you aren't using ReSharper, you should check it out.  It isn't really a toy but it makes coding so much more enjoyable and easier, it may as well be a toy.

I've been using ReSharper for a pretty long time, I blogged about it over a year ago.  But in Oct 2007 I started my current job and switched to VS 2008 (it was beta at the time) so I couldn't use ReSharper because it didn't work with LINQ and Lambdas and all of the other cool stuff in VS 2008.  I had hoped that ReSharper 4.0 would have been ready a little sooner since the VS 2008 was officially released quite some time ago, but unfortunately I had to wait.  But now it's here and packed full of cool enhancements.

One of the best features of ReSharper is that you don't need to compile your code to see all of the errors -- not that my code has any errors ;-).   But I wrote about that and some other old features already.  Here's some new features:

  • ReSharper fully supports LINQ and the other language enhancements.
  • ReSharper provides great code completion/intellisense in aspx pages (source view)! 
  • Also, ReSharper will create method stubs from an ASPX page source view - so if I type OnCommand="foo", ReSharper can create:

protected void foo(object sender, CommandEventArgs e)
{
     throw new NotImplementedException();
}

  • With "CamelHumps", you can abbreviate class names and method names and let ReSharper complete the word.  So if I have a method named GetAllDuplicates(), I can just Type GAD!

So far, I have only one complaint but it is really no big deal.  ReSharper wants to convert a lot of my local variables to the new variant type - var.  I use the var type in code where I want it, but I don't believe it should be over used like this.  If a variable is a string and I know it is a string, it should be typed as such, as a string.  ReSharper offers the suggestion to convert it to a var.  But the cool thing about ReSharper is that I can tell it I'm not interested in this and it won't bother me any more!

Believe me, there are a ton of other great features.  Just download a trial version or ReSharper 4.0 for yourself and check it out.

 

Sunday, June 15, 2008 9:09:56 PM (Eastern Standard Time, UTC-05:00)  #    Disclaimer  |  Comments [2]  |  Trackback
 Monday, January 07, 2008

From now on I will take my own advice.  I spent a good part Friday afternoon and Monday morning trying to figure out what was wrong with my code and I don't want to do that again!  The problem I was having was that I had an ASP.Net control that would not postback, no matter what I tried.

I have a page with several user controls on it.  One of these user controls includes an asp:Textbox that is hooked to some JavaScript so that when the use clicks "enter" in that control a postback will occur.  The postback is accomplished by some JavaScript that calls the click event on a button that is hidden (style="display:none").  But the postback wouldn't work.  I put a few alerts in the JavaScript and I knew it was getting called, but the postback still never occurred.  The problem was not so easy to see.  That's because I had a second user control on the page.  That second user control has an asp:RequiredFieldValidator in it.  To complicate things, that validation control was tucked in a modal popup (using the Ajax Control Toolkit's Modal Popup Extender).  But, I forgot to include the ValidationGroup property on the validator.  So any control on the page would cause validation to occur which will block the postback if validation fails.  But since the validator was in the popup (which of course, was not currently popped up), I didn't see the error message that was trying to tell me there was a problem.

The problem was easily fixed (if not easily found) by simply adding the ValidationGroup property to the validator and of course, matching it up on the button that is supposed to call that validation group.  Since the hidden button on my other control was not included in the same validation group, it was free to postback any time it wants.

Monday, January 07, 2008 10:59:52 PM (Eastern Standard Time, UTC-05:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Monday, December 17, 2007

The new ListView control in ASP.Net is pretty cool.  It is really flexible and often makes life pretty easy for me.  But there is one thing that doesn't make sense to me.

It isn't simple to figure out which item is the "Edit Item" during databinding.  It isn't impossible, but it seems more complicated then I'd expect.  During ItemDataBound() you can test the item's type:

        if (e.Item.ItemType == ListViewItemType.DataItem)
        {
            //do something
        }

Remember that with a ListView, there are no rows, so we refer to Items. 

Unfortunately, the options for ListViewItemType only include DataItem, InsertItem, and EmptyItem.  All of those are helpful, but what about EditItem? So the next logical thing to do is use the ListView's EditIndex.  All I'd have to do is compare the current index to the EditIndex and I'll know if I'm on the edit item.  ListViewItemEventArgs has an Item property but there doesn't seem to be an Index or CurrentIndex property.  So the comparison won't be so easy.  We'll here it is, the way to figure it out:

    protected void ListView1_ItemDataBound(object sender, ListViewItemEventArgs e)
    {
        if (e.Item.ItemType == ListViewItemType.DataItem)
        {
            ListViewDataItem dataItem = (ListViewDataItem)e.Item;

            if (dataItem.DisplayIndex == ListView1.EditIndex)
            {
                //do something
            }
        }
    }

I hope this information is helpful.

Monday, December 17, 2007 10:33:03 PM (Eastern Standard Time, UTC-05:00)  #    Disclaimer  |  Comments [5]  |  Trackback
 Thursday, November 15, 2007

I learned something new today, thought I'd share it.  Actually, I think I relearned it, it sounded familiar.  There are so many features to .Net these days it is tough to remember everything.  Anyway, here we go.

Did you know that you can put content directly inside a ContentPlaceHolder of a MasterPage?  I'm not talking about in a page's asp:Content control that matches up to the Master's asp:ContentPlaceHolder.  That is what we typically do.  But you can put content in the asp:ContentPlaceHolder control on the master.  I like to think of it as default content.  In the content page, if you put content inside the asp:Content control, it will override the "default" value from the Master.  If not, the Master's content will be shown.  Content, Content Pages, asp:Content, asp:ContentPlaceholders...confused?  Here's a simple example:

Here is some code from body of our MasterPage.  I'm only including the body to save space.  We have 2 contentplaceholders.  The first has some "default text"

<head runat="server">
    <title>Untitled Page</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:ContentPlaceHolder ID="ContentPlaceHolder1" runat="server">
            By Default, show this.
        </asp:ContentPlaceHolder>
    </div>
    <div>
        <asp:ContentPlaceHolder ID="ContentPlaceHolder2" runat="server">
        </asp:ContentPlaceHolder>
    </div>
    </form>
</body>

Now let's look at our first ContentPage.  It puts text in each of the Content areas:

<%@ Page Language="C#" MasterPageFile="~/Site1.Master" AutoEventWireup="true" 
CodeBehind="WebForm1.aspx.cs" Inherits="MasterPageSample.WebForm1" 
Title="Untitled Page" %>

<asp:Content ID="Content1" ContentPlaceHolderID="ContentPlaceHolder1" runat="server">
    content in placeholder 1
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="ContentPlaceHolder2" runat="server">
    content in placeholder 2
</asp:Content>

Next comes our second ContentPage.  This one only has one asp:Content control to match the second ContentPlaceHolder from the MasterPage:

<%@ Page Language="C#" MasterPageFile="~/Site1.Master" AutoEventWireup="true" 
CodeBehind="WebForm2.aspx.cs" Inherits="MasterPageSample.WebForm2" 
Title="Untitled Page" %>

<asp:Content ID="Content2" ContentPlaceHolderID="ContentPlaceHolder2" runat="server">
    content in placeholder 2
</asp:Content>

The last ContentPage has 2 asp:Content controls but only the second one has actual content. The first one is empty:

<%@ Page Language="C#" MasterPageFile="~/Site1.Master" AutoEventWireup="true" 
CodeBehind="WebForm3.aspx.cs" Inherits="MasterPageSample.WebForm3" 
Title="Untitled Page" %>

<asp:Content ID="Content1" ContentPlaceHolderID="ContentPlaceHolder1" runat="server">
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="ContentPlaceHolder2" runat="server">
    content in placeholder 2
</asp:Content>

So what are the results?  The first page renders both pieces of content:

image

The second one shows it's content and since the first ContentPlaceholder wasn't matched up with a Content control, it shows the content from the MasterPage:

image

The last page has both ContentPlaceHolders matched up, only the first has no content at all.  So in this case, it overrides the "default" content from the master with nothing!  All you get is the content from the second Content control:

image

Pretty cool, huh?

Thursday, November 15, 2007 8:51:40 PM (Eastern Standard Time, UTC-05:00)  #    Disclaimer  |  Comments [4]  |  Trackback
 Friday, September 28, 2007

This will be the first in series of posts I'll be writing about creating server controls.  I'll try to share some of what I learned from my research while I prepared for a recent presentation. 

With most software development, there isn't always a simple answer to a question.  This is of course true for creating Composite Controls for ASP.Net 2.0.  One area where this was apparent was the proper way to render a control.  Of the many examples I have found on the web it seems that most people put their rendering code within CreateChildControls() but some override the Render() method.  I'll show examples below.  So which is correct?  You know the answer...It Depends!   My personal preference seems to be outside of the norm but I prefer to include rendering code in the Render() method (as in Option A below) unless there is a compelling reason not to.

My sample CompositeControl will have 2 controls within it:

        private TextBox _textBox = new TextBox();
        private Label _label = new Label();

Option A:

        protected override void CreateChildControls()
        {
            Controls.Clear();
            _textBox.ID = "txtBox1";
            _label.ID = "label1";
            Controls.Add(_textBox);
            Controls.Add(_label);
            base.CreateChildControls();
        }
        protected override void Render(HtmlTextWriter writer)
        {
            EnsureChildControls();
            AddAttributesToRender(writer);
            writer.RenderBeginTag(HtmlTextWriterTag.Div);
            writer.AddAttribute(HtmlTextWriterAttribute.Cellpadding, "1", false);
            writer.RenderBeginTag(HtmlTextWriterTag.Table);
            writer.RenderBeginTag(HtmlTextWriterTag.Tr);
            writer.RenderBeginTag(HtmlTextWriterTag.Td);
            _label.RenderControl(writer);
            writer.RenderEndTag(); //</td>
            writer.RenderBeginTag(HtmlTextWriterTag.Td);
            _textBox.RenderControl(writer);
            writer.RenderEndTag(); //</td>
            writer.RenderEndTag(); //</tr>
            writer.RenderEndTag(); //</table>
            writer.RenderEndTag();
        }

In option A, CreateChildControls is used simple to set properties on the controls and add them to the Controls Collection.  The Render() method takes care of layout.  In particular, I like being able to use the HtmlTextWriterTag enumeration with the RenderBeginTag() method.  By the way, the RenderEndTag() method will "close" whatever tag is next in line to be closed within the nested hierarchy of tags.  Note that the Div tag I render isn't really necessary.  But I want all of my samples to render the same markup, so I put that in.  Use the AddAttribute() method to add attributes to whichever tag is written next after the AddAtttribute() call.  So in this sample, the CellPadding attribute will get written into the table as <table cellpadding="1">.

Option B:

        protected override void CreateChildControls()
        {
            Controls.Clear();
            _textBox.ID = "txtBox1";
            _label.ID = "label1";

            Controls.Add(new LiteralControl("<table cellpadding='1'><tr>"));
            Controls.Add(new LiteralControl("<td>"));
            Controls.Add(_label);
            Controls.Add(new LiteralControl("</td>"));
            Controls.Add(new LiteralControl("<td>"));
            Controls.Add(_textBox);
            Controls.Add(new LiteralControl("</td>"));
            Controls.Add(new LiteralControl("</tr></table>"));
            base.CreateChildControls();
        }

In option B, all of the rendering is done within CreateChildControls().  There is nothing wrong with this method, however, I don't like it as much.  I like my methods to do one thing only.  And since there is a method named Render(), doesn't it make sense to put the render code in it?  Also, within CreateChildControls, you don't get to use the RenderBeginTag() method with the enumerations.  That means more chances for mistakes, in my opinion.  If you are using this method within a CompositeControl, it is not necessary to override Render().  The default implementation of Render() will be called. 

Option C:

        protected override void CreateChildControls()
        {
            Controls.Clear();
            _textBox.ID = "txtBox1";
            _label.ID = "label1";
            
            Table table = new Table();
            table.CellPadding = 1;
            TableRow row1 = new TableRow();
            TableCell cell1 = new TableCell();
            cell1.Controls.Add(_label);
            TableCell cell2 = new TableCell();
            cell2.Controls.Add(_textBox);
            row1.Cells.Add(cell1);
            row1.Cells.Add(cell2);
            table.Rows.Add(row1);

            Controls.Add(table);
            base.CreateChildControls();
        }

Lastly, I put Option C to show what NOT to do (most of the time).  In this case, I have created my Table, Row and Cells as actual controls, not literals.  This is extra overhead and it is not needed because the only purpose for my table is to control layout.  The literal controls in options A and B will render quicker.  However, there are times when you NEED to have your Table, etc, included in the Controls collection.  In those cases, this technique can be used.  Once again, there is no need to override the Render() method with this technique.

Results:

So how does this all render in the browser?  I put this markup into an ASP.Net Page:

    <!--Option A -->
    <cc1:MyControl1 runat="server" ID="mc1" />
    <br />
    ------------------------------------------
    <!--Option B -->
    <cc1:MyControl2 runat="server" ID="mc2" />
    <br />
    ------------------------------------------
    <!--Option C -->
    <cc1:MyControl3 runat="server" ID="mc3" />

In the browser, I used "View Source" and got the following:

        <div id="mc1">
            <table cellpadding="1">
                <tr>
                    <td>
                        <span id="mc1_label1"></span>
                    </td>
                    <td>
                        <input name="mc1$txtBox1" type="text" id="mc1_txtBox1" /></td>
                </tr>
            </table>
        </div>
        <br />
        ------------------------------------------
        <div id="mc2">
            <table cellpadding='1'>
                <tr>
                    <td>
                        <span id="mc2_label1"></span>
                    </td>
                    <td>
                        <input name="mc2$txtBox1" type="text" id="mc2_txtBox1" /></td>
                </tr>
            </table>
        </div>
        <br />
        ------------------------------------------
        <div id="mc3">
            <table cellpadding="1" border="0">
                <tr>
                    <td>
                        <span id="mc3_label1"></span>
                    </td>
                    <td>
                        <input name="mc3$txtBox1" type="text" id="mc3_txtBox1" /></td>
                </tr>
            </table>
        </div>

Looks pretty close, huh?  There was one extra trick I did to make these match up.  First, remember that in Option A, I added an extra <div> tag when I rendered my control.  Without it, my option A control's outer tag would have been <table>.  Either way may work fine in your application.  Second, Options B and C also have the following code included:

        protected override HtmlTextWriterTag TagKey
        {
            get { return HtmlTextWriterTag.Div; }
        }
Without this override, my controls in Options B and C would have an outermost tag of <span> instead of <div>.  Many people prefer to have controls render with <div> tags and this is how to do it!

I hope this information is helpful to you.  Unfortunately, there isn't always a clear answer of which method is best. 

Friday, September 28, 2007 10:34:04 AM (Eastern Standard Time, UTC-05:00)  #    Disclaimer  |  Comments [5]  |  Trackback
 Thursday, September 06, 2007

I've got some speaking engagements coming up.  It almost sounds like an official tour is planned.

Presentation Topic: Developing Custom Controls for ASP.Net

Description:  In this demo I'll show you how to get started creating your own custom server controls for asp.net.  I'll show how to create controls, expose public properties to make them easier for other developers to implement, and handle events from the control.  We'll  create a ControlDesigner so Visual Studio can render the control at design time.  I'll even build some extra features into the control using Ajax.

Where:

I'm really excited to get out to some of these other groups for these presentations.  If you live near any of these groups, check them out.  They've all got regular, monthly meetings with great .Net content.  I've also got calls in to Madison Square Garden, LA Coliseum, Wembley Stadium and others.  But I haven't heard back from them yet.

Please, no brown M&M's.

Update:  When I get some time I'll be writing the demo out as a tutorial blog post.  In the mean time, if you are interested in the source code for this demo, here it is:

ControlsDemo.zip (1.05 MB)

Thursday, September 06, 2007 11:59:22 AM (Eastern Standard Time, UTC-05:00)  #    Disclaimer  |  Comments [2]  |  Trackback
 Friday, August 24, 2007

I recently started playing around with WatiN (Web Application Testing in .Net) and WatiN Test Recorder.  While these are cool tools that can greatly improve the testing process, I found that there isn't a lot of information out there to help get started.  I have put together a brief tutorial on some of the basics.  I hope it helps.  I'm including code samples in this post, plus you can download the entire VS Solution and a short video below.

WatiN, pronounced "What-in", is a library that allows you to create tests for web applications.  It is based in WatiR which was created for Ruby.  Basically, you can test your web pages just like any other classes.  In my case, I'll use NUnit to control the actual tests but they are written with WatiN.  The tests run in the browser via a class named "IE".  That immediately points out one of the downsides to WatiN -- it only works with Internet Explorer.  But the tests run in a real browser so it is very accurate.

Here's a quick code sample:
            IE ie = new IE("http://localhost/somewebsite/default.aspx");
            ie.Link(Find.ById("LinkButton1")).Click();

If you run that in a test, the browser will actually open, navigate to the default page, find the LinkButton and click it.  Seems pretty simple right?  It isn't too complicated but you'll have to get used to the WatiN classes and methods.  The fact is, the tests are a pain to write.  That's where WatiN Test Recorder comes in.  All you have to do is start the recorder and the tool will begin to record your actions in the browser (there is a browser built into WatiN Test Recorder) and translate your actions into code.  If you click LinkButton1, the recorder outputs "ie.Link(Find.ById("LinkButton1")).Click();" for you!  This is a big help but it is not exactly perfect.  I have found that it doesn't get the code right and some tweaking is needed.  I'm starting to get the hang of it so the changes to the code are getting easier. 

Sample Code and Downloads:

Download my VS 2005 solution:  WatiNDemo.zip  167KB
Download my Video of WatiN Test Recorder in action:  WatiNTestRecorderDemo.avi (Right click and "save target as")  2.22MB

I created a simple data entry web application with 2 pages that pretends to register users to a web site for outdoor enthusiasts.  The first page lists the registered users, the second page is a form to register:

image image

I want to make sure my data entry page works, so I fire up WatiN Test Recorder.  The video (see the link above) shows what happens in the recorder as I step through the page  and enter the data into the web form.  Here is the code that the recorder generated for me, with line numbers added here for reference:

          1   IE ie = new IE("http://localhost:49573/RegisteredList.aspx");
          2   ie.Link(Find.ById("LinkButton1")).Click();
          3   ie.TextField(Find.ById("txtFirst")).Click();
          4   ie.TextField(Find.ById("txtLast")).Click();
          5   ie.TextField(Find.ById("txtLast")).TypeText("andy");
          6   ie.TextField(Find.ById("txtEmail")).TypeText("schwam");
          7   ie.TextField(Find.ById("txtEmail")).Click();
          8   ie.TextField(Find.ById("txtEmail")).TypeText("andy@email.com");
          9   ie.RadioButton(Find.ByName("rdoSendInfo_0") && Find.ByValue("yes")).Checked = true;
          10  ie.TableCell(Find.ByCustom("innertext", " Choose New Jersey Pennsylvania")).Click();
          11  ie.SelectList(Find.ById("ddlState")).SelectByValue("Pennsylvania");
          12  ie.SelectList(Find.ById("ddlCounty")).Click();
          13  ie.SelectList(Find.ById("lstHobbies")).Click();
          14  ie.SelectList(Find.ById("lstHobbies")).Click();
          15  ie.SelectList(Find.ById("lstHobbies")).Click();
          16  ie.SelectList(Find.ById("lstHobbies")).Click();
          17  ie.Button(Find.ById("btnSubmit")).Click();
          18  ie.Link(Find.ByUrl("javascript:__doPostBack('GridView1','Select$0')")).Click();
          19  ie.Link(Find.ById("LinkButton1")).Click();

Fixing the Generated Code:

  • Line 1 and 2 are good:  Open Internet Explorer and navigate to my page.
  • Line 3, 4, 7, and 10 are not really needed but it won't cause any trouble.  The clicks seem to be irrelevant.
  • Line 5 and 6 are just wrong.  I put the text "andy" in the textbox named txtFirst, and "schwam" in txtLast.  I can easily fix them:  
    • ie.TextField(Find.ById("txtFirst")).TypeText("andy");
    • ie.TextField(Find.ById("txtLast")).TypeText("schwam");
  • Line 9 throws this exception if you let it run: "WatiN.Core.Exceptions.ElementNotFoundException: Could not find a 'INPUT (radio)' tag containing attribute name with value 'rdoSendInfo_0'".  It should be Find.ById, not Find.ByName.  This works instead:
    • ie.RadioButton(Find.ById("rdoSendInfo_0")).Checked = true;
  • Line 11 is good
  • Line 12.  Notice no county is selected. Try this:
    • ie.SelectList(Find.ById("ddlCounty")).SelectByValue("Delaware");
  • Lines 13, 14, 15, and 16 are just clicks but nothing is selected.  Try this:
    • ie.SelectList(Find.ById("lstHobbies")).SelectByValue("Hiking");
    • ie.SelectList(Find.ById("lstHobbies")).SelectByValue("Camping");
    • ie.SelectList(Find.ById("lstHobbies")).SelectByValue("Mountain Biking");
    • ie.SelectList(Find.ById("lstHobbies")).SelectByValue("Kayaking");
  • Line 18 is correct but worth mentioning.  This selects the first row in the grid which causes the application to display the users detail in the grid.

That doesn't seem to good for a first shot!  But even with all of the mistakes, I'd rather start with the generated code then nothing at all, especially if this was a longer, more complicated test.  Plus, I admit there are features of the recorder that I am not familiar with so maybe it would work better if I actually knew what I was doing! 

AJAX Issues:

Technically, the changes I made are correct but it still won't work right.  I am using ASP.Net Ajax to call back to the server when the state (ddlState) changes.  I'm populate a list of counties (ddlCounty) on the server.  When you run the test it goes so fast that it tries to select the county before the drop down list is loaded!  I found some documentation about a WaitUntil() method but I couldn't get it to work.  So I did it the old fashioned way and put the thread to sleep:

            ie.SelectList(Find.ById("ddlState")).SelectByValue("Pennsylvania");
            System.Threading.Thread.Sleep(100);
            ie.SelectList(Find.ById("ddlCounty")).SelectByValue("Delaware");

Validation:

After the submit button is clicked, the application returns to the list of users.  Since I am writing a test, I should validate the results.  I'll add in a few asserts:

            Assert.AreEqual("Andy", ie.TextField(Find.ById("txtFirst")).Text);
            Assert.AreEqual("Schwam", ie.TextField(Find.ById("txtLast")).Text);
            Assert.AreEqual("me@email.com", ie.TextField(Find.ById("txtEmail")).Text);

The Final Test Class:

The solution contains a class library with a class Tests . Using NUnit, I can execute the test easily.

using WatiN.Core;
using NUnit.Framework;

namespace Tests
{
    [TestFixture]
    public class Tests
    {
        [Test]
        public void FirstTest()
        {
            IE ie = new IE("http://localhost/WatinDemoSite/RegisteredList.aspx");
            ie.Refresh();
            ie.Link(Find.ById("LinkButton1")).Click();
            ie.TextField(Find.ById("txtFirst")).TypeText("Andy");
            ie.TextField(Find.ById("txtLast")).TypeText("Schwam");
            ie.TextField(Find.ById("txtEmail")).TypeText("me@email.com");
            ie.RadioButton(Find.ById("rdoSendInfo_0")).Checked = true;
            ie.SelectList(Find.ById("ddlState")).SelectByValue("Pennsylvania");
            System.Threading.Thread.Sleep(100);
            ie.SelectList(Find.ById("ddlCounty")).SelectByValue("Delaware");
            ie.SelectList(Find.ById("lstHobbies")).SelectByValue("Hiking");
            ie.SelectList(Find.ById("lstHobbies")).SelectByValue("Camping");
            ie.SelectList(Find.ById("lstHobbies")).SelectByValue("Mountain Biking");
            ie.SelectList(Find.ById("lstHobbies")).SelectByValue("Kayaking");
            ie.Button(Find.ById("btnSubmit")).Click();

            //VALIDATION

            ie.Link(Find.ByUrl("javascript:__doPostBack('GridView1','Select$0')")).Click();

            Assert.AreEqual("Andy", ie.TextField(Find.ById("txtFirst")).Text);
            Assert.AreEqual("Schwam", ie.TextField(Find.ById("txtLast")).Text);
            Assert.AreEqual("me@email.com", ie.TextField(Find.ById("txtEmail")).Text);

        }
    }
}

Running The Tests:

I use ReSharper from JetBrains which makes my life easier in so many ways.  One of which is that it includes a tool to run unit tests.  The tests within the Visual Studio solution included in this post work great when run through ReSharper.  But I found that when I ran the tests with NUnit GUI directly I got an exception:

Tests.Tests.FirstTest : System.Threading.ThreadStateException : The CurrentThread needs to have it's ApartmentState set to ApartmentState.STA to be able to automate Internet Explorer.

There are some posts out there about resolving this issue but I have a better solution.  Either install ReSharper or install UnitRun, a free tool that runs tests (also from JetBrains).  While I haven't tested UnitRun, I expect it will work the same as the ReSharper solution.

Friday, August 24, 2007 11:57:43 PM (Eastern Standard Time, UTC-05:00)  #    Disclaimer  |  Comments [12]  |  Trackback