Пример рабочего кода:const Nightmare = require('nightmare');
const nightmare = Nightmare({ show: true });
let results = [];
let res = nightmare
.goto('http://yahoo.com')
.type('form[action*="/search"] [name=p]', 'github nightmare')
.click('form[action*="/search"] [type=submit]')
.wait('#main')
.evaluate(function () {
return document.querySelector('#main .searchCenterMiddle li a').href;
})
.then(function(yahooResult) {
console.log('Yahoo result:', yahooResult);
results.push(yahooResult);
})
.then(function () {
return nightmare.goto('http://google.com')
.type('form[action*="/search"] [name=q]', 'github nightmare')
.click('form[action*="/search"] [type=submit]')
.wait('#main')
.evaluate(function () {
return document.querySelector('#search .g h3 a').href;
})
;
})
.then(function (googleResult) {
console.log('Google result:', googleResult);
results.push(googleResult);
})
.then(function () {
return nightmare.end();
})
.then(function() {
console.log('Full results:', results);
})
.catch(function (error) {
console.error('Search failed:', error);
})
;
Комментарии к коду:не совсем понятно, как в одном вызове можно использовать evaluate несколько раз и тем более в цикле, и можно ли вообще так действовать.
Почти все методы nightmare возвращают указатель на самого себя, чтобы вызовы методов можно было объединять в цепочки (как в jQuery).
Метод `evaluate()` также возвращает this (а не promise, как многие думают)!
Методами-исключениями, которые не возвращают `this` являются методы `.then()` и `.catch()` - они оба возвращают Promise.
Сами Promise также имеют методы `.then()` и `catch()`, поэтому при написании скриптом надо четко понимать и следить на объекте Promise или NightmareJS вызываются последующие методы.
Также nightmarejs при вызове цепочечных методов по факту просто добавляет нужные действия в буфер комманд. Команды из буфер начинают выполняться только при вызове методов `.then()`, и `.catch()`.
Поэтому код вроде приведенного ниже вообще даже не запустит браузер и не выполнит ничего, т.к. в нем нет вызова методов возвращающих промисы.
Код который даже не запустит браузерnightmare
.goto(url1).click(selector1).evaluate(func1)
.goto(url2).click(selector1).evaluate(func1)
.end()
Но следующий код будет выполнен браузером:
Код который будет выполнен в браузереnightmare
.goto(url1).click(selector1).evaluate(func1)
.goto(url2).click(selector1).evaluate(func1)
.end()
.catch(errorHandler)
Также надо иметь в виду, что если функция вызываемае в `.thеn()` методе возвращает promise, то этот promise также будет разрешаться/выполняться и результат его работы будет возвращемым значением передаваемым во второй `.then()` верхнего уровня.
Пример кода со вложенными промисами (https://jsfiddle.net/5kp62v7w/)promise1 = Promise.resolve('Promise 1')
.then(function(){ var str = 'Promise 1-1'; console.log(str); return str;})
.then(function(){ var str = 'Promise 1-2'; console.log(str); return str;})
.then(function(){ var str = 'Promise 1-3'; console.log(str); return str;})
.then(function(){
return Promise.resolve('Promise 2')
.then(function(){ var str = 'Promise 2-1'; console.log(str); return str;})
.then(function(){ var str = 'Promise 2-2'; console.log(str); return str;})
.then(function(){ var str = 'Promise 2-3'; console.log(str); return str;})
.then(function(){
return Promise.resolve('Promise 3')
.then(function(){ var str = 'Promise 3-1'; console.log(str); return str;})
.then(function(){ var str = 'Promise 3-2'; console.log(str); return str;})
.then(function(){ var str = 'Promise 3-3'; console.log(str); return str;})
;
})
;
})
;
promise1
.then(function(res) {console.log('Result value:', res );})
;
Результат выполнения кодаPromise 1-1
Promise 1-2
Promise 1-3
Promise 2-1
Promise 2-2
Promise 2-3
Promise 3-1
Promise 3-2
Promise 3-3
Result value: Promise 3-3
Поэтому при последовательном получении данных код должен быть такой структуры:
Пример кода при многократном вызове метода `.evaluate()`nightmare.goto(url1).click(selector1).evaluate(func1).then(saveHandler1)
.then(
return nightmare.goto(url2).click(selector2).evaluate(func2).then(saveHandler2)
)
.then(
return nightmare.goto(url3).click(selector3).evaluate(func3).then(saveHandler3)
)
.then(
return nightmare.end()
)
.catch(errorHandler)
;
Циклы можно реализовать через reduce() метод, но на данный момент мне кажется проще всего писать с использованием await/async функций.
Пример использования `.evaluate()` в цикле с помощью async/await-функцийvar urls = ['http://example.com', 'http://example2.com', 'http://example3.com'];
var results = []
async function collectTitles(urls){
for ( url of urls ) {
results.push(await nightmare.goto(url).wait('body').title())
}
await nightmare.end()
console.dir(results)
}
collectTitles(urls)
до вызова nightmare можно объявить любую переменную, к примеру var arr = [] и потом пытаться пушить в нее через evaluate
Так сделать нельзя. Код передаваемый в метод `.evaluate()` работает в окружении браузера, а не node-сервера, поэтому единственных способ передать данные из кода выполняемого в браузе в код выполняемый на nodejs это вернуть из браузерной ф-ции значение с помощью оператора return и использовать затем это значение внутри `.than()` ф-ции как аргумент функции:
Пример передачи значения из браузер в выполняемый скриптvar nodeArray = [];
nightmare
.goto(url)
.evaluate(function(){
})
.then(function(){ var ret = []; /* do some work with ret */ return ret; }) //т.к. этот код выполняется в окружении браузера, то у него нет доступа к переменным объявkенным в окружении nodejs, в том числе и к nodeArray. Значение `ret` будет передано первым аргументом в последющей then()-ф-ции
.then(function(browserRet){ nodeArray = nodeArray.concat(browserRet); }) // browserRet равен значению ret вычисленному в браузере, но переменная ret для окружения сервера не определена.
Для того чтобы понимать что происходит в скрипте рекомендую при запуске включить вывод отладочных сообщений как описано
в документации.
Также рекомендую ознакомиться с примерами кода и комментариями к ним в репозитории
rosshinkley/nightmare-examples