So I just started work on protractor tests and I'm facing the following problem - my tests fail inconsistently. Sometimes the test may pass and the next time it fails. Reasons to fail is very different, it may because it failed to find an element on a page or element does not have text in it (even if it has).
I'm running on Ubuntu 14.04, the same problem relevant for Chrome Version 71.0.3578.80 and Firefox Version 60.0.2. AngularJS Version 1.7.2 and Protractor Version 5.4.0. I believe the problem is somewhere in my code, so here below I provided an example of an existing code base.
Here is my protractor config
exports.config = {
rootElement: '[ng-app="myapp"]',
framework: 'jasmine',
seleniumAddress: 'http://localhost:4444/wd/hub',
specs: ['./e2e/**/*protractor.js'],
SELENIUM_PROMISE_MANAGER: false,
baseUrl: 'https://localhost/',
allScriptsTimeout: 20000,
jasmineNodeOpts: {
defaultTimeoutInterval: 100000,
},
capabilities: {
browserName: 'firefox',
marionette: true,
acceptInsecureCerts: true,
'moz:firefoxOptions': {
args: ['--headless'],
},
},
}
And here capabilities for chrome browser
capabilities: {
browserName: 'chrome',
chromeOptions: {
args: [ "--headless", "--disable-gpu", "--window-size=1920,1080" ]
}
},
And finally, my test kit that failed a few times
const InsurerViewDriver = require('./insurer-view.driver');
const InsurerRefundDriver = require('./insurer-refund.driver');
const { PageDriver } = require('#utils/page');
const { NotificationsDriver } = require('#utils/service');
const moment = require('moment');
describe(InsurerViewDriver.pageUrl, () => {
beforeAll(async () => {
await InsurerViewDriver.goToPage();
});
it('- should test "Delete" button', async () => {
await InsurerViewDriver.clickDelete();
await NotificationsDriver.toBeShown('success');
await PageDriver.userToBeNavigated('#/setup/insurers');
await InsurerViewDriver.goToPage();
});
describe('Should test Refunds section', () => {
it('- should test refund list content', async () => {
expect(await InsurerRefundDriver.getTitle()).toEqual('REFUNDS');
const refunds = InsurerRefundDriver.getRefunds();
expect(await refunds.count()).toBe(1);
const firstRow = refunds.get(0);
expect(await firstRow.element(by.binding('item.name')).getText()).toEqual('Direct');
expect(await firstRow.element(by.binding('item.amount')).getText()).toEqual('$ 50.00');
expect(await firstRow.element(by.binding('item.number')).getText()).toEqual('');
expect(await firstRow.element(by.binding('item.date')).getText()).toEqual(moment().format('MMMM DD YYYY'));
});
it('- should test add refund action', async () => {
await InsurerRefundDriver.openNewRefundForm();
const NewRefundFormDriver = InsurerRefundDriver.getNewRefundForm();
await NewRefundFormDriver.setPayment(`#555555, ${moment().format('MMMM DD YYYY')} (amount: $2,000, rest: $1,500)`);
await NewRefundFormDriver.setPaymentMethod('Credit Card');
expect(await NewRefundFormDriver.getAmount()).toEqual('0');
await NewRefundFormDriver.setAmount(200.05);
await NewRefundFormDriver.setAuthorization('qwerty');
await NewRefundFormDriver.submit();
await NotificationsDriver.toBeShown('success');
const interactions = InsurerRefundDriver.getRefunds();
expect(await interactions.count()).toBe(2);
expect(await InsurerViewDriver.getInsurerTitleValue('Balance:')).toEqual('Balance: $ 2,200.05');
expect(await InsurerViewDriver.getInsurerTitleValue('Wallet:')).toEqual('Wallet: $ 4,799.95');
});
});
});
And here some functions from driver's, that I'm referencing in the test above
// PageDriver.userToBeNavigated
this.userToBeNavigated = async function(url) {
return await browser.wait(
protractor.ExpectedConditions.urlContains(url),
5000,
`Expectation failed - user to be navigated to "${url}"`
);
};
this.pageUrl = '#/insurer/33';
// InsurerViewDriver.goToPage
this.goToPage = async () => {
await browser.get(this.pageUrl);
};
// InsurerViewDriver.clickDelete()
this.clickDelete = async () => {
await $('[ng-click="$ctrl.removeInsurer()"]').click();
await DialogDriver.toBeShown('Are you sure you want to remove this entry?');
await DialogDriver.confirm();
};
// NotificationsDriver.toBeShown
this.toBeShown = async (type, text) => {
const awaitSeconds = 6;
return await browser.wait(
protractor.ExpectedConditions.presenceOf(
text ? element(by.cssContainingText('.toast-message', text)) : $(`.toast-${type}`)
),
awaitSeconds * 1000,
`${type} notification should be shown within ${awaitSeconds} sec`
);
}
// InsurerRefundDriver.getRefunds()
this.getRefunds = () => $('list-refunds-component').all(by.repeater('item in $data'));
// InsurerViewDriver.getInsurerTitleValue
this.getInsurerTitleValue = async (text) => {
return await element(by.cssContainingText('header-content p', text)).getText();
};
I can't upload the whole code here to give you better understanding because I have a lot of code till this moment, but the code provided above is the exact sample of approach I'm using everywhere, does anyone see a problem in my code? Thanks.
First of all add this block before exporting your config
process.on("unhandledRejection", ({message}) => {
console.log("\x1b[36m%s\x1b[0m", `Unhandled rejection: ${message}`);
});
this essentially colorfully logs to the console if you missed async/await anywhere, and it'll give confidence that you didn't miss anything.
Second, I would install "protractor-console" plugin, to make sure there is no errors/rejections in the browser console (i.e. exclude possibility of issues from your app side) and add to your config
plugins: [{
package: "protractor-console",
logLevels: [ "severe" ]
}]
Then the next problem that I would expect with these signs is incorrect waiting functions. Ideally you have to test them separately as you develop your e2e project, but since it's all written already I'll tell you how I debugged them. Note, this approach won't probably help you if your actions are less than a sec (i.e. you can't notice them). Otherwise follow this chain.
1) I created run configuration in WebStorm, as described in my comment here (find mine) How to debug angular protractor tests in WebStorm
2) Set a breakpoint in the first line of the test I want to debug
3) Then execute your test line by line, using the created run config.
When you start debugging process, webstorm opens up a panel with three sections: frames, console, variables. When the variables section has a message connected to localhost and no variables listed, this means your step is still being executed. Once loading completed you can see all your variables and you can execute next command. So the main principle here is you click Step Over button and watch for variables section. IF VARIABLES APPEAR BEFORE THE APPS LOADING COMPLETED (the waiting method executed, but the app is still loading, which is wrong) then you need to work on this method. By going this way I identified a lot of gaps in my custom waiting methods.
And finally if this doesn't work, please attach stack trace of your errors and ping me
I'm concerned about this code snippet
describe(InsurerViewDriver.pageUrl, () => {
beforeAll(async () => {
await InsurerViewDriver.goToPage();
});
it('- should test "Delete" button', async () => {
await InsurerViewDriver.clickDelete();
await NotificationsDriver.toBeShown('success');
await PageDriver.userToBeNavigated('#/setup/insurers');
await InsurerViewDriver.goToPage(); // WHY IS THIS HERE?
});
describe('Should test Refunds section', () => {
it('- should test refund list content', async () => {
// DOESN'T THIS NEED SOME SETUP?
expect(await InsurerRefundDriver.getTitle()).toEqual('REFUNDS');
// <truncated>
You should not depend on the first it clause to set up the suite below it. You didn't post the code for InsurerRefundDriver.getTitle() but if that code does not send the browser to the correct URL and then wait for the page to finish loading, it is a problem. You should probably have await InsurerViewDriver.goToPage(); in a beforeEach clause.
After some time research I found what was the problem. The cause was the way I'm navigate through the app.
this.goToPage = async () => {
await browser.get(this.pageUrl);
};
Turns out, that browser.get method is being resolved when url changed, but now when angularjs done compile. I used the same approach in every test kit, that's why my tests were failing inconsistently, sometimes page was not fully loaded before test start.
So here is an approach that did the trick
this.goToPage = async () => {
await browser.get(this.pageUrl);
await browser.wait(EC.presenceOf(`some important element`), 5000, 'Element did not appear after route change');
};
You should ensure that page done all the compiling job before moving on.
It seems this could be due to asynchronous javascript.
browser.ignoreSynchronization = true; has a global effect for all your tests. you may have to set it back to false, so protractor waits for angular to be finished rendering the page. e.g. in or before your second beforeEach function
Related
I'm using a combination of protractor, selenium, jasmine and report portal for automated testing. The tests all run fine but when it comes to the last test it always hangs and it eventually fails, looking into it, it seems to come down to what is used in the afterAll function in my protractor.conf.js file.
jasmineEnv.afterAll(async (done) => {
await agent.getPromiseFinishAllItems(agent.tempLaunchId);
done();
});
Now the function it calls comes from the node modules reportportal-agent.js :
getPromiseFinishAllItems(launchTempId){
return this.client.getPromiseFinishAllItems(launchTempId)
}
I've noticed that written above this function is the comment
/*
* This method is used for frameworks as Jasmine and other. There is problems when
* it doesn't wait for promise resolve and stop the process. So it better to call
* this method at the spec's function as #afterAll() and manually resolve this promise.
*
* #return a promise
*/
I'm wondering is there a solution for how to properly resolve this promise? I have tried looking online but not found anything of any significance
EDIT -
protractor.conf.js
const ReportportalAgent = require('agent-js-jasmine');
const { SpecReporter } = require('jasmine-spec-reporter');
const suiteSettings = require('./suiteSettings');
const settings = require('./settings');
const logger = require('./tests/helpers/logger');
// This is a temporary solution because we have issues if instances=nodes. For now balance between nodes and instances that instances < 3
const nodeReduceCount = 5;
let isBrowserOpen = false;
exports.config = {
framework: 'jasmine',
seleniumAddress: settings.seleniumHubHost,
capabilities: {
'shardTestFiles': true,
'maxInstances': Math.max(settings.countOfStreams - nodeReduceCount, 1),
'browserName': settings.browser,
'loggingPrefs': {
performance: 'INFO',
},
'moz:firefoxOptions': getFirefoxOptions(),
'goog:chromeOptions': getChromeOptions(),
},
suites: [
suiteSettings.suite,
],
jasmineNodeOpts: {
defaultTimeoutInterval: settings.jasmineTimeout,
isVerbose: false,
includeStackTrace: true,
realtimeFailure: false,
},
onPrepare: async () => {
const jasmineEnv = jasmine.getEnv();
const capabilities = await browser.getCapabilities();
const config = await browser.getProcessedConfig();
global.consoleReporter = [];
console.log(capabilities);
if (!settings.useReportPortal) {
registerReporter(jasmineEnv);
} else {
registerConsoleReporter(jasmineEnv);
}
jasmineEnv.beforeEach(async () => {
jasmine.DEFAULT_TIMEOUT_INTERVAL = settings.jasmineTimeout;
const criticalCheck = String(config.specs);
if (criticalCheck.includes('critical')) {
process.env.RUN_WITH_SERVICE_WORKER = 'true';
} else {
process.env.RUN_WITH_SERVICE_WORKER = '';
}
if (isBrowserOpen) {
browser.restart();
}
await browser.driver.manage().window().setSize(settings.viewPort.width, settings.viewPort.height);
isBrowserOpen = true;
await logger.logMessage(`Opening Link ${settings.newPlanUrl()}`);
await browser.waitForAngularEnabled(false);
await browser.get(settings.newPlanUrl());
});
},
};
function registerReporter(jasmineEnv) {
jasmineEnv.addReporter(new SpecReporter({
spec: {
displayStacktrace: true,
},
}));
const config = {
id: browser.params.id,
...settings.reportPortal,
};
const agent = new ReportportalAgent(config);
const reporter = agent.getJasmineReporter();
jasmineEnv.afterAll(async (done) => {
await agent.getPromiseFinishAllItems(agent.tempLaunchId);
done();
});
global.reporter = reporter;
jasmineEnv.addReporter(reporter);
return agent;
}
function registerConsoleReporter(jasmineEnv) {
jasmineEnv.afterEach(async () => {
await browser.takeScreenshot().then((png) => {
const testSuite = settings.currentSuite;
const date = new Date();
const currentDay = `(Time-${date.getHours()}-${date.getMinutes()}-${date.getSeconds()})`;
logger.writeScreenShot(png, `screenshots/${currentDay}_${testSuite}.png`);
});
});
jasmineEnv.afterAll(async () => {
await console.log('\n---------------------------------');
await console.log('Test Results');
await global.consoleReporter.forEach((testResult) => {
console.log(testResult);
});
await console.log('---------------------------------');
});
}
function getFirefoxOptions() {
const options = {};
if (settings.headless) {
options.args = ['--headless'];
}
return options;
}
function getChromeOptions() {
const options = {
args: [
'--disable-gpu',
'--no-sandbox',
'--disable-extensions',
'--disable-dev-shm-usage',
'--disable-infobars',
],
};
if (settings.headless) {
options.args.push('--headless');
options.perfLoggingPrefs = {
enableNetwork: true,
};
}
return options;
}
Edit:
So the error I had before was due to adding:
agent.getExitPromise.
But I've noticed after removing that and running my test suite again to see if jenkins would log anything useful when it comes to the test that gets interrupted, it says:
13:43:26 Cancelling nested steps due to timeout
13:43:26 ...Sending interrupt signal to process
13:43:31 npm ERR! path /app
13:43:31 npm ERR! command failed
13:43:31 npm ERR! signal SIGTERM
13:43:31 npm ERR! command sh -c node generateTests.js && node start.js
does anyone have any idea what the cause of this could be?
So after comments with Sergey which helped massively. I realised I was looking in the wrong area and tried to think more of what is happening. Looking at the test runs I noticed that the last test was cut off so I figured I must be closing the connection somewhere before the last test has a chance to finish.
What I've done, which seems to work is do:
jasmineEnv.afterAll(async (done) => {
await agent.getPromiseFinishAllItems(agent.tempLaunchId);
await agent.getExitPromise();
done();
});
Getting the exit promise appears to have solved the issue
Okay so I finally found out what the issue was, we had files that were in the incorrect directory. That was it, once these were moved to the correct place, the issue stopped happening and the last test no longer gets hung up. So for anyone else that comes across this issue, this is something to check
I am using browser.saveDocumentScreenshot('folder/filename.png') I am getting error as browser.saveDocumentScreenshot is not a function
If you want support of all browser and devices use https://github.com/wswebcreation/wdio-image-comparison-service
Alternately, with WebdriverIO 6 (maybe with 5 as well) it's possible to use Puppeteer commands.
With Puppeteer, it's possible to take full-page screenshots, see https://github.com/puppeteer/puppeteer/blob/v5.3.1/docs/api.md#pagescreenshotoptions
// Mocha example
describe('Screenshot', () => {
// replace with beforeAll in Jasmine!
before(() => {
// add command to reuse easily everywhere in the project
// https://webdriver.io/docs/customcommands.html
browser.addCommand('takeFullPageScreenshot', function (options = {}) {
// Puppeteer commands should be wrapped with browser.call
// because they return Promises
// https://webdriver.io/docs/api/browser/call.html
return browser.call(async () => {
// https://webdriver.io/docs/api/browser/getPuppeteer.html
const puppeteer = await browser.getPuppeteer()
// now we interact with Puppeteer API
// https://github.com/puppeteer/puppeteer/blob/v5.3.1/docs/api.md#browserpages
const pages = await puppeteer.pages()
// https://github.com/puppeteer/puppeteer/blob/v5.3.1/docs/api.md#pagescreenshotoptions
return pages[0].screenshot({ ...options, fullPage: true })
})
})
})
it('should take full page screenshot', () => {
browser.url('https://stackoverflow.com/questions/64242220/how-to-take-full-web-page-screenshot-using-webdriverio-command')
// somehow wait for page to load
expect($('.user-details')).toBeVisible()
// take screenshot and save to file
browser.takeFullPageScreenshot({ path: './screenshot.png' })
// take screenshot but don't save to file
const screenshot = browser.takeFullPageScreenshot()
})
})
I'm making a simple API with Express and I'm trying to add tests with Jest but when I try to run the tests it displays the next error:
ReferenceError: You are trying to `import` a file after the Jest environment has been torn down.
at BufferList.Readable (node_modules/readable-stream/lib/_stream_readable.js:179:22)
at BufferList.Duplex (node_modules/readable-stream/lib/_stream_duplex.js:67:12)
at new BufferList (node_modules/bl/bl.js:33:16)
at new MessageStream (node_modules/mongodb/lib/cmap/message_stream.js:35:21)
at new Connection (node_modules/mongodb/lib/cmap/connection.js:52:28)
/home/jonathangomz/Documents/Node/Express/Devotionals/node_modules/readable-stream/lib/_stream_readable.js:111
var isDuplex = stream instanceof Duplex;
^
TypeError: Right-hand side of 'instanceof' is not callable
I'm not sure to trust the result if right after jest break (or something like that):
My test is:
const app = require("../app");
const request = require("supertest");
describe("Testing root router", () => {
test("Should test that true === true", async () => {
jest.useFakeTimers();
const response = await request(app).get("/");
expect(response.status).toBe(200);
});
});
My jest configuration on package.json:
"jest": {
"testEnvironment": "node",
"coveragePathIgnorePatterns": [
"/node_modules/"
]
}
Notes:
I read about jest.useFakeTimers() but It's not working and I'm not sure if I'm using in the wrong way. I also tried adding it to the beforeEach method but nothing.
In my case, I had to add the package to transformIgnorePatterns in the jest config.
Add jest.useFakeTimers('modern') before the asynchronous call. Add jest.runAllTimers() after the asynchronous call. This will fast-forward timers for you.
const app = require("../app")
const request = require("supertest")
describe("Testing root router", () => {
test("Should test that true === true", async () => {
//Before asynchronous call
jest.useFakeTimers("modern")
const response = await request(app).get("/")
//After asynchronous call
jest.runAllTimers()
expect(response.status).toBe(200)
})
})
Try adding --testTimeout=10000 flag when calling jest, it works for me.
Information based on Testing NodeJs/Express API with Jest and Supertest
--testTimeout flag - This increases the default timeout of Jest which is 5000ms. This is important since the test runner needs to refresh the database before running the test
By adding jest.useFakeTimers() just after all your import.
What about making your test async ?
const app = require("../app");
const request = require("supertest");
describe("Testing root router",async () => {
test("Should test that true === true", async () => {
jest.useFakeTimers();
const response = await request(app).get("/");
expect(response.status).toBe(200);
});
});
I am trying to mock request.js to mock a file upload but it alway hangs. I have increased the waiting time but it will always timeout.
The code I am trying to mock follows the request.post.form.append structure. If I put the form data in manually it always fails so for functionality reasons, the functioning code cannot be modified.
I setup my require.js mock to suit only one use case which is why it is written the way it is.
My require.js mock is as follows:
const request = {
post: jest.fn(() => ({
form: jest.fn(() => ({
append: jest.fn(test => {
console.log(test)
return Promise.resolve({
status: 200,
body: { test }
})
})
}))
}))
};
module.exports = request;
My jest code is as follows:
it('should upload a file', async () => {
mocks.post.mockReturnValueOnce({
status: 200,
body: { test: 'response' }
});
const res = await dataSource.uploadFile(
{ name: 'projects', id: '123' },
null,
{
filename: 'test',
encoding: '7bit',
mimetype: 'text/plain',
createReadStream: jest.fn
},
'12345'
);
console.log('RES', res); // never gets here
expect(mocks.post).toBeCalledWith('testName/123/files', {
filename: 'test',
encoding: '7bit',
mimetype: 'text/plain',
createReadStream: jest.fn
});
expect(res).toMatchSnapshot();
});
The applicable test return is as follows:
console.log folder/folderName/src/files/__mocks__/request.js:5
file
console.log folder/folderName/src/files/__mocks__/request.js:5
file
FAIL folder/folderName/src/files/__tests__/upload.spec.js (12.673s)
uploadFile
✕ should upload a file (5006ms)
● FilesAPI › uploadFile › should upload a file
Timeout - Async callback was not invoked within the 5000ms timeout specified by jest.setTimeout.Error:
I noticed that it logs test within my request mock twice, so I am guessing that it is waiting for something else to properly return. What am I missing?
The code it is running works great for actual uploads, I am just struggling to write a unit test for it.
So the timeout you specify here needs to be shorter than the default timeout.
The default timeout interval is 5 seconds if this method is not called. You can specify the timeout inside the test by adding
jest.setTimeout(10000); // 10 seconds
On official documentation explained,
A good place to do this is in the setupTestFrameworkScriptFile. https://jestjs.io/docs/en/jest-object#jestsettimeouttimeout
In some cases, you only need to do setup once, and if You already have jest.config.js file.
// jest.config.js
module.exports = {
// setup test framework after jest loaded
setupFilesAfterEnv: [
'./tests/setupTestFrameworkScriptFile.js' // The path to a module that runs some code to configure or set up the testing framework before each test
],
};
Now we can do One-Time Setup for setTimout every test case you have,
// ./tests/setupTestFrameworkScriptFile.js file
jest.setTimeout(10000) // we set timeout interval is 10 seconds for every test case
beforeAll(async () => {
await initializeDatabase(); // this is just an example
initializeOtherDepedency();
}, 3000) // assume we only need 3 seconds for initialization before runnng test case.
And the last one is just your test case file
// uploadfile.test.js
it('should upload a file', async () => {
})
See also this doc:
https://jestjs.io/docs/en/configuration#setupfilesafterenv-array
https://jestjs.io/docs/en/jest-object#jestsettimeouttimeout
https://jestjs.io/docs/en/api#beforeallfn-timeout
I'm using Jest Puppeteer and I have a situation where I'd like to run my login test (which sets cookie/localStorage for the authentication) first and run the others after, however, I know that Jest doesn't work this way - as it searches the local filesystem and runs tests based on the patterns in the filename, and the order in which they run is different.
I'm not entirely sure I'm going about this the correct way as I'm relying on a test to set the the authentication session for the other tests.
Is it possible to do the above, or do I need to rethink my approach?
This is not a timely answer. I had similar problem. I am able to run tests in order by nesting describe blocks as shown below. my tests are in separate files that I require here.
const puppeteer = require('puppeteer');
const login = require('./login');
const upload = require('./upload');
let browser;
beforeAll(async () => {
browser = await puppeteer.launch({
headless: false,
devtools: true,
slowMo: 50
});
})
describe('test suite', () => {
describe('login', () => {
test('url is correct', async () => {
const url = await login();
expect(url).toBe('https://uat2.onplanapp.com/#/');
}, 25000);
});
describe('upload', () => {
test('file upload ok', async () => {
url = await upload();
console.log('page.url');
expect(url).toBe('https://uat2.onplanapp.com/#/moduleLibrary');
//expect(url).toBe('https://uat2.onplanapp.com/#/uploadFile');
}, 10000);
});
afterAll(async done => {
console.log('GOT TO AFTER ALL');
browser.close()
done();
});
});