In this section of the chapter, we're going to be refactoring our Waits into their respective actions and writing the other two tests.
With this line 30, this was added to solve the challenge in the first section. This would be one of the many solutions you can go with.
So, let's start by refactoring.
I'm going to be copying this line (I'll cut it in this case).
I'm going to add it to the Goto
method that it belongs to, so this is within our DeckBuilderPage.
We'll go right to our Goto
method and we'll paste it right here. Now I don't need this first part, Pages.DeckBuilder
, because we're already inside the Deck Builder page, so I can just delete that.
public DeckBuilder Goto()
{
HeaderNav.Map.DeckBuilderLink.Click();
Driver.Wait.Until(drvr => Map.AddCardsManuallyLink.Displayed);
return this;
}
Go back to my CopyDeckTest and do the same thing here with line 32 (copy this).
This was in our AddCardsManually
method, so let's scroll down to it here and paste in.
public void AddCardsManually()
{
Map.AddCardsManuallyLink.Click();
Driver.Wait.Until(drvr => Map.CopyDeckIcon.Displayed);
}
Going back to our CopyDeckTests.cs, we then had our Yes
method.
Let's copy this (wait code line).
And this went into our CopyDeck.Yes
, which is inside of our CopyDeckPage.
Here's Yes
, and we can paste it in.
Let's make sure we get rid of this Pages.CopyDeck
.
public void Yes()
{
Map.YesButton.Click();
Driver.Wait.Until(drvr => Map.CopiedMessage.Displayed);
}
Save this.
Back to our CopyDeckTests.cs and let's clean this up a bit.
Here we go.
The last thing I want to do to finish this refactor is this Goto
method returns a DeckBuilderPage
.
Let's take a look at it real quick here.
Our Goto
is clicking on this DeckBuilderLink
, navigating to the DeckBuilderPage
, waiting until this thing is displayed, and then returning this
.
Now this word this
is referring to itself, the DeckBuilderPage
. Notice that when I double click it, it highlights the deck builder pages as well.
So, it's like I'm returning itself, which sounds kind of funny, but that really is what it's doing.
And it allows us to chain commands.
Going back to our CopyDeckTests.cs, I can say AddCardsManually
here because I can chain those commands.
[Test]
public void User_can_copy_the_deck()
{
Pages.DeckBuilder.Goto().AddCardsManually();
Pages.DeckBuilder.CopySuggestedDeck();
Pages.CopyDeck.Yes();
Assert.That(Pages.CopyDeck.Map.CopiedMessage.Displayed);
}
Pretty cool, right?
Let's make a test - this one we're going to call “User_opens_app_store”.
And the next one – this test will be “User_opens_google_play”.
There we go.
For this first one, we essentially want these same 3 steps.
So just paste it right back down here. But instead of saying “Yes”, we want to say “No”.
And when we say “No”, we then want to open the App Store.
[Test]
public void User_opens_app_store()
{
Pages.DeckBuilder.Goto().AddCardsManually();
Pages.DeckBuilder.CopySuggestedDeck();
Pages.CopyDeck.No().OpenAppStore();
}
Then we'll do the same thing for the second test.
But instead of opening the App Store, we want to open the Google Play.
[Test]
public void User_opens_app_store()
{
Pages.DeckBuilder.Goto().AddCardsManually();
Pages.DeckBuilder.CopySuggestedDeck();
Pages.CopyDeck.No().OpenGooglePlay();
}
The way that we're going to assert that we're on the App Store is to Assert
that Driver.Title
is equal to "Clash Royale on the App Store".
Assert.That(Driver.Title, Is.EqualTo("Clash Royale on the App Store"));
This should be the title that we're expecting.
And down here [on the next test] we'll actually do something a little bit different.
How about we say assert the AreEqual
(because we've been using these two throughout).
The first one is what's expected — this should be "Clash Royale - Apps on Google Play". And this should be equal to whatever the title is.
Assert.AreEqual("Clash Royale - Apps on Google Play", Driver.Title);
Now this looks like a different order because when we say that, this is just a Boolean expression unless you do a comma plus this Is.EqualTo
.
I'm kind of showing you a few different ways that you can do these assertions, but really both of these are just asserting that these 2 values are equal to each other.
When you use AreEqual
you have the left side is the expected and the right side is the actual.
Now you can kind of see this as sort of a TDD, or test-driven approach, where we're writing what we want the test to look like or behave like and then we're going to actually write the implementation to make the test pass.
Right now, you can see that Visual Studio Code is a little upset that we don't have this No
method even though we're trying to use it.
This No
method is going to be inside of our CopyDeckPage.
Let's go into our “CopyDeckPage.cs” and let's make a No
method — public void No()
.
Then we need to find the actual button that does this for us.
Let's switch over to a browser. We're going to go to our Deck Builder page, click “Add cards manually” and let's grab this first one here. Now we land on this page.
So, we already found the “Yes” button but now we need to find this “No” one. Let's inspect it.
Here we are.
We have id
of “not-installed”. We could probably give this guy a try.
And let's say “Document.QuerySelector” and we should be able to just paste this in. We found it. Yay.
So, there it is. Let's give it a click. And awesome. It opened up our stuff.
This is the one I want to use, so let's copy this selector.
Back over to our code.
Inside of here [on CopyDeckPages in the CopyDeckPageMap] we will add:
public IWebElement NoButton => Driver.FindElement(By.Id("not-installed"));
There we go.
And now we can use that inside of our No
method using Map.NoButton.Click
.
And now notice how when we did our No
in our CopyDeckTests, I said I want to say “No” and then immediately proceed to open the App Store.
And we're doing this because when I click “No” I'm still on this page. So, I can click “No” and then do something else, right?
So, we're kind of mimicking that behavior by saying No, and this should return the page.
Let's go back to our CopyDeckPage and then just say return this
.
Which means that we need this to be CopyDeckPage
because we're returning itself and itself is a CopyDeckPage
.
public CopyDeckPage No()
{
Map.NoButton.Click();
return this;
}
So now that we're saying No
in our CopyDeckTest, these errors will go away, because now it understands, yep, you're actually returning the CopyDeckPage
.
We now need two more methods — one called OpenAppStore
and one called OpenGooglePlay
.
Let's create those inside here (CopyDeckPage) too.
If we say No
, we then have public void OpenAppStore
and public void OpenGooglePlay
.
We're going to keep these 2 as a void
because these 2 will actually take us to a different place.
And so, the only 2 methods that are currently keeping us on the page are No
and Yes
.
Which means that this public void Yes()
let's make sure we change it to CopyDeckPage as well. And over here, return this
.
public CopyDeckPage Yes()
{
Map.YesButton.Click();
Driver.Wait.Until(drvr => Map.CopiedMessage.Displayed);
return this;
}
And now these two methods (the Yes
and the No
) are done.
We'll scroll down here and now we need to click on the App Store button or click on the Google Play button.
Let's switch back over to our page here and let's right click on this.
Let's see what we have here.
We have really, really long href
but really all I care about is this App Store. So, I'm just going to use this.
So, let's use some XPath here. And I'm going to say I want the a
element that has the text
that equals the thing I just copied.
There we go.
And do I have it? Yes, indeed I do. And if I do a click on it, we can make sure that it's taking us to the right place. Huzzah, it does!
So now we can go back and that's the one that I want.
I'm going to copy this xPath, switch back over to code:
public IWebElement AppStoreButton => Driver.FindElement(By.XPath("//a[text()='App Store']"));
There we go.
And we'll do the same thing — click No for the Google Play button.
“Google Play”, same thing. So that means that it would just be this with “Google Play”.
Let's see if we got it. Sure enough, we did. All right, so now we can copy this one.
Switch back over to code and paste it in.
public IWebElement GooglePlayButton => Driver.FindElement(By.XPath("//a[text()='Google Play']"));
And now we can use these inside of our methods.
public void OpenAppStore()
{
Map.AppStoreButton.Click();
}
public void OpenGooglePlay()
{
Map.GooglePlayButton.Click();
}
I do want to show us one interesting thing.
If we refresh the page here and then click No, you can see that this cookies thing like, Hey, got it, accept my cookies.
This thing displays and may actually overlap our buttons, breaking our tests.
If you're familiar with how Selenium works, it tries to click on the button that you specify, but if something is on top of it, it will then return an exception saying, "Hey, I tried to click on this, but it actually clicked on this element instead."
So, we need to make sure that we close this first.
Now there's a lot of ways of doing it — we're just going to do it the pure Selenium way of just finding it and doing it ourselves.
So, in here we have this “a.cc-btn.cc-dismiss”, right? We can actually just use this to click the “GOT IT!” button.
So, let's make sure that we can.
We're going to say Document.QuerySelector and then, let's see, use that CSS that I was talking about.
So, we had “a.cc-btn.cc-dismiss”. There it is. And if I do the “.Click” on it, sure enough, it goes away.
Now when we clicked on it, notice how it faded away — which was a little bit different, right?
It didn't just immediately disappear. The issue with that is once we click on it, our code is immediately going to try to click the next button.
But if it's taking its time to fade away, then our code may be going too fast and it will click on that anyway, even though it hasn't fully faded away yet.
So, what we need to do is we need to not just click the button, but we need to wait for it to disappear and then proceed.
Let's go to our code here and let's add the element for this dismissal. We did this by CSS.
public IWebElement AcceptCookiesButton => Driver.FindElement(By.CssSelector("a.cc-btn.cc-dismiss"));
Now let's scroll back up here and we're going to make a method — we're going to do it above these [two methods for opening the stores].
We're going to call it public void AcceptCookies
.
And all we want this to do is, well, accept cookies, right? So AcceptCookiesButton.click
.
Then we're going to say we want to wait until this is no longer displayed. We do that with this here.
public void AcceptCookies()
{
Map.AcceptCookiesButton.Click();
Driver.Wait.Until(drvr => !Map.AcceptCookiesButton.Displayed);
}
Normally we would say wait for the button to be displayed, but we want this to not be displayed, right?
So, we're going to put a logical “not” [!
] at the front of this.
And now this says wait until this button is not displayed.
Now that we have this, we can add it to our No
method.
Because for Yes
, that accept cookie banner thing will never get in the way of it. So, we don't really need to add it here. But we do need to add it to our No
method.
We're just going to say AcceptCookies
to make sure that we get rid of that to anytime we say No
.
public CopyDeckPage No()
{
Map.NoButton.Click();
AcceptCookies();
return this;
}
Our No
is not displayed by default. I click “No”, and now these [download options] display.
We want these to be displayed for us to take actions on them.
If our code goes too fast again, it's going to miss these.
Even though we're not going to be clicking on “Other Stores” because it doesn't really require that in any of our tests, we are going to use it because it's the last element in this div
here — so, if “Other Stores” is displayed, we know that the App Store and Google Play button as well should be displayed.
Let's inspect this.
We see it has a nice easy id
of “Other Stores”.
We'll switch back over to code and go to our element map where everything lives.
This one was By.Id
which is always just so nice of them to do.
public IWebElement OtherStoresButton => Driver.FindElement(By.Id("other-stores"));
And there we go.
We can now scroll up and use this in our No
method.
Once we accept cookies, we then want to make sure that this button is displayed. That way we know that our App Store and our Google Play store buttons are good to go.
So, we're going to say our Driver
and we do want our OtherStoresButton
to be displayed. Once it is, proceed.
public CopyDeckPage No()
{
Map.NoButton.Click();
AcceptCookies();
Driver.Wait.Until(drvr => Map.OtherStoresButton.Displayed);
return this;
}
Let's look at our test now, because our test, the errors that we had on line 40 and on line 49 are now gone because we've actually implemented these methods — No
and OpenAppStore
.
The last error that we're getting is this title.
And it says, "Hey, your custom driver doesn't have the definition of title."
Well let's fix that.
Let's go into our Framework > Selenium > Driver and inside of here [Driver.cs], sure enough, we do not have one.
If we scroll down, there's our Goto
and FindElement
and Quit
, but we don't have one that says Title
.
So, let's make sure that we add that, and we'll do it right here.
This is a String. We're just going to call this one Title
and this is going to be our Current.Title
.
public static string Title => Current.Title;
Save this and now our Driver
has a Title
property.
Let's go back to our test and sure enough these are happy as can be.
The last thing we need to do is just make sure our assertions work.
Now for this assertion — Assert.That(Driver.Title, Is.EqualTo("Clash Royale on the App Store"))
— all I'm doing is checking that the Title
I'm landing on is equal to "Clash Royale on the App Store"
And then the same thing for Google Play — Assert.AreEqual("Clash Royale - Apps on Google Play", Driver.Title)
Now it doesn't have to be this way.
Really all I'm trying to assert is that clicking that OpenAppStore
button took me to this page. You can assert that however you want to. This is just one idea of how to do that.
Now I'm grabbing the Title
of the page, but how do I know that it should be "Clash Royale on the App Store"?
Let's check that out on the UI.
I'm going to click “No” and then click on the “App Store” it'll take me here.
And then we can actually check. If we were to get the element here, “querySelector”, and let's check the title.
It gives me the title.
Now I want you to see what it looks like inside of this head element, though, because ... Okay, goodness. Lots of text.
If we go to this title here and open it up, this is what the title equals. It's this right here.
However, inside of the console, if I do this .innerText
, this is what it looks like here.
So, what I did was I copied this and then pasted it inside of my test.
Now when you run the test, you're going to get this really weird-looking error that says, "Hey, your string is different than my string."
That's something that I think you guys should kind of check out on your own.
And I'll give you a hint. It has to deal with some of this craziness that you're seeing in here. These like special symbols.
Because it's going to say that even though it looks identical, and you'll have some that looks like this, and then my test looks like this, but notice how you have this tiny little dot right here.
So, I'll leave that for you guys to figure out if you want to approach it in this way.
The next thing I want to talk about is we actually caught a bug.
Currently, what they do is whenever you click “No” and then click on the “Google Play Store”…
Let's check it out here.
Instead of taking you to the app on the Google Play store, it currently reroutes you to the App Store. Up here we see this, "Hey, this app is only available in the App Store for iOS devices."
The weird thing about that is that I myself have a Google phone, right? That has a Google Play store. And I do have the game on there. So, I would say this is probably a bug.
Now they may have a reason for it, but the cool thing is that this redirection, our test would have caught it.
And so, this test is currently going to fail saying, "Wait a second. The title you're currently on is Clash Royale on the App Store
, even though you expected to go to the Google Play store."
So, the cool thing is that we now have these automated tests that will be catching any behavior that was not expected. Good stuff.
We're just going to change the categories here [for our tests] to match what we want.
We'll say “copydeck” for all of our tests.
[Test, Category("copydeck")]
Save the file and we can now run our tests using the "copydeck" filter.
I hope you can agree that our tests are much more readable now.
As we're doing the Page Object Model with the Page Map Pattern, things are starting to see, well shoot, man, this is really readable stuff.
“Go to add cards manually”
Pages.DeckBuilder.Goto().AddCardsManually()
.
The issue that we have right now is, again, we're repeating ourselves quite a bit.
We'll be solving this in another chapter once we start kind of breaking out our test bases because each one of these suites kind of has their own unique setup, right?
All of these are right now going to the CopySuggestedDeck
by the AddCardsManually
, right?
And so, if all 3 of these tests have the same setup, we can put that into a test base. That's one thing we can improve.
I'm sure there are others, but for now we're going to kind of take a step back from the Page Object Model and start going into other aspects of test automation that we really need to get into as well to make a more successful framework.
Because as you start running these tests, where you're supposed to change things may get a little difficult.
And so, we're going to introduce logging in the next chapter.
I'm sure things on the website have changed since this recording so I'd be surprised if the CopyDeckTests were passing for you! I hope they are!
However, if they are not, the challenge is to solve the errors so they all pass consistently.
HINT: You may want to look at the Title value on the web page's HTML.