Mock a different window.location in puppeteer - jestjs

Is there a way to tell puppeteer to use a different url when referencing window.location etc?
I am running screenshot tests using jest-storyshots-puppeteer and there are some places where the current url is printed to the screen (not my design haha), so the test returns differently depending on where they are run (ie localhost vs ciserver vs dist etc). I'd like to
I was digging through the methods available on the page object (i am already injecting some CSS to remove transitions and hide images), but don't see any way to achieve this.
const beforeScreenshot = (page, { context: { kind, story }, url }) => {
return new Promise(resolve => {
if (envConfig.includeOverrideStyles) {
page.addStyleTag({
content: overrideStyles
});
}
setTimeout(() => {
resolve();
}, envConfig.timeout);
});
};

Related

Testing Angular Services with MSW causes timeout

EDIT: I should mention, that I only have problems during testing. When I run ng serve and use msw to serve the data everything works correctly.
I stumbled upon mswjs recently and wanted to use the mock service workers to test my frontend services without waiting on the backend team and avoid having to write mock-service classes. I setup everything according to the examples provided in the documentation.
At first I got the message that stating spec 'UserService should get list of users' has no expectations.
I researched this and added a done() function call at the end of my subscribe callback. After doing that, I get the following error:
Error: Timeout - Async function did not complete within 3000ms (set by jasmine.DEFAULT_TIMEOUT_INTERVAL) in node_modules/jasmine-core/lib/jasmine-core/jasmine.js (line 7609)
I already tried increasing the default_timout in Karma but even setting it to 30.000 did not change the result.
I also tried working around by using waitForAsync without any success. This way I get no error and the test succeeds but only because it still finds no expectations within the spec.
Most example I found online do not deal with mock service workers and instead resort to using mock-services and fakeasync which does not help in my case.
This is how my code looks like:
My Angular Service:
#Injectable({
providedIn: 'root'
})
export class UserService {
private url = 'http://localhost:3000/api/users';
constructor(private http: HttpClient) { }
getUser(id: string): Observable<User> {
return this.http.get<User>(`${this.url}/${id}`);
}
listUsers(): Observable<User[]> {
return this.http.get<User[]>(this.url);
}
}
My Test Code:
describe('UserService', () => {
let service: UserService;
beforeAll(() => {
worker.start();
});
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientModule],
});
service = TestBed.inject(UserService);
});
afterAll(() => {
worker.stop();
});
it('should be created', () => {
expect(service).toBeTruthy();
});
it('should get list of users', (done) => {
service.listUsers().subscribe((data) => {
expect(data.length).toBe(5);
done();
});
})
});
The Worker setup:
const handlers = [
rest.get('http://localhost:3000/api/users', (req, res, ctx) => {
return res(
ctx.status(200),
ctx.json(users))
})
]
export const worker = setupWorker(...handlers)
I managed to solve my own problem by using firstValueFrom and waitForAsync.
I changed the code in my tests to the following:
it('should get list of users', waitForAsync(async () => {
const source$ = service.listUsers();
const result = await firstValueFrom(source$);
expect(result.length).toEqual(5);
}));

Blocking specific resources (css, images, videos, etc) using crawlee and playwright

