I am trying to write functional tests using Intern framework which uses Leadfoot library to implement WebDriver API as I am using Selenium Grid setup to test my webapp on remote browsers. The app is already up and running.
The functional test I use is the following one:
define(function (require) {
var tdd = require('intern!tdd');
var assert = require('intern/chai!assert');
var url = '...';
var server = '...';
tdd.suite("Init suite",function(){
tdd.before(function () {
// executes before suite starts
});
tdd.after(function () {
// executes after suite ends
});
tdd.beforeEach(function () {
// executes before each test
});
tdd.afterEach(function () {
// executes after each test
});
tdd.test('Checking servers', function () {
var that = this.remote;
return that
.setFindTimeout(15000)
.get(url)
.findById('linkservers')
.click()
.then(function(){
console.log("Click resolved");
})
.end()
.findDisplayedByClassName('server-name-span-text')
.getVisibleText()
.then(function(texts){
var t=0;
var tlen = 0;
if (Array.isArray(texts))
{
tlen = texts.length
for (t=0;t<tlen;t++)
console.log("server["+t+"]: "+texts[t]);
assert.strictEqual(texts[0],server,"server is: "+texts[0]);
} else {
assert.strictEqual(texts,server,"server is: "+texts[0]);
}
})
.end();
});
});
});
To start my test I use the intern-runner: ./node_modules/.bin/intern-runner config=tests/intern.cfg -reporters=Runner
The nature of my issue is that findDisplayedByClassName('server-name-span-text') fails to find the class name, even though I am using findDisplayed call which waits for the element to show. The element doesn't show because for some reason the click() call doesn't trigger the event properly (guess). What I mean is that the click handler in the webapp code doesn't execute thus the element with class name server-name-span-text is not created. What I get in the end is failing findDisplayedByClassName due to timeout.
As I am running this locally I can actually observe and confirm that the click events doesn't happen. So the expected changes in my webapp do not occur.
The log from intern is the following:
Listening on 0.0.0.0:9000
Tunnel started
‣ Created session firefox on LINUX (2d94ea44-dea8-411a-8ee5-a3d7b749cc7b)
Click resolved
× firefox on LINUX - Init suite - Checking servers (15.588s)
NoSuchElement: An element could not be located on the page using the given search parameters.
at <node_modules/intern/node_modules/leadfoot/lib/findDisplayed.js:37:21>
at <node_modules/intern/node_modules/dojo/Promise.ts:393:15>
at run <node_modules/intern/node_modules/dojo/Promise.ts:237:7>
at <node_modules/intern/node_modules/dojo/nextTick.ts:44:3>
at doNTCallback0 <node.js:417:9>
at process._tickCallback <node.js:346:13>
at Command.findDisplayed <node_modules/intern/node_modules/leadfoot/Command.js:23:10>
at Command.prototype.(anonymous function) [as findDisplayedByClassName] <node_modules/intern/node_modules/leadfoot/lib/strategies.js:28:16>
at Test.test <tests/functional/loadgui.js:36:6>
at <node_modules/intern/lib/Test.js:211:24>
at <node_modules/intern/node_modules/dojo/Promise.ts:393:15>
at runCallbacks <node_modules/intern/node_modules/dojo/Promise.ts:11:11>
at <node_modules/intern/node_modules/dojo/Promise.ts:317:4>
at run <node_modules/intern/node_modules/dojo/Promise.ts:237:7>
at <node_modules/intern/node_modules/dojo/nextTick.ts:44:3>
at doNTCallback0 <node.js:417:9>
No unit test coverage for firefox on LINUX
firefox on LINUX: 1/1 tests failed
TOTAL: tested 1 platforms, 1/1 tests failed
The log of the selenium node doesn't show problems, at least on INFO level:
15:33:59.735 INFO [13] org.openqa.selenium.remote.server.DriverServlet - Executing: [find element: By.id: linkservers])
15:33:59.742 INFO [13] org.openqa.selenium.remote.server.DriverServlet - Done: [find element: By.id: linkservers]
15:33:59.750 INFO [13] org.openqa.selenium.remote.server.DriverServlet - Executing: [click: 9 [[FirefoxDriver: firefox on LINUX (84846fb0-1467-45a9-bbfe-a6333ddef515)] -> id: linkservers]])
15:33:59.811 INFO [13] org.openqa.selenium.remote.server.DriverServlet - Done: [click: 9 [[FirefoxDriver: firefox on LINUX (84846fb0-1467-45a9-bbfe-a6333ddef515)] -> id: linkservers]]
15:33:59.824 INFO [13] org.openqa.selenium.remote.server.DriverServlet - Executing: [find elements: By.className: server-name-span-text])
15:34:14.844 INFO [13] org.openqa.selenium.remote.server.DriverServlet - Done: [find elements: By.className: server-name-span-text]
15:34:14.957 INFO [192] org.openqa.selenium.remote.server.DriverServlet - Executing: [execute script: return (function getCoverageData(coverageVariable) {
var coverageData = (function () { return this; })()[coverageVariable];
return coverageData && JSON.stringify(coverageData);
}).apply(this, arguments);, [__internCoverage]])
15:34:14.970 INFO [192] org.openqa.selenium.remote.server.DriverServlet - Done: [execute script: return (function getCoverageData(coverageVariable) {
var coverageData = (function () { return this; })()[coverageVariable];
return coverageData && JSON.stringify(coverageData);
}).apply(this, arguments);, [__internCoverage]]
15:34:14.980 INFO [192] org.openqa.selenium.remote.server.DriverServlet - Executing: [delete session: 2d94ea44-dea8-411a-8ee5-a3d7b749cc7b])
15:34:15.047 INFO [192] org.openqa.selenium.remote.server.DriverServlet - Done: [delete session: 2d94ea44-dea8-411a-8ee5-a3d7b749cc7b]
How do I solve this issue?
Note:
Intern version is '3.0.6' and leadfoot is '1.6.4'.
I tried running same functional test using Selenium IDE and it runs successfully.
Couple of tips to fix this issue:
Before using findDisplayedByClassName make sure you can find the element with findByClassName
If it cannot find the element then make sure .findById('linkservers') works properly. To check you can try adding .sleep() before and after to see if it actually works and provides an element for you with the 'server-name-span-text' class.
Because the click() event was fired by the browser before the handler was attached.
The problem is between get() call and click() call:
...
.get(url)
.findById('linkservers')
.click()
...
What happened is that the browser fired the click event before the handler was attached. The handler is implemented in the app under test. This meant that at the time of firing click event there was no handler therefore the server-name-span-text element was not created hence the timeout of the findDisplayedByClassName operation. Note that I also tried findByClassName operation and in this cause I got exception NoSuchElement. The quick workaround, although very nasty, was to add a sleep() after the get() call.
Solution 1:
define(function (require) {
var tdd = require('intern!tdd');
var assert = require('intern/chai!assert');
var url = '...';
var server = '...';
tdd.suite("Init suite",function(){
tdd.before(function () {
// executes before suite starts
});
tdd.after(function () {
// executes after suite ends
});
tdd.beforeEach(function () {
// executes before each test
});
tdd.afterEach(function () {
// executes after each test
});
tdd.test('Checking servers', function () {
var that = this.remote;
return that
.setFindTimeout(15000)
.get(url)
.sleep(1000) //#Fix-1
.findById('linkservers')
.click()
.then(function(){
console.log("Click resolved");
})
.end()
.findDisplayedByClassName('server-name-span-text')
.getVisibleText()
.then(function(texts){
var t=0;
var tlen = 0;
if (Array.isArray(texts))
{
tlen = texts.length
for (t=0;t<tlen;t++)
console.log("server["+t+"]: "+texts[t]);
assert.strictEqual(texts[0],server,"server is: "+texts[0]);
} else {
assert.strictEqual(texts,server,"server is: "+texts[0]);
}
})
.end();
});
});
Update:
As explained earlier the explicit wait is a nasty solution for this kind of problem. The next solution is based on the execution of an asynchronous script at application side, executeAsync() call. This script returns a promise which has to be resolved by the application in order to end the wait on the asynchronous call. Of course the application has to cooperate with this a implemented deferred object so that it determined at which point the application has loaded. Otherwise such an approach won't work. The elegant way is presented as follows.
Solution 2:
define(function (require) {
var tdd = require('intern!tdd');
var assert = require('intern/chai!assert');
var url = '...';
var server = '...';
tdd.suite("Init suite",function(){
tdd.before(function () {
// executes before suite starts
});
tdd.after(function () {
// executes after suite ends
});
tdd.beforeEach(function () {
// executes before each test
});
tdd.afterEach(function () {
// executes after each test
});
tdd.test('Checking servers', function () {
var that = this.remote;
return that
.setFindTimeout(15000)
.get(url)
.executeAsync(function(done){
//application side.
application.loaded
.then(function(){
//at this moment the application has loaded
//so we resolve our intern side promise
done();
})
})
.findById('linkservers')
.click()
.then(function(){
console.log("Click resolved");
})
.end()
.findDisplayedByClassName('server-name-span-text')
.getVisibleText()
.then(function(texts){
var t=0;
var tlen = 0;
if (Array.isArray(texts))
{
tlen = texts.length
for (t=0;t<tlen;t++)
console.log("server["+t+"]: "+texts[t]);
assert.strictEqual(texts[0],server,"server is: "+texts[0]);
} else {
assert.strictEqual(texts,server,"server is: "+texts[0]);
}
})
.end();
});
});
If you wish you could also return some parameter to the callback which you resolve on the application side. For further information about this and more you can check out the docs.
Related
I am mocking navigator functions for simple clipboard functionality. Here is the relevant code:
// FUNCTION
/**
* Adds a click event to the button which will save a string to the navigator clipboard. Checks for
* clipboard permissions before copying.
*/
function loader(): void {
async function copyUrl(): Promise<void> {
const permission = await navigator.permissions.query({ name: "clipboard-write" });
if (permission.state == "granted" || permission.state == "prompt" ) {
await navigator.clipboard.writeText("the url");
} else {
console.error('Permission not supported');
}
}
const button = document.querySelector('button') as HTMLElement;
button.addEventListener('click', async () => {
await copyUrl();
});
}
// TEST
it('works', () => {
// mock navigator functions
Object.assign(navigator, {
permissions: {
query: jest.fn(async () => ({ state: "granted" }))
},
clipboard: {
writeText: jest.fn(async () => {})
}
});
// initialize DOM
document.body.innerHTML = '<button></button>';
loader(); // adds the event listener
// click the button!
const button = document.querySelector('button') as HTMLElement;
button.click();
expect(navigator.permissions.query).toHaveBeenCalledTimes(1);
expect(navigator.clipboard.writeText).toHaveBeenCalledWith('the url');
});
The test fails on expect(navigator.clipboard.writeText).toHaveBeenCalledWith('the url') with:
Expected: "the url" Number of calls: 0
Defeats the purpose of permissions, yes, but for the sake of debugging:
Try adding a clipboard call before permissions call like so?
// FUNCTION
// ...
async function copyUrl(): Promise<void> {
// add this
await navigator.clipboard.writeText('the url');
// keep the rest still
const permission = await navigator.permissions.query({ name: "clipboard-write" });
// ...
}
This fails on the first assertion now, expect(navigator.permissions.query).toHaveBeenCalledTimes(1) with
Expected number of calls: 1 Received number of calls: 0
With the addition above, I also changed the assertions to be:
expect(navigator.clipboard.writeText).toHaveBeenCalledWith('the url');
expect(navigator.clipboard.writeText).toHaveBeenCalledTimes(2);
expect(navigator.permissions.query).toHaveBeenCalledTimes(1);
... which failed on the second assertion because it expected 2 calls but only received 1.
I have been testing in a VSCode devcontainer and tried out the extension firsttris.vscode-jest-runner to debug the test. With breakpoints in the loader function, I'm able to see that every single line executes perfectly with my mockup but still fails at the end of debug.
I even changed the mock navigator.permissions.query function to return { state: 'denied' } instead. Both running and debugging, it did not satisfy the permission check and gave an error to the console as expected but the test still failed at expect(navigator.permissions.query).toHaveBeenCalledTimes(1) (with the added writeText call before it).
It seems to me that after the first call of a mock function, the others just don't work.
Am I missing something? Send help pls lol
EDITS
Using jest.spyOn as in this answer has the same issues.
Using an async test with an expect.assertions(n) assertion still produces the exact same issue.
When running my cypress test it skips the test and rund th after hooks straight away. It then logs this error msg and finishes:
Uncaught CypressError: Cannot call "cy.get()" outside a running test.
This usually happens when you accidentally write commands outside an it(...) test.
If that is the case, just move these commands inside an it(...) test.
Check your test file for errors.
https://on.cypress.io/cannot-execute-commands-outside-test
This error originated from your test code, not from Cypress.
When Cypress detects uncaught errors originating from your test code it will automatically fail the current test.
Cypress could not associate this error to any specific test.
We dynamically generated a new test to display this failure.
Any ideas what could be causing this strange behaviour. It should run the test first then run the after hooks. I am using cypress-cucumber-preprocessor and running a feature file.
Below is my index.js from support folder (for hooks):
import '#applitools/eyes-cypress/commands';
import './commands';
const xhrData = [];
//Hooks
before(function () {
cy.fixture('./TestData').as('TestData');
// runs once before all tests in the block
cy.server({
// Here we hanDle all requests passing through Cypress' server
onResponse: (response) => {
if (Cypress.env('record')) {
const url = response.url;
const method = response.method;
const data = response.response.body;
// We push a new entry into the xhrData array
xhrData.push({ url, method, data });
}
}
});
// This tells Cypress to hook into any GET request
if (Cypress.env('record')) {
cy.route({
method: 'GET',
url: '*',
});
cy.route({
method: 'POST',
url: '*',
});
}
// Load stubbed data from local JSON file
if (!Cypress.env('record')) {
cy.fixture('fixture')
.then((data) => {
for (let i = 0, length = data.length; i < length; i++) {
cy.route(data[i].method, data[i].url, data[i].data);
}
});
}
});
beforeEach(function () {
cy.visit(Cypress.config().baseUrl);
// runs before each test in the block
cy.eyesOpen({
appName: 'CafeTownsend',
testName: 'Complete Happy Path',
browser: {
"viewportWidth": 1000,
"viewportHeight": 660
},
});
});
after(function () {
// runs once after all tests in the block
if (Cypress.env('record')) {
const path = './cypress/fixtures/fixture.json';
cy.writeFile(path, xhrData);
cy.log("Wrote "+ xhrData.length +" XHR responses to local file "+path);
}
});
afterEach(function () {
// runs after each test in the block
cy.eyesClose();
cy.reload();
});
I found the solution to this issue.
I was using page object model and in my constructors I did a cy.get to see that we could locate an anchor element.
I instantiate several pages in the step defs, and these seemed to be called before the test is run, hence the error.
For now I am putting all cy.gets outside of the constructor.
perhaps there is a bettwe way to solve this issue.
browser.sleep(4000);
var discount_type = element(by.id("discount_type"));
var discount_value= element(by.id("discount_value"));
var grandTotal = element(by.id("grand-total"));
var subtotal = element(by.id("subtotal"));
var delivery_fee = element(by.id("delivery_fee"));
var last = protractor.promise.defer();
console.log("hallo");
subtotal.getText().then(function(sub) {
console.log("#############");
console.log(sub);
delivery_fee.getText().then(function(fee) {
console.log(fee);
var calc = parseFloat(sub) - parseFloat(fee) - parseFloat(config.promocodes.referral.discount);
console.log(calc);
last.fulfill(calc);
});
});
last.then(function(calc) {
console.log("final wait");
expect(grandTotal.getText()).toBe("$" + calc);
});
I need to calculate a value before I can be sure that my test ran ok.
In my tests, I always get to see "hallo" in the console.
Then I get a
> hallo
A Jasmine spec timed out. Resetting the WebDriver Control Flow.
> F
>
> Failures: 1) Refer a user and apply referral code should assign
> referral discount to user Message:
> Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL. Stack:
> Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.
> at Timer.listOnTimeout (timers.js:92:15)
It seems the code never enters the subtotal.getText() promise! Why is that???
You never return from the promise resolution function. And, you don't have to have a deferred:
var last = subtotal.getText().then(function(sub) {
return delivery_fee.getText().then(function(fee) {
return parseFloat(sub) - parseFloat(fee) - parseFloat(config.promocodes.referral.discount);
});
});
last.then(function(calc) {
expect(grandTotal.getText()).toBe("$" + calc);
});
I'm trying to write some Vows-based tests for my run-of-the-mill Express app.
Here's the test source:
var vows = require('vows');
var assert = require('assert');
var startApp = require('./lib/start-app.js');
var suite = vows.describe('tournaments');
suite.addBatch({
"When we setup the app": {
topic: function() {
return startApp();
},
teardown: function(topic) {
if (topic && topic.close) {
topic.close();
}
},
"it works": function(topic) {
assert.isObject(topic);
}
}
});
suite.run();
And here's start-app.js:
var app = require('../../app.js');
function start() {
var server = app.listen(56971, 'localhost');
return server;
}
module.exports = start;
app.js exports a regular Express.js app, created with express().
The problem is that whenever I run the test, topic.close() doesn't work in the teardown function, and the test hangs forever after succeeding. I've tried searching the web and adding lots and lots of console.logs, all to no avail.
I'm on the Windows x64 build of Node.js 4.2.0, and I'm using assert#1.3.0 and vows#0.8.1.
Any idea how I can make my test stop hanging?
Here's what I did to solve the issue in a project I was contributing: a final batch just to close the server.
suite.addBatch({
'terminate server': {
topic: function() {
server.close(this.callback); // this is a regular node require(`http`) server, reused in several batches
},
'should be listening': function() {
/* This test is necessary to ensure the topic execution.
* A topic without tests will be not executed */
assert.isTrue(true);
}
}
}).export(module);
Before adding this test, suite would never end executing. You can check the results at https://travis-ci.org/fmalk/node-static/builds/90381188
I'm trying to write a web-spider using phantom.js. However I got a lot of error message which I don't know why. My code is shown below:
Use Nightmare.js:
var Nightmare = require('nightmare');
new Nightmare()
.goto('http://www.amazon.com/Aroma-Housewares-ASP-137-3-Quart-Super/dp/B00024JQ3Q')
.evaluate( function(){
return document.getElementById('priceblock_ourprice').textContent;
}, function( numAnchors ){
console.log(numAnchors);
})
.run();
Use phantom.js:
var page = require('webpage').create();
console.log('The default user agent is ' + page.settings.userAgent);
page.open('http://www.amazon.com/Aroma-Housewares-ASP-137-3-Quart-Super/dp/B00024JQ3Q', function(status) {
if (status !== 'success') {
console.log('Unable to access network');
} else {
var ua = page.evaluate(function() {
return document.getElementById('priceblock_ourprice').textContent;
});
console.log(ua);
}
phantom.exit();
});
The error message are the same:
phantom stdout: ReferenceError: Can't find variable: ue.......
........
........
phantom stdout: TypeError: 'null' is not an object (evaluating 'old_error_handler.apply')
.......
$35.99
I can got the result in the last line but there are so many errors shown above, what's the reason of that?
This will happen alot. The current PhantomJS (1.9.7) is based on an old QtWebkit fork comparable to Chrome 13 or Safari 5. The page in question quite possibly uses something in its JavaScript that PhantomJS doesn't understand. Most of the time this is nothing to worry about because the site will still function as intended.