Using Jest with Puppeteer : EReferenceError : xxx is not defined - puppeteer

when learn puppeteer/jest, many tutorial articles example are the same,like:
const timeout = 10000;
beforeAll(async () => {
await page.goto(URL, { waitUntil: "domcontentloaded" });
});
describe("Test title and header of the homepage", () => {
test("Title of the page", async () => {
const title = await page.title();
expect(title).toBe("Learn Web Development with free Classes and Tutorials - Sabe.io");
}, timeout);
test("Header of the page", async () => {
const h1Handle = await page.$("h1");
const html = await page.evaluate(h1Handle => h1Handle.innerHTML, h1Handle);
expect(html).toBe("Become a better developer");
}, timeout);
});
and the jest.config.js like:
module.exports = {
preset: "jest-puppeteer",
globals: {
URL: "https://sabe.io"
},
testMatch: [
"**/test/**/*.test.js"
],
verbose: true
}
and they all success in article, but when i run that code on local, get an error :
ProtocolError: Protocol error (Page.navigate): Invalid parameters Failed to deserialize params.url - BINDINGS: mandatory field missing at position 49
or
ReferenceError: URL is not defined
does anybody get the same error ?

it success when jest.config.js chenge like this:
module.exports = {
// preset: "jest-puppeteer", // remove that
globals: {
URL: "https://sabe.io"
},
testMatch: [
"**/test/**/*.test.js"
],
verbose: true
}

Related

Cypress - Can't use custom task in tests when separating the infra from tests