I'm using crawlee#3.0.3 (not released yet, from github), and I'm trying to block specific resources from loading with playwrightUtils.blockRequests (which isn't available in previous versions). When I try the code suggested in the official repo, it works as expected:
import { launchPlaywright, playwrightUtils } from 'crawlee';
const browser = await launchPlaywright();
const page = await browser.newPage();
await playwrightUtils.blockRequests(page, {
// extraUrlPatterns: ['adsbygoogle.js'],
});
await page.goto('https://cnn.com');
await page.screenshot({ path: 'cnn_no_images.png' });
await browser.close();
I can see that the images aren't loaded from the screenshot. My problem has to do with the fact that I'm using PlaywrightCrawler:
const crawler = new PlaywrightCrawler({
maxRequestsPerCrawl: 3,
async requestHandler({ page, request }) {
console.log(`Processing: ${request.url}`);
await playwrightUtils.blockRequests(page);
await page.screenshot({ path: 'cnn_no_images2.png' });
},
});
This way, I'm not able to block specific resources, and my guess is that blockRequests needs launchPlaywright to work, and I don't see a way to pass that to PlaywrightCrawler.blockRequests has been available for puppeteer, so maybe someone has tried this before.
Also, i've tried "route interception", but again, I couldn't make it work with PlaywrightCrawler.
you can set any listeners or code before navigation by using preNavigationHooks like this:
const crawler = new PlaywrightCrawler({
maxRequestsPerCrawl: 3,
preNavigationHooks: [async ({ page }) => {
await playwrightUtils.blockRequests(page);
}],
async requestHandler({ page, request }) {
console.log(`Processing: ${request.url}`);
await page.screenshot({ path: 'cnn_no_images2.png' });
},
});

Chrome Extension - Monitoring network traffic with body data in background [duplicate]

It seems to be difficult problem (or impossible??).
I want to get and read HTTP Response, caused by HTTP Request in browser, under watching Chrome Extension background script.
We can get HTTP Request Body in this way
chrome.webRequest.onBeforeRequest.addListener(function(data){
// data contains request_body
},{'urls':[]},['requestBody']);
I also checked these stackoverflows
Chrome extensions - Other ways to read response bodies than chrome.devtools.network?
Chrome extension to read HTTP response
Is there any clever way to get HTTP Response Body in Chrome Extension?
I can't find better way then this anwser.
Chrome extension to read HTTP response
The answer told how to get response headers and display in another page.But there is no body info in the response obj(see event-responseReceived). If you want to get response body without another page, try this.
var currentTab;
var version = "1.0";
chrome.tabs.query( //get current Tab
{
currentWindow: true,
active: true
},
function(tabArray) {
currentTab = tabArray[0];
chrome.debugger.attach({ //debug at current tab
tabId: currentTab.id
}, version, onAttach.bind(null, currentTab.id));
}
)
function onAttach(tabId) {
chrome.debugger.sendCommand({ //first enable the Network
tabId: tabId
}, "Network.enable");
chrome.debugger.onEvent.addListener(allEventHandler);
}
function allEventHandler(debuggeeId, message, params) {
if (currentTab.id != debuggeeId.tabId) {
return;
}
if (message == "Network.responseReceived") { //response return
chrome.debugger.sendCommand({
tabId: debuggeeId.tabId
}, "Network.getResponseBody", {
"requestId": params.requestId
}, function(response) {
// you get the response body here!
// you can close the debugger tips by:
chrome.debugger.detach(debuggeeId);
});
}
}
I think it's useful enough for me and you can use chrome.debugger.detach(debuggeeId)to close the ugly tip.
sorry, mabye not helpful... ^ ^
There is now a way in a Chrome Developer Tools extension, and sample code can be seen here: blog post.
In short, here is an adaptation of his sample code:
chrome.devtools.network.onRequestFinished.addListener(request => {
request.getContent((body) => {
if (request.request && request.request.url) {
if (request.request.url.includes('facebook.com')) {
//continue with custom code
var bodyObj = JSON.parse(body);//etc.
}
}
});
});
This is definitely something that is not provided out of the box by the Chrome Extension ecosystem. But, I could find a couple of ways to get around this but both come with their own set of drawbacks.
The first way is:
Use a content script to inject our own custom script.
Use the custom script to extend XHR's native methods to read the response.
Add the response to the web page's DOM inside a hidden (not display: none) element.
Use the content script to read the hidden response.
The second way is to create a DevTools extension which is the only extension that provides an API to read each request.
I have penned down both the methods in a detailed manner in a blog post here.
Let me know if you face any issues! :)
To get a XHR response body you can follow the instructions in this answer.
To get a FETCH response body you can check Solution 3 in this article and also this answer. Both get the response body without using chrome.debugger.
In a nutshell, you need to inject the following function into the page from the content script using the same method used for the XHR requests.
const constantMock = window.fetch;
window.fetch = function() {
return new Promise((resolve, reject) => {
constantMock.apply(this, arguments)
.then((response) => {
if (response) {
response.clone().json() //the response body is a readablestream, which can only be read once. That's why we make a clone here and work with the clone
.then( (json) => {
console.log(json);
//Do whatever you want with the json
resolve(response);
})
.catch((error) => {
console.log(error);
reject(response);
})
}
else {
console.log(arguments);
console.log('Undefined Response!');
reject(response);
}
})
.catch((error) => {
console.log(error);
reject(response);
})
})
}
If response.clone().json() does not work, you can try response.clone().text()
I show my completed code if it can be some help. I added the underscore to get the request url, thanks
//background.js
import _, { map } from 'underscore';
var currentTab;
var version = "1.0";
chrome.tabs.onActivated.addListener(activeTab => {
currentTab&&chrome.debugger.detach({tabId:currentTab.tabId});
currentTab = activeTab;
chrome.debugger.attach({ //debug at current tab
tabId: currentTab.tabId
}, version, onAttach.bind(null, currentTab.tabId));
});
function onAttach(tabId) {
chrome.debugger.sendCommand({ //first enable the Network
tabId: tabId
}, "Network.enable");
chrome.debugger.onEvent.addListener(allEventHandler);
}
function allEventHandler(debuggeeId, message, params) {
if (currentTab.tabId !== debuggeeId.tabId) {
return;
}
if (message === "Network.responseReceived") { //response return
chrome.debugger.sendCommand({
tabId: debuggeeId.tabId
}, "Network.getResponseBody", {
"requestId": params.requestId
//use underscore to add callback a more argument, passing params down to callback
}, _.partial(function(response,params) {
// you get the response body here!
console.log(response.body,params.response.url);
// you can close the debugger tips by:
// chrome.debugger.detach(debuggeeId);
},_,params));
}
}
I also find there is a bug in chrome.debugger.sendCommand. If I have two requests with same URI but different arguments. such as:
requests 1:https://www.example.com/orders-api/search?limit=15&offer=0
requests 2:https://www.example.com/orders-api/search?limit=85&offer=15
The second one will not get the corrected responseBody, it will show:
Chrome Extension: "Unchecked runtime.lastError: {"code":-32000,"message":"No resource with given identifier found"}
But I debugger directly in background devtools, it get the second one right body.
chrome.debugger.sendCommand({tabId:2},"Network.getResponseBody",{requestId:"6932.574"},function(response){console.log(response.body)})
So there is no problem with tabId and requestId.
Then I wrap the chrome.debugger.sendCommand with setTimeout, it will get the first and second responseBody correctly.
if (message === "Network.responseReceived") { //response return
console.log(params.response.url,debuggeeId.tabId,params.requestId)
setTimeout(()=>{
chrome.debugger.sendCommand({
tabId: debuggeeId.tabId
}, "Network.getResponseBody", {
"requestId": params.requestId
//use underscore to add callback a more argument, passing params down to callback
}, _.partial(function(response,params,debuggeeId) {
// you get the response body here!
console.log(response.body,params.response.url);
// you can close the debugger tips by:
// chrome.debugger.detach(debuggeeId);
},_,params,debuggeeId));
},800)
}
I think the setTimeout is not the perfect solution, can some one give help?
thanks.

