Leadfoot + sauce: mapping a collection of elements using getAttr fails in mobile only - selenium

My use case varies for this, but in general i'm trying to collect a bunch of elements and then apply _.map() to each. The problem is that this series of .getAttribute() calls can cause a test that works locally to fail against a remote server like sauce/android.
One example: collecting all <div class='article'><a href='articles/{id}'> on a page and then getting the hrefs. it might look something like this, and this approach will work until i test on a mobile (android) sauce environment. then I get a timeout.
Is it possible this is an issue related to my android environment capabilities? To piling up so many requests? I've tried scaling my test down from using 75 articles to only 45 and i've upped the timeout to 60s and still the mobile test fails. locally with chromedriver is fine, chrome desktop + sauce is fine.
Not my actual test but an approximation of the code i'm talking about:
/// ... return this.remote
.findAllByTagName('div.article a')
.then(function (articles) {
var promises = articles.map(function(article) {
return article.getAttribute('href');
});
Promise.all(promises)
.then(function (hrefs) {
uniques = _.uniq(hrefs);
assert(hrefs.length === uniques.length);
});
});

Since you're seeing a timeout error, I'd suggest continuing to increase the test timeout until the test passes. The mobile testing environments on Sauce are both slower to initialize and slower to operate than the desktop environments, so it's quite possible that a test with many requests is simply very slow.
One way to speed things up would be to use an execute block to gather the references, like:
.then(function (articles) {
return this.parent.execute(function (articles) {
return articles.map(function (node) {
return node.getAttribute('href');
});
}, [ articles ]);
})
In the above snippet, the articles element array is passed as an argument to the execute block. The remote WebDriver will deserialize the element references into actual DOM elements that can be operated on in the execute code. This is significantly more efficient than using individual getAttribute requests for each element since only a single request will be made to the remote browser.

Related

TestCafe unable to use testController (t) outside of test run (e.g. as a conditional to skip a test)

