6 things to avoid when learning selenium automation

dontdoit-1

1. Aiming at becoming a Selenium automation expert

This goal is so high that you will probably fail.

You should replace it with something more achievable like “i want to get a test automation job in 1 year”.

 

2. Selenium WebDriver certifications

Selenium WebDriver certifications are scams. Even if they would be good, they wont help you getting hired. When studying for a certification, you study for the exam that you will take and not for the subject.

Passing an exam is a very poor way of measuring your programming skills.

 

I met people who passed with 90% grades TOEFL (test of english as a foreign language) and TWE (test of written english) but speak and write very bad english.

 

3. Jumping into writing Selenium WebDriver tests before learning a programming language

Test automation with Selenium WebDriver is basically programming.

Without knowing a programming language well, you will always have big obstacles in writing automation code. Selenium WebDriver is just a library that can be used through multiple languages.

Jumping to Selenium tests with no programming is similar with trying to cook lasagna with no cooking skills.

Shortcuts never work. There are no shortcuts to math, to learning french, to getting fit, to anything. You have to put the effort in and follow a process that takes time.

 

4. Looking for the best selenium book or online course

There is no such thing. You will find though many great books and online courses.

The best book for Selenium should include everything about Selenium, everything about the programming language (lets say Java), everything about an IDE (example: Eclipse), everything about unit testing, maven, code design, code refactoring, maven, etc.

Do you really think that it exists?

It does not.

To learn test automation with Selenium, you need to use a multitude of resources, books, blogs, youtube videos, online courses, webinars, etc.

Because the amount of information needed for an automation job is very high.

 

5. Learning by yourself

If you attempt learning by yourself, chances are high that you wont make it.

Find other people with the same goal, take instructor-led courses, find developers willing to help you, go to meetups, anything but being by yourself.

 

6. Selenium IDE

Record and play is not test automation

 

Advertisements

What is a selenium tester doing in real time

If you missed the previous day, you can find it here.

Morning of January 18

I start the day with something to bring my mood and energy up.
One cup of hot, strong coffee (no sugar, no milk) for the energy and Rammstein music for the mood.

Today, I need to focus on test stability issues.

I have around 20 Jenkins jobs that run daily automated tests related to various site features.

Some jobs pass consistently but a few fail from time to time.

The first one that I will look at is for the ChangePassword feature.

It runs all tests from the ChangePassword test class using a Maven command:

mvn clean -Dtest=automatedtests.changePassword.ChangePasswordTests test

Out of 5 job runs, there are 2 failures. The Jenkins console log says that assertions are failing. I will run the test class locally to see what is going on.

 

15 minutes later

From all change password tests, the one that changes the password successfully works. All other negative tests that verify that the password cannot be changed (if it is too short or if it includes special characters) fail. Each negative test attempts changing the password and then checks if an error message is displayed.

 

But the error message is all the time the same. It should say that the password is too short or that the password cannot include special characters. Instead, it always says that the passwords are not identical.

 

This seems like an application bug so I should speak to the developers. I will record a video with the tests execution and send it in an email asking for their opinion. Maybe there is a good explanation for it. I better get their opinion first and then, if they agree, log a bug to Jira.

 

10 minutes later

The email is out so I can look into the second failing Jenkins job. This one is about the user acount security questions.

The Jenkins console log points to timeout issues for different page elements.

There are a few possible reasons:

  • The site is very slow.
  • The test does not navigate from a page to the next one. Maybe the test executes the code for clicking a button and the button is not clicked.
  • The ids of the elements changed again.

The html of the site is pretty bad with many elements having either no ids or dynamic ids.

I will have to run these tests locally as well, then confirm which elements cannot be found, get their ids and compare them to the page element ids from Chrome Inspector.

So booooriiiing, does anyone like to do this part?

Anyways, as Maximus’s servant said it in Gladiator  (what a terrific movie! looking forward to watch it again) when asked if he likes what he does:

