Frage

Update: A Solution below!

I'm fairly new to website development but I've been tasked with developing e2e (end-to-end) tests for a developing website that uses AngularJS. As a result I've been looking down the road of using AngularJS's karma-run ngScenario testing wrapper.

Anyway, just getting started I want to make sure that a simple hyperlink's text matches part of its href address. There isn't need to know the structure of this code snippet, but these are thumbnails for user profiles, and you can click the thumbnail object completely (the first 'a') or you can click a link displaying their name (the second 'a').

There dozens of these on a page.

Here is what a part of the page looks like loaded with a user "PurplePenguin".

<div class="thumbnail__section">
    <a href="/profile/PurplePenguin">
    <div class="thumbnail__bottom">
        <div class="thumbnail__rating ng-binding"> </div>
            <div class="thumbnail__info">
                <div class="thumbnail__name">
                    <a class="ng-binding" href="/profile/PurplePenguin">PurplePenguin</a>
                </div>
    [...]

Essentially I want a test that will take the text of the second 'a' element and check it against the href attribute: "assert that href '/profile/PurplePenguin' equals '/profile/' + 'PurplePenguin'"

This is what I've made just wanting to test the first thumbnail's 'a', (in my time writing "PurplePengiun" is the first user every time so I could hard code it).

it('should have the performer\'s name match the link', function() {
    // "eq(n)" gets the nth instance of the element; 
    // "> a:eq(0)" grabs its first child a
    var nameElement = element('.thumbnail__name:eq(0) > a:eq(0)');

    // These work and pass
    expect(nameElement.text()).toBe('PurplePenguin');
    expect(nameElement.attr('href')).toBe('/profile/PurplePenguin');

    // This does not
    var textString = nameElement.text(); // textString == "[object Object]"
    expect(nameElement.attr('href')).toBe('/profile/' + textString);

This gets returned:

expect element '.thumbnail__name:eq(0) > a:eq(0)' get attr 'href' toEqual "/profile/[object Object]"
expected "/profile/[object Object]" but was "/profile/PurplePenguin"

So I've figured out how to find the particular element on the page I need, but trying to manipulate the text of a simple 'a' element only gives me [object Object].

Some things I've tried with my basic knowledge of JS:

nameElement.text().toString(), nameElement.text().value(), nameElement[0].text()

Trying things other than text()

nameElement.html() and nameElement.val()

They too return [object Object] when trying to use them as strings.

It seems that looking at the values and attributes of these elements only works when using the very specific API functions like .toBe or .toEqual, but I want to assert that a specifically formatted string is made.

Thank you for any help!


Solution


Thanks for that bit of insight Andyrooger, I had actually taken a stab at the query function before posting my question but gave up on it too quick. Your explanation gave me the idea to start looking deeper into the samples that have been posted in the official docs. I ended up taking a hint from a Adi Roiban's post to another Angular e2e writer's question talking about query, done() messages, and promises. This ended up leading me to my eventual solution.

So I've made myself a solution and in the spirit of cooperation made a set of examples for others to learn by. There are four examples, the first two are just getting the text and the href and comparing them against hard-coded values. The third one uses indexOf to do dirt-simple comparisons. The fourth one shows how you can make your own more specific pass/fail conditions (more than what Jasmine provides with its matchers).

Number 1: User name text vs hard coded value

it('should have the user\'s name be \'PurplePenguin\'', function() {

  var textPromise = element('.thumbnail__name:eq(0) > a:eq(0)').query(function (nameElement, done) {
    var text = nameElement.text(); // Can finally access this guy!

    // The first param null indicates a nominal execution, the second param is a return of sorts
    done(null, text); 
  });

  // Passes
  expect(textPromise).toBe('PurplePenguin')
});

Number 2: Profile href value vs hard coded value

it('should have the user\'s link be \'/profile/PurplePenguin\'', function() {

  var textPromise = element('.thumbnail__name:eq(0) > a:eq(0)').query(function (nameElement, done) {
    var href = nameElement.attr('href');

    // The first param null indicates a nominal execution, the second param is a return of sorts
    done(null, href); 
  });

  // Passes
  expect(textPromise).toBe('/profile/PurplePenguin')
});

Number 3: Simple string comparison

it('should have the user\'s name match the link', function() {

  var textPromise = element('.thumbnail__name:eq(0) > a:eq(0)').query(function (nameElement, done) {
    var text = nameElement.text();
    var href = nameElement.attr('href');

    // The first param null indicates a clean pass, the second param is a return of sorts
    done(null, href.indexOf(text)); // indexOf returns -1 if text is _not_ a sub-string of href 

  });

  expect(textPromise).toBeGreaterThan(-1);

  // In the case of failure reports this in the console (if using Karma):
  //    expect element .thumbnail__name:eq(0) > a:eq(0) custom query toBeGreaterThan -1
  //    expected -1 but was -1

  // In the runner.html page for the test simply reports this useless/misleading log:
  //    expected -1 but was -1

});

Number 4: More detailed string comparison doing it your own way plus a better error message

it('should have the user\'s name match the link', function() {

  var nameStringPromise = element('.thumbnail__name:eq(0) > a:eq(0)').query(function (nameElement, done) {
    var text = nameElement.text();
    var href = nameElement.attr('href');

    var message;
    if (href.indexOf(text) == -1)
      message = 'Did not find "' + text + '" in "' + href + '"';

    done(message, href.indexOf(text));
  });

  expect(nameStringPromise);

  // An expect() with no Jasmine matcher means that it only uses the done message to determine 
  //   a pass or fail for the test. So I wrote that if my conditions weren't met it would print
  //   an informative message in the case of failure
  // Example failure output with a broken url generator:
  //     Did not find "PurplePenguin" in "/profile/"

});
War es hilfreich?

Lösung

I'm struggling to find references to back this up or explain a little more nicely, other than these docs which don't have much info. Therefore this answer is mainly from memory of reading the relevant code.

When you write a test in ngScenario, you are not writing a series of immediate actions as you would do in a unit test. What you are actually doing queuing up a list of commands to execute when the test starts.

Methods that looks like normal jQuery functions, such as text() or attr() are actually part of ngScenario's DSL and return future objects (meaning they will at some point execute and have the result of the call you wanted.)

expect() already knows this so waits for the result before comparing to your expected value. The code underneath, in your example, is reading directly and immediately so will see the future object.

If you do need to read the element in this way I suggest looking at the query function which should allow it. On the other hand if you need to just check the formatting of the string you can use toMatch which has been implemented in ngScenario too.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top