Code Monkey home page Code Monkey logo

ember-cli-page-object's Introduction

Ember CLI Page Object

Build Status Ember Observer Score Latest version

Represent the screens of your web app as a series of objects. This ember-cli addon eases the construction of these objects for your acceptance and integration tests.

https://ember-cli-page-object.js.org/

What is a Page Object?

An excerpt from the Selenium Wiki

Within your web app's UI there are areas that your tests interact with. A Page Object simply models these as objects within the test code. This reduces the amount of duplicated code and means that if the UI changes, the fix need only be applied in one place.

The pattern was first introduced by the Selenium

You can find more information about this design pattern here:

Compatibility

  • Ember.js v3.16 or above
  • Ember CLI v2.13 or above
  • Node.js v12 or above

Let's work together to improve this addon!

You can find us on the official Ember Discord server, or open an issue on Github to request features, report bugs or just to ask any question.

Installation

$ ember install ember-cli-page-object

Or you can install the NPM package directly.

$ npm install --save-dev ember-cli-page-object

Documentation

Check the site for full documentation.

Blueprints

The addon includes the following blueprints

  • page-object Creates a new page object
  • page-object-component Creates a new component to be used in a page object
  • page-object-helper Creates a new helper to be used in a page object

You can create a new page object called users using the generate command

$ ember generate page-object users

installing
  create tests/pages/users.js

Contributing

See the Contributing guide for details.

Project's health

Build Status Ember Observer Score Dependency Status devDependency Status Codacy Badge Code Climate Coverage Status

Maintainers

  • Santiago Ferreira (@san650)
  • Juan Manuel Azambuja (@juanazam)
  • Jerad Gallinger (@jeradg)
  • Anna Andresian (@magistrula)
  • Ruslan Grabovoy (@ro0gr)

License

ember-cli-page-object is licensed under the MIT license.

See LICENSE for the full license text.

ember-cli-page-object's People

Contributors

alexlafroscia avatar alonski avatar bendemboski avatar demersus avatar dependabot[bot] avatar dfreeman avatar drewchandler avatar esbanarango avatar gnclmorais avatar hidnasio avatar jakesjews avatar jeradg avatar jmbejar avatar jrjohnson avatar juanazam avatar lolmaus avatar magistrula avatar mistahenry avatar olenderhub avatar pzuraq avatar renpinto avatar ro0gr avatar rtablada avatar san650 avatar sergeastapov avatar simonihmig avatar veelenga avatar vvscode avatar yoranbrondsema avatar yratanov avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ember-cli-page-object's Issues

relative scope for components

I haven't been able to figure out to get relative scope working for components. For example, given the following (apologies for the contrived example):

<div>
  <div class="animal-component">
    <h1>Zebra</h1>
  </div>
  <div>
    <div class="animal-component">
      <h1>Giraffe</h1>
    </div>
  </div>
</div>
let animalComponent = PO.component({
  title: text('h1')
});

let page = PO.build({
  zebraComponent: animalComponent,
  giraffeComponent: animalComponent 
});

How can I make sure that zebraComponent points to the first component, and giraffeComponent points to the second one? I understand I could define two different components:

let zebraComponent = PO.component({
  scope: '.outer-component:nth-child(1)',
  title: text('h1')
});

let giraffeComponent = PO.component({
  scope: '.outer-component:nth-child(2)',
  title: text('h1')
});

let page = PO.build({
  zebraComponent: zebraComponent,
  giraffeComponent: giraffeComponent
});

In this particular case I might also be able to swing it with a collection, but the real pain starts when you have a hierarchy of components:

<div id="your-parents">
    <div class="person-entry mother">
        <h2>Mother</h2>
        <div class="text-field first-name">
            <h3>First name</h3>
            <input type="text" />
        </div>
        <div class="text-field last-name">
            <h3>Last name</h3>
            <input type="text" />
        </div>
    </div>

    <div class="person-entry father">
        <h2>Father</h2>
        <div class="text-field first-name">
            <h3>First name</h3>
            <input type="text" />
        </div>
        <div class="text-field last-name">
            <h3>Last name</h3>
            <input type="text" />
        </div>
    </div>
</div>
let textField = PO.component({
  fieldName: text('h3'),
  inputValue: value('input')
});