Maximus: Do you find it hard to do your duty?
Cicero: Sometimes I do what I want to do. The rest of the time, I do what I have to.

I will do as well what I have to.

Reluctantly 😦

 

Later in the day

I am really upset now. All elements from the security questions page have different ids. I need to modify all locators from the SecurityQuestions.java class and retest all tests that use it.

This is such a waste of effort, modifying page element locators over and over and over.

Starting from scratch 🙂

Too bad that the developers are so busy with the new features that no one has time to add the custom ids that I keep asking for.

I am so looking forward to the end of the sprint. Maybe after it, I will be able to pair with a developer and go through all pages and add custom ids to all elements.

Fingers crossed!

 

Finally, the last failing Jenkins job.

This one is for the user registration tests.

The console log shows stale element exceptions for a few elements. This is weird since I am sure that these tests were running very well.

Maybe the developers enabled the auto postback setting for the page elements which makes the element regenerate every time it is interacted with. I solved this before for other pages by retrying a method on exception a few times so I can do the same here. But since it is later in the day, it will have to wait until tomorrow.

 

4 pm already?

Before I go home, someone mentioned on LinkedIn that page factory should not be used any longer.

It is the video of the keynote of a Selenium conference from 2016. I better watch it to get more details.

 

4.30 pm

Yes, Simon Stewart says that no one is using Page Factory and that its design could have been better.

But apparently, no one uses it correctly either and many people try to generate page objects automatically while using page factory.

Wow, never crossed my mind to generate page objects automatically!

So Page Factory works if you use it as it is supposed to be used.

I should watch this video again.

I laughed so much during it, Simon has a great sense of humor 🙂

Use streams for lists of web elements

2179048[1]

Every time you need to find multiple elements in your Selenium code, you get a list of web elements, so a collection.

Streams and predicates, introduced in Java 8, are the best way of working with collections.

When you use them, you don’t only get code that is shorter and easier to understand but also better performance.

Why is this useful for you, a Selenium WebDriver developer?

It is useful because instead of getting texts and attributes of all elements (matched by the same locator) with

private List< String > textValues(By locator) {
  List elements = driver.findElements(locator);

  List< String > values = new ArrayList<>();

  for (WebElement e : elements)
    values.add(e.getText());

  return values;
}

private List< String > attrValues(By locator, String name) {
  List elements = driver.findElements(locator);

  List< String > values = new ArrayList<>();

  for (WebElement e : elements)
    values.add(e.getAttribute(name));

  return values;
}

you can use

private List< String > textValues(By loc) {
  return getValues(loc, e -> e.getText());
}

