Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhanced support for page object model #414

Merged

Conversation

sknopf
Copy link
Collaborator

@sknopf sknopf commented Apr 12, 2015

This adds support for enhanced page object model. It doesn't yet include tests or updated documentation, as I wanted to first get feedback.

A couple notable features:

  • Supports sections (i.e., nested page objects)
  • Supports mixin functions used to encapsulate logic that doesn't fit the normal element/selector paradigm
  • Easy to share elements across page objects or sections
  • Simple API that mirrors the one already in Nightwatch

We've been using this for a few weeks internally at Twitter now and it's been working great. Though I'm sure there are some kinks to still get sorted out - one I am aware of supporting custom client commands and assertions whose first argument is not a selector.

@beatfactor let me know what you think.

@beatfactor
Copy link
Member

Quite a large feature. If you don't mind could you explain the differences between this and the existing implementation?

@sknopf
Copy link
Collaborator Author

sknopf commented Apr 13, 2015

Definitely - the existing feature allows for a page object that is more like a custom command except with special namespacing under client.api.page. This feature adds to that by allowing you to abstract the page, making it more similar to other page object libraries. The goal is to make for more readable and maintainable tests.

For comparison, here are the existing page object demo tests rewritten with the new feature:
https://github.com/beatfactor/nightwatch/blob/master/examples/pages/google.js
https://github.com/beatfactor/nightwatch/blob/master/examples/tests/google.js

tests/google.js:

module.exports = {
  tags: ['google'],
  'Demo test Google' : function (client) {
    var google = client.page.google();
    google.goTo() // if a url is provided on page object, will navigate there by default
      .assert.title('Google')
      .assert.visible('searchBar') // selectors live in page object, not hardcoded in tests
      .setValue('searchBar', 'nightwatch')
      .submit() // logic of waiting, clicking, then pause is encapsulated in page object
      .assert.containsText('results', 'Night Watch');

    client.end();
  }
};

pages/google.js:

var googleMixins = {
  submit: function() {
    this.waitForElementVisible('submitButton', 1000)
      .click('submitButton');
    this.api.pause(1000);
    return this; // For chaining to page object
  }
};

module.exports = {
  url: 'http://google.com',
  elements: {
    searchBar: { selector: 'input[type=text]' },
    submitButton: { selector: 'button[name=btnG]' },
    results: { selector: '#main' }
  },
  mixins: [googleMixins]
};

Key differences are:

  • Selectors are not harcoded in tests - they are defined together under elements property of the page object
  • Any special logic (waiting for element to be present, then click, then wait for something else) can be abstracted out of the test code and into the page object. This makes the test much more readable and enables sharing across tests. This is done via the mixins property.
  • To allow for chaining, page objects return the page object, rather than client.api
  • Provides convenience methods like goTo

For other features not illustrated in this example, check out the examples in the PR. There I use a more complicated site IMDB to better demonstrate some cool features:

Sections:
This allows you to model sections on a page (or sections on multiple pages). Sections are namespaced under the page (page.imdb.section.news). Selectors of elements under the section become nested with its parent (e.g., '.newsSection .search'). They can also be nested under other sections, if necessary.

Ability to share common selectors and functions across page objects:
Often selectors and a series of commands are shared across multiple pages. The new page object framework makes sharing across page objects and sections trivial with mixins and sharing element components. This is demonstrated in the imdbSearchResults page (see PR).

For reference, here's another page object library (written in Ruby) that I think does a nice job at providing a simple interface but also supports more advanced page object needs like sections:
https://github.com/natritmeyer/site_prism

Note also this feature is behind a feature flag use_nightwatch_page_object specified in the config file for backwards compatibility.

@beatfactor
Copy link
Member

Thanks, I'll look into it.

@sknopf
Copy link
Collaborator Author

sknopf commented Apr 17, 2015

Hey @beatfactor - have you had a chance to take a look? What do you think?

@beatfactor
Copy link
Member

I had a brief look but haven't reviewed everything yet. One question would
be I guess, why do you think it should be behind a toggle? Isn't compatible
with the existing implementation?

On Friday, April 17, 2015, Stephanie Madison [email protected]
wrote:

Hey @beatfactor https://github.com/beatfactor - have you had a chance
to take a look? What do you think?


Reply to this email directly or view it on GitHub
https://github.com/beatfactor/nightwatch/pull/414#issuecomment-93877665.

@maxgalbu
Copy link

It's not compatible probably because of this feature:

To allow for chaining, page objects return the page object, rather than client.api

But, isn't this a little bit confusing? nightwatch already has a page object feature, a use_new_pageobject flag could be better :)

@sknopf
Copy link
Collaborator Author

sknopf commented Apr 17, 2015

I put this behind a toggle more as a safeguard to ensure no backward compatibility issues. Here [1] we add methods previously just on client.api (element commands, assertions, etc) to the page object instance only if the toggle is set. Otherwise if someone happens to have a method called click using the current page object feature, this feature will break their code.

[1] https://github.com/sknopf/nightwatch/blob/add_support_for_page_object_model/lib/core/api.js#L376

@sknopf
Copy link
Collaborator Author

sknopf commented Apr 17, 2015

Oh and we can definitely change the name to @maxgalbu's point. I'm not sure I like the term "new" but what about use_enhanced_page_object or something like that?

Also @beatfactor I'm aware of some style inconsistencies since I posted this - I was going to wait to fix those until you've decided if you want to move forward with this but LMK if you want me to address those sooner.

@beatfactor
Copy link
Member

I'm thinking if there's any way to merge this feature into the existing
implementation and make it backwards compatible too. That would be my
preference. Maybe we can detect if the page is written for the new style or
the existing one somehow? I've done something similar for the new
assertions style which it was introduced in 0.4.

On Friday, April 17, 2015, Stephanie Madison [email protected]
wrote:

Oh and we can definitely change the name to @maxgalbu
https://github.com/maxgalbu's point. I'm not sure I like the term "new"
but what about use_enhanced_page_object or something like that?

Also @beatfactor https://github.com/beatfactor I'm aware of some style
inconsistencies since I posted this - I was going to wait to fix those
until you've decided if you want to move forward with this but LMK if you
want me to address those sooner.


Reply to this email directly or view it on GitHub
https://github.com/beatfactor/nightwatch/pull/414#issuecomment-94018486.

@sknopf
Copy link
Collaborator Author

sknopf commented Apr 17, 2015

OK, I can think of a few ways offhand but not sure they are ideal. I'll check out what you did for assertions, thanks for the tip.

@sknopf
Copy link
Collaborator Author

sknopf commented Apr 17, 2015

I ended up adding the condition:
typeof pageFnOrObject == 'object' && (pageFnOrObject.elements || pageFnOrObject.sections)

It may actually be enough to just check that it's an object as the nightwatch documentation says that the page object should be a (non-instantiated) class. But I threw in the check for elements or sections attributes to be safe.

@sknopf
Copy link
Collaborator Author

sknopf commented Apr 27, 2015

Hey @beatfactor since this is such a big change I thought it would be helpful to draft what the documentation would look like after this change, so you can see better how it gets used:

nightwatchjs/nightwatch-docs#4

I'll start working on tests for this as well. Would definitely love to hear any feedback you have like if you think this is something you would consider adding to Nightwatch.

@sknopf sknopf force-pushed the add_support_for_page_object_model branch from c63b654 to 09127f9 Compare April 29, 2015 17:11
@sknopf
Copy link
Collaborator Author

sknopf commented Apr 29, 2015

Added tests. I also rebased as my branch was stale.

@sknopf
Copy link
Collaborator Author

sknopf commented Apr 29, 2015

In the latest I added jsdoc markup and also a small refactor to loadClientCommands so that only actual element commands are loaded onto the page object (not client commands like end, etc). The refactor is based on feedback I got internally from my company who have been using the forked version this PR is based on (dogfooding! 👍).

With the additional documentation + tests, I'm definitely happy with the feature as it stands.. If there's anything else I can do to aid your review please let me know.

@beatfactor
Copy link
Member

Sounds great, I'll integrate this soon.

On Thu, Apr 30, 2015 at 12:04 AM, Stephanie Madison <
[email protected]> wrote:

In the latest I added jsdoc markup and also a small refactor to
loadClientCommands so that only actual element commands are loaded onto
the page object (not client commands like end, etc). The refactor is
based on feedback I got internally from my company who have been using the
forked version this PR is based on (dogfooding! [image: 👍]).

With the additional documentation + tests, I'm definitely happy with the
feature as it stands.. If there's anything else I can do to aid your review
please let me know.


Reply to this email directly or view it on GitHub
https://github.com/beatfactor/nightwatch/pull/414#issuecomment-97601505.

@sknopf
Copy link
Collaborator Author

sknopf commented Apr 30, 2015

Great news! With this latest I made a change to accommodate calling assert module assertions on page objects. LMK if you want me to squash commits before merging in..

@beatfactor
Copy link
Member

Yeah, that would be nice

On Thursday, April 30, 2015, Stephanie Madison [email protected]
wrote:

Great news! With this latest I made a change to accommodate calling assert
module assertions on page objects. LMK if you want me to squash commits
before merging in..


Reply to this email directly or view it on GitHub
https://github.com/beatfactor/nightwatch/pull/414#issuecomment-97871829.

@sknopf sknopf force-pushed the add_support_for_page_object_model branch from 6d6c5bf to 0f0cb18 Compare April 30, 2015 16:47
@sknopf
Copy link
Collaborator Author

sknopf commented Apr 30, 2015

Cool all set

@sknopf sknopf changed the title Add support for page object model Enhanced support for page object model May 1, 2015
@beatfactor
Copy link
Member

@sknopf would it be ok if I merge this in the next major version, i.e. 0.7? I'm currently working on an another major new feature (new assertion library) and I think it would be nice to also include this as a headliner. Besides, this is a large enough enhancement to increase the version number.

@sknopf
Copy link
Collaborator Author

sknopf commented May 7, 2015

Yeah definitely. I look forward to the new assertion library..

Also FYI I've been thinking about an improvement that would replace some logic here [1] where you can actually nest elements inside a section instead of combining css selectors of an element and its parent section. That would allow the user to combine xpath and css which they can't do currently. As I understand the elementIdElement endpoint [2] it seems like that should be doable but I still need to test and implement it.

If I can get that working do you want me to add the diff here or wait for a later release?

  1. https://github.com/sknopf/nightwatch/blob/add_support_for_page_object_model/lib/page-object/page-utils.js#L12
  2. https://code.google.com/p/selenium/wiki/JsonWireProtocol#/session/:sessionId/element/:id/element

@beatfactor
Copy link
Member

Yeah, that sounds like a nice improvement.
On Thu 7 May 2015 at 16:44 Stephanie Madison [email protected]
wrote:

Yeah definitely. I look forward to the new assertion library..

Also FYI I've been thinking about an improvement that would replace some
logic here [1] where you can actually nest elements inside a section
instead of combining css selectors of an element and its parent section.
That would allow the user to combine xpath and css which they can't do
currently. As I understand the elementIdElement endpoint [2] it seems like
that should be doable but I still need to test and implement it.

If I can get that working do you want me to add the diff here or wait for
a later release?

https://github.com/sknopf/nightwatch/blob/add_support_for_page_object_model/lib/page-object/page-utils.js#L12
2)
https://code.google.com/p/selenium/wiki/JsonWireProtocol#/session/:sessionId/element/:id/element


Reply to this email directly or view it on GitHub
https://github.com/beatfactor/nightwatch/pull/414#issuecomment-99893626.

@sknopf
Copy link
Collaborator Author

sknopf commented May 13, 2015

@beatfactor I got the nested elements working in this last commit so you can now combine xpath and css in sections.

One thing to note - I create some new protocol actions as well as a useRecurse function. As it is, these are exposed on the client - though maybe they're better off to keep internal to Nightwatch. Anyway let me know your thoughts.

@beatfactor
Copy link
Member

@sknopf if you want to have a look at the new assertion library it's in the features/chai-expect branch. To see an example, refer to google.js example.

@sknopf
Copy link
Collaborator Author

sknopf commented May 16, 2015

The new library is so cool! It's also really nice that it eliminates the need to write a lot of custom assertions that are variants of more common assertions (with negation, waiting, etc).

