Playwright pass variable into eval with JavaScript (Node) - node.js

Note: this is for Playwright, the browser API, like puppeteer.
I'm trying to find all elements on a page, then want to filter those elements down into values.
The values have specific selectors on them (css classes, etc).
The problem I have is that I cannot pass outside variables into the $$eval function so that my CSS selectors can be dynamic.
EG:
const cardsPerPage = await page.$$eval('.ais-hits--item', (repoCards) => {
return repoCards.map(card => {
const name = card.querySelector('a.product-thumbnail__title'); // < this is the problem
return {
name: toText(name),
};
});
});
If I try to change my CSS selector to a variable it doesn't work, I've tried just about everything.
const customSelector = 'a.product-thumbnail__title';
const cardsPerPage = await page.$$eval('.ais-hits--item', (repoCards) => {
return repoCards.map(card => {
const name = card.querySelector(customSelector); // < this is the problem
return {
name: toText(name),
};
});
});
The error is:
error: page.$$eval: ReferenceError: customSelector is not defined
at eval (eval at evaluate (:3:2389), <anonymous>:4:57)
at Array.map (<anonymous>)
at eval (eval at evaluate (:3:2389), <anonymous>:2:38)
at t.default.evaluate (<anonymous>:3:2412)
at t.default.<anonymous> (<anonymous>:1:44)
at Scraper.main (/home/steve/dev/test/src/scraper/index.js:67:49)

You'll need to do something like
const customSelector = 'a.product-thumbnail__title';
const cardsPerPage = await page.$$eval('.ais-hits--item', (repoCards, selector) => { // Your selector from ➀ becomes the last param of your evaluated lambda
return repoCards.map(card => {
const name = card.querySelector(selector);
return {
name: toText(name),
};
});
}, customSelector); // ➀ Your selector is passed in as the arg param
Additionally, if toText was declared outside of $$eval which it seems to be, then you'll get an error for that, too.

Related

How to solve react-hydration-error in Next.js when using `useLocalStorage` and `useDebounce`