private List< String > attrValues(By loc, String name) {
  return getValues(loc, e -> e.getAttribute(name));

private List< String > getValues(
By loc, Function<WebElement,String > pred) {

  List< WebElement > elements = driver.findElements(loc);       

  List< String > values = elements.stream().map(pred)
                                  .collect(Collectors.toList());

  return values;


}

Read along to understand why the second code version is much better.

How to use collections the old way

Assume that you have a Result class for results of a page:

public class Result {

  private String name;
  private int price;
  private String owner;
  private boolean onlineOnly;

  public Result(String name, int price, String owner, boolean onlineOnly) {
    this.name = name;
    this.price = price;
    this.owner = owner;
    this.onlineOnly = onlineOnly;
  }

  public String name() {
    return this.name;
  }

  public int price() {
    return this.price;
  }

  public String owner () {
    return this.owner;
  }

  public boolean onlineOnly() {
    return this.onlineOnly;
  }

  @Override
  public String toString() {
    return name + " - " + price + " - " + owner + " - " + onlineOnly;
  }

}

You need also a Results class (for lists of results):

public class Results {

  List< Result > results;

  public Results(List results) {
    this.results = results;
  }

  public List< Result > get() {
    return this.results;
  }

  public Result getResult(int i) {
    return this.results.get(i);
  }

  public int size() {
    return this.results.size();
  }

  @Override
  public String toString() {
    return this.results.toString();
  }

}

So far, Results class has methods for returning

  • the list of results
  • an element of the list
  • the size of the list
  • a String value for the list

Pretty basic, so far.

We would like to add filtering methods to the Results class such as:

public Results filterByOnlineOnly() {
  List< Result > list = new ArrayList()<>;

  for (Result r : this.results)
    if (r.onlineOnly() == true)
      list.add(r);

  return new Results(list);
}

public Results filterByOwner(String name) {
  List< Result > list = new ArrayList()<>;

  for (Result r : this.results)
    if (r.owner().equals(name))
      list.add(r);

  return new Results(list);
}

public Results filterByName(String name) {
  List< Result > list = new ArrayList()<>;

  for (Result r : this.results)
    if (r.name().equals(name))
      list.add(r);

  return new Results(list);
}

public Results filterByPrice(int price) {
  List< Result > list = new ArrayList()<>;

  for (Result r : this.results)
    if (r.price() == price)
      list.add(r);

  return new Results(list);
}

So far, we have filtering methods by name, owner, price and online only.

But, what should we do if we need more filtering methods such as

  • by price in a range
  • with a keyword in the name
  • with a keyword in the name and a specific owner

Adding more methods to the Results class is not the solution.

The more filters we need, the more methods.

One thing that is easy to notice is that all filtering methods have very similar code.

The only exception is the condition that the list elements should match.

How to use collections with streams and predicates

We can replace all methods with one method that uses a predicate parameter (for the filtering condition).

public Results filterBy(Predicate predicate) {

  List< Result > list = this.results.stream()
                  .filter(predicate)
                  .collect(Collectors.toList());

  return new Results(list);
}

How does this work?

  1. First, the results list is converted to a stream by the stream() method.
  2. Then, the filter() method filters the stream with the predicate . This is done by going through each element of the stream, applying the predicate to each element and selecting only the elements that match the predicate.
  3. Finally, collect() collects all selected elements and returns them as another list.

Amazing, isn’t it?

How can this method be used?

Results products = results.filterBy(r -> r.onlineOnly() == true);

Results products = results.filterBy(r -> r.name().equals("IPHONE 8"));

Results products = results.filterBy(r -> r.price() == 500);

We can get really creative now:

Results list = results.filterBy(r -> r.name().contains("IPHONE"));

Results products = results.filterBy(r -> r.name().contains("IPHONE") &&
                                         r.price() > 500 &&
                                         r.price() < 1000 &&
                                         r.onlineOnly() == false);

What else can you do with streams?

How about creating the list of all names, or all prices or all owners?

public List < String > values(Predicate predicate) {
  List list = this.results.stream()
                          .map(predicate)
                          .collect(Collectors.toList());

  return list;
}

Notice that the filter() method is replaced by the map().
map() extracts the value as specified by the predicate.
The method returns a list of Strings since names and owners are Strings:

List< String > owners = results.values(r -> r.owner());

List< String > names = results.values(r -> r.names());

There are many other things that can be done with streams and predicates:

Results onlineProducts = results.stream()
                                .filter(Result::onlineOnly)
                                .skip(1)
                                .collect(Collectors.toList());

Results cheapProducts = results.stream()
                           .filter(r -> r.price() <= 100)                                               .filter(r -> r.owner().equals("Rogers"))
                           .collect(Collectors.toList());

List< String > distinctOwners = results.stream()
                             .map(Result::owner)
                             .distinct()
                             .collect(Collectors.toList());

int priceSum = results.stream()
                      .map(Result::price)
                      .reduce(0, Integer::sum);

assertTrue(results.stream()
                  .anyMatch(r -> r.owner().equals("Bell")));

Java 8 In Action is a great book for learning more on streams and predicates.