FYI some small things I noticed running the google.js test:

  1. If you change #lst-ib to input[name=q] it'll pass cross-browser (I saw failures in PhantomJS with the original)
  2. My US google doesn't have 'Norge' as its text for the DOM element #hplogo though if I explicitly navigate to the .no TLD it works - we could change that URL it navigates to so it passes outside Norwary
  3. Maybe add a friendly error message if an assertion fails because the element is not present (ie if this.elementResult is null. Otherwise you get a stacktrace like [1] below. To reproduce, call .to.have.css on an element that is not present
  4. fyi I get double "element not found" in the failed assertion: Expected element <#lst-ib> to be enabled - element was not found - element was not found - Expected "enabled" but got: "not found". To reproduce, call that line on an element that is not present.

What's the best way for me to incorporate this into the page object? Should I merge it in and update the PR? It looks like it should be pretty simple to add this new library on page objects.

Also I was thinking since the common test example is Google I should change the IMDB example I have for page object to Google. I will work on that as well.

[1] An error occurred while running the tests:
TypeError: Cannot read property 'ELEMENT' of null
at CssAssertion.executeCommand (/Users/sknopf/workspace/nightwatch/lib/api/expect/css.js:19:56)
at CssAssertion.BaseAssertion.onPromiseResolved (/Users/sknopf/workspace/nightwatch/lib/api/expect/_baseAssertion.js:71:8)
at CssAssertion.retryCommand (/Users/sknopf/workspace/nightwatch/lib/api/expect/css.js:48:8)
at Timer.listOnTimeout (timers.js:110:15)

@beatfactor
Copy link
Member

Glad you like it. Yeah, I guess that will work for updating the page object branch. I'll look at that problem with the css assertion and update the google example soon. I also created a pull request if you have more comments you can leave them there: https://github.com/beatfactor/nightwatch/pull/456.

@sknopf sknopf force-pushed the add_support_for_page_object_model branch from 9b79629 to 7714456 Compare May 17, 2015 23:47
Stephanie Madison added 3 commits May 17, 2015 20:07
Remove old Google page object example test
Add expect.section to page objects and sections which is an alias for expect.element
@sknopf
Copy link
Collaborator Author

sknopf commented May 20, 2015

@beatfactor just pushed the latest which merges in the Chai assertion branch and gets it working with page objects. I also swapped out the IMDB demo test for a Google test.

@beatfactor
Copy link
Member

Great, but jshint is failing.

@sknopf
Copy link
Collaborator Author

sknopf commented May 20, 2015

Hm, I think jshint doesn't like the chai assertion syntax: Expected an assignment or function call and instead saw an expression.

Fails on assertions like client.expect.element('#lst-ib').to.be.enabled;

@beatfactor
Copy link
Member

From what I saw it was something about a semicolon, but I only had a brief
look.

On Wednesday, May 20, 2015, Stephanie Madison [email protected]
wrote:

Hm, I think jshint doesn't like the chai assertion syntax: Expected an
assignment or function call and instead saw an expression.

Fails on assertions like client.expect.element('#lst-ib').to.be.enabled;


Reply to this email directly or view it on GitHub
https://github.com/beatfactor/nightwatch/pull/414#issuecomment-104026985
.

@sknopf
Copy link
Collaborator Author

sknopf commented May 20, 2015

Ah yes I fixed that - you will see though in the latest travis build it's
complaining about some tests.

On Wed, May 20, 2015 at 5:52 PM, Andrei Rusu [email protected]
wrote:

From what I saw it was something about a semicolon, but I only had a brief
look.

On Wednesday, May 20, 2015, Stephanie Madison [email protected]
wrote:

Hm, I think jshint doesn't like the chai assertion syntax: Expected an
assignment or function call and instead saw an expression.

Fails on assertions like client.expect.element('#lst-ib').to.be.enabled;


Reply to this email directly or view it on GitHub
<
https://github.com/beatfactor/nightwatch/pull/414#issuecomment-104026985>

.


Reply to this email directly or view it on GitHub
https://github.com/beatfactor/nightwatch/pull/414#issuecomment-104049618
.

@beatfactor beatfactor merged commit 57e2648 into nightwatchjs:master Jun 21, 2015
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants