Cypress - How to verify data from a PDF file using cypress command - cypress

Below is my code in cypress. How to print 'pdf' content and verify content using cypress .contains or .eq? when I run the code it prints object{6} but I want to print my pdf file content. I would really appreciate the help.
**Plugins/index.js:**
const fs = require('fs')
const pdf = require('pdf-parse')
const path = require('path')
const repoRoot = path.join("C:/Users/XXXXX/Downloads/loginCy-excel")
const parsePdf = async (pdfName) => {
const pdfPathname = path.join(repoRoot, pdfName)
let dataBuffer = fs.readFileSync(pdfPathname);
return await pdf(dataBuffer)
}
module.exports = (on, config) => {
on('task', {
getPdfContent (pdfName) {
return parsePdf(pdfName)
},
})
}
**cypress spec file has these code:**
it('tests a pdf', () => {
cy.task('getPdfContent', 'sample.pdf').then(content => {
cy.log(content)
})
})

pdf method will return an object, so I guess cy.log() can't print it like that. If you want to see what the function gathered in your pdf file, you can stringify the result:
cy
.log(JSON.stringify(content));
If you want to get only text from your pdf, you need to work with text property:
cy
.log(content.text);

Anyone struggling with testing PDF files with cypress can refer to these two very good blog posts precisely on this topic:
https://filiphric.com/testing-pdf-file-with-cypress
https://glebbahmutov.com/blog/cypress-pdf/
It wasn't asked in this question, but here is a little addition from me on how to download files (was tested on PDFs) from a URL:
cy.request({
url: '<file url>',
gzip: false,
encoding: 'base64',
}).then((response) => {
cy.writeFile(
Cypress.config('downloadsFolder') + '/<name of the file>.pdf',
response.body,
{ encoding: 'base64' }
);

Related

How to post/send an image to an API using Axios?

I am trying to post/send an image to an API through axios.
Here is my frontend code (ReactJS):
const handleImage = (e) => {
const myImg = e.target.files[0];
const config = {
headers: {
"Content-Type": "multipart/form-data"
}
};
if (myImg !== undefined) {
let form = new FormData();
form.append("file", myImg);
axios.post("/upload", form, config)
.then(res => console.log(res))
.catch(err => console.log(err));
}
}
Here is my backend code (NodeJS & ExpressJS):
app.post("/upload", (req, res) => {
console.log(req.body);
res.send(req.body);
});
console.log(req.body) printing an empty object i.e. {} on console window.
So, in short, my doubt is why its printing an empty object? Is there something that I am missing in my code?
This is probably because your backend is not ready to receive files upload.
You can use a library called Multer which will make easier setting up your backend for supporting files upload.
There is a lot of content explaining how to use Multer. You can check this one out

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

Rendering image from API response in NextJS - just downloads base64 file

I am working on a new project and learning ReactJS. I have saved an image in base64 to a MySQL database and I am now trying to do a GET request so the image is shown in the browser instead of it being downloaded, however, although it attempts to download the file, the file is just a file containing the base64 string instead of an actual image.
The file in the database looks like the below (only a snippet of the base64)
data:image/png;base64,iVBORw0KGgoAAAA
The API to fetch the image is as below:
export default async function handler(req : NextApiRequest, res : NextApiResponse) {
const prisma = new PrismaClient()
if (req.method === "GET")
{
const {image_id} = req.query;
const image = await prisma.images.findFirst({
where: {
imageId: parseInt(image_id)
}
});
const decoded = Buffer.from(image.content).toString("base64");
console.log(decoded)
res.status(200).send(decoded);
}
else
{
res.status(405).json(returnAPIErrorObject(405, "Method not supported by API endpoint"));
}
}
I've modified the next.config.js to provide a custom header response which contains the below:
module.exports = {
async headers() {
return [
{
source: '/api/images/:image_id',
headers: [
{
key: 'Content-Type',
value: 'image/png:Base64'
}
]
}
]
}
}
As mentioned, when I go to the URL http://localhost:3000/api/images/4 (4 being the image id) it downloads a file that contains the base64 string that is in the database, instead of showing the image in the browser.
UPDATE
Based on the link in the comment from #sean w it now attempts to display the image but instead of showing the actual picture, it just shows a blank window with a white square as shown in the screenshot below.
My code now looks like the following:
const {image_id} = req.query;
const image = await prisma.images.findFirst({
where: {
imageId: parseInt(image_id)
}
});
const decoded = Buffer.from(image.content, 'base64').toString();
let imageContent = decoded.replace(/^data:image\/png;base64,/, '');
console.log(decoded);
res.writeHead(200, {
'Content-Type': 'image/png',
'Content-Length': imageContent.length
});
res.end(imageContent);
Below is a screenshot showing what actually gets rendered on the page instead of my actual image.
I've figured out the issue, instead of creating the buffer from the database which I think looks like Prisma as the column was a blob was giving me a buffer anyway. , I first extract the base64 string from the DB and remove the data:image/png;base64 from the string and then create a buffer from that string and send that for the response:
const {image_id} = req.query;
const image = await prisma.images.findFirst({
where: {
imageId: parseInt(image_id)
}
});
const decoded = image.content.toString().replace("data:image/png;base64,", "");
const imageResp = new Buffer(decoded, "base64");
res.writeHead(200, {
'Content-Type': 'image/png',
'Content-Length': imageResp.length
});
res.end(imageResp);

YouTube search API not filtering embeddable/syndicatable videos

I'm embedding YouTube videos into an app I'm writing, but some of the results are videos that aren't allowed to be played on my site. I've tried setting both videoSyndicated and videoEmbeddable to true in the params but it doesn't seem to fix my problem.
const axios = require('axios');
const ROOT_URL = 'https://www.googleapis.com/youtube/v3/search';
const search = (options, callback) => {
if(!options.key) {
throw new Error('Youtube Search expected key, received undefined');
}
const params = {
type: 'video',
videoEmbeddable: true,
videoSyndicated: true,
part: 'snippet',
key: options.key,
q: options.term,
};
axios.get(ROOT_URL, { params })
.then((response) => {
if(callback) { callback(response.data.items); }
})
.catch((error) => {
console.error(error);
});
};
export default search;`
This looks like a copyright claim by a 3rd party, which happens outside of the YouTube API. You can check this separately by scraping the video page and searching for the copyright text.

how to upload image in register API in strapi?

In defaut registration API, I need to uplaod the image of user in registration API. So how could I manage it ? I'm sending in a formData and it works fine. I can see (binary) in network.
I tried to add image field and it works in admin panel but from API side I tried to send the file in key names like files, profileImage.
I didn't get the error in res. I got success in res.
Issue: When I reload the admin panel, I didn't get user's profile image.
Try this way. I used in react and it works fine for me.
signUpHandler = () => {
console.log("SignUp data ::: ", this.state);
let data = {
username: this.state.signUpForm.username.value,
phone: this.state.signUpForm.phone.value,
email: this.state.signUpForm.email.value,
password: this.state.signUpForm.password.value
}
axios.post('http://0.0.0.0:1337/auth/local/register', data)
.then(res => {
console.log(res);
return res.data.user.id;
})
.then(refId =>{
const data = new FormData();
data.append('files', this.state.selectedFile);
data.append('refId', refId);
data.append('ref', 'user');
data.append('source', 'users-permissions');
data.append('field', 'profileImage');
return axios.post('http://0.0.0.0:1337/upload', data)
})
.then(res =>{
console.log(res);
alert("You registered successfully...");
this.props.history.push('/login');
})
.catch(error =>{
console.log(error);
})
}
First, you will have to customize your user-permission
To do so, you will have to understand this concept: https://strapi.io/documentation/3.0.0-beta.x/concepts/customization.html
Then you will have to find the function you want to update - in your case, the register function.
And tada here it is https://github.com/strapi/strapi/blob/master/packages/strapi-plugin-users-permissions/controllers/Auth.js#L383.
So you will have to create ./extensions/users-permissions/controllers/Auth.js with the same content as the original file.
Then you will have to add
const { parseMultipartData, sanitizeEntity } = require('strapi-utils');
const uploadFiles = require('strapi/lib/core-api/utils/upload-files');
on the top of your file.
And in your function use this
const { data, files } = parseMultipartData(ctx); to parse data and files.
Then you will have to replace ctx.request.body by data to make sure to use the correct data.
After that you will have to add this after the user creation line
https://github.com/strapi/strapi/blob/master/packages/strapi-plugin-users-permissions/controllers/Auth.js#L510
if (files) {
// automatically uploads the files based on the entry and the model
await uploadFiles(user, files, { model: strapi.plugins['users-permissions'].models.user })
}
Solution for Strapi v4:
var myHeaders = new Headers();
myHeaders.append("Authorization", "Bearer XXXX");
var formdata = new FormData();
formdata.append("files", fileInput.files[0], "XXX.png");
formdata.append("refId", "46");
formdata.append("field", "image");
formdata.append("ref", "plugin::users-permissions.user");
var requestOptions = {
method: 'POST',
headers: myHeaders,
body: formdata,
redirect: 'follow'
};
fetch("http://localhost:1337/api/upload", requestOptions)
.then(response => response.text())
.then(result => console.log(result))
.catch(error => console.log('error', error));

Resources