How to get all fonts with style using puppeteer - puppeteer

I am using puppeteer to get list of fonts that for a given webpage.
Tried the following snippet to get all fonts for a given page.
const selector = 'html';
const getFontProperty = async (page) => {
const font = await page.evaluate((selector) => {
let elements = Array.from(document.querySelectorAll(selector));
console.log(elements)
let links = elements.map(element => {
console.log(element)
console.log(getComputedStyle(element).font)
});
}, selector);
return font;
}
However, elements comes out as undefined.
[ref link: puppeteer page.evaluate querySelectorAll return empty objects

It happens because you don't actually return font definitions, but console.log them. If you don't use a short form of arrow function, you need to explicitly return a value:
let links = elements.map(element => {
console.log(element)
return getComputedStyle(element).font
});
Otherwise you can just write:
let links = elems.map(element => getComputedStyle(element).font);
Update: You also must return data you're seeking from page.evaluate:
const font = await page.evaluate(selector => {
let elements = Array.from(document.querySelectorAll(selector));
let links = elems.map(element => getComputedStyle(element).font);
return links; // <-- return data from page.evaluate
}, selector);

Related

How do I write an async request to get a markdown file's content? Svelte

I'm having a great time building my blog with Svelte, but I'm switching the structure to to be accessed through a JSON API.
Right now it's easy to get the markdown metadata and path, but I'd love to also get the content.
How would I modify this posts.json.js file to also get the content?
const allPostFiles = import.meta.glob('../blog/posts/*.md')
const iterablePostFiles = Object.entries(allPostFiles)
const allPosts = await Promise.all(
iterablePostFiles.map(async ([path, resolver]) => {
const { metadata } = await resolver()
const postPath = path.slice(2, -3)
return {
meta: metadata,
path: postPath
}
})
)
const sortedPosts = allPosts.sort((a, b) => {
return new Date(b.meta.date) - new Date(a.meta.date)
})
return {
body: sortedPosts
}
Install and enable the vite-plugin-markdown
// svelte.config.js
import { plugin as markdown, Mode } from "vite-plugin-markdown";
/** #type {import('#sveltejs/kit').Config} */
export default {
kit: {
vite: {
plugins: [markdown({ mode: Mode.HTML })],
},
},
};
then the content will be available as html and frontmatter data as attributes
iterablePostFiles.map(async ([path, resolver]) => {
const { attributes, html } = await resolver();
return {
attributes,
html,
path: path.slice(2, -3),
};
})
(I suggest adding the metadata into the markdown files via frontmatter )
The answer above works perfectly, but it also works to tweak the API with this code:
const allPosts = await Promise.all(
iterablePostFiles.map(async ([path, resolver]) => {
const { metadata } = await resolver()
// because we know every path will start with '..' and end with '.md', we can slice from the beginning and the end
const postPath = path.slice(2, -3)
const post = await resolver()
const content = post.default.render()
return {
meta: metadata,
path: postPath,
text: content
}
})
)
The important addition is this:
const post = await resolver()
const content = post.default.render()
using these variable chains to avoid using the JS reserved word default.

fetching data with react hook returns undefined on nested obj properties

Im trying to display data that has been fetched. but i cannot seem to display nested objects properties in react. Any ideas? if i log the data i first get a undefined, then the correct data.
my guess is that i need to wait for the data to be loaded then display it. but it does work for the title that is not in a nested obj.
function SingleBeneficiary({ match }) {
const [data, setData] = useState({ data: []});
const id = match.params.id
useEffect(() => {
async function fetchData() {
const response = await fetch(`http://localhost:8081/v1/beneficiary/${id}`);
const jsonData = await response.json()
setData(jsonData)
}
fetchData();
}, [])
return (
{data.title} // works
{data.address.careOf} // dont work
The data
{
"title":"myTitle",
"address":{
"careOf": "my adress"
}
}
Can you try like this?
I set initial data to null, and in return I check if it is not null.
If address can be null, additional null check is required.
function SingleBeneficiary({ match }) {
const [data, setData] = useState(null);
const id = match.params.id
useEffect(() => {
async function fetchData() {
const response = await fetch(`http://localhost:8081/v1/beneficiary/${id}`);
const jsonData = await response.json()
setData(jsonData)
}
fetchData();
}, [])
return (
<div>
{data && (
<div>
<p>data.title</p>
<p>data.address.careOf</p>
</div>
)}
</div>
);
}
You should check if address has careOf property before using it because first time data will be undefined and in second render it will have the data after the api call.
{data.address && data.address.careOf}
For anyone who is having a similar issue(i.e. fetching data via api and only the first time it runs, it will show the data as undefined but after manual refreshing, it works fine), here is a quick and sketchy addition you might consider alongside with 1. "Inline If with Logical && Operator" method and 2. using useState for checking if the api loading is over. With those three, mine worked.
Try fetching the desired data in the previous page of your app; in this case, add the following lines in any page you'll see before "SingleBeneficiary".
const response = await fetch(`http://localhost:8081/v1/beneficiary/${id}`);
const jsonData = await response.json()
Maybe it has to do with npm cache, but not really sure what's going on.
replace
return (
{data.title}
{data.address.careOf}
)
with
return (
{data?.title}
{data?.address?.careOf}
)

I'm getting a 404 error when trying to render my results for my API Hack assignment

I'm working on an API Hack assignment for my class with Thinkful and my issue has been that I've been trying to make a call to spoonacular's food api and render the results onto the DOM. However, when I try to do that, All I get in return is a 404 error. I'm wondering if i did something wrong or is some unforeseen problem that is beyond my control?
I've already look at manually typing the composed URL and postman as well.
function queryParams(params) {
const queryItems = Object.keys(params).map(key => `${encodeURIComponent(key)}= ${encodeURIComponent(params[key])}`)
return queryItems.join('&');
}
function displayResults(responseJson){
console.log(responseJson);
$('#results-list').empty();
for(let i = 0; i < responseJson.results.length; i++){
$('#results-list').append(
`<li><h3>${responseJson.results[i].id},${responseJson.results[i].protein}</h3>
<p>By ${responseJson.results[i].calories}</p>
<img src='${responseJson.results[i].image}'>
</li>`
)};
$('#results').removeClass('hidden');
};
function getRecipe(query,maxResults,){
const params ={
q:query,
number: maxResults,
};
const queryString = queryParams(params)
const url = searchUrl+'?'+ queryString +'?apiKey='+ apikey;
console.log(url);
fetch(url,option)
.then(response =>{
if(response.ok){
return response.json();
}
throw new Error(response.statusText);
})
.then(response => console.log(responseJson))
.catch(err =>{
$('#js-error-message').text(`Something went wrong: ${err.message}`);
});
}
function watchForm() {
$('form').submit(event => {
event.preventDefault();
const searchRecipe = $('.js-search-recipe').val();
const maxResults = $('.js-max-results').val();
getRecipe(searchRecipe, maxResults);
});
}
$(watchForm);
It looks like you have a couple issues:
First, you're constructing an invalid url:
const url = searchUrl+'?'+ queryString +'?apiKey='+ apikey;
notice the 2 ?s
Also, when you're constructing the query params, you're adding a space between the = and the value of your param
${encodeURIComponent(key)}= ${encodeURIComponent(params[key])}
If you're using the correct path and a valid API key, fixing those things may be enough to make it work.

Empty GET response on requesting JSON file's content | koa2

I am a new to koa2, and I trying to GET the contents of a JSON file with koa2
app.use( async ( ctx ) => {
let url = ctx.request.url;
if (url == "list") {
let res = ctx.request.get('http://domain/hello.json');
ctx.body = res.body;
}
})
The JSON file hello.json looks like the following:
{"da": "1212", "dad": "12addsf12"}
I want the route /list to return the contents of hello.json, however, the response is empty. What do I do?
Update:
Change the following lines of code:
let res = ctx.request.get('http://domain/hello.json');
ctx.body = res.body;
to:
let res = ctx.get('http://domain/hello.json');
ctx.body = res;
You should get the content now.
Koa by itself does not support routing, only middleware, you need to have a router middleware for that, try koa-router.
Your app would look something like
const route = require('koa-route')
app.use(route.get('/list', ctx => {
// Route handling logic
});
Also note that ctx.get is an alias for ctx.request.get which returns header information.
This may not be Koa's way of doing things, but this is what I tried and worked for me (complete code for noobs like me):
// jshint ignore: start
const koa2 = require("koa2");
const router = require('koa-simple-router');
const app = new koa2();
const request = require('request-promise-native');
// response
app.use(router(_ => {
_.get('/list', async (ctx) => {
const options = {
method: 'GET',
uri: 'http://www.mocky.io/v2/5af077a1310000540096c672'
}
await request(options, function (error, response, body) {
// I am leaving out error handling on purpose,
// for brevity's sake. You must in your code.
ctx.body = body;
})
});
}));
app.listen(3000);
And, like what J Pichardo's answer points out, Koa by itself does not support routing. You need to use some routing middleware.

How do I pass this string value into my page.evaluate in a reasonable way?

my code is pretty straightforward, the crawler object is a puppeteer instance:
crawler.selectorReturner = async function(page, selector) {
await page.waitForSelector(selector);
var returnSelector = async function(){
return selector;
}
await page.exposeFunction('returnSelector', returnSelector);
var getSelections = await page.evaluate(
() => {
var resultsobj = {
selections: []
};
var selector = returnSelector();
var selections = Array.from(document.body.querySelectorAll(selector), ({ selected }) => { return selected; });
resultsobj.selections = selections;
return resultsobj;
}
);
return getSelections;
}
an example of how I use crawler.selectorReturner
const initialhrefsObj = await crawler.selectorReturner(page,"a[href]");
but in all the various ways I have tried I am not able to get document.body.querySelectorAll(selector) to work
Evaluation failed: DOMException: Failed to execute 'querySelectorAll' on 'Element': '[object Promise]' is not a valid selector.
I obviously don't want to have a bunch of functions with the selectors hard coded I want to pass it in to the page evaluate. How can I pass a string in and have that string be available inside of my page.evaluate?
The #evaluate takes a variadic parameter in position 2 onward, so you're free to pass in selectors that way. This should look like:
var getSelections = await page.evaluate(
(selectorString) => {
var resultsobj = {
selections: []
};
var selector = returnSelector(selectorString);
var selections = Array.from(document.body.querySelectorAll(selector), ({ selected }) => { return selected; });
resultsobj.selections = selections;
return resultsobj;
},
'.my-selector' // Selector argument here
);