I've tried to added a new custom task to my plugins file that located outside the tested project.
I've compiled it and configured his path in the config.json as well.
All the other plugins from this file it works ok.
The error I got from Cypress during the execution is ->
"value": "CypressError: `cy.task('queryDb')` failed with the following error:\n\nThe task 'queryDb' was not handled in the plugins file. The following tasks are registered: log\n\nFix this in your plugins file here:\n./../testilize/cypress/plugins/index.ts\n at ...
The configuration file is extend to the base config file outside the tested project ->
{
"extends": "./../testilize/cypress.json",
"baseUrl": "https://www.blabla.com/",
"env": {
"client": "https://www.blabla.com/",
"server": "https://www.blabla.com/"
},
"pluginsFile": "./../testilize/cypress/plugins/index.ts",
"supportFile": "./../testilize/cypress/support/index.js",
"fixturesFolder": "e2e-tests/fixtures",
"integrationFolder": "e2e-tests/test-files"
}
plugins file ->
// cypress/plugins/index.ts
/// <reference types="cypress" />
/**
* #type {Cypress.PluginConfig}
*/
const preprocess = require('./preprocess');
const deepmerge = require('deepmerge')
const path = require('path');
require('dotenv').config({ path: './../testilize/.env' , override: true })
import { my_connection } from '../support/db-handlers/connections';
function queryTestDb(query, config) {
// start connection to db
my_connection.connect();
// exec query + disconnect to db as a Promise
return new Promise((resolve, reject) => {
my_connection.query(query, (error, results) => {
if (error) reject(error);
else {
my_connection.end();
// console.log(results)
return resolve(results);
}
});
});
}
module.exports = (on, config) => {
require('cypress-log-to-output').install(on)
on('task', {
log (message) {
console.log(message)
return true
}
})
const configJson = require(config.configFile)
if (configJson.extends) {
const baseConfigFilename = path.join(config.projectRoot, configJson.extends)
const baseConfig = require(baseConfigFilename)
console.log('merging %s with %s', baseConfigFilename, config.configFile)
configJson.env.my_db_name = process.env.my_DB_NAME;
configJson.env.my_db_host = process.env.my_DB_HOST;
configJson.env.my_db_user = process.env.my_DB_USER;
configJson.env.my_db_password = process.env.my_DB_PASSWORD;
configJson.env.my_db_port = process.env.my_DB_PORT;
return deepmerge(baseConfig, configJson);
}
on("file:preprocessor", preprocess);
on('before:browser:launch', (browser , launchOptions) => {
if (browser.name === 'chrome' && browser.isHeadless) {
launchOptions.args.push('--disable-gpu', '--no-sandbox', '--disable-dev-shm-usage', '--window-size=1920,1080');
return launchOptions
}
})
// Usage: cy.task('queryDb', query)
on('task', {
'queryDb': query => {
return queryTestDb(query, config);
}
});
return configJson
}
Test file ->
/// <reference types="./../../../testilize/node_modules/cypress" />
let allProjectIDs: any = [];
describe('Tests', () => {
it('send graphQL request for internal api', () => {
cy.task(
'queryDb',
`SELECT project_id FROM table_name LIMIT 100;`
).then(res => {
console.log(res);
allProjectIDs.push(res);
console.log(allProjectIDs);
});
});
});
Stack::
TypeScript 4.6
Node 14x
Cypress 9.6
It might be because you have two on('task', { sections in plugins.
The first one looks like the default supplied by Cypress, try commenting it out.

Adding JSON data to React

I have been able to pull data from an API that I built using MongoDB and Express, but am having trouble rendering the nested data to my React component.
For example, if I type in <p>{restaurant.cuisine}</p> I am able to retrieve Burgers, American, but if I try and access {restaurant.status.delivery}, I get an error that says:
Cannot read property 'delivery' of undefined.
But if I {console.log(restaurant.status} I can see the object? I tried turning the object into an array using Object.values, but that didn't work either.
The same thing happens if I try to access the nested objects in {restaurant.images} and {restaurant.geometry}.
Here's a copy of my React hook:
import { useReducer, useEffect } from 'react';
import axios from 'axios';
const ACTIONS = {
MAKE_REQUEST: 'make-request',
GET_DATA: 'get-data',
ERROR: 'error',
};
function reducer(state, action) {
switch (action.type) {
case ACTIONS.MAKE_REQUEST:
return { loading: true, restaurant: [] };
case ACTIONS.GET_DATA:
return {
...state,
loading: false,
restaurant: action.payload.restaurant,
};
case ACTIONS.ERROR:
return {
...state,
loading: false,
error: action.payload.error,
restaurant: [],
};
default:
return state;
}
}
export default function useFetchSingleRestaurant({ id }) {
const [state, dispatch] = useReducer(reducer, {
restaurant: [],
loading: true,
});
useEffect(() => {
dispatch({ type: ACTIONS.MAKE_REQUEST });
axios
.get('http://localhost:4444/restaurants/' + id)
.then((res) => {
dispatch({
type: ACTIONS.GET_DATA,
payload: { restaurant: res.data.restaurant },
});
})
.catch((e) => {
dispatch({
type: ACTIONS.ERROR,
payload: { error: e },
});
});
}, [id]);
return state;
}
I'm accessing it in my SingleRestaurant component:
function SingleRestaurant({ match }) {
const { restaurant } = useFetchSingleRestaurant({ id: match.params.id });
return (
<p>{restaurant.status.delivery}</p>
)
}
And then here's my backend setup as well:
showRestaurant = async (req, res) => {
const restaurant = await Restaurant.findById(req.params.id)
.populate({ path: 'reviews', populate: { path: 'author' } })
.populate('author');
if (!restaurant) {
req.flash('error', 'Restaurant not found.');
return res.redirect('/restaurants');
}
res.send({ restaurant });
};
Until your server request returns restaurant it will be set as the default [] that you have set.
An empty array does not have a property of status, so hence the error.
if you change your default to null:
const [state, dispatch] = useReducer(reducer, {
restaurant: null,
loading: true,
});
And then check for a value:
function SingleRestaurant({ match }) {
const { restaurant } = useFetchSingleRestaurant({ id: match.params.id });
if (!restaurant) return 'Loading'
return (
<p>{restaurant.status.delivery}</p>
)
}
You could also pass back the loading state from your hook and then do a check on that.

My response from api is undefined on frontend

I got list of items from my database mySql and also button 'edit'.
When I clicked edit (by id) I want to see all fields filled by data.
But I only have in my console: undefined
If I tested my api by postman it works fine.
There is how I am getting list.
{
const id = this.actRoute.snapshot.paramMap.get('id');
this.studentApi.GetStudent(id).subscribe((res: any) => {
console.log(res.data);
this.subjectArray = res.data;
console.log(this.subjectArray);
this.studentForm = this.fb.group({
id: [res.id, [Validators.required]],
domain_id: [res.domain_id, [Validators.required]],
source: [res.source, [Validators.required]],
destination: [res.destination]
});
});
}
There is my api.service.ts
GetStudent(id): Observable<any> {
const API_URL = `${this.endpoint}/read-student/${id}`;
return this.http.get(API_URL, { headers: this.headers })
.pipe(
map((res: Response) => {
return res || {};
}),
catchError(this.errorMgmt)
);
}
And there is my route
studentRoute.get('/read-student/:id', (request, response) => {
const id = request.params.id;
con.query('SELECT * FROM students WHERE id = ?', id, (error, result) => {
if (error) throw error;
response.send(result);
});
});
There is response from 'postman'
[
{
"id": 5,
"domain_id": 2,
"source": "tester0700#test.pl",
"destination": "testw#test.pl"
}
]
It seems like the response is an array, containing an object.
In that case, there is no need to use res.data, as that would imply the returned observable, res has a property named data, and that you are trying to access the value within that property. You can simply assign res to the subjectArray property. I am pretty sure res would be defined.
this.studentApi.GetStudent(id).subscribe((res: any) => {
console.log(res);
this.subjectArray = res;
// handle the rest here.
});

What's the best way to mock a nested function?

consider a function
exports.projectNotifyLaunch = (admin, functions) => {
return functions.database.ref("/projects/{pid}").onCreate(snap => {
const { title } = snap.val();
const notification = {
title: `${title} just launched!`,
body: `We just heard about a new cryptocurrency project called ${title}`
};
return admin.messaging().sendToTopic("premium", { notification });
});
};
How should I mock deeply nested functions such as
functions.database.ref("/projects/{pid}").onCreate(snap => {});
or
admin.messaging().sendToTopic("premium", { notification });
in Jest? I want to fire off the snap=>{} callback and assert against the value of notification.
I was able to make this work
This works but it's quite verbose. I'm wondering if there is a better way, or a type of testing I'm not aware of with Jest.
describe("send notification to premium users on new project", () => {
// INPUTS
const snap = {
val: () => ({
title: "Test Title"
})
};
const functions = {
database: {
ref: () => ({
onCreate: callback => callback(snap)
})
}
};
// outputs
let topicStub = null;
let notificationStub = null;
const admin = {
messaging: () => ({
sendToTopic: (topic, notification) => {
topicStub = topic;
notificationStub = notification;
}
})
};
projectNotifyLaunch(admin, functions);
test("title is correct", () => {
expect(notificationStub.notification.title).toBe(
"Test Title just launched!"
);
});
test("topic is premium", () => {
expect(topicStub).toBe("premium");
});
});

How to know when the feathers-client is connected to a service

I'm trying to test an event filter however there's a timing issue that I'm not sure how to resolve. Other than wrapping the REST request in a setTimeout, how could I get this working?
const app = require('../../src/app');
const feathers = require('feathers/client')
const socketio = require('feathers-socketio/client');
const hooks = require('feathers-hooks');
const io = require('socket.io-client');
const rp = require('request-promise');
const service = app.service('users');
let server = null;
describe('\'users\' service', () => {
beforeEach((done) => {
server = app.listen('3030');
server.once('listening', done);
});
afterEach((done) => {
server.close(done);
});
it('returns stuff #test', (done) => {
const socket = io('http://localhost:3030');
const app = feathers()
.configure(hooks())
.configure(socketio(socket));
const messageService = app.service('users');
messageService.on('created', message => {
console.log('Created a message', message);
done();
});
socket.on('connection', () => {
//
// The messageService is not connected yet
// so messages.filters.js will not fire
//
// Giving it a chance to connect with setTimeout does work...
// setTimeout(() => {
rp({
method: 'POST',
url: 'http://localhost:3030/users',
body: {
test: 'Message from REST'
},
json: true
});
// }, 500);
});
});
});
I have tried replacing the socket.on with these as well:
messageService.on('connection'
service.on('connection' (based on Node.js EventEmitter)
and so on...
Edit
I have since found service.on('newListener' works however it is being triggered many times. I need to track down the single connection:
const messageService = app.service('users');
messageService.on('created', message => {
console.log('Created a message', message);
done();
});
It's simply service.on('newListener'.
https://nodejs.org/api/events.html#events_event_newlistener
Listeners registered for the 'newListener' event will be passed the event name and a reference to the listener being added.
However, when I implemented this I found that it is listening to 5 different events. So, you need to filter those down:
service.on('newListener', (event, listener) => {
if (event === 'created') {
rp({
method: 'POST',
url: 'http://localhost:3030/users',
body: {
test: 'Message from REST'
},
json: true
});
}
});