check current page URL with Nightmare - node.js

I am writing an automated test with Nightmare.js and typescript. The first objective of this test is to test I've landed at the correct page.
I have done some research, but have not found anything useful to me.
Here is my code:
import * as Nightmare from "nightmare";
describe("Login Page", function() { this.timeout("30s");
let nightmare = null; beforeEach(() => {
nightmare = new Nightmare({ show: true }); });
describe("give correct details", () => {
it("should log-in, check for current page url", done => {
nightmare
.goto(www.example.com/log-in)
.wait(5000)
.type("input[type='email']", "username")
.type("input[type='password']", "password")
.click(".submit")
.wait(3000)
SOMETHING THAT SHOULD CHECK URL HERE
.end()
.then(result => {
done();
})
.catch(done);
});
});
});
Where it says "SOMETHING THAT SHOULD CHECK URL HERE" I would like something to put the current url into a variable for later reference then console.log it. All answers are appreciated

Many ways to do it. I'll be using the simplest solution for this.
let SOMEGLOBALVAR; // so we can access it later
// Codes in between ...
.click(".submit")
.wait(3000)
.url() // <-- use the built in url
.end()
.then(url => {
SOMEGLOBALVAR = url; // set it to a global variable if you want
done();
})
You can change it however you want.
Note: Global vars are considered bad practice. The above snippet is just for illustrating the .url function.

Related

Page.evaluate() won't execute in a Promise chain

Just started using Puppeteer. Trying to parse a page but the evaluate method won't work somehow.
var Browser
var Page
var Result
puppeteer.launch()
.then(function (browser) {
console.log('Browser Created\nCreating Blank Page')
Browser = browser
return Browser.newPage()
})
.then(function (page) {
console.log('Page Created\nVisiting URL')
Page = page
return Page.goto(URL)
})
.then(function (resp) {
console.log('Website Loaded')
return Page.evaluate(function () {
// Completely Sync Stuff
console.log('Evaluating Selectors')
var myElems = document.getElementsByClassName('challenge-type light')
Result = myElems
})
})
.then(function (val) {
console.log(Result)
console.log('Done! Exiting')
Browser.close()
process.exit()
})
.catch(function (err) {
Browser.close()
console.log(err)
process.exit(1)
})
Output :
Browser Created
Creating Blank Page
Page Created
Visiting URL
Website Loaded
undefined
Done! Exiting
What could possibly be the error? Would prefer a solution without async/await.
EDIT: "Evaluating Selectors" is not logged to the console as well, so the code never reaches there, is my concern.
I would double check that
document.getElementsByClassName('challenge-type light')
returns a result.
I believe you're using a headless browser, so sometimes elements may not load as you might expect.
Got the things working finally.
Console inside evaluate will be in page context, so that's the console of chromium page.
We need to return something from the evaluate function. DOM elements won't be returned as is because they lose context outside evaluate.
This worked :
.then(function (resp) {
console.log('Website Loaded')
return Page.evaluate(function () {
return document.querySelector('.cover-heading').innerText
})
})
OK you are on the right path but you have a few problems.
From your own answer: you noted that the console logs executed in the page context when they are executed in the evaluate method. You are correct in saying that but you are incorrect in saying that you can't return DOM elements from the evaluate method. You can just your code isn't quite correct.
So what you have is this:
.then(function (resp) {
console.log('Website Loaded')
return Page.evaluate(function () {
// Completely Sync Stuff
console.log('Evaluating Selectors')
var myElems = document.getElementsByClassName('challenge-type light')
Result = myElems
})
})
.then(function (val) {
console.log(Result)
console.log('Done! Exiting')
});
This won't work since you're trying to assign myElems to the Result variable inside the evaluate method. The evaluate method is executed in the browser. It has no idea that a Result variable exists in your puppeteer script. This is why your variable outputs as undefined at the end.
How to resolve this is as follows:
.then(function () {
return Page.evaluate(function () {
// Return the array of elements from inside the evaluate method
return document.getElementsByClassName('challenge-type light')
});
})
.then(function (elements) {
console.log(elements) // Will be your array of elements
});
Hopefully this helps!

Mocha tests with async initialization code

I am writing tests for a REST client library which has to "login" against the service using the OAuth exchange. In order to prevent logging in for every endpoint I am going to test I'd like to write some sort of "test setup" but I am not sure how I am supposed to do this.
My test project structure:
test
endpoint-category1.spec.ts
endpoint-category2.spec.ts
If I had only one "endpoint category" I had something like this:
describe('Endpoint category 1', () => {
let api: Client = null;
before(() => {
api = new Client(credentials);
});
it('should successfully login using the test credentials', async () => {
await api.login();
});
it('should return xyz\'s profile', async () => {
const r: Lookup = await api.lookup('xyz');
expect(r).to.be.an('object');
});
});
My Question:
Since the login() method is the first test there, it would work and the client instance is available for all the following tests as well. However, how can I do some sort of setup where I make the "logged in api instance" available to my other test files?
Common code should be moved to beforeEach:
beforeEach(async () => {
await api.login();
});
At this point should successfully login using the test credentials doesn't make much sense because it doesn't assert anything.
describe('Endpoint category 1', () => {
let api: Client = null;
beforeEach(() => {
api = new Client(credentials);
});
afterEach(() => {
// You should make every single test to be ran in a clean environment.
// So do some jobs here, to clean all data created by previous tests.
});
it('should successfully login using the test credentials', async () => {
const ret = await api.login();
// Do some assert for `ret`.
});
context('the other tests', () => {
beforeEach(() => api.login());
it('should return xyz\'s profile', async () => {
const r: Lookup = await api.lookup('xyz');
expect(r).to.be.an('object');
});
});
});
Have you had a look at https://mochajs.org/#asynchronous-code ?
You can put in a done-parameter in your test functions and you will get a callback with this you have to call.
done() or done(error/exception)
This done would be also available in before and after.
When calling done() mocha knows your async-code has finished.
Ah. And if you want to test for login, you shouldn't provide this connection to other tests, because there is no guarantee of test order in default configuration.
Just test for login and logout afterwards.
If you need more tests with "login-session", describe a new one with befores.