let personEntry = PO.component({
  title: text('h2'),
  firstNameField: textField,
  lastNameField: textField
});

let page = PO.build({
  mother: personEntry,
  father: personEntry
});

Given the above, splitting the components into MotherFirstName, MotherLastName, FatherFirstName, FatherLastName, is a huge waste of time, and not at all DRY.

I think the solution is to allow a component to declare the relative scope of its child components, in the same way that you can use a helper such as text. Imagine something like:

let textField = PO.component({
  fieldName: text('h3'),
  inputValue: value('input')
});

let personEntry = PO.component({
  title: text('h2'),
  firstNameField: textField.relative('.first-name')
  lastNameField: textField.relative('.last-name')
});

let page = PO.build({
  scope: '#your-parents',
  mother: personEntry.relative('.mother'),
  father: personEntry.relative('.father')
});

So if I wanted to get the title of the mother's first name field, I could do page.mother().firstNameField().fieldName(), which would run a selector of #your-parents .mother .first-name h3.

Alternative syntax:

  mother: relativeComponent(personEntry, '.mother'),

Please tell me if I'm missing something obvious. Otherwise if this sounds like something you guys are interested in I'll see if I can work up a PR.

visitable queryParam support

First off, this is a great addon and a pleasure to work with.

I'd like to see .visitable being able to support queryParams.
Query params are optional and would just be tacked onto the end of the existing url, if they are specified.

Here's the idea:

pages/books.js

export default PO.build({
  visit: visitable('/books/:category')
});

acceptance/books-test.js

import page from '../pages/books';

test('visiting adventure books', assert => {
  page.visit({category: 'adventure'},  {page: 3, limit: 10 });

  andThen(() => {
    assert.equal(currentURL(), '/books/adventure?page=3&limit=10');
  });
});

Possible fix

function visitable(params = {}, queryParams = {}) {

  let path = this.path;

  if (path.indexOf(':') !== -1) {
    path = fillInDynamicSegments(path, params);
  }

  if (Ember.keys(queryParams).length > 0) {
    path += "?" + Ember.$.param(queryParams);
  }

  visit(path);

  return this.page;
};

[1.0] Change `.clickOnText` to match current element

Current API implementation only considers the following scenario

<div class="modal">
  ...
  <button>Save</button> <button>Cancel</button>
</div>
var page = PageObject.create({
  clickOn: clickOnText('.modal')
});

page.clickOn('Save');

This triggers a click event on the Save button element.

Now consider the following scenario (same HTML)

var page = PageObject.create({
  clickOn: clickOnText('button')
});

page.clickOn('Save');

This won't work because the current implementation looks for elements inside button that contain the text but not the element itself.

We want both scenarios to work, so the idea is to first look for child elements and if none it's found, then try with the element itself.

Inherit scope by default on components and collections

Right now, PO.components and PO.collections don't have a way to inherit parent scope. This is a problem mostly when using components inside a PO.collection.

It would be useful to change the default behavior to inherit scopes in components and collections and be able to reset it at component level.

This is how I imagine scopes should work:

collection inherits parent scope by default

<div class="todo">
  <input type="text" value="invalid value" class="error" placeholder="To do..." />
  <input type="text" placeholder="To do..." />
  <input type="text" placeholder="To do..." />
  <input type="text" placeholder="To do..." />

  <button>Create</button>
</div>
var page = PageObject.build({
  scope: '.todo',

  todos: collection({
    itemScope: 'input',

    item: {
      value: value(),
      hasError: hasClass('error')
    },

    create: clickable('button')
  });
});
translates to
page.todos().create() click('.todo button')
page.todos(1).value() find('.todo input:nth-of-type(1)').val()

You can reset parent scope by setting the scope attribute on the collection declaration.

var page = PageObject.build({
  scope: '.todo',

  todos: collection({
    scope: null, // or ''
    itemScope: 'input',

    item: {
      value: value(),
      hasError: hasClass('error')
    },

    create: clickable('button')
  });
});
translates to
page.todos().create() click('button')
page.todos(1).value() find('input:nth-of-type(1)').val()

itemScope is inherited as default scope on components defined inside the item object.

