I want to get a web-vitals metrics result from the reportWebVitals function but unable to achieve the result using playwright.
import type { ReportHandler } from 'web-vitals';
const reportWebVitals = (onPerfEntry?: ReportHandler) => {
if (onPerfEntry && onPerfEntry instanceof Function) {
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS(onPerfEntry);
getFID(onPerfEntry);
getFCP(onPerfEntry);
getLCP(onPerfEntry);
getTTFB(onPerfEntry);
});
}
};
export default reportWebVitals;
import reportWebVitals from '../src/reportWebVitals';
test.only('Performance Checks using web-vitals library', async ({ browser }) => {
const context = await browser.newContext();
const page = await context.newPage();
await page.goto(HOME_PAGE_URL, { waitUntil: 'networkidle' });
const getVitals = (args) => {
console.log('args', args);
};
await page.evaluate(async () => {
reportWebVitals(getVitals);
});
});
Related
The following code runs on every one of my requests and I'm afraid that it's trying to launch the browser every time and causing server issues on Heroku. I want to launch puppeteer like a Singleton instance where I only launch it once and then after that my requests will just trigger browser.newPage(). I'm not experienced in JS to resolve this.
(async () => {
const browser = await puppeteer.launch({ headless: true});
const page = await browser.newPage();
await page.on('response', interceptedResponse =>{
let status = interceptedResponse.status();
interceptedResponse.text()
.then((text) => {
handleResponse(text)
browser.close();
})
.catch(err => {
console.error(`interceptedResponse error: ${err}`)
browser.close();
});
});
await page.goto(url);
})();
You can create a class handling this for you. It may not be "official singleton" but id does what you want:
checkout browser.js:
var puppeteer = require('puppeteer')
class PuppeteerApi {
browser = null
constructor(config) {
this.config = config
}
setConfig(config) {
this.config = config
}
async newBrowser() {
return await puppeteer.launch(this.config)
}
async getBrowser() {
if (!this.browser) {
this.browser = await this.newBrowser()
}
return this.browser
}
async newPage() {
const browser = await this.getBrowser()
const page = await browser.newPage()
return page
}
async handBack(page) {
// close the page or even reuse it?.
await page.close()
// you could add logic for closing the whole browser instance depending what
// you want.
}
async shutdown() {
await this.browser.close()
}
}
const config = {
headless: false
}
const browserApi = new PuppeteerApi(config)
export default browserApi
// use it like:
// import and set config once!.
var browserApi = require('./browser.js')
const config = { headless: true }
browserApi.setConfig(config)
// in an request handler you could do this:
(async () => {
var page = await browserApi.newPage()
// do some stuff..
// in the end hand the page back for eitehr closing it
// or maybe putting it in a pool? .
await browser.handBack(page)
})()
I do not know the behaviour of puppeteer when for example 30 pages would be opened. Here would be an example which could open a given amount of browser instances in parallel.
var puppeteer = require('puppeteer')
class PuppeteerApi {
browsers = []
index = 0
constructor(browserLimit, config) {
this.config = config
this.browserLimit = browserLimit
if (typeof this.browserLimit !== 'number' || this.browserLimit < 1) {
throw 'BrowserLimit needs atleast to be 1!!'
}
}
setConfig(config) {
this.config = config
}
async newBrowser() {
return await puppeteer.launch(this.config)
}
async getBrowser() {
if (this.index >= this.browserLimit) {
this.index = 0
}
if (!this.browsers[this.index]) {
this.browsers[this.index] = await this.newBrowser()
}
// iterate through browsers.
return this.browsers[this.index++]
}
async newPage() {
const browser = await this.getBrowser()
const page = await browser.newPage()
return page
}
async handBack(page) {
await page.close()
}
async shutdown() {
const proms = this.browsers.map(b => b.close())
await Promise.all(proms)
}
}
const config = {
headless: false
}
const limit = 5
const browserApi = new PuppeteerApi(limit, config)
export default browserApi
If you like a functional style (which is less code), it is fastly to adapt. Here is the first example:
var puppeteer = require('puppeteer')
let browser = null
let config = {
headless: false
}
const newBrowser = async() => {
return await puppeteer.launch(this.config)
}
export const setPuppeteerConfig = (_config) => {
config = _config
}
export const getPage = async() => {
const browser = await getBrowser()
return await browser.newPage()
}
const getBrowser = async() => {
if (!browser) {
browser = await newBrowser()
}
return browser
}
export const handback = async(page) => {
await page.close()
}
export const shutdown = async() => {
await browser.close()
}
// usage:
const { setPuppeteerConfig , shutdown, getPage, handback } = require('./browser')
// setconfig..
(async () => {
const page = await getPage()
// do some stuff..
await handback(page)
})
Feel free to leave a comment if anything is not working as indendet.
I need to run this test case but currently it gets stuck in an infinite loop. This is the minimalistic code, Any suggestion is appreciated.
Test.tsx file:
it('verify useDeleteExclusions', async () => {
deleteExclusionDevices.mockResolvedValue([])
const {result} = renderHook(() => useDeleteExclusions(['1234']), {
wrapper: AllTheProviders,
})
act(() => {})
await waitFor(() => {
expect(result.current).toEqual({"errorExclusionIds": [], "loading": false, "successExclusionIds": []})
})
})
})
Hook that needs to be tested:
export function useDeleteExclusions(exclusionIds) {
const [response, setResponse] = useState<any>([])
useEffect(() => {
async function deleteExclusionDevicesAsync(exclusionIds) {
const res = await deleteExclusionDevices(exclusionIds)
}
deleteExclusionDevicesAsync(exclusionIds)
}, [exclusionIds])
return { response }
}
Api call function (used by hook):
export async function deleteExclusionDevices(exclusionIds: any): Promise<any> {
const token = await readToken()
const response = []
return response
}
Test gets stuck like this:
Existing code:
loginUser.js:
import { getUserDetails } from '../api/userDetails';
import { mapApiObjectToModel } from '../mapper/userProfileMapper';
import axios from 'axios';
export const getLoggedInUserDetails = async () => {
axios
.get('/api/getUserDetails')
.then(response => {
return mapApiObjectToModel(response);
})
.catch(err => {
console.log('error==', err);
});
};
userProfileMapper.js:
export const mapApiObjectToModel = inputObj => {
const outputObj = {};
const authorizedRoles = ['Admin'];
if (inputObj) {
outputObj.fullName = '';
if (inputObj.data) {
outputObj.fullName = inputObj.data.data;
}
outputObj.role = 'Admin';
outputObj.isAuthorized = authorizedRoles.includes(outputObj.role);
}
console.log('outputObj', outputObj);
return outputObj;
};
loginUser.test.js:
import axios from 'axios';
import getLoggedInUserDetails from '../../action/loginUser';
jest.mock('axios');
describe('routes using memory router', () => {
it('Get Admin message', async () => {
const data = 'Admin';
axios.get.mockImplementationOnce(() => Promise.resolve(data));
console.log(data);
await expect(getLoggedInUserDetails()).resolves.toEqual(data);
expect(axios.get).toHaveBeenCalledWith('/api/getUserDetails');
});
it('fetches erroneously data from an API', async () => {
const errorMessage = 'Network Error';
axios.get.mockImplementationOnce(() => Promise.reject(new Error(errorMessage)));
await expect(getLoggedInUserDetails()).rejects.toThrow(errorMessage);
});
});
I'm really new to all these, so any assistance would be appreciated. Even any suggestions on TDD for userProfileMapper.js would be appreciated :)
The mapApiObjectToModel returns an object like,
{
role: 'ADMIN',
isAuthorized: true
}
However, in your test, you are expecting it to be equal to a string 'Admin'
const data = 'Admin';
...
await expect(getLoggedInUserDetails()).resolves.toEqual(data); // Note that you have initialize data to 'Admin'
Try changing data to be an object, like,
const data = {
role: 'Admin',
isAuthorized: true
};
...
await expect(getLoggedInUserDetails()).resolves.toEqual(data);
Updated: loginUser.js:
import { getUserDetails } from '../api/userDetails';
import { mapApiObjectToModel } from '../mapper/userProfileMapper';
import axios from 'axios';
export const getLoggedInUserDetails = () => {
return axios
.get('/api/getUserDetails')
.then(response => {
return mapApiObjectToModel(response);
})
.catch(err => {
console.log('error==', err);
throw err;
});
};
You function getLoggedInUserDetails had following issues,
You were not returning the promise from the function.
You don't need async here as you are accessing expect(Promise).resolves in your test file loginUser.test.js:.
You need to throw err from catch block, if you want to test the rejection of promise or remove the catch block.
I have updated following items to the function getLoggedInUserDetails,
removed the async from export const getLoggedInUserDetails = async () => {
returned promise from axios.get('/api/getUserDetails')
added throw err to catch block
You should not mix usage of Promise.then and async/await for more information on the difference between them check here and here
Trying to mock GET request to API but always get
Timeout - Async callback was not invoked within the 10000ms timeout specified by jest.setTimeout.
even though I increased the timeout it still throws error.
Hook
export default function apiCaller() {
const [rawApiData, setRawApiData] = useState({});
const [errorMsg, setErrorMsg] = useState('');
const callApi = async (inputValue) => {
try {
const apiData= await axios.get(
`https://cloud.iexapis.com/stable/stock/market/batch?types=chart&symbols=${inputValue}&range=3m&token=lalaccf0`
);
setRawApiData(apiData);
} catch (err) {
setErrorMsg(
'Error occured!! ' +
(Boolean(err.response) ? err.response.data : err.message)
);
}
};
return { rawApiData, callApi, errorMsg };
}
Axios mock
export default {
get: jest.fn().mockResolvedValue({ data: {} }),
};
Test
import { renderHook, act } from 'react-hooks-testing-library';
import apiCaller from '../components/stock-chart/stockApiCaller';
import axios from 'axios';
jest.mock('axios');
it('should set error properly when api call is unsuccessfull because of bad data', async () => {
axios.get.mockResolvedValueOnce({ data: { test: '123' } });
const { result, waitForNextUpdate } = renderHook(() => apiCaller());
act(() => result.current.callApi('fb/tsla'));
await waitForNextUpdate();
expect(result.current.rawApiData.data.test)
.toBe(123)
}, 10000);
I finally got the issue resolved. There is new way to write act() i.e. async act(). Please find below the updated version of test which works fine.
it('should set rawData properly when api call is successfull because of', async () => {
axios.get.mockResolvedValueOnce({ data: { test: '123' } });
const { result, waitForNextUpdate } = renderHook(() => apiCaller());
await act(async () => {
result.current.callApi('fb/tsla');
await waitForNextUpdate();
});
expect(result.current.rawApiData.data.test).toBe('123');
});
Update react to 16.9.0-alpha.0
https://github.com/facebook/react/releases/tag/v16.9.0-alpha.0
I am having problem with getting the code into the beforeAll function finish and wait for the promise that resolves the storyLinks. The console log at the end of the snippet returns undefined but I need it to return the hrefs of the stories in my storybook. I cannot wrap this into an async function because of the testing pipeline being clogged on fail.
const puppeteer = require('puppeteer');
const { toMatchImageSnapshot } = require('jest-image-snapshot');
expect.extend({ toMatchImageSnapshot });
const timeout = 5000;
describe('visual tests', () => {
let page, browser, storyLinks;
const selector = `a[href*="selectedStory="]`;
beforeAll(async() => {
browser = await puppeteer.connect({browserWSEndpoint});
page = await browser.newPage();
await page.goto('http://localhost:8080');
await page.evaluate(() => {
const components = Array.from(document.querySelectorAll('div[data-name]'));
for(let i = 1; i < components.length; i++) {
components[i].addEventListener('click',() => {});
components[i].click();
}
});
storyLinks = await page.evaluate((selector) => {
const stories = Array.from(document.querySelectorAll(selector));
const links = stories.map(story => {
let href = story.href;
let name = story.text.replace(/[^A-Z0-9]/ig, '-').replace(/-{2,}/,'-');
let component = href.match(/selectedKind=(.*?)\&/).pop();
return {href: href, name: component + '-' + name};
});
return links;
}, selector);
}, timeout);
afterAll(async () => {
await page.close();
await browser.disconnect();
})
console.log(storyLinks);
}, timeout);
There's a few things I notice might be causing your issues. You need to add async to your describe block. Also, "describe" groups together multiple tests so you're missing an it or test block. Jest docs also note adding the expect.assertions(NUM_OF_ASSERTIONS); I'd do something like:
const puppeteer = require('puppeteer');
const { toMatchImageSnapshot } = require('jest-image-snapshot');
expect.extend({ toMatchImageSnapshot });
const timeout = 5000;
async function myStoryLinkTest(page) {
const selector = `a[href*="selectedStory="]`;
await page.goto('http://localhost:8080');
await page.evaluate(() => {
Array.from(document.querySelectorAll('div[data-name]'), item => {
item.addEventListener('click', () => {});
item.click();
});
});
const storyLinks = await page.evaluate(selector => {
return Array.from(document.querySelectorAll(selector), story => {
let href = story.href;
let name = story.text.replace(/[^A-Z0-9]/gi, '-').replace(/-{2,}/, '-');
let component = href.match(/selectedKind=(.*?)\&/).pop();
return { href: href, name: component + '-' + name };
});
});
return storyLinks;
}
describe('visual tests', async () => {
let page, browser;
beforeAll(async () => {
browser = await puppeteer.connect({ browserWSEndpoint });
page = await browser.newPage();
});
afterAll(async () => {
await page.close();
await browser.disconnect();
});
it('should do something with storyLinks', async () => {
expect.assertions(1);
const storyLinkResult = await myStoryLinkTest(page);
expect(storyLinkResult).toEqual('Some value you expect');
}, timeout);
});