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:
Related
I'm relatively new to using WebDriverJS and trying out a simple script to begin with.
However, am facing a lot of issues and did not find any resources that were helpful.
Scenario being Tested:
Launch browser
Navigate to google.com
Capture Title of the page
Add a wait statement (driver.sleep)
Enter some text in Search box
Here is the code snippet:
var webdriver = require('selenium-webdriver'),
By = webdriver.By,
until = webdriver.until;
var driver = new webdriver.Builder().forBrowser('chrome').build();
driver.get("http://www.google.com");
driver.getTitle().then(function(title) {
console.log("Title is: " + title);
});
console.log('Before sleep');
driver.sleep(10000);
console.log('After sleep');
driver.findElement(By.name('q')).sendKeys("Hello");
Here is the output:
Before sleep
After sleep
DevTools listening on ws://127.0.0.1:52449/devtools/browser/aea4d9eb-20ee-4f10-b53f-c2003c751796
Title is:
As can be seen, it is a very straight forward scenario. However none of it is working as expected.
Below are my queries/ observations:
console.log for Before/ After sleep is executed as the very first statement even before browser is launched whereas it is not clearly the intention.
Title is returned an empty String. No value printed.
driver.sleep() never waited for the specified duration. All commands got immediately executed. How to make driver hard wait when driver.sleep is not working?
Tried adding implicit wait, however that resulted in error as well.
What are the best practices to be followed?
I did not find very many helpful webdriver javascript resources and it is not clear how to proceed.
Any guidance is appreciated. TIA.!
I referred the documentation as well and similar steps are given there. Not sure if there is some issue from my end. https://github.com/SeleniumHQ/selenium/wiki/WebDriverJs
Assuming that you example is written in JavaScript and runs on Node.js, it looks to be as if you would miss all the waiting for asynchronous functions to have finished processing. Please be aware that most functions return a promise and you must wait for the promise to be resolved.
Consider the following example code:
const {Builder, By, Key, until} = require('selenium-webdriver');
(async function example() {
let driver = await new Builder().forBrowser('firefox').build();
try {
await driver.get('http://www.google.com/ncr');
await driver.findElement(By.name('q')).sendKeys('webdriver', Key.RETURN);
await driver.wait(until.titleIs('webdriver - Google Search'), 1000);
} finally {
await driver.quit();
}
})();
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]"))
I am using the following code, which uses the imagesLoaded package with a callback to tell me when an element with a particular csspath has finished loading all of its images:
imagesLoadedScript = "imagesLoaded( '#{csspath}', { background: true }, function(message) { console.log('PHANTOM CLIENT REPORTING: #{csspath} Images Loaded'); return message; })"
imagesLoadedScript = imagesLoadedScript.strip.gsub(/\s+/,' ')
#session.evaluate_script(imagesLoadedScript)
The timing of the console.log statement, on inspection of PhantomJS logs with debug on, indicates that Capybara/Poltergiest is not waiting for the images to load, as expected, before it moves on to the next statement. I also cannot return a true (or false) value from inside the callback as I would like.
Capybara responds with
{"command_id":"678f1e2e-4820-4631-8cd6-413ce6f4b66f","response":"(cyclic structure)"}
Anyone have any ideas on how to return a value from inside a callback in a function executed via evaluate_script?
Many thanks.
TLDR; You can't
evaluate_script doesn't support asynchronous functions - you must return the result you want from the function passed in. One way to do what you want would be to execute the imagesLoaded script and have the callback set a global variable, and then loop on an evaluate_script fetching the result of the global until it's what you want - A very basic implementation would be something like
imagesLoadedScript = "window.allImagesLoaded = false; imagesLoaded( '#{csspath}', { background: true }, function() { window.my_images_loaded = true })"
#session.execute_script(imagesLoadedScript)
while !#session.evaluate_script('window.allImagesLoaded')
sleep 0.05
end
Obviously this could be made more flexible with a timeout ability, etc.
A second option would to write a custom capybara selector type for images with a loaded filter, although with the need for background image checking it would become pretty complicated and probably too slow to be useful.
Just in case someone finds this later.
I did roughly what Thomas Walpole suggested in his answer, in a more roundabout fashion, but taking advantage of Poltergeist's inherent waiting capabilities;
#to check that the target has loaded its images, run images loaded
#after a small timeout to allow the page to get the images
#append a marker div to the dom if the images have successfully loaded
imagesLoadedScript = "var item = document.querySelector('#{csspath}');
window.scroll(0, item.offsetTop);
function imagesDone(path, fn) {
imagesLoaded( path, function(instance) {
console.log('PHANTOM CLIENT REPORTING: ' + path + ' Images Loaded');
fn(true);
})
}
setTimeout(function(){
imagesDone('#{csspath}', function(done) {
var markerDiv = document.createElement('div');
markerDiv.id = 'ImagesLoadedMarker';
document.getElementsByTagName('html')[0].appendChild(markerDiv);
});
}, 1000)"
#then we strip the new lines and spaces that we added to make it readable
imagesLoadedScript = imagesLoadedScript.strip.gsub(/\s+/,' ')
#now we just execute the script as we do not need a return value
#session.execute_script(imagesLoadedScript)
#then we check for the marker, using capybara's inbuilt waiting time
if #session.has_xpath? "//*[#id ='ImagesLoadedMarker']"
Rails.logger.debug "!!!!! PhantomClient: Images Loaded Reporting: #{csspath} Images Loaded: Check Time #{Time.now} !!!!!"
#session.save_screenshot(file_path, :selector => csspath)
else
Rails.logger.debug "!!!!! PhantomClient: Images Loaded Reporting: #{csspath} Images NOT Loaded: Check Time #{Time.now} !!!!!"
#session.save_screenshot(file_path, :selector => csspath)
end
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'm trying to verify that an account was created successfully, but after clicking the submit button, I need to wait until the next page has loaded and verify that the user ended up at the correct URL.
I'm using pollUntil to check the URL client side, but that results in Detected a page unload event; script execution does not work across page loads. in Safari at least. I can add a sleep, but I was wondering if there is a better way.
Questions:
How can you poll on something like this.remote.getCurrentUrl()? Basically I want to do something like this.remote.waitForCurrentUrlToEqual(...), but I'm also curious how to poll on anything from Selenium commands vs using pollUntil which executes code in the remote browser.
I'm checking to see if the user ended up at a protected URL after logging in here. Is there a better way to check this besides polling?
Best practices: do I need to make an assertion with Chai or is it even possible when I'm polling and waiting for stuff as my test? For example, in this case, I'm just trying to poll to make sure we ended up at the right URL within 30 seconds and I don't have an explicit assertion. I'm just assuming the test will fail, but it won't say why. If the best practice is to make an assertion here, how would I do it here or any time I'm using wait?
Here's an example of my code:
'create new account': function() {
return this.remote
// Hidden: populate all account details
.findByClassName('nextButton')
.click()
.end()
.then(pollUntil('return location.pathname === "/protected-page" ? true : null', [], 30000));
}
The pollUntil helper works by running an asynchronous script in the browser to check a condition, so it's not going to work across page loads (because the script disappears when a page loads). One way to poll the current remote URL would be to write a poller that would run as part of your functional test, something like (untested):
function pollUrl(remote, targetUrl, timeout) {
return function () {
var dfd = new Deferred();
var endTime = Number(new Date()) + timeout;
(function poll() {
remote.getCurrentUrl().then(function (url) {
if (url === targetUrl) {
dfd.resolve();
}
else if (Number(new Date()) < endTime) {
setTimeout(poll, 500);
}
else {
var error = new Error('timed out; final url is ' + url);
dfd.reject(error);
}
});
})();
return dfd.promise;
}
}
You could call it as:
.then(pollUrl(this.remote, '/protected-page', 30000))
When you're using something like pollUntil, there's no need (or place) to make an assertion. However, with your own polling function you could have it reject its promise with an informative error.