<ul class="todos">
  <li>
    <span>To do</span>
    <input value="" />
  </li>
  ...
</ul>
var page = PageObject.build({
  scope: '.todos',

  todos: collection({
    itemScope: 'li',

    item: {
      label: text('span'),
      input: {
        value: value('input')
      }
    }
  });
});
translates to
page.todos(1).input().value() find('.todos li:nth-of-child(1) input).val()

component inherits parent scope by default

<div class="search">
  <input placeholder="Search..." />
  <button>Search</button>
</div>
var page = PageObject.build({
  search: {
    scope: '.search',

    input: {
      fillIn: fillable('input'),
      value: value('input')
    }
  }
});
translates
page.search().input().value() find('.search input').val()

You can reset parent scope by setting the scope attribute on the component declaration.

var page = PageObject.build({
  search: {
    scope: '.search',

    input: {
      scope: 'input',

      fillIn: fillable(),
      value: value()
    }
  }
});
translates
page.search().input().value() find('input').val()

Doesn't install correctly

Just FYI - I had to manually add the test-support files into /tests to get this to work - seems fine after doing that though

Page Objects don't install correct in addon projects

In reference to this other issue: #61 (comment)

The basic gist of it is that the loader can't find Page Object inside an addon project because it expects the PageObject to live inside <%= project_name%>/tests/page-object when it actually lives inside dummy/tests/page-object

`clickOnText` attribute

The idea is to have an action attribute that allows to click an element by it's innerText.

let page = PageObject.build({
  visit: visitable('/dummy'),
  click: clickOnText('.scope')
}) 
<div class="scope">
<!-- ... -->

  <button>Submit</button>
  <button>Cancel</button>
</div>

And the text could do something like

test('...', function(assert) {
  page
    .visit()
    .click('Submit');

  // ...
  page.click('Cancel');
});

The implementation is pretty straightforward

function clickOnText(text) {
  let selector = /*... get the full qualified selector ... */

  return click(`${selector} :contains("${text}"):last`;
}

Note that the text param needs to be sanitized before using it in the CSS selector. We could ship a first version without worrying too much on the sanitization but it would be great if we can tackle that sooner than later.

[1.0] Documentation

API changes a lot from 0.11 to 1.0 and we need to inform users. I think we should:

Must

  • Write documentation for 1.0 features
  • Update README.md file
  • Remove DOCUMENTATION.md file in favor of using the website
  • Update website to have both 0.11 and 1.0 documentation
  • Write a complete release notes explaining all the changes
  • Migration path

Nice to have

  • Generate API documentation from source code comments (jsdoc)

API Suggestion: Multi-Attribute fillables

Just ran into this and it's a small issue but since you are working towards v1.0 figured I'd drop a line.

I have a search form where I'd like to enter values into the inputs, but also assert on those values (like loading the form from server data and verifying defaults are filled in). Currently this requires two attributes:

export default PageObject.create({
  nameValue: value('#search_name'), // for loading from server and asserting the value
  name: fillable('#search_name'), // for querying
});

It would be nice to have just one 'name' field I could re-use. Maybe something like

page.name('Homer'); // fills in
page.name(); // returns 'Homer';

I could see this applying to things like clickables as well, where you want to assert on the button label as well as click the button.

Should component scope inside a collection preserve parent selector?

Hi there!

I've just recently started using this awesome addon (thank you so much!) and noticed what seems to be an issue, but I wanted to confirm if it's indeed one.

I have a situation where I have a tree of nested elements and all the scoping for each collection of items within another collection works great, with exception of a case where I'm trying to use a component as an attribute of an item in a collection, but I can't find a way to find this component inside the item without manually appending an extra class on each property for the component.

Consider this HTML below as the leaf node of my tree:

... lines omitted for brevity
<li class="topic">
    <div class="flagging-menu is-open">
        <div class="mark urgent">Urgent</div>
        <div class="mark current">Current</div>
        <div class="mark past">Past</div>
    </div>
</li>
... lines omitted for brevity

Then what I'm trying to do is to define a component for the flagging-menu within thetopic item so that I can simply access the properties of the component without having to pass the component selector every time.

My first attempt was this:

topics:  collection({
  itemScope: '.topic',
  item: {
    title:      text('.title'),    

    flaggingMenu: {
      scope: '.flagging-menu',
      isOpen: hasClass('is-open'),

      selectCurrent: clickable('.mark.current'),
      selectPast: clickable('.mark.past'),
      selectUrgent: clickable('.mark.urgent')
    }
  }
})

The issue I ran into was that by setting scope: '.flagging-menu', the selector for the component dropped the parent scope (.topic) and matched any item on the page matching the broader .flagging-menu selector, when what I would have liked it to do was to append the component scope to the parent selector (i.e. .topic .flagging-menu).

The only way I found to get what I was trying to accomplish was to pretend I had a collection of flagging-menu, since by using itemScope I am able to preserve the parent scope while also narrowing down to the specific element I'm interested in.

topics:  collection({
  itemScope: '.topic',
  item: {
    title:      text('.title'),    

    // Hackish way to be able to have flaggingMenu preserve scope
    // of parent item. Using scope: attribute replaces parent scope.
    flaggingMenu: collection({
      itemScope: '.flagging-menu',

      item: { 
        isOpen: hasClass('is-open'),

        selectCurrent: clickable('.mark.current'),
        selectPast: clickable('.mark.past'),
        selectUrgent: clickable('.mark.urgent')
      }
    })
  }
})

This works, but seems a bit hackish to me. Is this behavior of not preserving a parent's scope when you have a component inside a collection's item the expected one?

Thank you so much for looking into this.

Best,
Leo

Define attributes without a selector if the page has an scope defined

Sometimes is handy to create a page or component and be able to define child attributes without setting a selector. For example:

<div class="modal">
  <!-- modal content -->
</div>
let page = PageObject.build({
  modal: component({
    scope: '.modal',

    isVisible: isVisible(),
    isHidden: isHidden(),
    isError: hasClass('error')
  })
});

In this case, the attributes isVisible, isHidden and isError validate properties on the .modal element.

Right now this works but is not officially supported, so we need to create tests and documentations for this behavior.

As an added bonus, this definition can be improved using the new ES6 object literal shorthand assignments:

let page = PageObject.build({
  modal: component({
    scope: '.modal',

    isVisible,
    isHidden,
    isError: hasClass('error')
  })
});

Assert selector matches single element?

I trolled myself earlier because I chose a .text selector that matched more than one element (awkwardly enough it matched the desired element, as well as a parent/ancestor element of that desired element), and when I asserted on the value, it kept telling me the value was "Login Login". I thought it was a cli-page-object bug until I dug in more.

My foolish class-naming aside, it seems very unlikely that you'd want page object components to match multiple elements, so perhaps this library should assert that exactly one element is matched?

Checkbox

How do I trigger a change in a checkbox

[Feature] .property query

I find myself having to write this sort of thing a lot:

assert.equal(page.foo.attribute('disabled'), 'disabled');

It would be good to have a .property query to check for element boolean properties, ex.:

assert.ok(page.foo.property('disabled'));

page object VS page object component

What's the difference between the two? They look very similar, other then the fact that one uses PO.build and the other uses PO.component..

Would love documentation on components, helpers and pages. How they work together.

Document folder conventions

One of the ideas @juanazam and I have is to have some conventions on where to generate the object pages.

We would like to propose and document these conventions so everyone can take advantage of them.

Edit: take a look at #27 and #32

Folder structure

  1. Put your Page Objects in <root>/tests/pages/ folder
  2. Put you custom components in <root>/tests/pages/components/ folder

An example could be

myApp/tests
โ””โ”€โ”€ pages
 ย ย  โ”œโ”€โ”€ team.js
 ย ย  โ”œโ”€โ”€ users.js
 ย ย  โ”œโ”€โ”€ users-add.js
 ย ย  โ””โ”€โ”€ components
 ย ย   ย ย  โ”œโ”€โ”€ search-box.js
 ย ย   ย ย  โ””โ”€โ”€ gear-menu.js

Naming conventions (?)

This is something we didn't discuss yet and we would like to hear ideas.

For simple routes like users.index it's easy to pick a name for the page object <root>/tests/pages/user.js, but when you have complex nested routes it's no so trivial to pick a name. It would be great to have some guidelines.

zero-indexing of collections?

Is there a specific reason that the items in a collection are indexed starting at 1 and not 0? It was somewhat surprising to realize that I needed to use page.someCollection(1) to get the first object (and that page.someCollection(0) returned the entire collection rather than an individual item). Would you accept a PR that changed scopeWithIndex and the collection method to accept 0 to get the first item?

Component blueprint generates deprecation warning

Making a new page object component with this command:

ember generate page-object-component my-component

generates code that uses PageObject.component which gives the following deprecation message:

`component` is deprecated in favor of using plain JavaScript objects

What should the blueprint generate for components to avoid that deprecation warning? something like this?

export default {
  title: text('h3')
};

is there a way to set the find scope? to be able to test modal dialog elements

Am really enjoying this addon.

And I was wondering if there is a way to test modal-dialogs which attach themselves to the body and float outside the #ember-testing-container which all the find('class-selector') are usually scoped to.

I can do this ( with plain old ember test helper find ):

find('model-class-selector', 'body') // where I can change the scope to 'body'

but is there a way to do this and still use your clickable and value, fillable and all the other helper methods? Not sure how to send this scope of 'body' for the find call into your helper methods?

has anyone done this already?
any hints?

"visit" with dynamic segments

I do I handle dynamic segment in a route

In a normal test I would do

    visit(`/booking/${booking.id}/account`);

This is what I want

export default PageObject.build({
  visit: visitable('/booking/:id'),
});

And then in my test

pageObject.visit({id: booking.id})

Is this sort of thing supported or is there a workaround for this?

[Question]: navigation composition

Huge fan of your work and this great OSS project! One discussion I often get into with others is ...should we include menu/nav "clickable" inside specific page objects (and use composition to reuse a set of functions that do this) or ... should you just use more than 1 page object in your tests? One for the navigation /menu work and one for the content you are testing specifically ?

Thanks for the feedback!

Add blueprint for components and helpers

After #27 is merged it would be great to add a couple of additional blueprints to ease the creation of reusable components and helpers.

1. page-object-component

$ ember generate page-object-component navbar

installing
  create tests/pages/components/navbar.js

which generates...

import PageObject from '../page-object';

export default PageObject.component({
});

2. page-object-helper

$ ember generate page-object-helper checked

installing
  create tests/pages/helpers/checked.js
import PageObject from '../page-object';

function checked(selector, options) {
  return "a value";
}

export default PageObject.customHelper(checked);

Custom helpers

One of the problems we have right now with custom components is that we don't have information about the surrounding context, making it difficult to have app specific but generic components.

i.e.

var input = function(selector) {
  return component({
    value: value(selector),
    hasError: hasClass('is-error', selector)
  });
}

var page = PageObject.build({
  search: input('.header input'),

  users: collection({
    itemScope: 'tr',
    item: {
      // This won't work because `input` helper
      // doesn't have context awareness, so the
      // the selector don't get prefixed with the current row.
      userName: input('input:first')
    }
  })
});

// If we take a look at the __private__ `qualifedSelector`
page.users(3).userName().qualifiedSelector() // => `input:first` and not `tr:nth-of-type(3) input:first`

We need to have a way to define custom helpers that can have context awareness

Something like:

var input = customHelper(function(selector, scope, options) {
    return {
        scope: scope,
        value: value(selector, options)
    };
});

var page = PageObject.build({
  search: input('.header input'),

  users: collection({
    itemScope: 'tr',
    item: {
      // This would work fine now
      userName: input('input:first')
    }
  })
});

// If we take a look at the __private__ `qualifedSelector`
page.users(3).userName().qualifiedSelector() // => `tr:nth-of-type(3) input:first`

Cannot find module 'silent-error'

Hi I just, installed the latest version of page-object and when I try to run a generator I got this error:

Cannot find module 'silent-error' Error: Cannot find module 'silent-error' at Function.Module._resolveFilename (module.js:339:15) at Function.Module._load (module.js:290:25) at Module.require (module.js:367:17) at require (internal/module.js:16:19) at Object.<anonymous> (/Users/johnny/Projects/Cometa/FrontEnd/liberty/node_modules/ember-simple-auth/blueprints/authenticator/index.js:2:19) at Module._compile (module.js:413:34) at Object.Module._extensions..js (module.js:422:10) at Module.load (module.js:357:32) at Function.Module._load (module.js:314:12) at Module.require (module.js:367:17)

I am running ember-cli 2.3.0

Thanks for your assistance

Red test after installation: Uncaught SyntaxError: Unexpected token =

Hi, I'm new in acceptance testing with page-objects. I had acceptance test which was green.
I use visit('path').
After PO installation I get global failure: SourceUncaught SyntaxError: Unexpected token =
from /assets/web-app-ember.js:10789

This is error from this function:
`
function visitable(params = {}) {
let path = this.path;

if (path.indexOf(':') !== -1) {
  path = fillInDynamicSegments(path, params);
}

this.page.lastPromise = visit(path);

return this.page;

}
`

I tried add page object like:

var page = PageObject.build({ visit: visitable('/time-sessions'), });
or simply replace visit by visibable.
What I am doing wrong?

An action for datetimepicker

There can't be many site out there without datetimepickers; can't get fillable action to work with datetimepicker. Is this a short-coming or am I doing it wrong?

var page = PageObject.create({
name: PageObject.fillable('.input-start-date')
});

Allow to access parent component in custom helpers

Currently build function receives parent but does not uses it at all.

See https://github.com/san650/ember-cli-page-object/blob/master/test-support/page-object/build.js#L52

What do you think about allowing custom helpers to access parent from component object, e.g. put parent as a component property at build step and pass it in options at buildPageObjectAttribute step.

If there is no major objection, I will prepare pull request with implementation and example which implements custom attribute on collection component and reuses item component from it.

[Feature] Support for component integration tests

(Discussion continued from #79.)

Page objects would be useful in component integration tests. To make this possible, a page object would need access to the test's this context, in order to access this.$(), this.render(), etc.

Implement Namespaces

Given the following HTML

<div class="princing-modal">
  <div class="irrelavant-for-testing-info">
  </div>
  <div class="important-stuff">
    <span class="cost">30$</span>
    <span class="billing-date">30/12/2015</span>
  </div>
</div>

In order to test this comfortably with the current implementation I would build a PO component which looks like:

{
  scope: '.pricing-modal',
  importantSuff: {
     scope: '.important-stuff' 
     cost: text('.cost'),
     billingDate: text('.billing-date')
  }
}

So I don't have to repeat the '.important-stuff selector' for both texts I want to check.

In order to access those values, I would have to write page.pricingModal().importantStuff().cost() to get the actual cost text. It would be great that page.pricingModal().cost() returned the proper selector, without having to repeat the .important-stuff selector on each property.

Here come namespaces:

{
  scope: '.pricing-modal',
  importantSuff: namespace({
     scope: '.important-stuff' 
     cost: text('.cost'),
     billingDate: text('.billing-date')
  })
}

Would generate the proper selectors without having to repeat selector and generating extra calls in tests.

Allow mapping of collections

Please correct me if I'm wrong, but I think it's not possible to do the following:

page.users().mapBy('name')

Or elsewise get a list of users without supplying every index. This would be nice to verify the UI is showing the correct list of users.

[1.0] Change `.visitable` method signature

Current API

var page = PageObject.create({
  visit: visitable('/path/:user_id')
});

page.visit({ user_id: 5 }, { myQueryParam: 'foo' });  // "/path/5?foo=bar"

The generated function receives two different objects, the first one is for the dynamic segments and the second is for query params.

The problem arise when you don't have dynamic segments but you want to use query params

page.visit({}, { myQueryParam: 'foo' });  // "/path/?foo=bar"

So, I would like to change the API so the function generated by .visitable receives only one parameter when it's invoked.

var page = PageObject.create({
  visit: visitable('/path/:user_id')
});

page.visit({ user_id: 5, foo: 'bar' }); // "/path/5?foo=bar"

The idea is to extract all dynamic segments first from the parameter and use the rest of the key/values as query params.

Note that If you don't set the values for all the dynamic segments, then an exception will be thrown (this is the current behavior).

`findWithAssert` undefined during component unit test.

I'm new to PageObjects, so let me know if what I'm trying shouldn't be done.

I want to make a page object component that I can both:

  • Use as part of a larger PageObject during an acceptance test
  • Use in isolation in that component's unit test

I'm trying the 2nd thing first, but I've run into an issue. It appears that the functions like clickable and text use findWithAssert to find things in the DOM. This is a test helper injected by the Ember app when it's running an acceptance test.

The problem is: the entire Ember app doesn't run during unit tests. During unit tests, findWithAssert is undefined.

Any thoughts on what I should try to work around/fix this?

Improve collection item selector

Given the following HTML

<ul>
  <li class="original">Original</li>
  <li>Item 1</li>
  <li>Item 2</li>
</ul>

In order to select all items except the original one I would build a PO collection which looks like:

items: collection({
  scope: 'li:not(.original)',

  item: {
  }
})

In order to access the items, I would have to write something like page.items(1), but this is not working with the current implementation because the selector will be $("li:not(.original):nth-of-type(1)") and the result will be empty in this case.

Use plain JavaScript objects as components

It would be great if plain objects could be used to define components. Right now, the only way of defining a components in your page object is by using the .component({ ... }) helper.

i.e.

before

var login = PageObject.build({
  title: text('header'),

  form: component({
    userName: fillable('#username'),
    password: fillable('#password'),
    submit: clickable('button')
  })
});

after

var login = PageObject.build({
  title: text('header'),

  form: {
    userName: fillable('#username'),
    password: fillable('#password'),
    submit: clickable('button')
  }
});

This way we continue decluttering the page object definition.

Implementation

We can convert this plain object to a component under the hood. We could even create a component only if the plain object has an attribute that is a PageObject attribute, if not, just return the plain object.

Right now, PageObject.build isn't recursive, it only processes the attributes of the first level. By doing it recursive, each time it encounters a plain object it could peak it to see if it has any Page Object attribute defined, if so, convert it to a component.

Feature: Page objects in integration tests for components

This addon is great in acceptance tests, so I thought I would try using page objects to DRY up my integration tests for Ember components. This seemed like it might be straightforward, since pages are structured based on components anyway, and almost all of the page-object methods would be useful in component tests (except visitable()).

This doesn't currently work, however, because ember-cli-page-object uses the global find() to query selectors, which is only available in acceptance tests. (There may be other issues as well, but this one blocked me from looking any further.)

To fix this in integration tests, I suspect that the page object would just need access to this.$() instead of find(). I'm happy to try this out and (hopefully) submit a PR with page objects that can be configured to work in integration tests.

Just wanted to be sure that this isn't already a feature-in-progress, or that this use case hasn't been ruled out for some reason.

Component lookup

If we go with #21 and decide that custom components go in tests/pages/components folders, we can actually try to load the components by name in the page object definition.

Let's say we have a component tests/pages/components/context-menu.js, we could load it automatically from the page definition:

var page = PageObject.build({
  visit: visitable('users'),
  contextMenu: component('context-menu'),
  // ...
});

We can overload the component attribute helper so when it receives only a string parameter it can lookup a component with the name context-menu in the folder tests/pages/components.

Better errors

Right now several attributes (text, value, etc.) throw an exception when the element is not found on the page. The problem with this error is that it's not very descriptive and can be really hard sometimes to know what attribute of the PageObject was the one that failed.

Suppose we have the following page object

var page = PageObject.create({
  title: text('.a-selector', { sope: '.a-scope'})
});

The current error looks like this

Error: Element .a-scope .a-selector not found.
Expected:
true
Result:
false
Diff:
true false

It would be great to generate a better error message that let users know which attribute of the page object was the one that failed.

We could show an error message like:

Error: Element not found
Definition: title: text('.a-selector', { sope: '.a-scope'})
  => find(".a-scope .a-selector")

[ENHANCEMENT] move code to test-support directory

This looks like a great project! I'm excited to try this out.

Something that I noticed was that all the code is in the addon directory. It is not really needed outside of testing. Because of that, I might recommend moving the code into the test-support directory so as not to bloat the production build of your hosting app.

I was unaware of this feature until @bcardarella pointed it out in an addon that I am heavily involved with. I also wrote a really quick blog post (mainly for myself) about the feature.

Again, looking forward to trying out this addon. Thanks!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.