Skip to content

Commit

Permalink
feat: Promisify section (#340)
Browse files Browse the repository at this point in the history
* Update packages

* compiler -> require-module

* Downgrade jsdom to 11.5.1

* Fix require-module config

* Fix execution result handling

* Update peer dependencies

* Add Section cinstructor to API

* Fix code-style issues

* Add missing run to page object test

* Add documentation on the Section object (#343)

* Add documentation on the Section object

* Fix section test

* Add @amurdock as a contributor

* Add @clayreimann as a contributor

* Fix contributors

* Fix eslint issue
  • Loading branch information
Igor Muchychka committed Feb 7, 2018
1 parent e180976 commit d319afe
Show file tree
Hide file tree
Showing 8 changed files with 181 additions and 34 deletions.
14 changes: 14 additions & 0 deletions .all-contributorsrc
Expand Up @@ -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": []
}
]
}
4 changes: 4 additions & 0 deletions lib/index.js
Expand Up @@ -42,3 +42,7 @@ function getClientProxy (subPages) {
}

module.exports.client = getClientProxy([])

module.exports.Section = function () {
return runner.nightwatchApi.Section.apply(this, arguments)
}
10 changes: 9 additions & 1 deletion 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'] })
Expand All @@ -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) {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -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": {
Expand Down
62 changes: 62 additions & 0 deletions 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.
1 change: 1 addition & 0 deletions site/data/index.md
Expand Up @@ -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

Expand Down
54 changes: 54 additions & 0 deletions test/assertions.test.js
Expand Up @@ -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', () => {
Expand Down Expand Up @@ -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})
})
})
})
68 changes: 36 additions & 32 deletions test/page-objects.test.js
Expand Up @@ -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', () => {
Expand Down Expand Up @@ -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})
})
})
})

0 comments on commit d319afe

Please sign in to comment.