diff --git a/.all-contributorsrc b/.all-contributorsrc index a2cdc83..ebbc577 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -188,6 +188,20 @@ "contributions": [ "question" ] + }, + { + "login": "amurdock", + "name": "Alasdair Murdock", + "avatar_url": "https://avatars3.githubusercontent.com/u/2389179?v=4", + "profile": "https://github.com/amurdock", + "contributions": [] + }, + { + "login": "clayreimann", + "name": "Clay Reimann", + "avatar_url": "https://avatars3.githubusercontent.com/u/930039?v=4", + "profile": "http://clayreimann.me", + "contributions": [] } ] } diff --git a/lib/index.js b/lib/index.js index e436699..e762787 100644 --- a/lib/index.js +++ b/lib/index.js @@ -42,3 +42,7 @@ function getClientProxy (subPages) { } module.exports.client = getClientProxy([]) + +module.exports.Section = function () { + return runner.nightwatchApi.Section.apply(this, arguments) +} diff --git a/lib/nightwatch-api.js b/lib/nightwatch-api.js index b5374f3..66eeef4 100644 --- a/lib/nightwatch-api.js +++ b/lib/nightwatch-api.js @@ -1,3 +1,4 @@ +const util = require('util') const co = require('co') const pify = require('pify') const fs = pify(require('fs'), { include: ['readFile'] }) @@ -15,13 +16,20 @@ const Nightwatch = { ErrorHandler: require('nightwatch/lib/runner/cli/errorhandler'), Assertion: require('nightwatch/lib/core/assertion'), Expect: require('nightwatch/lib/api/expect'), - queue: require('nightwatch/lib/core/queue.js') + queue: require('nightwatch/lib/core/queue.js'), + Section: require('nightwatch/lib/page-object/section') } module.exports = class NightwatchApi { constructor (options, colorsEnabled) { this.options = options this.colorsEnabled = colorsEnabled + const self = this + this.Section = function () { + Nightwatch.Section.apply(this, arguments) + self.promisifySection(this) + } + util.inherits(this.Section, Nightwatch.Section) } _startSession (options) { diff --git a/package.json b/package.json index 3e8ea32..49d5c76 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,7 @@ "resources" ], "peerDependencies": { - "cucumber": "3.0.0 - 4.0.0", + "cucumber": "4.0.0 - 4.0.0", "nightwatch": "0.9.0 - 0.9.19" }, "devDependencies": { diff --git a/site/data/advanced-features/section.md b/site/data/advanced-features/section.md new file mode 100644 index 0000000..de2b1b1 --- /dev/null +++ b/site/data/advanced-features/section.md @@ -0,0 +1,62 @@ +## Creating dynamic sections + +You can create sections dynamically by using the modified Nightwatch `Section` +constructor exported by Nightwatch Cucumber. Consider the following example +using nightwatch to test Wikipedia. + +```javascript +//page-objects/wikipedia.js +const { Section } = require('nightwatch-cucumber') +module.exports = { + url: 'https://en.wikipedia.org/wiki/Cucumber_(software)', + elements: { + toc: 'div#toc' + }, + commands: [{ + getHeading: function(heading) { + const props = { + parent: this, + selector: `//h2/*[text()="${heading}"]/..`, + using: 'xpath', + elements: { + editLink: { + selector: '//*[@class="mw-editsection"]//a[text()="edit"]', + locateStrategy: 'xpath' + } + }, + commands: [{ + verifyEditSection: function() { + return Promise.resolve(true); + } + }] + } + return new Section(props); + }, + getSectionTitles: function() { + return Promise.resolve([/* MAGIC! */]); + } + }] +} +``` + +Now we can use the `getHeading` command to test each of the edit links to ensure +that they edit the appropriate section. + +```javascript +//step-definitions/yahoo.js + +const { client } = require('nightwatch-cucumber'); +const { Given, Then, When } = require('cucumber'); +const wikipedia = client.page.wikipedia(); + +Given(/^I open each section's edit link$/, () => { + wikipedia.navigate(); + return Promise.all( + wikipedia.getSectionTitles() + .map((title) => wikipedia.getHeading(title).verifyEditSection()) + ); +}); +``` +The advantage of creating sections of the fly like this that your page object +code can be much DRYer, especially when there are many similar objects on the +page that you want to test. diff --git a/site/data/index.md b/site/data/index.md index 6c6e77d..9ea4e27 100644 --- a/site/data/index.md +++ b/site/data/index.md @@ -47,6 +47,7 @@ @import './advanced-features/html-reports.md' @import './advanced-features/junit-reports.md' @import './advanced-features/languages.md' +@import './advanced-features/section.md' # Contributors diff --git a/test/assertions.test.js b/test/assertions.test.js index eb61120..9a61da2 100644 --- a/test/assertions.test.js +++ b/test/assertions.test.js @@ -3,6 +3,7 @@ const chai = require('chai') chai.should() const testCaseFactory = require('./test-case-factory') +let calculator describe('Assertion features', () => { it('should handle nightwatch assert.ok', () => { @@ -214,4 +215,57 @@ describe('Assertion features', () => { result.features[0].scenarios[0].result.stepCounts.should.deep.equal({passed: 4, failed: 1}) }) }) + + it('should handle nightwatch page object expect.section', () => { + return testCaseFactory + .create('client-in-page-object-expect-section') + .pageObject('calculator', ` + const commands = { + setA: function (value) { + return this.setValue('@a', value) + }, + setB: function (value) { + return this.setValue('@b', value) + }, + pressAdd: function () { + this.api.pause(1000) + return this.click('@add') + }, + checkResult: function (expectedResult) { + return this.expect.section('@result').text.to.equal(expectedResult) + }, + } + module.exports = { + url: 'http://yahoo.com', + elements: { + body: 'body', + a: '#a', + b: '#b', + add: '#add', + searchBar: 'input[name="p"]' + }, + sections: { + result: { + selector: '#result' + } + }, + commands: [commands] + }`) + .feature('addition') + .scenario('small numbers') + .prependStepDefinition('const calculator = client.page.calculator()') + .given('User is on the simple calculator page', () => client.init()) + .and('User enter 4 in A field', () => calculator.setA(4)) + .and('User enter 5 in B field', () => calculator.setB(5)) + .when('User press Add button', () => calculator.pressAdd()) + .then('The result should contain 9', () => calculator.checkResult(9)) + .then('The result should contain -9', () => calculator.checkResult(-9)) + .run() + .then((result) => { + result.features[0].result.status.should.be.failed + result.features[0].result.scenarioCounts.should.deep.equal({failed: 1}) + result.features[0].scenarios[0].result.status.should.be.failed + result.features[0].scenarios[0].result.stepCounts.should.deep.equal({passed: 5, failed: 1}) + }) + }) }) diff --git a/test/page-objects.test.js b/test/page-objects.test.js index 7c78758..038eaf8 100644 --- a/test/page-objects.test.js +++ b/test/page-objects.test.js @@ -4,6 +4,7 @@ const chai = require('chai') chai.should() const testCaseFactory = require('./test-case-factory') let calculator +let dynamicSection describe('Assertion features', () => { it('should enable the usage of client in page object custom commands', () => { @@ -162,56 +163,59 @@ describe('Assertion features', () => { }) }) - it('should enable the usage of sections in page object custom commands', () => { + it('should enable the usage of section constructor', () => { return testCaseFactory - .create('client-in-page-object-custom-commands-test') + .create('section-constructor-test') .pageObject('calculator', ` + const { Section } = require('../../../lib/index') const commands = { - setA: function (value) { - return this.setValue('@a', value) - }, - setB: function (value) { - return this.setValue('@b', value) - }, - pressAdd: function () { - this.api.pause(1000) - return this.click('@add') - }, - checkResult: function (expectedResult) { - return this.expect.section('@result').text.to.equal(expectedResult) + getDynamicSection(expectedResult) { + return new Section({ + name: 'Dynamic Section', + parent: this, + selector: 'body', + commands: [{ + setA: function (value) { + return this.setValue('#a', value) + }, + setB: function (value) { + return this.setValue('#b', value) + }, + pressAdd: function () { + return this.click('#add') + }, + checkResult: function () { + return this.assert.containsText('#result', expectedResult) + } + }] + }) } } module.exports = { url: 'http://yahoo.com', elements: { body: 'body', - a: '#a', - b: '#b', - add: '#add', searchBar: 'input[name="p"]' }, - sections: { - result: { - selector: '#result' - } - }, commands: [commands] }`) .feature('addition') .scenario('small numbers') - .prependStepDefinition('const calculator = client.page.calculator()') + .prependStepDefinition(` + const calculator = client.page.calculator(); + const dynamicSection = calculator.getDynamicSection(9); + `) .given('User is on the simple calculator page', () => client.init()) - .and('User enter 4 in A field', () => calculator.setA(4)) - .and('User enter 5 in B field', () => calculator.setB(5)) - .when('User press Add button', () => calculator.pressAdd()) - .then('The result should contain 9', () => calculator.checkResult(9)) - .then('The result should contain -9', () => calculator.checkResult(-9)) + .and('User enter values', () => dynamicSection.setA(4) + .then(() => dynamicSection.setB(5)) + .then(() => dynamicSection.pressAdd())) + .then('The result should contain 9', () => dynamicSection.checkResult()) .run() .then((result) => { - result.features[0].result.status.should.be.failed - result.features[0].result.scenarioCounts.should.deep.equal({failed: 1}) - result.features[0].scenarios[0].result.status.should.be.failed - result.features[0].scenarios[0].result.stepCounts.should.deep.equal({passed: 5, failed: 1}) + result.features[0].result.status.should.be.passed + result.features[0].result.scenarioCounts.should.deep.equal({passed: 1}) + result.features[0].scenarios[0].result.status.should.be.passed + result.features[0].scenarios[0].result.stepCounts.should.deep.equal({passed: 3}) }) }) })