angular / protractor

E2E test framework for Angular apps
http://www.protractortest.org
MIT License
8.75k stars 2.31k forks source link

Syntax reboot #156

Closed juliemr closed 10 years ago

juliemr commented 10 years ago

Need a shorter syntax for finding elements, and a migration path from angular scenario tests.

Consider something like:

describe("api/ng.$http", function() {
  beforeEach(function() {
    ptor.get("index-jq-nocache.html#!/api/ng.$http");
  });

  it('should make a JSONP request to angularjs.org', function() {
    webelem.by.css(':button:contains("Sample JSONP")').click();
    webelem.by.css(':button:contains("fetch")')).click();
    expect(webelem.by.binding('status').getText()).toBe('200');
    expect(webelem.by.binding('data').getText()).toMatch(/Super Hero!/);
  });
});

// The protractor runner would expose some additional things to the global
// namespace. It could now be:
// - all the Jasmine scaffolding (describe, it, expect, matchers).
// - protractor, the protractor namespace.
// - ptor, an instance of protractor.
// - element, providing utilities to find WebElements.
// - elements, find multiple elements.
sidwood commented 10 years ago

Exposing WebElement finders on the protractor instance like so would be nice. Also, in this happy future mocha/chai is supported.

describe('api/ng.$http', function() {
  beforeEach(function() {
    client.get('index-jq-nocache.html#!/api/ng.$http');
  });

  it('should make a JSONP request to angularjs.org', function() {
    client.find.css(':button:contains("Sample JSONP")').click();
    client.find.css(':button:contains("fetch")')).click();
    client.find.binding('status').text().should.eventually.eql('200');
    client.find.binding('data').text().should.eventually.match(/Super Hero!/);
  });
});
juliemr commented 10 years ago

Here's the design for this, based on the following goals:

Protractor will expose a limited set of global variables

  protractor // the namespace, this contains everything available on the
             // webdriver namespace such as Key and Button. This 
             // contains only static stuff. Backwards compatible.
  browser    // an instance of protractor, this wraps an instance of 
             // webdriver, can be used for navigation, getting the title
             // and page source, etc. 
  element    // function for element location, described below.
  by         // for element location, described below. 
  $          // an alias for element(by.css())

Protractor will expose a new type: enhanced element locators. This will be available via the element function.

element(by.<strategy>(<string>));

This element locator can either be used directly

browser.findElement(by.css('.foo')).getText();

or it can be used via one of the methods that enhanced element locators expose - these generally correspond to actions taken over the webdriver wire protocol.

element(by.css('.foo')).getText();
$('.foo').getText(); // equivalent.

Some helper functions will be added, such as a count() function for repeaters. This will be backwards compatible - you can still do

var ptor = protractor.getInstance();
ptor.findElement(protractor.By.css('.foo'));

in that case, ptor === browser and protractor.By.css is a subset of element(by.css).

A Page Object DSL could be created like this:

var TodoPage = function(element) {
  // element locators - no call is made over the webdriver
  // wire protocol by just defining these, only when
  // they are used.
  this.todoList = element(by.repeat('todo in todos'));
  this.newTodo = element(by.model('todoText'));
  this.addTodoButton = element(by.css('.btn-primary'));

  this.addNewTodo = function(todo) {
    this.newTodo.sendKeys(todo);
    this.addTodoButton.click();
  };

  this.getLatestTodo = function() {
    return this.todoList.row(0).getText();
  };
}

Tests would look like:

  todoPage = new TodoPage();
  todoPage.addNewTodo('bake cookies');
  expect(todoPage.getLatestTodo()).toMatch('cookie');

The underlying webdriver instance can be accessed with

browser.driver

A complicated action, e.g. something with an action sequence, would look like:

browser.actions().dragAndDrop(element, {x: 30, y: 0}).perform();

Sending a global key press would look like:

 element(by.css('.body')).sendKeys(protractor.Key.ESCAPE);