TestStack / TestStack.Seleno

Seleno helps you write automated UI tests in the right way by implementing Page Objects and Page Components and by reading from and writing to web pages using strongly typed view models.
http://teststack.github.com/TestStack.Seleno/
MIT License
180 stars 60 forks source link

Seleno: Using strongly typed controller action expressions to navigate to the page via routing. #239

Open wijdenchebbi opened 8 years ago

wijdenchebbi commented 8 years ago

Hi, I am a software engineer, my task is to realise an test acceptance for ASP.net MVC C# project. I want to generate the page object by specifying navigation by selecting a controller and action (rather than a Url string). this is my code:

_EditCustomerPage = BrowserHost.Instance.NavigateToInitialPage<ClientController,  EditCustomerPage>(x => x.Edit(10)); 

And i add an instance of seleno host:

  Instance.Run("Project", 13050, configure =>
 {      configure.WithRouteConfig(RouteConfig.RegisterRoutes);
 });

And the link generated is wrong:http://localhost:13050/fr/?Controller=Client&Action=Edit&id=22 Can you help me? Thanks

robdmoore commented 8 years ago

You need to use the appropriate configuration overload to register a route table that contains the routes from your mvc project.

Let me know if you want me to dig up an example code snippet from our example projects.

On 5 Feb 2016, at 1:06 am, wijdenchebbi notifications@github.com wrote:

Hi, I am a software engineer, my task is to realise an test acceptance for ASPnet MVC C# project I want to generate the page object by specifying navigation by selecting a controller and action (rather than a Url string) this is my code: _EditCustomerPage = BrowserHostInstanceNavigateToInitialPage(x => xEdit(10)); And i add an instance of seleno host: InstanceRun("Project", 13050, configure => { configureWithRouteConfig(RouteConfigRegisterRoutes); }); And the link generated is wrong:http://localhost:13050/fr/?Controller=Client&Action=Edit&id=22 Can you help me? Thanks

— Reply to this email directly or view it on GitHub.

mwhelan commented 8 years ago

Hi @wijdenchebbi. Here is an example from our docs, where RouteConfig.RegisterRoutes would be the method that configures the routes in your web application:

 public static class Host
 {
     public static readonly SelenoHost Instance = new SelenoHost();

     static Host()
     {
         Instance.Run("Name.Of.Your.Web.Project", 12346, c => c
             .UsingLoggerFactory(new ConsoleFactory())
             // If you are using MVC then do this where RouteConfig is the class that registers your routes in the "Name.Of.Your.Web.Project" project
             // If you aren't using MVC then don't include this line
             .WithRouteConfig(RouteConfig.RegisterRoutes)
         );
     }
 }

You can read more about it here.

wijdenchebbi commented 8 years ago

Thanks for getting back so quickly.

i used the appropriate route table, when i used a static URL ,it works correctly, but when i used the controller and action it doesn't work.

robdmoore commented 8 years ago

Can you paste in your configuration code?

And the navigation code?

On 5 Feb 2016, at 7:08 pm, wijdenchebbi notifications@github.com wrote:

Thanks for getting back so quickly.

i used the appropriate route table, when i used a static URL ,it works correctly, but when i used the controller and action it doesn't work.

— Reply to this email directly or view it on GitHub.

wijdenchebbi commented 8 years ago

To have an instance on seleno host i make the class BrowserHost

public static class BrowserHost
{

    public static readonly SelenoHost Instance = new SelenoHost();
    static BrowserHost()
    {
            Instance.Run("BigBrother.MVC.Intranet", 13050, configure => configure
            .WithRouteConfig(BigBrother.MVC.Intranet.MvcApplication.RouteConfig.RegisterRoutes)
            .WithMinimumWaitTimeoutOf(System.TimeSpan.FromSeconds(3))
          );
    }
}

the navigation code used in a method in the class EditCustomerStory.cs

  private void AndIAmOnTheEditAdministrativeInfosPage()
    {
        _EditCustomerPage = BrowserHost.Instance.NavigateToInitialPage<ClientController, EditCustomerPage>(x => x.Edit(22)); 
    }

EcitCustomerPage.cs is the page object ClientController.cs is the controller in my project

wijdenchebbi commented 8 years ago

Hi,

when i use a static URL: _EditCustomerPage = BrowserHost.Instance.NavigateToInitialPage("/fr/Client/Edit/22"); Sometimes it works and sometimes it doesn't, i don't know what to do. Can you help me

robdmoore commented 8 years ago

Re: your first problem, if you use Navigate.To<Clientcontroller, EditCustomerPage>(c => c.Edit(22)); within a page object (i.e. ignoring NavigateToInitialPage for now) does that work? I'm trying to ascertain if there is a bug in NavigateToInitialPage using the controller syntax or if there is still a configuration issue on your side. I know for a fact that Navigate.To works because we use that in our acceptance tests.

Re: your second problem when you say "sometimes it doesn't" work what do you mean? Do you get an exception? Does the page change, but to the wrong URL? Does nothing happen? ...

Thanks :)

wijdenchebbi commented 8 years ago

Thanks for your response, I tried to use Navigate.to: _EditCustomerPage = BrowserHost.Instance.Navigate.To<ClientController, EditCustomerPage>(x => x.Edit(22)); but i get this error: TestStack.Seleno.Configuration.SelenoHost' does not contain a definition for 'Navigate' and no extension method 'Navigate' accepting the first argument. And when I used NavigateToInitialPage, the url generated is wrong: http://localhost:13050/en/?Controller=Client&Action=Edit&id=22 In my second prob:When i used a static URL, the seleno navigate to wrong url like: localhost:13050/fr/Account/LogOn?ReturnUrl=%2ffr%2fClient%2fCreate

