Skip to content

Commit c6238f9

Browse files
committed
Adding waitAfterLoadedFor setting, and basic link following
1 parent 66c8857 commit c6238f9

File tree

5 files changed

+125
-14
lines changed

5 files changed

+125
-14
lines changed

README.md

+11-4
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,11 @@ Currently
1818
* ~~`0.3.0` Stream support (instead of promises)~~
1919
* ~~`0.3.1` Migrated to [nightmare](https://github.com/segmentio/nightmare) - 3x faster than Webdriver/Phantom2 and option to open up devTools~~
2020
* ~~`0.3.2` Stream usage cleanup~~
21-
* ~~`0.4.0` Support to keep the session alive~~
22-
* *[pending]* Handle console errors that occur after `DOMContentLoaded` (with configurable timeout)
23-
* *[pending]* Follow and track all internal links
21+
* ~~`0.4.0` Support to keep the session alive via `keepAlive`~~
22+
* ~~`0.5.0` Handles console errors that occur after `DOMContentLoaded` (with configurable timeout `waitAfterLoadedFor (ms)`). Follows links now.~~
23+
* *[pending]* Prevent visiting the same link twice.
24+
* *[pending]* Allow max number of links to follow or timeout.
25+
* *[pending]* Filter function to use for following links (defaults to implicit domain name, within same protocol://host:port)
2426
* *[pending]* Allow for `setup`/`teardown` actions in nightmare (such as login) (or perhaps just use cookies)
2527

2628
##Usage
@@ -56,4 +58,9 @@ const hunt = hounds.release({
5658
}).on('error', console.error)
5759
```
5860

59-
> All [nightmare 2.7.0 options](https://github.com/segmentio/nightmare/tree/2.7.0#nightmareoptions) are supported
61+
##Options
62+
63+
* `url` base URL to start from
64+
* `keepAlive` don't end the stream or the nightmare session when complete (when combined with `nightmare.show`, allows you to interact with the browser when done).
65+
* `waitAfterLoadedFor` The number of milliseconds to wait after each page is loaded before following the next link in the queue
66+
* `nightmare` All [nightmare 2.7.0 options](https://github.com/segmentio/nightmare/tree/2.7.0#nightmareoptions) are supported

example/index.js

+4-3
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,13 @@ app.listen(4441)
1313

1414
const hunt = hounds.release({
1515
url: 'http://localhost:4441',
16-
keepAlive: true,
17-
waitAfterLoadedFor: 1000,
16+
// keepAlive: true,
17+
waitAfterLoadedFor: 600,
1818
nightmare: {
19-
show: true, openDevTools: true
19+
// show: true, openDevTools: true
2020
}
2121
}).on('error', console.error)
22+
.on('end', process.exit)
2223

2324
const quarry = new Writable({
2425
objectMode: true,

index.js

+14-3
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,15 @@ exports.release = (options) => {
1212
const queue = [ options.url ]
1313
const nightmare = new Nightmare(options.nightmare)
1414

15+
// Note: this is potentially buggy (see pending test)
16+
// We'd prefer that the page event below emit the URL at error time
17+
let url
18+
1519
let session = nightmare
1620
.on('page', function(type, message, stack) {
1721
const stackTrace = stack.split('\n').map(l => l.trim())
1822
quarry.push({
19-
url: options.url,
23+
url,
2024
message,
2125
stackTrace
2226
})
@@ -30,11 +34,18 @@ exports.release = (options) => {
3034

3135
return
3236
}
33-
const url = queue.pop()
37+
url = queue.shift()
3438
session
3539
.goto(url)
3640
.wait(options.waitAfterLoadedFor)
37-
.then(() => {
41+
.evaluate(() => {
42+
/* eslint-env browser */
43+
const anchors = document.querySelectorAll('a[href]')
44+
return [].slice.call(anchors).map(a => a.href)
45+
})
46+
.then(anchors => {
47+
anchors = Array.isArray(anchors) ? anchors : [ anchors ]
48+
anchors.forEach(href => queue.push(href))
3849
processNextInQueue()
3950
})
4051
.catch(e => quarry.emit('error', e))

test/fixture/empty.html

Whitespace-only changes.

test/test.js

+96-4
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ describe('hounds', function() {
2323
})
2424

2525
beforeEach(() => {
26-
this.options = { url: 'http://localhost:4441' }
26+
this.options = { url: 'http://localhost:4441/' }
2727

2828
this.assertErrorReceived = () => {}
2929

@@ -43,6 +43,10 @@ describe('hounds', function() {
4343
this.hunt = hounds.release(this.options)
4444
})
4545

46+
afterEach(() => {
47+
this.hunt.unpipe(this.quarry)
48+
})
49+
4650
it('detects the single console error', done => {
4751
this.assertErrorReceived = (chunk, callCount) => {
4852
if (callCount !== 1) return
@@ -68,13 +72,101 @@ describe('hounds', function() {
6872
})
6973
})
7074

71-
describe('returns error async after load if kept alive', () => {
75+
76+
describe('when setup on an empty page', () => {
77+
beforeEach(() => {
78+
this.options.url += 'empty.html'
79+
})
80+
81+
describe('when released', () => {
82+
beforeEach(() => {
83+
this.hunt = hounds.release(this.options)
84+
})
85+
86+
afterEach(() => {
87+
this.hunt.unpipe(this.quarry)
88+
})
89+
90+
it('then it ends the stream when complete', done => {
91+
this.hunt.on('error', done).on('end', done).pipe(this.quarry)
92+
})
93+
})
94+
95+
describe('when keepAlive is provided and relesed', () => {
96+
beforeEach(() => {
97+
this.options.keepAlive = true
98+
this.hunt = hounds.release(this.options)
99+
})
100+
101+
afterEach(() => {
102+
this.hunt.unpipe(this.quarry)
103+
})
104+
105+
it('then it does NOT end the stream when complete', done => {
106+
this.hunt.on('error', done).on('end', () => { throw Error('The hunt was ended early!') }).pipe(this.quarry)
107+
setTimeout(done, 2000)
108+
})
109+
})
110+
111+
})
112+
113+
describe('returns error from followed page', () => {
72114
beforeEach(() => {
73-
this.options.keepAlive = true
74115
this.hunt = hounds.release(this.options)
75116
})
76117

77-
it('detects the single console error', done => {
118+
it('detects the console error in a followed page', done => {
119+
this.assertErrorReceived = (chunk, callCount) => {
120+
if (callCount !== 3) return
121+
assert.equal(`${this.options.url}second.html`, chunk.url, 'URL is passed through')
122+
assert.equal('Uncaught Error: This is supposed to happen', chunk.message, 'Page error on second.html caught')
123+
assert.equal(3, chunk.stackTrace.length, 'Page error stacktrace is captured')
124+
done()
125+
}
126+
127+
this.hunt.on('error', done).pipe(this.quarry)
128+
})
129+
})
130+
131+
describe('when waiting on each page for 600ms after load before moving on', () => {
132+
beforeEach(() => {
133+
this.options.waitAfterLoadedFor = 600
134+
this.hunt = hounds.release(this.options)
135+
})
136+
137+
afterEach(() => {
138+
this.hunt.unpipe(this.quarry)
139+
})
140+
141+
it('then it detects a timed out error of 500ms', done => {
142+
this.assertErrorReceived = (chunk, callCount) => {
143+
if (callCount !== 3) return
144+
assert.equal(this.options.url, chunk.url, 'URL is passed through')
145+
assert.equal('Uncaught Error: Error after 500ms', chunk.message, 'Page error 500ms after load is caught')
146+
assert.equal(3, chunk.stackTrace.length, 'Page error stacktrace is captured')
147+
done()
148+
}
149+
150+
this.hunt.on('error', done).pipe(this.quarry)
151+
})
152+
})
153+
154+
// Known bug:
155+
// We aren't guaranteed that the URL is still valid here,
156+
// as it may have changed between when this error was thrown and now.
157+
// A better solution is for nightmare to also emit `win.webContents.getURL()`
158+
// as another argument in https://github.com/segmentio/nightmare/blob/master/lib/runner.js#L115
159+
xdescribe('when waiting on each page for 400ms after load before moving on', () => {
160+
beforeEach(() => {
161+
this.options.waitAfterLoadedFor = 400
162+
this.hunt = hounds.release(this.options)
163+
})
164+
165+
afterEach(() => {
166+
this.hunt.unpipe(this.quarry)
167+
})
168+
169+
it('then it still detects the timed out error of 500ms, with the correct, previous URL', done => {
78170
this.assertErrorReceived = (chunk, callCount) => {
79171
if (callCount !== 3) return
80172
assert.equal(this.options.url, chunk.url, 'URL is passed through')

0 commit comments

Comments
 (0)