my question concerns end to end testing scenario involving a responsive web app. I have written my test scenario for the pages to test with different test cases depending on the screen resolution. I am using array variables to store the different selectors linked to the same element, for example:
it('should display the log in page', function () {
gVar.signInButton = element(by.css(gVar.signInButtonSelector[gVar.SelectedMode]));
gVar.signInButton.click().then(function(){
expect(element(by.css(gVar.titleLoginPageSelector[gVar.SelectedMode])).getText()).toEqual(gVar.titleLoginPage[i]);
});
Here I am trying to select the login page title to test it. Depending on the resolution, only the selector is different, and I stored them in arrays...
In my conf.js I have a parameter variable that I use in the command line to set the configuration I want to use:
exports.config = {
//...
params:{
resolutionConfig:'default'
},
//...
}
the run command can go:
protractor conf.js --params.resolutionConfig=Classic or
protractor conf.js --params.resolutionConfig=Mobile or
protractor conf.js --params.resolutionConfig=Tablet ...
(Then I have a matching table to associate this parameter to the above integer value: gVar.SelectedMode)
What I would like to do now, is to set different resolutions values for my browser, a different one for each value of resolutionConfig parameter I am testing. So far, I know how to set that resolution with hardcoded values:
exports.config = {
//...
capabilities: {
browserName: 'chrome',
chromeOptions: {
args: ['--window-size=100,100'] // THIS!
}
//...
}
I have heard of "multicapabilities" to run parallel tests, but it is not exactly what I want… is it possible to have the resolution parameters in variable and add a logic to it? Something like:
if(resolutionConfig) is "mobile" then: ['--window-size=xx,xx'];
if(resolutionConfig) is "tablet" then: ['--window-size=yy,yy'];
To answer your initial question you can use browser.driver.manage().window().setSize() to manually set the resolution of the browser you want.
I'm not sure exactly what is in that array you mentioned but I would probably approach your problem in the a different way to you:
Setting params:
params: {
resolutionConfig: 'default', //could be default, mobile or tablet
default: { //set whatever res you need
resWidth: 1700,
resHeight: 1500,
titleLocator: '//div[#title="defaultTitle]"'
},
mobile: {
resWidth: 800,
resHeight: 1000,
titleLocator: '//div[#title="mobileTitle]"'
},
tablet: {
resWidth: 1200,
resHeight: 1200,
titleLocator: '//div[#title="tabletTitle]"'
}
},
onPrepare: {
//See below for explanation
let requiredHeight = browser.params[browser.params.resolutionConfig].resHeight;
let requiredWidth = browser.params[browser.params.resolutionConfig].resWidth;
browser.driver.manage().window().setSize(requiredHeight, requiredWidth)
}
You could launch protractor in the same way you are intending
protractor conf.js --params.resolutionConfig=Classic
protractor conf.js --params.resolutionConfig=Mobile
protractor conf.js --params.resolutionConfig=Tablet
You would locator you dynamic title element like
element(by.xpath(browser.params[browser.params.resolutionConfig].titleLocator))
//which is equivalent to
element(by.xpath(browser.params['default'].titleLocator))
//and as we declared above
browser.params['default'].titleLocator = "//div[#title="defaultTitle]"
//so therefore we are actually doing
element(by.xpath("//div[#title="defaultTitle]"))
Related
I am facing a scenario where the element tag name and attribute is changing from env to env, but the text content alone is unique.
Therefore I am not able to define any value for Selector('could not define anything here').
How could I write a path to locate the element ?
I don't see a direct solution for this, but a workaround that could solve your issue. You could have an object that holds environments specific data for you and which helps you for specific cases as the one that you seem to be confronted with. In this object, you could also store environment specific Selectors. This could, written in TypeScript, look as follows:
import { Selector } from "testcafe";
interface EnvironmentData {
envName: string;
myVariableSelector: Selector;
}
// Set up a list that contains environment specific data objects
const CONFIGS: EnvironmentData[] = [
{
envName: "MyEnv1",
myVariableSelector: Selector("my css selector 1").withText("my text 1")
},
{
envName: "MyEnv2",
myVariableSelector: Selector("my css selector 2").withText("my text 2")
},
{
envName: "MyEnv3",
myVariableSelector: Selector("my css selector 3").withText("my text 3")
}
]
// Assuming that you're CI for instance sets a environment variable ENVIRONMENT_NAME to
// any of the specific environments MyEnv1, MyEnv2 or MyEnv3
function getConfigForEnvironment(envDataSets: EnvironmentData[]): EnvironmentData {
const envData = envDataSets.find((c) => c.envName === process.env.ENVIRONMENT_NAME);
if (envData === undefined) {
console.error(`No suitable data for environment '${process.env.ENVIRONMENT_NAME}' found!`);
process.exit(1);
}
return envData;
}
// Determine the right object before the tests start
const envData = getConfigForEnvironment(CONFIGS);
fixture`My awesome tests`.page("myTestUrl");
test("My test", async (t) => {
// Make use of the object that holds the data for the desired environment
await t.expect(envData.myVariableSelector.exists).ok("Should be fine for any environment!");
});
I did it in a simpler way using the or operator (,)
i,e I wrote the selector with Selector('div,span').withText('Value')
The attribute div and span are changing with environment so I used , to pass both and it worked.
I tried to supply test data to nightwatch but i don't know how. How to supply any dynamic test data to Nightwatch testing?
I don't want to hardcoded the value into the code. I want to supply it from file.
EDIT:
.setValue('selector', 'DEBBIE A/P EKU')
Since you mentioned it in one of your comments you want to read the values from a file, I recommend you doing it via pseudo-JSON (actually .js). Also a solution I applied in my company.
I have multiple json files which contain certain test data that I didn't want to have in the code. The basic structure of those looks like this:
module.exports = {
WHATEVER_IDENTIFIER_I_WANT: 'Some shiny value'
}
My Page Object contains a line like this:
const STATIC = require('path/to/static/file')
…
.setValue('selector', STATIC.WHATEVER_IDENTIFIER_I_WANT)
And yea, it is not highly sophisticated but it fulfils the purpose.
If you don't want to use module.exports and .js you can still use some node methods to load and parse a JSON. E.g.
fs.readFileSync / fs.readFile (to load the JSON file)
const file = fs.readFileSync('path/to/file')
JSON.parse() (to retrieve a JavaScript Object)
const STATIC = JSON.parse(file)
Hope this is useful for you :)
I've been through the same issue. At the moment my set up is like this:
Raw data are in the excel sheet. I use node.js to convert excel sheet into json file. Then use json data in nightwatch.
Here is the code to read the json file in nightwatch:
module.exports = {
tags: ['loginpage'],
// if not regular size logout button is not visible
'Login' : function (client) {
var credentials;
try{
credentials = require('./path/to/inputJsonData.json');
} catch(err) {
console.log(err);
console.log ('Couldn\'t load the inputJsonData file. Please ensure that ' +
'you have the inputJsonData.json in subfolder ./path/to ' +
'in the same folder as the tests');
process.exit();
}
Here is the code that use data from it:
client
.url(credentials.url)
.waitForElementVisible('body', 1000)
.assert.title('Sign In Home Page')
.login(credentials.username,credentials.password)
// some more steps here
.logout()
.end();
}
};
inputJsonData.json data
{
"url": "http://path/to/my/input/credentials/file",
"username": "yourUserName",
"password": "yourPassword"
}
My problem/question:
How to find the count of elements read into the json object from a file when the file has following format?:
[
{
....
},
{
....
},
.....
{
....
}
]
My failed attempt to get the number of elements: JSON.parse(company).count where company is another json read file like credentials in above code.
Answer: use standard javascript array property length company.length
TheBayOr answered the question concisely regarding the use of files. Just to add that if you don't literally mean a non 'code' file but simply using a different location to store the values then the most common approach is using globals.
You can place an array of values in either your nightwatch.json...
"test_settings" : {
"default" : {
"selenium_port" : 4444,
"selenium_host" : "localhost",
"silent": true,
"globals" : {
"VARIABLE_1" : "i'm a variable",
"VARIABLE_2" : "i'm a variable too"
},
"desiredCapabilities": {
"browserName": "chrome",
"javascriptEnabled": true,
"acceptSslCerts": true
}
},
"other_environment" : {
"globals" : {
"VARIABLE_1" : "i'm a different variable",
"VARIABLE_2" : "i'm a different variable too"
You can use them in tests with something like....
.url(browser.globals.VARIABLE_1)
Notice in the above you can have sets of globals under different environments. This has the advantage of meaning you can have multiple sets and use the one you want by running nightwatch -e 'my desired environment'.
Similarly this can be achieved by putting your array of data in a globals file e.g. globals.js and referencing it in your 'globals.path'.
If you want to get really into it you can even store your variables in global.js then use the 'fs' library to write the values to a file, then have your tests read from there. I'd recommend a new question if thats what you intend.
Hopefully that adds something :)
In my case I just created a function which read variables , data ,etc
more details here: https://stackoverflow.com/a/64616920/3957754
So every once in a while, it's honestly hard to if there's a true cause or if this is kind of random, my protractor E2E tests "pause" in execution and in order to "resume" them I have to hit enter in console.
Am I missing a configuration property maybe, or is this just kind of a thing?
If you'd like to see my current config file please just ask, it's a little lengthy so I would like to hold off unless needed. Wasn't sure if this was a common thing or if there would be an 'off the top of your head' solution.
Running latest version at the time of this post of webdrivers, protractor, and jasmine.
UPDATE (As per comment below):
onPrepare: function () {
global.driver = browser.driver;
browser.ignoreSynchronization = true;
browser.getCapabilities().then(function (caps) {
if (caps.get('browserName').toLowerCase() == 'chrome' || caps.get('browserName').toLowerCase() == 'firefox' || caps.get('browserName').toLowerCase() == 'microsoftedge') {
console.log('Chrome, FF, or Edge: skipping browser maximize');
}
else {
console.log('FF or IE, using maximize function');
console.log(caps.get('browserName'));
var width = 800;
var height = 600;
browser.driver.manage().window().setSize(width, height);
browser.driver.manage().window().maximize();
}
});
jasmine.getEnv().addReporter(report);
},
I had to use a hacky way to maximize the browser since each browser reacted differently. So that's the conditional stuff in the on prepare.
At time of the pause:
Immediately hitting enter quickly runs through the entire spec:
When testing mobile form factors, Chrome screenshots just the visible window. I'm fine with that being the standard and expected behaviour. However, I additionally want to capture the full scrolled height of the page so I can inspect the rendering of the entire page.
I thought the simplest solution was to set the chrome window height to be a sufficiently large value, and job done. However, the Chrome window height seems bound by my physical screen height, ie. I set it to 5,000 with browser.manage().window().setSize(375,5000);, but it only resizes to a height of 1,200.
I already know [According to the WebDriver specification][1], the [takeScreenshot() function][2] is not supposed to capture the entire page, but should make a screenshot of the visible area only.
OP EDIT: I went with the final option below which I've labelled "Working solution!!"
Below are the different grouped by type strategies to solve the problem.
Scroll, take screenshot, append
Quoting the author of the screenshot system at the CrossBrowserTesting.com:
As the author of the screenshot system for CrossBrowserTesting.com, I can tell you that the only way we've been able to get full page screenshots consistently and reliably across browsers is to scroll, capture, and append images.
Here is a sample working implementation of scrolling and taking visible area screenshots using cnn.com as an example target. Using scrollHeight, clientHeight and scrollTop to determine where we are on a vertical scroll position and how much more to scroll down. Since we are dealing with promises in a loop, we have to make a recursive function with a "we are at the bottom" base condition:
var fs = require('fs'),
Utils = {
screenShotDirectory: '',
writeScreenShot: function (data, filename) {
var stream = fs.createWriteStream(this.screenShotDirectory + filename);
stream.write(new Buffer(data, 'base64'));
stream.end();
},
getSizes: function () {
return browser.executeScript("return {scrollHeight: document.body.scrollHeight, clientHeight: document.body.clientHeight, scrollTop: document.body.scrollTop};");
},
scrollToBottom: function (height, index) {
var self = this;
self.getSizes().then(function (data) {
// continue only if we are not at the bottom of the page
if (data.scrollTop + data.clientHeight < data.scrollHeight) {
browser.executeScript("window.scrollTo(0, arguments[0]);", height).then(function () {
browser.takeScreenshot().then(function (png) {
self.writeScreenShot(png, "test" + index + ".png");
});
});
self.scrollToBottom(height + data.clientHeight, index + 1);
}
});
}
};
describe("Scrolling and saving screenshots", function () {
beforeEach(function () {
browser.ignoreSynchronization = true;
browser.get("http://www.cnn.com/");
});
it("should capture an entire page", function () {
Utils.getSizes().then(function (data) {
Utils.scrollToBottom(data.clientHeight * 2, 1);
});
});
});
It would produce multiple test<index>.png images that you can then glue together.
To concatenate images in a "single column image", you may, for instance, use the GraphicsMagick Image Processing System through the gm nodejs module. The .montage() method with the concatenate option in the 1x mode would be helpful. Sample code:
var gm = require('gm');
Utils.getSizes().then(function (data) {
var index = Utils.scrollToBottom(data.clientHeight * 2, 1);
var op = gm();
for (var i = 1; i <= index; i++) {
op = op.in("test" + i + ".png");
}
op = op.montage().mode("concatenate").tile("1x");
op.write('output.png', function (err) {
if (err) console.log(err);
});
});
Change the Browser
In Chrome, you would always get only the visible area on the resulting screenshot, here is the relevant chromedriver issue with a lot of information about the issue and multiple workarounds:
ChromeDriver2 take screenshot is not full page
Somewhat surprisingly, it should though work in Firefox - switch to it if possible:
Chrome screenshots that take up the entire screen are not like Firefox's. Firefox will capture the entire screen, even parts of it that are not currently viewable. Chrome will not!
Tweak the Screen Size
Another option would be to use services like BrowserStack or SauceLabs to start your tests on a specific platform in a specific browser and, using a specific large enough resolution. Protractor supports Sauce Labs and BrowserStack out-of-the-box.
Example configuration for BrowserStack:
exports.config: {
browserstackUser: "user",
browserstackKey: "key",
capabilities: {
'browserstack.local': true,
'browserstack.debug': true,
browserName: "Chrome",
os: "Windows",
os_version: "8",
resolution: "2048x1536"
},
}
Then, maximize the browser window (inside onPrepare(), for instance):
browser.driver.manage().window().maximize();
And make a screenshot.
Working solution!!!
Another option could be to run tests in a Virtual Display. I you would follow this blogpost and use Xvfb, when you will run the Xvfb server, you may specify the resolution:
/usr/bin/Xvfb :99 -ac -screen 0 2048x6000x24 &
Also see related information on this topic here:
AngularJS Headless End to End Testing With Protractor and Selenium
What is a good headless browser to run with protractor?
You may also use the docker-selenium solution which allows to configure the screen size.
I want to know how to use a varaible globally in phantomjs so that it can be used in the page.evaluate function also.
I have gone through some previous answers but but able to understand well
JSON-serializable arguments can be passed to page.evaluate.
Here is a very basic the following example using this technique :
page.open('http://stackoverflow.com/', function(status) {
var title = page.evaluate(function(s) {
return document.querySelector(s).innerText;
}, 'title');
console.log(title);
phantom.exit();
});