I am trying to automate a test case through Specflow, by using the Gherkin format, but I keep getting the error:
OpenQA.Selenium.NoSuchElementException : no such element: Unable to locate element: {"method":"xpath","selector":"./ancestor-or-self::form"}
The button that cannot be located is commented in the AddCart Page below.
The website that I am testing is: http://automationpractice.com/index.php
What I am trying to achieve is as seen below:
Feature: Adding products in the cart
Scenario Outline: Adding a product of the HomePage in the cart and continue shopping
Given That the user is on the HomePage
When User clicks the Add to cart button
And User clicks the Continue shopping button
Then The user will stay on the HomePage
The code of the Step Definition is:
namespace WebsiteTestingSpecflow.Steps
{
[Binding]
public sealed class AddingCartContinueStep
{
AddCartPage addcart = null;
[Given(#"That the user is on the HomePage")]
public void GivenThatTheUserIsOnTheHomePage()
{
IWebDriver webDriver = new ChromeDriver();
webDriver.Navigate().GoToUrl("http://automationpractice.com/index.php");
addcart = new AddCartPage(webDriver);
}
[When(#"User clicks the Add to cart button")]
public void WhenUserClicksTheAddToCartButton()
{
addcart.AddCart();
}
[When(#"User clicks the Continue shopping button")]
public void WhenUserClicksTheContinueShoppingButton()
{
addcart.ContinueShopping();
}
[Then(#"The user will stay on the HomePage")]
public void ThenTheUserWillStayOnTheHomePage()
{
addcart.Verifyelement();
}
}
}
The code of the AddCart Page is:
namespace WebsiteTestingSpecflow.Pages
{
public class AddCartPage
{
private readonly WebDriverWait wait;
public IWebDriver Webdriver { get; }
public AddCartPage(IWebDriver webDriver)
{
Webdriver = webDriver;
wait = new WebDriverWait(webDriver, TimeSpan.FromSeconds(30));
}
public IWebElement BtnAddCart => Webdriver.FindElement(By.CssSelector("#homefeatured > .ajax_block_product:nth-child(1) .button:nth-child(1) > span"));
// This is the button that I keep getting error.
public IWebElement btnContinueCart => Webdriver.FindElement(By.CssSelector(".continue > span"));
public void AddCart() {
BtnAddCart.Submit();
}
public void ContinueShopping() {
wait.Until(ExpectedConditions.ElementToBeClickable(By.CssSelector(".continue > span")));
btnContinueCart.Submit();
}
public void Verifyelement() => Webdriver.FindElement(By.CssSelector(".sfHover > .sf-with-ul"));
}
}
The CSS Selector of that button is as mentioned in the AddCart Page, but it still unable to locate the element.
May anyone know how can I fix this problem?
Thank you in advance.
Looks like you are trying to find element at the moment when it's not visible yet, because it will be visible only when you click to Add to cart and only then that element can be reached.
Try code below and also consider using Page Object model
public void ContinueShopping() {
IWebElement btnContinueCart => Webdriver.FindElement(By.CssSelector(".continue > span"));
wait.Until(ExpectedConditions.ElementToBeClickable(By.CssSelector(".continue > span")));
btnContinueCart.Submit();
}
You need to incorporate Actions into your code since this button is not visible unless you hover over it. Also add a wait condition around it
WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10));
Actions action = new Actions(driver);
var btn = wait.Until(ExpectedConditions.ElementIsVisible(By.CssSelector("#homefeatured > .ajax_block_product:nth-child(1) .button:nth-child(1) > span")));
action.MoveToElement(driver.FindElement(By.CssSelector("#homefeatured > .ajax_block_product:nth-child(1) .button:nth-child(1) > span"))).Build().Perform();
btn.Click();
Add the following to the top:
using OpenQA.Selenium.Interactions;
using OpenQA.Selenium.Support.UI;
Related
I am trying to write a xamarin app that will display a login page before a master detail page but I am running into issues.
Right now I have my app.xaml calling an appbootstrapper as follows:
public App()
{
this.InitializeComponent();
RxApp.SuspensionHost.CreateNewAppState = () => new AppBootstrapper();
RxApp.SuspensionHost.SetupDefaultSuspendResume();
this.MainPage = RxApp.SuspensionHost
.GetAppState<AppBootstrapper>()
.CreateMainPage();
}
/// <summary>Gets the Router associated with this Screen.</summary>
public RoutingState Router { get; } = new RoutingState();
With the app bootstrapper as follows:
public class AppBootstrapper : ReactiveObject, IScreen
{
public AppBootstrapper(IMutableDependencyResolver dependencyResolver = null)
{
SetupLogging();
this.RegisterParts(dependencyResolver ?? Locator.CurrentMutable);
this.Router.Navigate.Execute(new LoginPageViewModel(this));
}
/// <summary>Gets the Router associated with this Screen.</summary>
public RoutingState Router { get; } = new RoutingState();
public Page CreateMainPage()
{
return new RoutedViewHost();
}
private static void SetupLogging()
{
var logger = new Logger { Level = LogLevel.Debug };
Locator.CurrentMutable.RegisterConstant(logger, typeof(ILogger));
}
private void RegisterParts(IMutableDependencyResolver dependencyResolver)
{
dependencyResolver.RegisterConstant(this, typeof(IScreen));
dependencyResolver.Register(() => new LoginPage(), typeof(IViewFor<LoginPageViewModel>));
dependencyResolver.RegisterConstant(new LoginService(), typeof(ILoginService));
}
}
This gets me to my login screen no problem, and I can perform my login operation. Then, once login is successful, I try to navigate to the master detail page, but this is where I run into issues.
public LoginPageViewModel(IScreen screen)
{
this.loginService = Locator.Current.GetService<ILoginService>();
this.HostScreen = screen ?? Locator.Current.GetService<IScreen>();
this.PrepareObservables();
}
........................................................
private void PrepareObservables()
{
...
this.LoginCommand = ReactiveCommand.CreateFromTask(
async execute =>
{
var loginSuccessful = await this.loginService.Login(this.Username, this.Password);
if (loginSuccessful)
{
this.HostScreen.Router.NavigateBack.Execute().Subscribe();
}
}, canExecuteLogin);
...
You can see that my login command is trying to perform a navigate and reset to go to the Main Page (which is my master detail page). This is not working and is resulting in an unhandled exception stating:
An object implementing IHandleObservableErrors has errored, thereby breaking its observable pipeline. To prevent this, ...>
Does anyone know what to do here? I need a good pattern for handling the use case of Login -> Master Detail Page in Xamarin Forms using ReactiveUI. Thanks.
this.LoginCommand = ReactiveCommand.CreateFromTask(
async execute =>
{
var loginSuccessful = await this.loginService.Login(this.Username, this.Password);
if (loginSuccessful)
{
this.HostScreen.Router.NavigateBack.Execute().Subscribe();
}
}, canExecuteLogin);
The above code is navigating back on successful login. I think you mean to use Router.NavigateAndReset.Execute(new MainPageViewModel()).Subscribe();
I have to test a web app in c# selenium and all functions need login before test. Is there any way i can skip login steps in my tests? as they are repeating and wasting time...I have read about saving login details to cookie, but not sure how and where to add cookie and how to call them in test methods. Also that if i use cookies, i will not be able to run them parallel by adding [Parallelizable] thing in it
namespace ParallelGrid {
[TestFixture]
[Parallelizable]
public class ParallelGrid1
{
public static IWebDriver driver;
[SetUp]
public void Setup()
{
ChromeOptions options = new ChromeOptions();
driver = new ChromeDriver();
}
[Test]
public void Test1()
{
driver.Navigate().GoToUrl(" ");
//enter username
//enter password
//press submit
//go to home screen
//perform test 1
}
[Test]
public void Test2()
{
driver.Navigate().GoToUrl(" ");
//enter username
//enter password
//press submit
//go to home screen
//perform test 2
}
[Test]
public void Test3()
{
driver.Navigate().GoToUrl(" ");
//enter username
//enter password
//press submit
//go to home screen
//perform test 3
}
}
}'''
You can use user-data-dir inside of chromeoptions to save profile data, them you can check if you are logged in at the init of every test.
Example:
public void Setup ( )
{
string ProfileDirect=Directory.GetCurrentDirectory()+"\\MyProfile";
if ( !Directory.Exists ( ProfileDirect ) )
{
//create data folder if not exist
Directory.CreateDirectory ( ProfileDirect );
}
// Create new option with data folder
var options=new ChromeOptions();
options.AddArgument ( #"user-data-dir="+ProfileDirect );
// Instance new Driver , with our current profile data.
Driver=new ChromeDriver(options);
if ( !IsLoggedIn ( ) )
{
Login ( );
}
}
public bool IsLoggedIn ( )
{
// Check if button logout is visible
return Driver.FindElement(By.XPath ( "//a[contains(#href,'logout')]" ))!=null;
}
public void Login ( )
{
//Some code to login
}
After the first execution the cookies will be saved in the profile folder and after the second execution you will be logged, them you can call every test without login in every one
Add driver.Url = "http:/yoururlhere to [SetUp], as it is executed once before each test
https://nunit.org/docs/2.2.10/fixtureSetup.html
The project I am working on contains the following structure:
When app is launched, user sees a Welcome page. At that point user has two options. They can either login or register. If logged in == true; then go to master detail page. Or in registration, if register == success then go to login page and follow the same process and end up in the master detail page.
-> Login Page ||
Welcome Page >> ================== || => MasterDetailPage
-> Register Page -> Login page ||
I am using MVVM Light to handle my navigation stack via INavigationService as my UI and business logic is separated via MVVM. Everything works pretty good except for I need to reset the navigation stack so the user will not be able to access any page before the "MasterDetailPage" showed above. Right now users can go back to login or registration or whatever page they were before, by using the hardware back button on Android or swiping from the left edge on iOS. Plus, There is a navigation back button on top navigation bar anyway.
My App.cs looks something like this
public App()
{
var nav = RegisterNavigationService();
SimpleIoc.Default.Register<INavigationService>(() => nav);
InitializeComponent();
var initialPage = new NavigationPage(new WelcomePage());
nav.Initialize(initialPage);
MainPage = initialPage;
}
private NavigationService RegisterNavigationService()
{
var nav = new NavigationService();
nav.Configure(Locator.LoginForm, typeof(LoginForm));
nav.Configure(Locator.RegisterSuccessPage, typeof(RegisterSuccessPage));
nav.Configure(Locator.RegistrationForm, typeof(RegistrationForm));
nav.Configure(Locator.WelcomePage, typeof(WelcomePage));
nav.Configure(Locator.MasterMainPage, typeof(MasterMainPage));
return nav;
}
On my view models, I handle the navigation commands like this:
public class LoginFormViewModel : BaseViewModel
{
private readonly INavigationService _navigationService;
public Command NavigateToMainPage { get; }
public LoginFormViewModel(INavigationService navigationService)
{
_navigationService = navigationService ?? throw new ArgumentNullException("navigationService");
NavigateToMainPage = new Command(() => NavigateToMainApp());
}
private void NavigateToMainApp()
{
_navigationService.NavigateTo(Locator.MasterMainPage);
}
}
Finally, my NavigationService.cs looks like this... I barely touched this part of the code... The only thing I tried is the 'ClearNavigationStack' method but that was a failure.
public class NavigationService : INavigationService, INavigationServiceExtensions
{
private Dictionary<string, Type> _pagesByKey = new Dictionary<string, Type>();
private NavigationPage _navigation;
public string CurrentPageKey
{
get
{
lock (_pagesByKey)
{
if (_navigation.CurrentPage == null)
{
return null;
}
var pageType = _navigation.CurrentPage.GetType();
return _pagesByKey.ContainsValue(pageType)
? _pagesByKey.First(p => p.Value == pageType).Key
: null;
}
}
}
public void GoBack()
{
_navigation.PopAsync();
}
public void NavigateTo(string pageKey)
{
NavigateTo(pageKey, null);
}
public void NavigateTo(string pageKey, object parameter)
{
lock (_pagesByKey)
{
if (_pagesByKey.ContainsKey(pageKey))
{
ConstructorInfo constructor;
object[] parameters;
var type = _pagesByKey[pageKey];
if (parameter == null)
{
constructor = type.GetTypeInfo()
.DeclaredConstructors
.FirstOrDefault(c => !c.GetParameters().Any());
parameters = new object[] { };
}
else
{
constructor = type.GetTypeInfo()
.DeclaredConstructors
.FirstOrDefault(
c =>
{
var p = c.GetParameters();
return p.Count() == 1
&& p[0].ParameterType == parameter.GetType();
});
parameters = new[] { parameter };
}
if (constructor == null)
{
throw new InvalidOperationException("No suitable constructor found for page " + pageKey);
}
var page = constructor.Invoke(parameters) as Page;
_navigation.PushAsync(page);
}
else
{
throw new ArgumentException(
string.Format("No such page: {0}. Did you forget to call NavigationService.Configure?", pageKey), "pageKey");
}
}
}
public void Configure(string pageKey, Type pageType)
{
lock (_pagesByKey)
{
if (_pagesByKey.ContainsKey(pageKey))
{
_pagesByKey[pageKey] = pageType;
}
else
{
_pagesByKey.Add(pageKey, pageType);
}
}
}
public void ClearNavigationStack()
{
lock (_pagesByKey)
{
foreach (var pageKey in _pagesByKey.Keys)
{
_pagesByKey.Remove(pageKey);
}
}
}
public void Initialize(NavigationPage navigation)
{
_navigation = navigation;
}
}
I've taken this bit from the following git repo: https://github.com/mallibone/MvvmLightNavigation.XamarinForms
by following this tutorial:
https://mallibone.com/post/xamarin.forms-navigation-with-mvvm-light
Note: It is a PCL.
Any suggestion is welcome as I've been on this for the last 2 days.
EDIT: Just now, I've managed to "hide" the nav stack by setting my MainPage to something like this
App.Current.MainPage = new MasterMainPage();
But it seems like a code smell and looks like a horrific hack. Plus I am not too sure if it "violates" the concepts I am following... And I guess this navigation stack will never be gone anyway as I will do other navigation stacks inside the master detail pages.
From your picture I see that you have Master/Detaied page inside Navigation page. Xamarin doesn't recommend to do that. I don't know how you are going to do it in MVVM Light but in regular Forms you have couple options to achieve what you want:
If you ever need to go back to your Login or register page you should use
await Navigation.PushModalAsync(new YourMasterDetailPage());
Then you can popmodal to get back to them BUT in this case Hardware button will still bring you to Login. You can use part of method 2 to clear stack after you navigated to you master-detail page but be careful - you cannot remove a page from stack if it is root and currently displayed page, so you will need to clear regular navigation stack only after login page is not displayed.
I wouldn't recommend that option as "Modal views are often temporary and brought on screen only long enough for the user to complete a task."
http://blog.adamkemp.com/2014/09/navigation-in-xamarinforms_2.html
If you don't need to go back you can use the follow to clear Navigation stack, it will also remove Back button
await Navigation.PushAsync(new YourMasterPage());
var pages = Navigation.NavigationStack.ToList();
foreach (var page in pages)
{
if (page.GetType() != typeof(YourMasterPage))
Navigation.RemovePage(page);
}
My code with implicitly wait works well. But I read information about waits and understood, that I need using explicitly wait in my projects. That`s why I am trying implement my test project with it.
When step of my alhorithm equel click to button, I have error : http://joxi.ru/BA0GMyDhnY0n2y
Please, help me with it.
Base class:
using NUnit.Framework;
using System;
using LinkedinAddContacts.Pages;
using LinkedinAddContacts.TestData;
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.Support.UI;
namespace LinkedinAddContacts
{
[TestFixture]
public class TestClass
{
private IWebDriver webDriver;
private WebDriverWait waitDriver;
[SetUp]
public void InitializeBrowser()
{
webDriver = new ChromeDriver();
waitDriver = new WebDriverWait(webDriver, TimeSpan.FromSeconds(10));
webDriver.Manage().Window.Maximize();
webDriver.Manage().Timeouts().PageLoad = TimeSpan.FromSeconds(30);
webDriver.Navigate().GoToUrl("https://www.linkedin.com/");
}
[Test]
public void TestMethod()
{
Authorization authorizationData = new Authorization();
StartPage objStartPage = new StartPage(waitDriver);
NetworkPage objNetworkPage = new NetworkPage(waitDriver);
objStartPage.EntrySystem(authorizationData);
objNetworkPage.ConnectPeople();
}
[TearDown]
public void CloseBrowser()
{
webDriver.Quit();
}
}
}
Secondary class:
using NUnit.Framework;
using LinkedinAddContacts.TestData;
using OpenQA.Selenium;
using OpenQA.Selenium.Support.UI;
namespace LinkedinAddContacts.Pages
{
public class StartPage
{
// private IWebDriver webDriver;
private WebDriverWait waitDriver;
#region Objects
public StartPage(WebDriverWait waitDriver)
{
this.waitDriver = waitDriver;
}
private IWebElement EmailInput
{
get
{
return waitDriver.Until(ExpectedConditions.ElementToBeClickable(By.Name("session_key")));
//return webDriver.FindElement(By.Name("session_key"));
}
}
private IWebElement PasswordInput
{
get
{
return waitDriver.Until(ExpectedConditions.ElementToBeClickable(By.Name("session_password")));
// return webDriver.FindElement(By.Name("session_password"));
}
}
private IWebElement LoginButton
{
get
{
return waitDriver.Until(ExpectedConditions.ElementToBeClickable(By.Name("login-submit")));
//return webDriver.FindElement(By.Id("login-submit"));
}
}
private IWebElement RegistrationForm
{
get
{
return waitDriver.Until(ExpectedConditions.ElementToBeClickable(By.Id("regForm")));
// return webDriver.FindElement(By.Id("regForm"));
}
}
#endregion
#region Methods
public void CloseRegistrationForm()
{
IJavaScriptExecutor js = waitDriver as IJavaScriptExecutor;
js.ExecuteScript("document.getElementById('regForm').style.display = 'none';");
// ((IJavascriptExecutor)driver).executeScript("scroll(0,400)");
}
public void EntrySystem(Authorization authorizationData)
{
// CloseRegistrationForm();
EmailInput.SendKeys(authorizationData.Email);
PasswordInput.SendKeys(authorizationData.Password);
LoginButton.Click();
}
#endregion
}
}
Error there:
public void EntrySystem(Authorization authorizationData)
{
// CloseRegistrationForm();
EmailInput.SendKeys(authorizationData.Email);
PasswordInput.SendKeys(authorizationData.Password);
LoginButton.Click();
}
When I understand it properly your code crashes at this line:
return waitDriver.Until(ExpectedConditions.ElementToBeClickable(By.Name("login-submit")));
Now, taking a look at the startpage of linkedIn reveals that the login-submit button doesn't have a name attribute defined, but you can use it's id instead.
<input tabindex="1" id="login-submit" class="login submit-button" type="submit" value="Einloggen">
So you should be using By.id() instead of By.name().
It is important to notice which web driver you use.
First of all, As #Robert says, its better to find by Id whenever it is available to you.
Second, I think LoginButton.Click() does not work. I had such a problem with chrome driver. When page scale(zooming) is changed, the Click method does not work properly, or clicks elsewhere on the page.
I recommend you to use SendKeys for any click action.
Just like this:
LoginButton.SendKeys(Keys.Enter);// or Keys.Return
Never ever use Click method
Please share your experience on the timeouts implementation in case of Page Factory model.
In case of simple variable usage (var loginButton = driver.FindElement(By.Id("login")), there are possibility to make extension like:
public static class WebDriverExtensions
{
public static IWebElement FindElement(this IWebDriver driver, By by, int timeoutInSeconds)
{
if (timeoutInSeconds > 0)
{
var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(timeoutInSeconds));
return wait.Until(drv => drv.FindElement(by));
}
return driver.FindElement(by);
}
}
Code belongs to user Loudenvier
Reference to original discussion
But I can not add attributes to FindsByAttribute sealed class. At this moment my WebElements look like:
[FindsBy(How = How.Id, Using = "tbUsername")]
private IWebElement Username;