Axios 'GET' request is pending but it never reaches the server

I am using axios on my React app to get data from my server (Node). My GET request stays pending in chrome developer tools and does not reach the server if I refresh the app on the provided route (e.g., http://localhost:3000/category/5d68936732d1180004e516cb). However, if I redirect from the home page, it will work.
I have tried different variations of making sure I end my responses on the server end of things.
Several posts have had related problems (e.g., request not reaching the server, POST request does not reach the server) but unfortunately not been helpful in my situation.
Here is my main call in my react app:
componentDidMount() {
console.log('I am here!'); // this gets executed even on page refresh
axios.get(`/api/categories/${this.props.id}`)
.then( (res) => {
this.setState({
title: res.data.category.title,
module: res.data.category.module ? true : false,
...res.data
})
}, (err) => console.log(err))
.catch(err => console.log(err));
}
On my back end I call this function after going through user verification:
module.exports.publishedCategories = async function(req, res) {
try {
// some code that I removed for clarity
res.json({
category,
children,
flagged: flaggedCategories
});
} catch(err) {
console.log(err);
res.sendStatus(500).end();
}
}
Some more code regarding my routing:
index.js
<Route
path='/category/:id'
render={ (props) => {
return <Category id={props.match.params.id} />
}}
/>
I do not get any error messages...
I was overzealous with my initial solution of switching to componentDidUpdate(). This only worked for page refreshes but not for redirects (i.e., I had the reverse problem). My final solution is as follows:
componentDidMount = async () => {
setTimeout( async () => {
this.loadCategory();
}, 10)
}
componentDidUpdate = async (props, state) => {
if(props.match.params.id !== this.props.match.params.id) {
this.loadCategory();
return;
}
return false;
}
loadCategory = async () => {
let result = await axios.get(`/api/categories/${this.props.match.params.id}`);
this.setState({
title: result.data.category.title,
module: result.data.category.module ? true : false,
...result.data
});
}
I am not sure why adding a setTimeout() works for componentDidMount().
As per the documentation componentDidUpdate(), it is good for netork updates however you need to compare prior props with current props.
Unforunately I am not sure how to get around the setTimeout() hack but it seems to work.

Associate file extension with Electron application

When a user double clicks on a file with an extension of cmf, I would like to automatically launch the Electron application that I've built. I've been searching around and I've seen several mentions of the electron-builder but no examples of how this can be used to create this association.
You want to look at the protocol functionality. I don't have enough experience with it to understand the finer points: like which app takes precedence if multiple app register the same protocol. Some of that might be user-defined.
const { app, protocol } = require('electron')
const path = require('path')
app.on('ready', () => {
protocol.registerFileProtocol('atom', (request, callback) => {
const url = request.url.substr(7)
callback({ path: path.normalize(`${__dirname}/${url}`) })
}, (error) => {
if (error) console.error('Failed to register protocol')
})
})

Resources