Can I add cypress screenshot in mocha awesome html report - mocha.js

Im able to generate mocha awesome html report everything fine, but I want to add screenshots in html report im taking cypress screenshot but want to add in html report. Is there any way I can add them?

Consider mochawesome addContext. This will inject about any value into the report. I'm fairly sure that the html report that is generated will display the image given its path. This may require some further reading on addContext.
const { expect } = require('chai');
const addContext = require('mochawesome/addContext');
describe('Cypress tests.', function () {
before(function () {
// Perform Cypress things.
// Take screenshots.
};
after(function () {
const title = 'Screenshot of thing.';
const value = 'path/to/screenshot.png';
addContext(this, {
title,
value,
};
it('Foo should equal batz.', function () {
// Expect some things.
};
};

I use the following code to add screenshots automatically to any (and only) failed tests:
import addContext from 'mochawesome/addContext'
Cypress.on('test:after:run', (test, runnable) => {
if (test.state === 'failed') {
addContext({ test }, {title: 'Screenshot', value: `<path/to/screenshots/folder>/${Cypress.spec.name}/${runnable.parent.title.replace(':', '')} -- ${test.title} (failed).png`})
addContext({ test }, {title: 'Video', value: `<path/to/videos/folder>/${Cypress.spec.name}.mp4`})
}
});
Put it in support/index.js as this file is loaded before any test.
Make sure to update the <path/to/.../folder> above to wherever you save screenshots/videos. The path is relative to the generated html report.

Related

Pre-scan web page for dynamic tests

Looking for a definitive answer to the question posed by #JeffTanner here about generating dynamic tests. From that question and the Cypress samples, it's clear that we need to know the number of tests required before generating them.
Problem
We have a web page containing a table of Healthcare analytic data that is refreshed many times during the day. Each refresh the team must check the data, and to divvy up the work we run each row as a separate test. But the number of rows varies every time which means I must count the rows and update the system on each run. Looking for a way to programmatically get the row count.
The HTML is a table of <tbody><tr></tr></tbody>, so the following is enough to get the count but I can't run it in a beforeEach(), the error thrown is "No tests found"
let rowCount;
beforeEach(() => {
cy.visit('/analytics')
cy.get('tbody tr').then($els => rowCount = $els.length)
})
Cypress._.times(rowCount => {
it('process row', () => {
...
})
})
The before:run event fires before the tests start, you can scan the web page there.
Set the event listener in setupNodeEvents(). Cypress commands won't run here, but you can use equivalent Node commands.
const { defineConfig } = require("cypress");
module.exports = defineConfig({
e2e: {
setupNodeEvents(on, config) {
on('before:run', async (details) => {
try {
const fetch = require('node-fetch');
const fs = require('fs-extra');
const jsdom = require("jsdom");
const { JSDOM } = jsdom;
const response = await fetch(config.env.prescan); // default below
const body = await response.text(); // or pass in command line
const dom = new JSDOM(body);
const rows = dom.window.document.body.querySelectorAll('tr') // query
// save results
fs.writeJson('./cypress/fixtures/analytics-rows.json', {rows:rows.length})
} catch (error) {
console.log('error:', error)
}
})
},
},
env: {
prefetch: 'url-for-analytics-page'
}
})
Test
import {rows} from './cypress/fixtures/analytics-rows.json' // read row count
Cypress._.times(rows, (row) => {
it(`tests row ${row}`, () => {
...
})
}
You can add a script scan-for-rows.js to the project scripts folder, like this
const rp = require('request-promise');
const $ = require('cheerio');
const fs = require('fs-extra');
rp('my-url')
.then(function(html) {
const rowCount = $('big > a', html).length
fs.writeJson('row-count.json', {rowCount})
})
.catch(function(err){
//handle error
});
Then in package.json call a pre-test script every time a new version of the web page appears.
One possibility is to run the above Cypress test in a pretest script which will always run before your main test script.
// package.json
{
...
"scripts": {
"pretest": "npx cypress run --spec cypress/e2e/pre-scan.cy.js",
"test": "npx cypress run --spec cypress/e2e/main-test.cy.js",
}
}
// pre-scan.cy.js
it('scans for table row count', () => {
cy.visit('/analytics');
cy.get('tbody tr').then($els => {
const rowCount = $els.length;
cy.writeFile('cypress/fixtures/rowcount.json', rowCount);
});
});
Here's a way to get the row count in the spec file without using extra packages, plugins, test hooks, or npm scripts.
Basically, you can create a separate module that makes a synchronous HTTP request using the XMLHTTPRequest class to the /analytics endpoint and use the browser's DOMParser class to find the return the number of <tr> tags.
// scripts/get-row-count.js
export function getRowCount() {
let request = new XMLHttpRequest();
// Set async to false because Cypress will not wait for async functions to finish before looking for it() statements
request.open('GET', '/analytics', false);
request.send(null);
const document = new DOMParser().parseFromString(request.response, 'text/html');
const trTags = Array.from(document.getElementsByTagName('tr'));
return trTags.length;
};
Then in the spec file, import the new function and now you can get an updated row count whenever you need it.
import { getRowCount } from '../scripts/get-row-count';
Cypress._.times(getRowCount() => {
it('process row', () => {
...
})
})
The reason for XMLHTTPRequest instead of fetch is because it allows synchronous requests to be made. Synchronous requests are needed because Cypress won't wait for async requests to come back before parsing for it() blocks.
With this, you always have the most up to date row count without it going stale.

How to run the single test with different data set in parallel by using cypress on single machine

I just have the below Test.json file in the fixture folder:
[
{
"searchKeyword":"cypress"
},
{
"searchKeyword":"QA automation"
},
{
"searchKeyword":"stackoverflow"
}
]
The above file contains three different dataset.
I just have the below spec file and It contains one It (Test case) and It will run multiple times based on the above input.
Test.spec.js file:
describe("Run the test parallel based on the input data",() =>{
const baseUrl = "https://www.google.com/";
before("Login to consumer account", () => {
cy.fixture('Test').then(function (data) {
this.data = data;
})
});
it("Search the keyword", function () {
this.data.forEach((testData) =>{
cy.visit(baseUrl);
cy.xpath("//input[#name='q']").type(testData.searchKeyword);
cy.xpath("//input[#value='Google Search']").click();
cy.get("//ul/li[2]").should("be.visible");
});
});
});
The above code is working as expected. But I just want to run the above single test parallelly by using different dataset.
Example: Three browser instance open and it should pick three different data from the Test.json file and run the single test which is available in the Test.spec.js file.
I just need logic to implement for one of my project, But I'm not able to share the code which is more complex that's reason just create some dummy test data and test script to achieve my logic.
Can someone please share yours thoughts to achieve this.
One way to run multiple instances of Cypress in parallel is via the Module API, which is basically using a Node script to start up the multiple instances.
Node script
// run-parallel.js
const cypress = require('cypress')
const fixtures = require('./cypress/fixtures/Test.json')
fixture.forEach(fixture => {
cypress.run({
env: {
fixture
},
})
})
Test
describe("Run the test for given env data",() =>{
const testData = Cypress.env('fixture')
...
it("Search the keyword", function () {
cy.visit(baseUrl);
cy.xpath("//input[#name='q']").type(testData.searchKeyword);
...
});
});
Awaiting results
cypress.run() returns a promise, so you can collate the results as follows
Videos and screenshots are troublesome, since it tries to save all under the same name, but you can specify a folder for each fixture
const promises = fixtures.map(fixture => {
return cypress.run({
config: {
video: true,
videosFolder: `cypress/videos/${fixture.searchKeyword}`,
screenshotsFolder: `cypress/screenshots/${fixture.searchKeyword}`,
},
env: {
fixture
},
spec: './cypress/integration/dummy.spec.js',
})
})
Promise.all(promises).then((results) => {
console.log(results.map(result => `${result.config.env.fixture.searchKeyword}: ${result.status}`));
});

Generate simple html based on jasmine-allure-reporter

I'm using jasmine-allure-reporter and the report is simply awesome. Only complaint over the reporter is that I miss option to enable only failed screenshots to be saved and possibility to send it via e-mail.
I know that is not possible:
How to send an email of allure report?
My question is whether I can somehow generate a simple html file with few data based on the allure reports, so that I'll be able to send it via e-mail to relevant people.
Hope you have added this in your conf file:
onPrepare: function () {
browser.manage().timeouts().implicitlyWait(15000);
var AllureReporter = require('jasmine-allure-reporter');
jasmine.getEnv().addReporter(new AllureReporter({
allureReport: {
resultsDir: 'allure-results'
}
}));
jasmine.getEnv().afterEach(function (done) {
browser.takeScreenshot().then(function (png) {
allure.createAttachment('Screenshot', function () {
return new Buffer(png, 'base64');
}, 'image/png')();
done();
});
});
}
After running the files , go to allure-results where you can see the screenshots and xml reports.
Copy-Paste the folder i.e. allure-results to \node_modules\jasmine-allure-reporter where you can see a pom.xml file.
Install Maven in your machine (This is mandatory)
Now from same path i.e. \node_modules\jasmine-allure-reporter run the following command
mvn site -Dallure.results_pattern=allure-results
After Successfull run of above command,
Go to
\node_modules\jasmine-allure-reporter\target\site\allure-maven-plugin
and open index.html
This is how it looks:
The following code works for me. It takes screenshots of only failed tests.
var originalAddExpectationResult = jasmine.Spec.prototype.addExpectationResult;
jasmine.Spec.prototype.addExpectationResult = function () {
if (!arguments[0]) {
browser.takeScreenshot().then(function (png) {
allure.createAttachment('Screenshot', function () {
return new Buffer(png, 'base64')
}, 'image/png')();
})
}
return originalAddExpectationResult.apply(this, arguments);
};
var AllureReporter = require('jasmine-allure-reporter');
jasmine.getEnv().addReporter(new AllureReporter({
resultsDir: 'allure-results'
}));

Jasmine Testing get the name of the full describes/it's

I was wondering, is it possible to get the full nested describe path for the tests?
Given:
describe('Smoke Testing - Ensuring all pages are rendering correctly and free of JS errors', function () {
describe('app', function () {
describe('app.home', function () {
it('should render this page correctly', function (done) {
//name here should be: Smoke Testing - Ensuring all pages are rendering correctly and free of JS errors app app.home should render this page correctly
done()
})
})
describe('app.dashboard', function () {
describe('app.dashboard.foobar', function () {
it('should render this page correctly', function (done) {
//name here should be: Smoke Testing - Ensuring all pages are rendering correctly and free of JS errors app app.dashboard app.dashboard.foobar should render this page correctly
done()
})
})
})
})
})
Both jasmine.Suite and jasmine.Spec have method getFullName(). Works as you'd expect:
describe("A spec within suite", function() {
it("has a full name", function() {
expect(this.getFullName()).toBe('A spec within suite has a full name.');
});
it("also knows parent suite name", function() {
expect(this.suite.getFullName()).toBe('A spec within suite');
});
});
<script src="http://searls.github.io/jasmine-all/jasmine-all-min.js"></script>
Notice: this answer is now bit dated and uses Jasmine 1.3.1 in the example.
When you are inside the describe callback function this is set to a "suite" object which has the description of the suite (the text you pass to describe) and a property for the parent suite.
The example below gets the concatenation of the description nested describe calls, I'm not sure about how to access the description of the "it". But this will get you part way there.
var getFullDesc = function(suite){
var desc = "";
while(suite.parentSuite){
desc = suite.description + " " + desc;
suite = suite.parentSuite;
}
return desc;
}
describe('Outer describe', function(){
describe('Inner describe', function(){
console.log(getFullDesc(this));
it('some test', function(){
});
});
});

Running into Error while waiting for Protractor to sync with the page with basic protractor test

describe('my homepage', function() {
var ptor = protractor.getInstance();
beforeEach(function(){
// ptor.ignoreSynchronization = true;
ptor.get('http://localhost/myApp/home.html');
// ptor.sleep(5000);
})
describe('login', function(){
var email = element.all(protractor.By.id('email'))
, pass = ptor.findElement(protractor.By.id('password'))
, loginBtn = ptor.findElement(protractor.By.css('#login button'))
;
it('should input and login', function(){
// email.then(function(obj){
// console.log('email', obj)
// })
email.sendKeys('josephine#hotmail.com');
pass.sendKeys('shakalakabam');
loginBtn.click();
})
})
});
the above code returns
Error: Error while waiting for Protractor to sync with the page: {}
and I have no idea why this is, ptor load the page correctly, it seem to be the selection of the elements that fails.
TO SSHMSH:
Thanks, your almost right, and gave me the right philosophy, so the key is to ptor.sleep(3000) to have each page wait til ptor is in sync with the project.
I got the same error message (Angular 1.2.13). My tests were kicked off too early and Protractor didn't seem to wait for Angular to load.
It appeared that I had misconfigured the protractor config file. When the ng-app directive is not defined on the BODY-element, but on a descendant, you have to adjust the rootElement property in your protractor config file to the selector that defines your angular root element, for example:
// protractor-conf.js
rootElement: '.my-app',
when your HTML is:
<div ng-app="myApp" class="my-app">
I'm using ChromeDriver and the above error usually occurs for the first test. I've managed to get around it like this:
ptor.ignoreSynchronization = true;
ptor.get(targetUrl);
ptor.wait(
function() {
return ptor.driver.getCurrentUrl().then(
function(url) {
return targetUrl == url;
});
}, 2000, 'It\'s taking too long to load ' + targetUrl + '!'
);
Essentially you are waiting for the current URL of the browser to become what you've asked for and allow 2s for this to happen.
You probably want to switch the ignoreSynchronization = false afterwards, possibly wrapping it in a ptor.wait(...). Just wondering, would uncommenting the ptor.sleep(5000); not help?
EDIT:
After some experience with Promise/Deferred I've realised the correct way of doing this would be:
loginBtn.click().then(function () {
ptor.getCurrentUrl(targetUrl).then(function (newURL){
expect(newURL).toBe(whatItShouldBe);
});
});
Please note that if you are changing the URL (that is, moving away from the current AngularJS activated page to another, implying the AngularJS library needs to reload and init) than, at least in my experience, there's no way of avoiding the ptor.sleep(...) call. The above will only work if you are staying on the same Angular page, but changing the part of URL after the hashtag.
In my case, I encountered the error with the following code:
describe("application", function() {
it("should set the title", function() {
browser.getTitle().then(function(title) {
expect(title).toEqual("Welcome");
});
});
});
Fixed it by doing this:
describe("application", function() {
it("should set the title", function() {
browser.get("#/home").then(function() {
return browser.getTitle();
}).then(function(title) {
expect(title).toEqual("Welcome");
});
});
});
In other words, I was forgetting to navigate to the page I wanted to test, so Protractor was having trouble finding Angular. D'oh!
The rootElement param of the exports.config object defined in your protractor configuration file must match the element containing your ng-app directive. This doesn't have to be uniquely identifying the element -- 'div' suffices if the directive is in a div, as in my case.
From referenceConf.js:
// Selector for the element housing the angular app - this defaults to
// body, but is necessary if ng-app is on a descendant of <body>
rootElement: 'div',
I got started with Protractor by watching the otherwise excellent egghead.io lecture, where he uses a condensed exports.config. Since rootElement defaults to body, there is no hint as to what is wrong with your configuration if you don't start with a copy of the provided reference configuration, and even then the
Error while waiting for Protractor to sync with the page: {}
message doesn't give much of a clue.
I had to switch from doing this:
describe('navigation', function(){
browser.get('');
var navbar = element(by.css('#nav'));
it('should have a link to home in the navbar', function(){
//validate
});
it('should have a link to search in the navbar', function(){
//validate
});
});
to doing this:
describe('navigation', function(){
beforeEach(function(){
browser.get('');
});
var navbar = element(by.css('#nav'));
it('should have a link to home in the navbar', function(){
//validate
});
it('should have a link to search in the navbar', function(){
//validate
});
});
the key diff being:
beforeEach(function(){
browser.get('');
});
hope this may help someone.
I was getting this error:
Failed: Error while waiting for Protractor to sync with the page: "window.angular is undefined. This could be either because this is a non-angular page or because your test involves client-side navigation, which can interfere with Protractor's bootstrapping. See http://git.io/v4gXM for details"
The solution was to call page.navigateTo() before page.getTitle().
Before:
import { AppPage } from './app.po';
describe('App', () => {
let page: AppPage;
beforeEach(() => {
page = new AppPage();
});
it('should have the correct title', () => {
expect(page.getTitle()).toEqual('...');
})
});
After:
import { AppPage } from './app.po';
describe('App', () => {
let page: AppPage;
beforeEach(() => {
page = new AppPage();
page.navigateTo();
});
it('should have the correct title', () => {
expect(page.getTitle()).toEqual('...');
})
});
If you are using
browser.restart()
in your spec some times, it throws the same error.
Try to use
await browser.restart()

Resources