Sinon spy calledWithNew not working

I'm trying to use sinon or sinon-chai's calledWithNew (or simply called), but can't seem to get it to work, I've looked at a few suggestions online without luck either, here is the function I'm trying to test:
users.js
exports.create = function (data) {
//some validation
var user = new User(data);
return user.save().then((result) => {
return mailer.sendWelcomeEmail(data.email, data.name).then(() => {
return {
message: 'User created',
userId: result.id
};
});
}).catch((err) => {
return Promise.reject(err);
});
}
Here is my test:
users.test.js
beforeEach(() => {
saveStub = sandbox.stub(User.prototype, 'save').resolves(sampleUser);
spy = sandbox.spy(User);
});
afterEach(() => {
sandbox.restore();
});
it('should call user.save', async () => {
result = await users.create(sampleArgs);
expect(saveStub).to.have.been.called; //-> true
expect(spy).to.have.been.called; //-> false, calledWithNew returns same result as well
});
I found several posts suggesting spying on (window, 'className') but I'm using mocha, not a browser.
Trying to spy on (global, User / User.prototype) didn't work either.
User is a module-level variable in users.js. Sinon cannot affect it. When you do this in your test file:
spy = sandbox.spy(User);
You're creating a spy in the scope of your test file, sure, but your module is still using the original.
To do something like this, you need to export your constructor inside an object, then both invoke it and spy on it through that object:
Wherever User is coming from, let's say user.js:
class User {
// whatever your User implementation is
}
module.exports = { User };
users.js:
const userModule = require('./user');
...
var user = new userModule.User(data);
Then, in your test file:
const userModule = require('./user');
spy = sandbox.spy(userModule, 'User');
Another way to do this would be to use something like proxyquire. It can make these kinds of tests less obtrusive, but can make your tests more confusing to readers.
My preference is generally to keep constructors very simple so I don't ever have to spy on them. I've never used calledWithNew in any of my own projects for this reason. :\ It's up to you, though.

nightmare/electron, how to carry out different operations depend on whether an element exists?

I want to use Nightmare to access a page and do different operations based on whether a specified element exists. I know there is an exists function to test whether an element exists on the page, but I don't know how to use it or whether it can be used here. Can someone give me an example on how to do this task? Thank you!
Nightmare is thenable, so if you want to use the return value of an exists() function as logic, you can use .then() for method chaining. This also goes for visible() or evaluate() or any function returning a value.
The example I provided searches Stackoverflow if the searchbox selector exists, goes to Google, returns the title and then conditionally logs the result. You can continue chaining logic as necessary.
var Nightmare = require('nightmare');
var nightmare = Nightmare({ show: true });
nightmare
.goto("http://stackoverflow.com")
.exists("#search input[type=text]")
.then(function (result) {
if (result) {
return nightmare.type("#search input[type=text]", "javascript\u000d")
} else {
console.log("Could not find selector")
}
})
.then(function() {
return nightmare
.goto("http://www.google.com")
.wait(1000)
.title()
})
.then(function (title) {
if (title == "Google") {
console.log("title is Google")
} else {
console.log("title is not Google")
}
return nightmare.end()
})
.catch(function (error) {
console.log(error)
})

My nightmare test isnt getting into my evaluate statement

Getting some practice running tests with mocha chai and nightmare. Everything seems to work until I get into my evaluate block.
var Nightmare = require('nightmare'),
should = require('chai').should()
describe('Frontend Masters', function() {
this.timeout(20000);
it('should show form when loaded', function(done) {
var nightmare = new Nightmare({show: true})
nightmare
.goto('https://frontendmasters.com/')
.wait('a[href*="https://frontendmasters.com/login/"]')
.click('a[href*="https://frontendmasters.com/login/"]')
.wait('#rcp_login_form')
.evaluate(function() {
return window.document.title;
}, function(result) {
result.should.equal('Login to Frontend Masters');
done();
})
.run(function(){
console.log('done')
});
});
});
I've thrown in console logs and it never makes it into the evaluate. I've tried passing in several selectors into my wait() function, but it doesnt seem to be having an effect. Only error I'm receiving is that my timeout has been exceeded. But it doesnt matter how long i set it for
What version of Nightmare are you using?
The signature for .evaluate() has changed, and I think that may be the source of your issues. The second function you're passing in - the one that used to be for handling the results of the evaluation - is actually getting passed as an argument to the first .evaluate() argument. In other words, the second argument is never run, done() is never called, and your test will time out.
Also worth mentioning: .run() is not directly supported. Use.then() instead.
Finally, let's modify your source to reflect the above to get you started:
var Nightmare = require('nightmare'),
should = require('chai')
.should()
describe('Frontend Masters', function() {
this.timeout(20000);
it('should show form when loaded', function(done) {
var nightmare = new Nightmare({
show: true
})
nightmare
.goto('https://frontendmasters.com/')
.wait('a[href*="https://frontendmasters.com/login/"]')
.click('a[href*="https://frontendmasters.com/login/"]')
.wait('#rcp_login_form')
.evaluate(function() {
return window.document.title;
})
.then(function(result) {
result.should.equal('Login to Frontend Masters');
console.log('done')
done();
})
});
});

Resources