Configure screenshot folder in Cypress - cypress

I am using Cypress as my user interface test automation framework.
Currently my folder structure for spec file (logical organization of test files) is:
~/myAccount/header/header.spec.js
~/myAccount/footer/footer.spec.js
~/myAccount/mainTabs/home.spec.js
and so on...
Now when I configure my screenshot folder in cypress.json to screenshots and save screenshots of failed test cases, cypress internally creates a folder structure inside screenshots folder. For instance if a test fails in footer.spec.js, it saves the screenshot in
~/screenshots/myAccount/footer/footer.spec.js
I want to get rid of this recursive folder structure and save all screenshots inside screenshots folder (so that I can easily access these screenshots and add it to my mochawesome report).
Is there any way to do it ?
Any help will be appreciated and let me know if I was unable to put my question properly. I am willing to add more information.

Yes, you can use the Cypress screenshot API:
for example:
// cypress/plugins/index.js
const fs = require('fs')
module.exports = (on, config) => {
on('after:screenshot', (details) => {
// details will look something like this:
// {
// size: 10248
// takenAt: '2018-06-27T20:17:19.537Z'
// duration: 4071
// dimensions: { width: 1000, height: 660 }
// multipart: false
// pixelRatio: 1
// name: 'my-screenshot'
// specName: 'integration/my-spec.js'
// testFailure: true
// path: '/path/to/my-screenshot.png'
// scaled: true
// blackout: []
// }
// example of renaming the screenshot file
const newPath = '/new/path/to/screenshot.png'
return new Promise((resolve, reject) => {
fs.rename(details.path, newPath, (err) => {
if (err) return reject(err)
// because we renamed/moved the image, resolve with the new path
// so it is accurate in the test results
resolve({ path: newPath })
})
})
})
}
You could also create symlinks if you wanted the image in two places, for example.

