how to run a series of 'function' tests, sequentially, with nightwatch.js - javascript

I have one really long function tests, that starts from the login panel of my web application and goes all the way through deeper features. It is my first time using nightwatch.js - I'm wondering if there is a way to break this huge function up, into multiple, segmented functions, that run sequentially. I tried, literally breaking it up into separate functions, just like my main, large one, I also tried wrapping each separated function in between 'browser' and 'browser.end();' but this also did not work - what happens is, they don't run sequentially - so of course they don't find the next underlying elements, etc. because it starts over. Any advice?
this.LoginScreen = function(browser) {
browser
.url(Data.urls.home)
.waitForElementVisible('#login', 1000, false)
.click('#login')
.waitForElementVisible('div.side-panel.open', 4000, false)
// Ton more here, i'd like to modulate
Errors.checkForErrors(browser);
browser.end();
};

That's very simple and available in Nightwatch Getting Started Page http://nightwatchjs.org/gettingstarted
module.exports = {
'Verify Added customer': function (browser) {
// In above first function with name 'Verify Added customer'
browser
.click(rc.registeredCustomers)
.pause(t.averagePauseLimit)
},
'Verify Email Button Present': function (browser) {
// in above there is second function
browser
.click(rc.registeredCustomers)
.pause(t.averagePauseLimit)
.getText(rc.primaryEmail, function (result) {
this.assert.equal(result.value, 'Primary : ' + email)
})
.pause(t.minimumPauseLimit)
.click(rc.verifyCustomer)
.pause(1000)
.assert.elementNotPresent(rc.verifyEmail, 'Verify Email is not present')
},

Related

How to move from jest with user journey like tests to #playwright/test using fixtures?

Personally I see playwright as a tool that goes into the direction of system/end-to-end tests. So I used playwright + jest to build-up user journeys and integrated them in a CI/CD process.
Since playwright created it's own test runner with useful features like taking videos and trace on failures, it would make sense to switch from jest to #playwright/test. On their home page playwright recommends to use test fixtures so I definitly want to include them in the switch.
In the following I will take amazon as an example.
With playwright + jest the first thing that I did was to create a function for generic setup of the environment:
function setupSuite({ browserOptions, userConfig }){
// create playwright browser and use browserOptions as overrides
// create page from browser
// register self-implemented trace and screenshot reporters to page
// go to baseUrl of current environment (e.g. local, branch etc..)
// click on cookie banner, since it's blocking the UI
// create a user based on userConfig (e.g. user has amazon prime user? payment options? etc.)
// return { browser, page, user, ... }
}
And of course a function to clean up everything again:
function teardownSuite(suite){
// close browser
// delete user
// etc..
}
Then I would use a file for each user journey. In case of amazon a user journey could be the successful processing of an order:
describe("Successful Order", () => {
let suite
beforeAll(async () => {
const userConfig = { isPrime: false, paymentOptions: [ "paypal", "visa" ] }
suite = await setupBrowser({ userConfig })
// I actually extracted that logic in a function to be able to use it in other tests too,
// but just want to make clear whats happening here
const { page, user } = suite
await page.fill(".login-username-input", user.username)
await page.fill(".login-password-input", user.password)
await page.click(".login-submit-button")
})
afterAll(() => teardownSuite(suite))
test("search for toothbrush with suggestions", async () => {
const { page } = suite
await page.fill(".search-input", "tooth")
await page.click("text='toothbrush")
// hit enter
// do some assertions to check if the search was really successful
})
test("click on first item and add to chart", async () => {
// page actions and assertions
})
test("go back, click on second item and add to chart", async () => {
// page actions and assertions
})
test("go to chart and pay", async () => {
// page actions and assertions
})
test("check order confirmation mail", async () => {
// page actions and assertions
})
})
As you can see I split up my test in logical parts to make it more readable and also to see at which step (test block) it failed.
What would be the best way to migrate this to #playwright/test + fixtures?
How would you migrate setupSuite / teardownSuite ? Yes you could use a fixture, but setupSuite expects arguments like the userConfig . Is it possible to have parameterized fixtures?
How would you structure tests with fixtures? If you want to simulate complete user journey the tests are getting bigger than just testing a login for example. A test block would then have a lot of lines without the possibility to structure them.
Is it possible to setup a page so it's shared accross all tests? The beforeAll hook doesn't receive any page and the each test block always receives its own page. This means that there is no connection between test blocks. If you create a page manually in beforeAll and use the same page instance in every test it would probably be a bad practice and video and tracing would probably not work.. so what can be done here?
Are user journey like tests actually bad? I feel like they can't be combined well with the fixture approach of playwright as mentioned in the docs. Fixtures in playwright feel like very data-driven which doesn't really fit to end-to-end testing IMO.

How to trigger function based on API call and callback in 3rd party app

I am trying to listen for a javascript callback from a 3rd party app on my site. The app is minified so it is quite hard to reverse engineer. However, having used the Chrome debugger, the callback I want to capture is below, is there any way, I can trigger a function when that 'CollectEvent' callback is fired, with access to the 'email' variable? You can see in the console, that the callbacks are being created on the window, although of course they are named differently each time the code runs.
Recognising that I cannot edit that code directly as it is part of a 3rd party library.
!function() {
var _0x14bdc8 = {
'CollectEvent': function(_0x4a9e64, _0x3ac5b7) {
if (_0x4a9e64) {
_0x14bdc8[_0x304d('0xa7')] && (_0x30053a('COUPON_CODE_COOKIE_NAME', _0x4a9e64[_0x304d('0xd7')], 0x1),
_0x14bdc8[_0x304d('0x6a')]());
var _0x562cf7 = {
'shopId': _0x14bdc8[_0x304d('0xc2')],
'campaignId': _0x14bdc8[_0x304d('0x79')],
'email': encodeURIComponent(_0x4a9e64[_0x304d('0x23')]),
'code': _0x4a9e64['code'],
'customFields': encodeURIComponent(JSON[_0x304d('0x3')](_0x3ac5b7)),
'domain': window[_0x304d('0x73')][_0x304d('0x4a')],
'currentUrl': window[_0x304d('0x73')][_0x304d('0x6b')]
};
_0x14bdc8[_0x304d('0xa0')](_0x986b46 + '/api/wheelioapp/collectemail', _0x562cf7, function(_0xea4ea9) {
_0xea4ea9[_0x304d('0x89')] && _0x14bdc8[_0x304d('0x8f')](!0x1, !0x1, !0x0, !0x1);
});
} else
alert(_0x304d('0x80'));
},
...
}
}
You can see here the Wheelio app object in the console and the callbacks which have been created (although they have different names each session).
I just need to log it
Well, ok. We can't change functions created on-the-fly, but we can change other window functions.
For example we can use encodeURIComponent. See this line:
'email': encodeURIComponent(_0x4a9e64[_0x304d('0x23')]),
It means that somehow the email will go into the encodeURIComponent. Good, because we can read it there:
/* At the beginning */
// This is helper function, detects correct email:
function validateEmail(email) {
const re = /^(([^<>()\[\]\\.,;:\s#"]+(\.[^<>()\[\]\\.,;:\s#"]+)*)|(".+"))#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
return re.test(String(email).toLowerCase());
}
// Keep old function
let oldEncodeURIComponent = window.encodeURIComponent;
// Crete new one
window.encodeURIComponent = (data) => {
if (validateEmail(data)) {
// Gotcha!
console.log('[encodeURIComponent]', data);
}
return oldEncodeURIComponent(data);
}
/* Here program works as normal, but creates lots of logs... */
/* In the end */
// If we can understand when we need to stop looking for email,
// we will disconnect our function:
window.encodeURIComponent = oldEncodeURIComponent;
So the idea is to read all data passing thru encodeURIComponent.
P.S. Email validator is here

Testing angular forms

I am trying to test an angular app in protrator and I do not understand something.
I put this line there
beforeEach(function() {
browser.get('http://juliemr.github.io/protractor-demo/');
});
And I want to refenrence the elements of an HTML form using this line:
fdescribe('Protractor Demo App', function() {
var goButton = element(by.id('gobutton'));
...and test its result
In protractor we do not put the test files corresponding to a specific .ts file in its folder, so this test searches in the whole project for an id gobutton or it searches for an id gobutton that belongs specifically to the link that I have put on browser.get()??
And my next question is if the page I wanna test requires a login, can I just use browser.get url of this page and test the elements or I should do the part of logging during my test?
And is it normal if my browser closes itself afterexecuting one test, or it should stay opened?
This is a lot of questions ;)
Protractor will search the whole DOM for the element's id. Even if you work with the mentioned *.po.ts files in the component folders, they'll eventually only deliver the elements id represented by a string you then search via element(by.id()).
If a login is required, you'll have to consider it during your test. I doubt that you can bypass the login mechanism, except the login can be switched off on the testing stage and switched on on the production stage.
Yes, it is the standard behavior of Protractor to close the browser after having finished the test. This is because you have your protocol file afterwards.
This is how my protractor.conf.js looks like. The given path target is located on the level of /src.
let HtmlScreenshotReporter = require('protractor-jasmine2-screenshot-reporter');
// always create test-report.html, screen shots only in case of failures
let reporter = new HtmlScreenshotReporter({
dest: 'target/test-screenshots',
filename: 'test-report.html',
reportOnlyFailedSpecs: false,
captureOnlyFailedSpecs: true,
pathBuilder: function (currentSpec, suites, browserCapabilities) {
// will return chrome/your-spec-name.png
return browserCapabilities.get('browserName') + '/' + currentSpec.fullName;
}
});
exports.config = {
...
beforeLaunch: function () {
return new Promise(function (resolve) {
reporter.beforeLaunch(resolve);
});
}

Passing variables between "pipes" in Gulp

I'm trying to write a gulp tasks that takes some user input via the gulp-prompt plugin. But I'm having trouble passing that input along to other eg:
gulp.task('userinput', function(){
var myVar = 'MONKEY';
gulp.src('./templates/_component.*')
.pipe(prompt.prompt([
{
type: 'input',
name: 'userInput',
message: 'Say something'
}
], function(res){
myVar = res.userInput;
}))
.pipe(prompt.confirm('You said ' + myVar));
});
Assuming I enter hello at the prompt, I was expecting the confirmation to say You said Hello, but it says You said MONKEY.
Is this possible with Gulp?
The issue here is that you are creating the second prompt ('You said ' + myVar) before the first prompt has been executed:
Set myVar to 'MONKEY'
Create streams
Create src stream, which is asynchronous
Create first prompt, and add it to the src stream
Create second prompt using current value of myVar, and add it to the first prompt stream
Only now are the streams executed processed
Load sources
Run first prompt, set the myVar
Run the second prompt using previously generated message
The only solution if you want to keep it all as a single stream is to use the variable within something that allows for a closure (function). Some plugins already accept a closure as an argument, but most don't.
One solution to wrap a stream in a closure that would work here is gulp-tap, which isn't designed for this scenario specifically, but should work. it looks like this:
var tap = require('gulp-tap');
//...
gulp.task('userinput', function(){
var myVar = 'MONKEY';
gulp.src('./templates/_component.*')
.pipe(prompt.prompt([
{
type: 'input',
name: 'userInput',
message: 'Say something'
}
], function(res){
myVar = res.userInput;
}))
.pipe(tap(function(file, t) {
// format is t.through(stream-function, [arguments...])
return t.through(prompt.confirm, ['You said ' + myVar]);
});
});
Because this is wrapped in a closure, and evaluated for each file, it will pick up the current value for the variable. However, because it works on each file, you'll see the prompt once for each file processed.
An better solution would be to separate your task into multiple, dependent tasks. That would look something like this:
var myVar = 'MONKEY';
gulp.task('userinput1', function(){
return gulp.src('./templates/_component.*', {read: false})
.pipe(prompt.prompt([
{
type: 'input',
name: 'userInput',
message: 'Say something'
}
], function(res){
myVar = res.userInput;
}));
});
gulp.task('userinput', ['userinput1'], function() {
return gulp.src('./templates/_component.*')
.pipe(prompt.confirm('You said ' + myVar));
});
Now the first task (userinput1) will run and complete before the second one is processed (userinput2), so the variable will be set correctly.
NOTE: Make sure you return the stream from your tasks, otherwise they are processed synchronously, and your variable won't get set.
Finally, it might make more sense to forgo the gulp-prompt task altogether, because it doesn't really have much to do with the stream. You'd probably be better off using straight Node JavaScript within your task to gather the user's input (preferably in a synchronous manner), then processing your files in a gulp-stream after that.

Using window.setTimeout() and window.setInterval() in this situation

This might be a newb question but....
Recently, I have been using window.setTimeout which makes a recursive call to the parent function, which makes an ajax poll to the server.
function connectToVM(portal) {
//making the ajax call here....
if(response.responseText !== "")
{
windowInterval = window.setTimeout(function() {
connectToVM(portal)
}
, 4000);
}
}
windowInterval is my global var here.
if(!checkIfChartExists()) {
window.clearInterval(windowInterval);
}
Now, instead of making use of variables here, I know that I can simple pass the function to clearTimeout, but that also causes all the other intervals to be stopped :(
The reason why I am doing this is the server does a timeout, only when there is a response.
My scenario is, I have a chart which updates every timeout interval.
AFAIK, when we set the interval, there is a specific value set to the variable(if set to a variable). So when I print my variable(every time when the timeout function is called), I get some int values which are unique.
I have many tabs, and many tabs can have same chart.. which just makes use of the same interval which is triggered earlier.
Now I have just 1 chart.. But I have many charts to show which are of the same type. Say gauge Chart.
I also have to clear the timeout whenever there is no chart present in the current selected tab - which I am doing.
So I am planning to make just 1 function which just makes the call to the server by passing in the required params to this function.
But in order to poll, I am using window.setTimeout thing I mentioned above.
This works for 1 chart.
Now, I try to add 1 more chart, with different set of params to poll the server, I will need to make use of some different setTimeout function, which has a id different than that of the earlier triggered timeout.
I also have to consider that if the 1st chart is already present, the timeout is already triggered and have to keep it running.
So, now I have to trigger the second timeout.
But there is no second timeout here.
I was wondering if there is any alternate approach to this, as I can't really predict how many chart's there will be on runtime.
Question 1 : Can we flood our browser with many timeout's?
Question 2 : How to get the id of that particular timeout, so that I can clearTimeout(id) on it?
Question 3 : Since we can't assign / make variables on the fly, how to set / make such a data structure which can hold such a pointer to the particular chart's index / id.. so that we can easily get hold of it and clear it.
Question 4 : Is this the only way we can poll the server(via AJAX) if we have to poll continually?
Lastly, I recognize this is a very complex issue I have posted in here. But I am sure I will find some useful information about the approach from the forums.
I don't have much experience doing all these stuffs in JS, but any help is appreciated!
Update
Sorry I have to post my code in here.. But I am using Extjs to get my chart portlets. My code for the function connectToVM is this :
function connectToVM(portalId, host, port, user, passwd, db) {
try{
if(Ext.getCmp(portalId))
{
var gaugeChartForTitle = Ext.getCmp(portalId);
if(typeof portalOriginalTitle === 'undefined')
portalOriginalTitle = gaugeChartForTitle.title;
var gaugeChartDiv = document.getElementById(portalId);
Ext.Ajax.request({
url: "/connectToVM?" +
Ext.urlEncode({
host: host,
port: port,
user: user,
passwd: passwd,
db: db
}),
method: 'GET',
success: function (response, options) {
if(response.responseText !== "")
{
gaugeChartDiv.style.background = "";
gaugeChartForTitle.setTitle(portalOriginalTitle);
console.log("Virtual Machine at "+ host +" : BUSY % : "+response.responseText);
virtualMachineStore.loadData(generateVirtualMachineData(response.responseText)); //Setting the data1 value of the store and loading it for display!
windowInterval = window.setTimeout(function() {
connectToVM(portalId, host, port, user, passwd, db)
}
, 4000);
}
else
{
windowInterval = window.setTimeout(function() {
connectToVM(portalId, host, port, user, passwd, db)
}
, 10000); //Retry every 10 seconds to check if the connection is established!
gaugeChartDiv.style.background = "red";
gaugeChartForTitle.setTitle(portalOriginalTitle +" - Connection Failure. Reconnecting!");
}
},
failure: function ( result, request) {
}
});
}
}
catch(err) {
}
}
Now, I trigger my function using this :
function setWindowIntervalForVM(portalId) {
//console.log("isIntervalActivated inside setWindowIntervalForVM() : "+isIntervalActivated);
if(!isIntervalActivated) {
connectToVM(portalId, host, port, user, pwd, db);
}
}
function checkIfWindowIntervalIsActivated(portal) {
if(!isIntervalActivated) {
setWindowIntervalForVM(portal.id);
isIntervalActivated = true;
} else {
window.clearInterval(windowInterval);
windowInterval = null;
isIntervalActivated = false;
}
}
So checkIfWindowIntervalIsActivated() is my parent function call which I call in these scenarios :
1) Whenever the Gauge Chart is newly created.. I Trigger this call and have a boolean isIntervalActivated which if it is false, triggers the server poll.
2) So now if I have the chart already in tab 1(since the user selected it), I now change to tab 2 which does not have it. So I simply set isIntervalActivated to true which stops the poll. This is handled for 1 chart. Now the question here is, if I want to make this function re-usable, say I want to drop one more chart of same type but with different server parameters to poll, how to make use of the same windowInterval variable which has my 1st chart's triggered timeout value. P.S: The value changes for every ajax request it makes. So there'z no 1 single value :(
3) I stop the poll whenever there is no chart of same type present.. in other tab. which makes perfect sense. Now, I am caching all my portlets whenever user drops in a new portlet / on the page load, pulling all the user configured portlets. In such a case, I have to trigger all of the charts' ajax calls.. each polling to its configured destination. Now, I do not know how many charts there will be, as in my function name, I am polling to VM's. So if the user consumes VM1, it switches to VM2 and so on.
So it's absolutely impossible to just create same function for many such similar charts.
So just wanted to check if I can re-use the same timeOut thing, or take a totally different approach to this problem :( :(
I hope it's a bit clear now, if not I can explain my situation more.
Please ask me more questions if required :)
Thanks again!
If I understood correctly and you're trying to support multiple charts updating concurrently, I'd switch from keeping the chart data inside the connectToVM() closure to an explicit array of chart objects and use a single interval to update all charts.
Something like the following (treat it as pseudo-code):
var charts = [
// an array of chart objects, see addChart()
];
function addChart() {
// when you need to add or remove a chart, update the charts object, like this:
charts.push({
update: updateChart,
nextUpdateTime: null, // or Date.now() if you don't care about old browsers.
chartData: {host: ..., port: ..., user: ..., passwd: ..., db: ...,
originalTitle: ..., portalId: ...},
});
restartUpdates();
}
var activeInterval = null;
function restartUpdates() {
if (activeInterval) {
clearInterval(activeInterval);
}
activeInterval = setInterval(updateCharts, 5000);
}
// updates all active charts
function updateCharts() {
var now = new Date().getTime();
for (var i = 0; i < charts.length; i++) {
var chart = charts[i];
if (chart.nextUpdateTime !== null && chart.nextUpdateTime < now) {
chart.nextUpdateTime = null; // chart.update() will re-set this
try {
chart.update(chart);
} catch(e) {
// handle the error
}
}
}
// update a single chart.
// #param |chart| is an item from the |charts| array.
function updateChart(chart) {
// ...same as your connectToVM() using properties from chart.chartData...
Ext.Ajax.request(
// ...
success: function (response, options) {
// ...same as before...
// ...but instead of re-setting the timeout:
// windowInterval = window.setTimeout(function() {
// connectToVM(portalId, host, port, user, passwd, db)
// }
// , 4000);
// ...update the chart's nextUpdateTime:
chart.nextUpdateTime = (new Date().getTime()) + 4000;
}
);
}
initial answer below
Thanks for the detailed question! It feels you're missing something very obvious wrt questions #2/3, but it's hard to tell what specifically without seeing more of your code. Can you post a more complete, yes simple example demonstrating the problem you're trying to solve? Perhaps the function handling changing the active tab in pseudocode would help, like this:
function selectTab(tabID) {
// ...activate tab #tabID in the GUI...
if (tabID == 1) {
// there's chart #1 on tab #1, need to stop any active timeouts and start a new one
connectToVM("chart #1");
} else if (tabID == 2) {
// no charts on tab #2.. need to stop any active timeouts
} else if (tabID == 3) {
// ...
}
}
One thing I don't understand is whether there's always a single chart, that needs updating, at any point of time?
Also, do you know the concepts mentioned in A re-introduction to JavaScript, specifically objects?
As for the questions:
1: yes, too many timeouts should be avoided (thousands a second will probably make the CPU hot and the browser sluggish), although I'd be more worried about the server, which has to handle the requests from multiple clients.
2/3: see above.
4: The Comet page lists a lot of alternatives to basic AJAX polling (server-sent events, long-polling, websockets), but I'd worry about this later.
Yes
var x = window.setTimeout(...); window.clearTimeout(x);
Store it as a data attribute on the active tab, a property on your object, or as a global variable. Many different ways. Example code would have made it easier to answer this.
Based on your comments:
var windowInterval;
function connectToVM(portal) {
if(windowInterval)window.clearTimeout(windowInterval);
windowInterval = window.setTimeout(function() { ... }, 4000);
}

Categories