I'm trying to check which browser we're running tests on, and then skip a test/fixture based on the result (as mentioned in this TestCafe Issue).
import { t } from 'testcafe';
fixture `test`
.page('https://testcafe.devexpress.com')
if (t.browser.name.includes('Chrome')) {
test('is Chrome?', async () => {
console.log(t.browser.name);
await t.expect(t.browser.name.includes('Chrome').ok();
});
} else {
test.skip('is Chrome?')
};
Results in...
ERROR Cannot prepare tests due to an error.
Cannot implicitly resolve the test run in the context of which the test controller action should be executed. Use test function's 't' argument instead.
Is there any way I can call the testObject (t) outside of the test?
I don't have a solution to exactly your question. But I think it's better to do it slightly differently, so the outcome will be the same, but the means to achieve it will differ a bit. Let me explain.
Wrapping test cases in if statements is, in my opinion, not a good idea. It mostly clutters test files so you don't only see test or fixture at the left side, but also if statements that make you stop when reading such files. It presents more complexity when you just want to scan a test file quickly from top to bottom.
The solution could be you introduce meta data to your test cases (could work well with fixtures as well).
test
.meta({
author: 'pavelsaman',
creationDate: '16/12/2020',
browser: 'chrome'
})
('Test for Chrome', async t => {
// test steps
});
Then you can execute only tests for Chrome like so:
$ testcafe --test-meta browser=chrome chrome
That's very much the same as what you wanted to achieve with the condition, but the code is a bit more readable.
In case you want to execute tests for both chrome and firefox, you can execute more commands:
$ testcafe --test-meta browser=chrome chrome
$ testcafe --test-meta browser=firefox firefox
or:
$ testcafe --test-meta browser=chrome chrome && testcafe --test-meta browser=firefox firefox
If your tests are in a pipeline, it would probably be done in two steps.
The better solution, as mentioned in one of the comments in this question is to use the runner object in run your tests instead of the command line. Instead of passing the browser(s) as a CLI argument, you would pass it as an optional argument to a top-level script.
You would then read the browser variable from either the script parameter or the .testcaferc.json file.
You would need to tag all tests/fixtures with the browser(s) they apply to using meta data.
You then use the Runner.filter method to add a delegate that returns true if the browser in the meta data is equal to the browser variable in the top level script
var runner = testcafe.createRunner();
var browser = process.env.npm_package_config_browser || require("./testcaferc.json").browser;
var runner.filter((testName, fixtureName, fixturePath, testMeta, fixtureMeta) => {
return fixtureMeta.browser === browser || testMeta.browser === browser ;
}

Is there a way to get the number of registered nodes by Selenium Grid other than with http://localhost:4444/grid/console

In order to run multiple tests in parallel, I would like to know how many nodes are already running at some point.
I have looked into many posts on this subject, but all of them include using http://localhost:4444/grid/console : I don't want to check this page.
I was thinking about sending a message to the hub each time a node is created. so the hub increments its count. But I can't find a way to do that.
Does anyone have a different solution ? Maybe using seleniumgrid parameters or command, I'm surprised this number is not stored somewhere?
The selenium grid has an API. You can do this:
http://hub_ip_address:4444/grid/api/hub
and parse the json it returns for "slotCounts"
{
"success":true,
"capabilityMatcher":"org.openqa.grid.internal.utils.DefaultCapabilityMatcher",
"newSessionWaitTimeout":-1,
"throwOnCapabilityNotPresent":true,
"registry":"org.openqa.grid.internal.DefaultGridRegistry",
"cleanUpCycle":5000,
"custom":{
},
"host":"XX.XXX.XX.XXX",
"maxSession":10,
"servlets":[
"ConsoleServlet"
],
"withoutServlets":[
],
"browserTimeout":0,
"debug":false,
"port":4444,
"role":"hub",
"timeout":300000,
"enablePassThrough":true,
"newSessionRequestCount":0,
"slotCounts":{
"free":9,
"total":12
}
}

Running multiple browser instances in the same test spec

If I have a single spec that is using page object model, how do I run multiple browser instance for that same spec?
For example I have spec:
it('should run multi browser', function() {
browser.get('http://example.com/searchPage');
var b2 = browser.forkNewDriverInstance();
b2.get('http://example.com/searchPage');
var b3 = browser.forkNewDriverInstance();
b3.get('http://example.com/searchPage');
SearchPage.searchButton.click();
b2.SearchPage.searchButton.click(); //fails here
b3.SearchPage.searchButton.click();
});
How do I reuse vars declared in the SearchPage page object for the other browser instances?
This is a really interesting question that is not covered in Using Multiple Browsers in the Same Test or in the interaction_spec.js.
The problem with page objects is that page object fields are usually defined with a globally available element or browser which in your case would always point to the first browser instance. But you basically need to call element() using a specific browser:
b2.element(by.id('searchInput'));
instead of just:
element(by.id('searchInput'));
FYI, element is just a shortcut for browser.element.
I am really not sure whether this is a reliable solution and would actually work, but you can redefine global element this way. Think about it as switching the search context to different browser instances:
SearchPage.searchButton.click();
global.element = b2.element;
SearchPage.searchButton.click();
global.element = b3.element;
SearchPage.searchButton.click();
global.element = browser.element;

Access window object / browser scope from protractor

I'm running tests with protractor, but it seems impossible to access the JS 'window' object. I even tried adding a tag in my html file that would contain something like
var a = window.location;
and then try expect(a) but I couldn't make it work, I always get undefined references...
How should I process to access variables that are in the browser scope ?
Assuming you are using a recent version of Protractor, let's say >= 1.1.0, hopefully >= 1.3.1
Attempting to access Browser side JS code directly from Protractor won't work because Protractor runs in NodeJS and every Browser side code is executed through Selenium JsonWireProtocol.
Without further detail, a working example:
browser.get('https://angularjs.org/');
One-liner promise that, as of today, resolves to '1.3.0-rc.3'
browser.executeScript('return window.angular.version.full;');
You can use it directly in an expect statement given Protractor's expect resolves promises for you:
expect(browser.executeScript('return window.angular.version.full;')).
toEqual('1.3.0-rc.3');
Longer example passing a function instead of a string plus without expect resolving the promise for you. i.e. for more control and for doing some fancy thing with the result.
browser.driver.executeScript(function() {
return window.angular.version.full;
}).then(function(result) {
console.log('NodeJS-side console log result: ' + result);
//=> NodeJS-side console log result: 1.3.0-rc.3
});

Possible dijit.Tree Cookie issue (SaveStateCookie)

So our app is set up like the standard left frame with the tree, right frame has the main content (loaded from clicking the tree).
Our web app inconsistently displays a blank page in the main frame in Firefox. By inconsistent I mean everyday for a few, rarely for others, never for most. Once we get this, going to any other page through our tree results in a blank page. We found that deleting the "aTreeSaveStateCookie" restores normal operation. "aTree" is the name of our Div. I found "SaveStateCookie" strings in dijit/Tree.js.
This also happens in IE, except I would get a browser error page which I can't recall right now. I would then delete the only cookie I could find for our app (not sure how to do the Firefox steps in IE)
Any ideas on why this would happen?
Thanks
Dojo 1.3 through http://ajax.googleapis.com/ajax/libs/dojo/1.3/dojo/dojo.xd.js
Firefox 3.1x
IE 8
Windows XP
In my case, I don't recall ever changing browser settings around Private Data.
Please check to see if the response code is 413 (413 = request entity too large), usually this happens when the cookie(s) used to store the tree(s) expansion state (aTreeSaveStateCookie) exceed(s) the maximum request size for your server
You could try increasing the maximum request size (follow instructions for your specific web app server) or at least display a meaningful error message like "please clear your browser cache" when the 413 error code is encountered
If the persist property is set to a truthy value, dijit.Tree is persisting its state to remember which nodes were expanded, and expand them after a page reload. If you need to persist the tree state in presence of a very large data structure, I recommend overriding Tree to use localStorage instead of dojo.cookie.
This is Dojo v. 1.9, but similar changes can be done to the non-AMD version 1.3
_saveExpandedNodes: function(){
if(this.persist && this.cookieName){
var ary = [];
for(var id in this._openedNodes){
ary.push(id);
}
// Was:
// cookie(this.cookieName, ary.join(","), {expires: 365});
localStorage.setItem(this.cookieName, ary.join(","));
}
},
And:
_initState: function(){
// summary:
// Load in which nodes should be opened automatically
this._openedNodes = {};
if(this.persist && this.cookieName){
// Was:
// var oreo = cookie(this.cookieName);
var oreo = localStorage.getItem(this.cookieName);
if(oreo){
array.forEach(oreo.split(','), function(item){
this._openedNodes[item] = true;
}, this);
}
}
},