When I try to use https://usehooks-ts.com/react-hook/use-local-storage in Next.js in the following way, I get
Unhandled Runtime Error Error: Text content does not match
server-rendered HTML.
See more info here:
https://nextjs.org/docs/messages/react-hydration-error
const [toleranceH, setToleranceH] = useLocalStorage<number>('toleranceH', 3);
const [toleranceS, setToleranceS] = useLocalStorage<number>('toleranceS', 3);
const [toleranceL, setToleranceL] = useLocalStorage<number>('toleranceL', 3);
const [results, setResults] = useState<MegaColor[]>([]);
const debouncedToleranceH = useDebounce<number>(toleranceH, 200);
const debouncedToleranceS = useDebounce<number>(toleranceS, 200);
const debouncedToleranceL = useDebounce<number>(toleranceL, 200);
useEffect(() => {
const targetColorDetailsObject = getColorDetailsObject(targetColor);
const degreeTolerance = (360 / 100) * debouncedToleranceH;
const [hueMin, hueMax] = getHueTolerance(targetColorDetailsObject.hue(), degreeTolerance);
const filteredColors = getFilteredColors(targetColorDetailsObject, loadedMegaColors, hueMin, hueMax, debouncedToleranceS, debouncedToleranceL);
setResults(filteredColors);
return () => {
// console.log('cleanup');
};
}, [targetColor, loadedMegaColors, debouncedToleranceH, debouncedToleranceS, debouncedToleranceL]);
From that help page, I still can't figure out what to adjust so that I can use both useLocalStorage and useDebounce.
I found https://stackoverflow.com/a/73411103/470749 but don't want to forcefully set a localStorage value (it should only be set by the user).
I'd suggest checking out this excellent post on rehydration by Josh W Comeau.
Since Next.js pre-renders every page by default you need to ensure that the component in which you are calling window.localstorage is only rendered on the client.
A simple solution is to:
Keep a hasMounted state
const [hasMounted, setHasMounted] = useState(false);
Toggle it inside a useEffect
useEffect(() => {
// This will only be called once the component is mounted inside the browser
setHasMounted(true);
}, []);
Add a check so that Next.js won't complain about prerendering stuff on the server that won't match the stuff that gets rendered on the client
if (!hasMounted) {
return null;
}
Ensure that the client-side stuff comes after the check
To make it more reusable you could use one of these two methods which essentially do the same:
ClientOnly Component
function ClientOnly({ children, ...delegated }) {
const [hasMounted, setHasMounted] = React.useState(false);
React.useEffect(() => {
setHasMounted(true);
}, []);
if (!hasMounted) {
return null;
}
/**
* Could also replace the <div></div> with
* <></> and remove ...delegated if no need
*/
return (
<div {...delegated}>
{children}
</div>
);
}
...
<ClientOnly>
<MyComponent /> // <--- client only stuff, safe to use useLocalStorage in here
</ClientOnly>
or
Custom useHasMounted hook
function useHasMounted() {
const [hasMounted, setHasMounted] = React.useState(false);
React.useEffect(() => {
setHasMounted(true);
}, []);
return hasMounted;
}
...
function ParentComponent() {
const hasMounted = useHasMounted();
if (!hasMounted) {
return null;
}
return (
<MyComponent />
);
}
...
function MyComponent() {
const [toleranceH, setToleranceH] = useLocalStorage<number>('toleranceH', 3);
const [toleranceS, setToleranceS] = useLocalStorage<number>('toleranceS', 3);
const [toleranceL, setToleranceL] = useLocalStorage<number>('toleranceL', 3);
...
}
...
Note:
By overdoing this or using this method at the top level of your component tree, you are killing the Next.js prerendering capabilities and turning your app into more of a "client-side heavy" app (see performance implications). If you are using window.localstorage (outside of components, where you don't have useEffect available), you should always wrap with:
if (typeof window !== 'undefined') {
// client-side code
}

send data from file to file in express

so what I want to do is sending an string which is a path for my image .. I want to do that in routes file . because I receive in it the image path like that
const imageReko = require("./image-Reko");
if(req.body.act === "post") {
if(req.body.img){
imageReko(req.body.img)
}
const post = new Post({
content: req.body.content,
firstName:req.body.firstName,
lastName: req.body.lastName,
img: req.body.img,
});
so what I have done is export all my image recognition file like that
const imageReko = () =>{
const photo = 'image2.jpg' // i want to put my string that i have passed it from my route files
// somecode
}
I described in comment what I want to do ..
From what I understand you want to get a string from the request body then pass it into a function in ./image-Reko which will store it in a variable.
Function Parameters
First of all the function in `image-Reko` has no parameters. This means when you pass `req.body.img` the function will ignore it since it cannot use it. To fix this you need to add a parameter to the function like so:
const imageRecko = (img) => {
// Code here
}
const vs. var and let
In the function you defined a variable called `photo` with the keyword `const` which declares a constant variable. This variable can not be re declared or redefined so you can change this variable with your function. To fix this:
const imageReko = (img) => {
var photo = img;
}
This way you can assign the variable inside the function.
This is what i understood from your question let me know if i missed something. Good luck with your project!
Since you pass req.body.img as parameter for imageReko() you can use it in the imageReko function
const imageReko = (img) =>{
const photo = img
// somecode
}

Is there a way to change the mocked value of a required dependency?

I'm facing a problem I'm not able to resolve on my own, maybe some of you faced the same problem.
Let me show you what I'm trying to do, here is the mock:
let mockConfig = {name: 'dude'};
jest.mock('../../../configManager', () => mockConfig);
configManager is a dependency of the function I'm trying to test.
It works well but I want to change the returning object of configManager in another test so the tested function behaves differently.
Let me show you, here is the function I'm testing:
const config = require('../../../configManager');
module.exports = () => {
if (config.name === 'dude') {
do stuff;
}
if (config.name === 'dudette') {
do something else;
}
So, typically, I want to change the config.name to 'dudette' to be able to test the second part of my function.
Naturally, when I want to do this with an imported function, I just do:
let mockJsonQueryResult = { value: 'stuff' };
jest.mock('json-query', () => jest.fn(() => mockJsonQueryResult));
and then in the test, I directly set another value to mockJsonQueryResult:
mockJsonQueryResult = { value: 'hotterStuff' };
But I don't find any way of doing this with a dependency that returns an object, with a dependency returning a function, no problem.
Is there even any way of doing this?
Thanks in advance!
Edit: this is not the same as how to change jest mock function return value in each test? as #Dor Shinar suggested because his problem is to mock a function, even if it is inside a returning object it is still a function, I just want to change a value inside the returned object.
So, I found a solution I'm not completely satisfied with but it works:
I simply set the original full object and then for my tests, change the value of specific properties by setting them directly before calling the function I want to test.
example:
let mockConfig = { person: { name: 'dude', origin: {country: 'France'} } };
jest.mock('../../../configManager', () => mockConfig);
mockConfig.person = {};
mockConfig.person.name = 'dudette';
You don't need to mock the module at all.
If your module export is just an object with property values then just change the properties as needed.
Here is a simple working example to demonstrate:
configManager.js
module.exports = {
name: 'original'
}
code.js
const config = require('./configManager');
module.exports = () => `name: ${config.name}`;
code.test.js
const config = require('./configManager');
const func = require('./code');
test('func', () => {
expect(func()).toBe('name: original'); // Success!
config.name = 'dude';
expect(func()).toBe('name: dude'); // Success!
config.name = 'dudette';
expect(func()).toBe('name: dudette'); // Success!
})
Details
A module binding can't be directly changed to something else:
const config = require('./configManager');
config = { name: 'mock' }; // <= this doesn't work
...but you can change the properties of an object representing a module binding:
const config = require('./configManager');
config.name = 'mock'; // <= this works!
...and any code using the module will automatically see the changes.

How to solve Converting circular structure to JSON in node?

I was watching a course that showed how to make an console.log with custom configs, like the color or depending on your env mode, you show the log or not.
But i keep getting the error TypeError: Converting circular structure to JSON
and I don't know why this is happening and how to solve it.
In the course, that works fine, but it doesn't to me.
node version => v8.11.2
require('colors')
const _ = require('lodash')
const config = require('../config/config')
const noop = () => { }
const consoleLog = config.logging ? console.log.bind(console) : noop
const logger = {
log: () => {
const args = _.toArray(arguments)
.map(arg => {
if (typeof arg === 'object') {
let str = JSON.stringify(arg, 2)
return str.magenta
} else {
arg += ''
return arg.magenta
}
})
consoleLog.apply(console, args)
}
}
module.exports = logger
Edit1: arguments can be anything, since logger will be used to log things with different colors in the console.
logger.log('some thing you want to log')
logger.log() is an arrow function, so arguments are not arguments of this function (see Arrow functions: No binding of arguments), but arguments of a parent function, in this case — Node.js wrapper function that compiles modules and has arguments with circular dependencies.
Try to use common function here:
const logger = {
log() {
// ...
}
};

How to access variable in another file in node js

I have 2 files: export.js and server.js
I'm trying to access a variable in export.js in server.js, but I get undefined.
Note that I'm using knexjs and I gave it the name 'db';
export.js
let count;
const livePending = (db) => {
db('cart')
.count('id').where('status','=','Pending')
.then(data => {
if (data[0]) {
count = data[0].count;
}
}).catch(err => res.status(400).send('DB Connection failed!'));
}
module.exports = {
livePending: livePending,
pCount: count
}
server.js
[...]
const anotherFile = require('./export');
anotherFile.livePending(db);
console.log(import.pCount);
When I try to console log inside the livePending function in export.js, I get the desired count which is 1.
The reason I'm doing this is to lessen the lines of code in my server.js.
If I do the exact same function in my server.js, I get the correct result.
I created a small node app to test a variation of your code. Two important findings:
1.
const import = require('./export');
import is reserved (as well as export). Node will throw a SyntaxError: Unexpected token if you attempt to use either one as a variable name.
2.
console.log(import.count);
In your code, you're attempting to log a variable that's already been returned. You'll notice the log statement will return undefined. Instead, create a function that you can call to get the value from the actual variable in the other file.
To make things clearer, here's a little demo to show these concepts in action.
export.js
let count;
const setCount = num => count = num;
const getCount = () => count;
// Shortcut: If the key: value is the same, we can just omit the value
module.exports = {
setCount,
getCount
}
server.js
const ex = require('./export');
ex.setCount(5);
console.log(ex.getCount()); // 5

Resources