Related

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}`));
});

Cypress - How can I verify if the downloaded file contains name that is dynamic?

In cypress, the xlsx file I am downloading always starts with lets say "ABC" and then some dynamic IDs. How can I verify if the file is downloaded successfully and also contains that dynamic name?
Secondly, what if the downloaded file is like "69d644353f126777.xlsx" then how can i verify that the file is downloaded when everything in the name is dynamic.
Thanks in advance.
One way that suggests itself is to query the downloads folder with a task,
/cypress/plugins/index.js
const fs = require('fs');
on('task', {
downloads: (downloadspath) => {
return fs.readdirSync(downloadspath)
}
})
test
cy.task('downloads', 'my/downloads/folder').then(before => {
// do the download
cy.task('downloads', 'my/downloads/folder').then(after => {
expect(after.length).to.be.eq(before.length +1)
})
})
If you can't direct the downloads to a folder local to the project, provide a full path. Node.js (i.e Cypress tasks) has full access to the file system.
To get the name of the new file, use a filter and take the first (and only) item in the result.
const newFile = after.filter(file => !before.includes(file))[0]
Maybe this will works but this also requires a filename to be assert.Write this code in index.js
on('task', {
isExistPDF(PDFfilename, ms = 4000) {
console.log(
`looking for PDF file in ${downloadDirectory}`,
PDFfilename,
ms
);
return hasPDF(PDFfilename, ms);
},
});
Now add custom command in support/commands.js
Cypress.Commands.add('isDownloaded', (selectorXPATH, fileName) => {
//click on button
cy.xpath(selectorXPATH).should('be.visible').click()
//verify downloaded file
cy.task('isExistPDF', fileName).should('equal', true)
})
Lastly write this code in your logic area
verifyDownloadedFile(fileName) {
//Clear downloads folder
cy.exec('rm cypress/downloads/*', {
log: true,
failOnNonZeroExit: false,
})
cy.isDownloaded(this.objectFactory.exportToExcelButton, fileName)
}
and call this function in your testcase
I ended up using something similar to #user14783414.
However I keep getting that the downloads' folder length was 0. I then added an cy.wait() which solved the issue.
cy.task('downloads', 'my/downloads/folder').then(before => {
// do the download
}).then(() => {
cy.wait(500).then(() => {
cy.task('downloads', 'my/downloads/folder').then(after => {
expect(after.length).to.be.eq(before.length +1)
})
})
})
})
Another approach is to leverage Nodes fs.watch(...) API and to define a plugin task which waits for a new download to be available and returns the filename:
/cypress/plugins/index.js
const fs = require('fs');
module.exports = (on, config) => {
on('task', {
getDownload: () => {
const downloadsFolder = config['downloadsFolder'];
return new Promise((resolve, reject) => {
const watcher = fs.watch(downloadsFolder, (eventType, filename) => {
if (eventType === 'rename' && !filename.endsWith('.crdownload')) {
resolve(filename);
watcher.close();
}
});
setTimeout(reject, config.taskTimeout); // Or another timeout if desired
});
},
});
};
And then it is fairly easily used within a test spec as follows:
/sometest.spec.js
it('downloads a file', () => {
cy.get(downloadButtonSelector).click();
cy.task('getDownload').then(fileName => {
// do something with your newly downloaded file!
console.log('Downloaded file:', fileName);
});
});
Now technically there may be a bit of a race condition if the file is downloaded extremely quickly and the file exists on disk before the watcher begins, but in my testing - even with relatively small files and fast network speed - I have never observed this.
For the solution of Nicholas, in firefox, the '.crdownload' doesn't exist so we need to add a condition on the '.part' :
if (eventType === 'rename' && !filename.endsWith('.crdownload') && !filename.endsWith('.part'))

Can I add cypress screenshot in mocha awesome html report

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.

Nativescript imagepicker .getImage() is not a function error

I have been trying to implement the answer to this question but keep getting the error "selected.getImage is not a function".
I have tried multiple different code examples at this point and I'm stumped. It seems like this is a type error, but I'm not sure where I can correct this.
I am looking to select a single image and return the path to that image in order to upload to the server. I don't need to display it on the device, though that is an option I suppose. Seems easy enough, but apparently I'm missing something.
I'm using v. 6.0.1 or the imagepicker plugin. I'd quote the code, but at this point I am using the exact example provided by Shiva Prasad in the above question.
Adding code per Max Vollmer:
var context = imagepickerModule.create({
mode: "single" // allow choosing single image
});
context
.authorize()
.then(function () {
return context.present();
})
.then(function (selection) {
console.log("Selection done:");
setTimeout(() => {
selection.forEach(function (selected) {
selected.getImage().then((source) => {
console.log(selected.fileUri); // this is the uri you need
});
});
}, 1000);
}).catch(function (e) {
console.log(e);
});
I was facing the exact same error yesterday.
I use the fromAsset function directly on the "selected" because apparently with the new version of this plugin, "selected" is an Asset. So you got an imageSource and you can use the "saveToFile" function that will copy the Asset into a new location (get this location using fileSystemModule from TNS). Use the path of this new location for your UI, and the image will appear. You can also create a file object from this location fileSystemModule.File.fromPath(path);, I use for upload.
context
.authorize()
.then(function () {
return context.present();
})
.then(function (selection) {
selection.forEach(function (selected) {
let file;
if (selected._android) {
file = fileSystemModule.File.fromPath(selected._android);
//viewModel.uploadFile(file);
}else{
imageSourceModule.fromAsset(selected).then((imageSource) => {
const folder = fileSystemModule.knownFolders.documents().path;
const fileName = "Photo.png";
const path = fileSystemModule.path.join(folder, fileName);
const saved = imageSource.saveToFile(path, "png");
if (saved) {
console.log("Image saved successfully!");
file = fileSystemModule.File.fromPath(path);
//viewModel.uploadFile(file);
}else{
console.log("Error! - image couldnt save.");
}
});
}
});
}).catch(function (e) {
console.log(e);
// process error
});
Explanation
The uncommented snippet (//viewModel.uploadFile(file);), viewModel reference (will be different on your app) and the function: uploadFile for example is where you would pass the file to upload it or set it to the image.src etc
Make sure to declare imageSourceModule at the top.
const imageSourceModule = require("tns-core-modules/image-source");

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'
}));

Resources