mwhelan commented 8 years ago

Hi @wijdenchebbi . Your second problem looks like the URL you are trying to navigate to requires authorization and so it is redirecting you to the login page. You will have to login earlier in the test before making that call.

With regards to your Navigate.To method, you need to add the generic arguments to the call.

It's quite hard to know what is happening with the NavigateToInitialPage method. Could you possibly provide a small sample of code that I can download and look at more closely? You might also want to install MvcRouteTester and try the same controller/action combinations you are trying here and see if you get the results you expect. (Our code is generating URL from the route table in a similar way to MvcRouteTester).

wijdench commented 8 years ago

Hi, In my test, i verify the login issue in the first step in my scenario. For my code: BrowserHost.cs `using BigBrother.MVC.Intranet; using System.Web; using System.Web.Mvc; using System.Web.Routing; using TestStack.Seleno.Configuration; using TestStack.Seleno.Configuration.WebServers; namespace BigBrother.MVC.Intranet.UITests { public static class BrowserHost {

    public static readonly SelenoHost Instance = new SelenoHost();
    static BrowserHost()
    {
            Instance.Run("BigBrother.MVC.Intranet", 13050, configure =>
           {
            configure.WithRouteConfig(BigBrother.MVC.Intranet.MvcApplication.RouteConfig.RegisterRoutes);
            configure.WithMinimumWaitTimeoutOf(System.TimeSpan.FromSeconds(3));
           });
    }
}

} The page object: EditCustomerPage.cs using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using OpenQA.Selenium; using TestStack.Seleno.PageObjects; namespace BigBrother.MVC.Intranet.UITests { class EditCustomerPage : Page { //*** Test on attributes public EditCustomerPage EnterName(string Name) { Find.Element(By.Id("Name")).Clear(); Find.Element(By.Id("Name")).SendKeys(Name);

        return this;
    }

    //*********** Test on error messages
    public bool IsNameErrorMessageVisible
    {
        get
        {
            return Find.Element(By.ClassName("field-validation-error")).Displayed;
        }
    }

    public T SubmitApplication<T>() where T : UiComponent, new()
    {
        return Navigate.To<T>(By.Id("save"));

    }

}

} For the story:EditCustomerStory.cs using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using BigBrother.MVC.Intranet; using NUnit.Framework; using TestStack.BDDfy; using TestStack.Seleno.PageObjects.Locators; using BigBrother.MVC.Intranet.Controllers; using TestStack.Seleno.Configuration; namespace BigBrother.MVC.Intranet.UITests { [TestFixture] [Story(AsA = "As a administrator", IWant = "I want to modify the administrative information of a client", SoThat = "So that I can save the modifications", Title = "Acceptance Tests related to Edit Administrative Information page")] public class EditCustomerStory {

    private LoginPage _loginPage;
    private EditCustomerPage _EditCustomerPage;
    private const string userLogin = "Administrateur.UI_Test@kyalis.com";
    private const string userPwd = "Montreal2015";
    private const string clientName = "";

    [Test]
    public void EditCustomerErrorMessageVisible()
    {
        this.Given(x => GivenIAmLoggedIn())
            .And(x => AndIAmOnTheEditAdministrativeInfosPage())
            .And(x => AndTheFieldNameIsEmpty())
            .When(x => WhenISaveWithAnEmptyNameField())
            .Then(x => ThenIShouldSeeAnErrorMessage())
            .BDDfy();
    }

    private void GivenIAmLoggedIn()
    {
        _loginPage = BrowserHost.Instance.NavigateToInitialPage<LoginPage>();
        var testlogin = _loginPage.IsLoggedIn();

        if (testlogin)
        {
            _loginPage.Disconnect<LoginPage>();
            _loginPage = BrowserHost.Instance.NavigateToInitialPage<LoginPage>();
            _loginPage.EnterLogin(userLogin)
           .EnterPwd(userPwd)
           .SubmitApplication<LoginPage>();
        }
        else
        {
            _loginPage = BrowserHost.Instance.NavigateToInitialPage<LoginPage>();
            _loginPage.EnterLogin(userLogin)
           .EnterPwd(userPwd)
           .SubmitApplication<LoginPage>();
        }
    }

    private void AndIAmOnTheEditAdministrativeInfosPage()
    {
        _EditCustomerPage = BrowserHost.Instance.NavigateToInitialPage<EditCustomerPage>("/fr/Client/Edit/22");
    }

    private void AndTheFieldNameIsEmpty()
    {
        _EditCustomerPage.EnterName(clientName);
    }

    private void WhenISaveWithAnEmptyNameField()
    {
        _EditCustomerPage.SubmitApplication<EditCustomerPage>();
    }

    private void ThenIShouldSeeAnErrorMessage()
    {
        var isNameErrorMessageVisible = _EditCustomerPage.IsNameErrorMessageVisible;

        Assert.That(isNameErrorMessageVisible, Is.True);
    }
}

} ` For the UITest, the version of Selenium.WebDriver installed is 2.47.0 and the version of TestStack.Seleno installed is 0.9.11.

mwhelan commented 8 years ago

Hi @wijdenchebbi . It looks like you are using Areas in your URL. If that's the case then you need to use the overload of Navigate (of NavigateToInitialPage and navigation within page objects) that takes a dictionary of route values. I wrote a bit of background about that here.

Another point I would make is that NavigateToInitialPage is only designed to be called once to get you the first page object. From that point on the methods in the page objects should return page objects using the UIComponent Navigate.To method. When using BDDfy methods like you are you will need to have page objects fields that get set by the first method and then the second method will use the same field.