Stubbing of "watchPosition" in cypress - javascript

I try to inject position changes to an JS/REACT-Application. The Application registering at window.navigator.geolocation.watchPosition. My idea is to stub the "watchPosition" method to get a handle on the callback function. Then calling the callback function from the application directly.
Like:
const watchPositionFake = (successCallback, errorCallback, options) => {
console.debug("PROXY set callback watchPosition");
originalWatchPositionSuccessCallback = successCallback;
};
cy.visit("/", {
onBeforeLoad(win) {
cy.stub(win.navigator.geolocation, "watchPosition").callsFake(watchPositionFake);
}
});
This doesn't work with function registering in the Application on the watchPosition. But this does work with function in the cypress-step file. (Working as in in the console.log I see changes in position according to the values I send in via originalWatchPositionSuccessCallback ).
Any idea who to fake a position change?

There is a different way to solve the issue of getting the callbacks of the registered function to navigator.geolocation.watchPosition triggered. The code in the question tried to solve this by cy.stub(win.navigator.geolocation, "watchPosition"), but this turned to be not working reliably (too soon, too late, different browser/window context, another iframe, ...), the precise reason varied.
An alternative solution to trigger the registered watchPosition callbacks without modifying the production code is the undocumented cypress (v6.2) automation interface in cypress to CDP.
export const setFakePosition = position => {
// https://chromedevtools.github.io/devtools-protocol/tot/Emulation/#method-setGeolocationOverride
console.debug(`cypress::setGeolocationOverride with position ${JSON.stringify(position)}`);
cy.log("**setGeolocationOverride**").then(() =>
Cypress.automation("remote:debugger:protocol", {
command: "Emulation.setGeolocationOverride",
params: {
latitude: position.latitude,
longitude: position.longitude,
accuracy: 50
}
})
);
};
And verifying with:
let positionLogSpy;
When("vehicle is located in {string}", city => {
const position = cityLocationMap[city];
cy.window()
.then(win => {
const expectedLogMessage = `new position lat: ${position.latitude}, lng: ${position.longitude}`;
positionLogSpy = cy.spy(win.console, "log").withArgs(expectedLogMessage);
})
.then(() => {
setFakePosition(position);
});
});
Then("vehicle has moved to {string}", () => {
expect(positionLogSpy).to.be.called;
});

Related

How to mock/simulate in the jest test PubNub event which added in pubnub.addListener?

I have a package that uses PubNub, I', try to cover all package files with jest tests, but I have a problem: I can't find the way to cover events inside the listener
// add listener
const listener = {
// Need to cover these cases (status and message)
status: (statusEvent) => {
if (statusEvent.category === "PNConnectedCategory") {
console.log("Connected");
}
},
message: (messageEvent) => {
// Process message
}
};
this.pubnub.addListener(listener);
this.pubnub.subscribe({
channels: [this.channel]
});
I attached a screen with the part which I need to cover test
[![uncovered file part][1]][1]
How to mock/simulate in the jest test PubNub event which added in pubnub.addListener?
describe("publishPubNub test suites", () => {
const sideEffect = function (options) {
pubnubService.publishPubNub(options);
return true;
}
it("successfull", () => {
//TODO: mock event here
const isCompleted = sideEffect(publishPubNubOptions)
expect(isCompleted).toBeTruthy();
});
})
Thanks for any helps or advice.
[1]: https://i.stack.imgur.com/tF3c2.png
The listener status handler will be invoked whenever a connection is established (or some other connection event happens). The message handler will be invoked whenever your application receives a message that it has previously subscribed to.
You could either:
Test your application against a real PubNub instance, though that would require an Internet connection.
Create a mocked library. PubNub does not offer an official mocked library so you would need to roll your own. Something like the following based on your image:
'use strict';
class PubNub {
constructor(pubKey, subKey, uniqueId) {
this.listener = {}
}
addListener(listener) {
this.listener = listener;
}
subscribe(channelsObj) {
this.listener.status({"category": "PNConnectedCategory"})
}
publishPubNub(options) {
this.listener.message({"message": {"request": {"decision": "approved"}}})
}
}
module.exports = PubNub;

Cypress not intercepting request with render function vue

I'm trying to do a pretty simple intercept in Cypress using a Vue's application. My component has a setup method using render function as such:
setup() {
useInfiniteLoading({ runner: ... })
}
Then on my tests I do the following:
describe("List todo resource", () => {
it("Checks it loads more todos when scrolling to the bottom", function () {
cy.intercept('/todo').as('getTodos');
cy.visit("/todos");
cy.wait("#getTodos").then(({response}) => {
console.log(response);
})
})
})
When running the test I see that the intercept is not stubbing the response.
As you can see from the image the request makes a request to my actual server running locally and the response is stubed. The weird part is that in a previous test I have:
it("Checks the todo list gets updated when clicking on to resolve it (from true to false)", function () {
cy.visit("/todos");
const resolved = false;
const shouldHaveClass = resolved
? "mdi-checkbox-marked-outline"
: "mdi-checkbox-blank-outline";
cy.intercept("GET", "todo", {
fixture: "resources/todo/list.todo.json",
}).as("getTodos");
cy.intercept("PUT", "todo", {
body: { data: { ...this.updateTodoFixture.data, resolved } },
}).as("updateTodo");
cy.get(".todo-list-item__resolve")
.first()
.each((btn) => {
btn.click();
});
cy.get(".todo-list-item__resolve")
.first()
.should("satisfy", ($el) => {
const classList = Array.from($el[0].classList);
return classList.includes(shouldHaveClass);
});
});
And the response is stubbed using intercept as you can see from the previous screenshot. Is it possible that the previous test is affecting the next test? I have tried taking a look into "Intercept too soon" but no luck on trying to apply the fix described in the page.
Any idea on what could be causing the stub not to happen?

Testing Cross Browser Extension With Jest, How To Mock Chrome Storage API?

After putting off testing for a while now due to Cypress not allowing visiting chrome:// urls, I decided to finally understand how to unit/integration test my extension - TabMerger. This comes after the many times that I had to manually test the ever growing functionality and in some cases forgot to check a thing or two. Having automated testing will certainly speed up the process and help me be more at peace when adding new functionality.
To do this, I chose Jest since my extension was made with React (CRA). I also used React Testing Library (#testing-library/react) to render all React components for testing.
As I recently made TabMerger open source, the full testing script can be found here
Here is the test case that I want to focus on for this question:
import React from "react";
import { render, fireEvent } from "#testing-library/react";
import * as TabFunc from "../src/Tab/Tab_functions";
import Tab from "../src/Tab/Tab";
var init_groups = {
"group-0": {
color: "#d6ffe0",
created: "11/12/2020 # 22:13:24",
tabs: [
{
title:
"Stack Overflow - Where Developers Learn, Share, & Build Careersaaaaaaaaaaaaaaaaaaaaaa",
url: "https://stackoverflow.com/",
},
{
title: "lichess.org • Free Online Chess",
url: "https://lichess.org/",
},
{
title: "Chess.com - Play Chess Online - Free Games",
url: "https://www.chess.com/",
},
],
title: "Chess",
},
"group-1": {
color: "#c7eeff",
created: "11/12/2020 # 22:15:11",
tabs: [
{
title: "Twitch",
url: "https://www.twitch.tv/",
},
{
title: "reddit: the front page of the internet",
url: "https://www.reddit.com/",
},
],
title: "Social",
},
};
describe("removeTab", () => {
it("correctly adjusts groups and counts when a tab is removed", () => {
var tabs = init_groups["group-0"].tabs;
const { container } = render(<Tab init_tabs={tabs} />);
expect(container.getElementsByClassName("draggable").length).toEqual(3);
var removeTabSpy = jest.spyOn(TabFunc, "removeTab");
fireEvent.click(container.querySelector(".close-tab"));
expect(removeTabSpy).toHaveBeenCalledTimes(1);
expect(container.getElementsByClassName("draggable").length).toEqual(2); // fails (does not remove the tab for some reason)
});
});
I mocked the Chrome API according to my needs, but feel that something is missing. To mock the Chrome API I followed this post (along with many others, even for other test runners like Jasmine): testing chrome.storage.local.set with jest.
Even though the Chrome storage API is mocked, I think the issue lies in this function which gets called upon initial render. That is, I think the chrome.storage.local.get is not actually being executed, but am not sure why.
// ./src/Tab/Tab_functions.js
/**
* Sets the initial tabs based on Chrome's local storage upon initial render.
* If Chrome's local storage is empty, this is set to an empty array.
* #param {function} setTabs For re-rendering the group's tabs
* #param {string} id Used to get the correct group tabs
*/
export function setInitTabs(setTabs, id) {
chrome.storage.local.get("groups", (local) => {
var groups = local.groups;
setTabs((groups && groups[id] && groups[id].tabs) || []);
});
}
The reason I think the mocked Chrome storage API is not working properly is because when I manually set it in my tests, the number of tabs does not increase from 0. Which forced me to pass a prop (props.init_tabs) to my Tab component for testing purposes (https://github.com/lbragile/TabMerger/blob/f78a2694786d11e8270454521f92e679d182b577/src/Tab/Tab.js#L33-L35) - something I want to avoid if possible via setting local storage.
Can someone point me in the right direction? I would like to avoid using libraries like jest-chrome since they abstract too much and make it harder for me to understand what is going on in my tests.
I think I have a solution for this now, so I will share with others.
I made proper mocks for my chrome storage API to use localStorage:
// __mocks__/chromeMock.js
...
storage: {
local: {
...,
get: function (key, cb) {
const item = JSON.parse(localStorage.getItem(key));
cb({ [key]: item });
},
...,
set: function (obj, cb) {
const key = Object.keys(obj)[0];
localStorage.setItem(key, JSON.stringify(obj[key]));
cb();
},
},
...
},
...
Also, to simulate the tab settings on initial render, I have a beforeEach hook which sets my localStorage using the above mock:
// __tests__/Tab.spec.js
var init_ls_entry, init_tabs, mockSet;
beforeEach(() => {
chrome.storage.local.set({ groups: init_groups }, () => {});
init_ls_entry = JSON.parse(localStorage.getItem("groups"));
init_tabs = init_ls_entry["group-0"].tabs;
mockSet = jest.fn(); // mock for setState hooks
});
AND most importantly, when I render(<Tab/>), I noticed that I wasn't supplying the id prop which caused nothing to render (in terms of tabs from localStorage), so now I have this:
// __tests__/Tab.spec.js
describe("removeTab", () => {
it("correctly adjusts storage when a tab is removed", async () => {
const { container } = render(
<Tab id="group-0" setTabTotal={mockSet} setGroups={mockSet} />
);
var removeTabSpy = jest.spyOn(TabFunc, "removeTab");
var chromeSetSpy = jest.spyOn(chrome.storage.local, "set");
fireEvent.click(container.querySelector(".close-tab"));
await waitFor(() => {
expect(chromeSetSpy).toHaveBeenCalled();
});
chrome.storage.local.get("groups", (local) => {
expect(init_tabs.length).toEqual(3);
expect(local.groups["group-0"].tabs.length).toEqual(2);
expect(removeTabSpy).toHaveBeenCalledTimes(1);
});
expect.assertions(4);
});
});
Which passes!!
Now on to drag and drop testing 😊

Immersive Reader onPreferencesChanged event not firing as preferences are changing

In this javascript code which calls the immersive reader SDK, the onExit fires as expected, but the onPreferencesChanged never fires. What am I missing here?
async function LaunchImmersiveReader(title) {
const data = {
title: title,
chunks: [{
content: title,
lang: 'en'
}]
};
const token = await getImmersiveReaderTokenAsync();
const subdomain = await getImmersiveReaderSubdomainAsync();
const options = {
onPreferencesChanged: onPreferencesChangedCallback,
onExit: exitCallback
};
ImmersiveReader.launchAsync(token, subdomain, data, options);
}
function exitCallback() {
// this fires as I click the immersive reader exit/back button
}
function onPreferencesChangedCallback(value) {
// this never fires as I change font, size, etc.
}
What version of the SDK are you using? The preferences callback is part of 1.1.0
I did notice that a lot of our sample projects and documentation is using 1.0.0 still, which might be leading to the confusion

RxJS: Observable.webSocket() get access to onopen, onclose…

const ws = Observable.webSocket('ws://…');
ws.subscribe(
message => console.log(message),
error => console.log(error),
() => {},
);
I want to observe my WebSocket connection with RxJS. Reacting to onmessage events by subscribing to the observable works like a charm. But how can I access the onopen event of the WebSocket? And is it possible to trigger the WebSocket .close() method? RxJS is pretty new to me and I did research for hours, but maybe I just don't know the right terms. Thanks in advance.
Looking at the sourcecode of the Websocket there is a config object WebSocketSubjectConfig which contains observables which you can link to different events. You should be able to pass a NextObserver typed object to config value openObserver like so:
const openEvents = new Subject<Event>();
const ws = Observable.webSocket({
url: 'ws://…',
openObserver: openEvents
});
openEvents
.do(evt => console.log('got open event: ' + evt))
.merge(ws.do(msg => console.log('got message: ' + msg))
.subscribe();
The link of #Mark van Straten to the file is dead. The updated link is here. I also wanted to highlight the usage as suggested in the docs. A in my opinion better copy and paste solution to play around with:
import { webSocket } from "rxjs/webSocket";
webSocket({
url: "wss://echo.websocket.org",
openObserver: {
next: () => {
console.log("connection ok");
},
},
closeObserver: {
next(closeEvent) {
// ...
}
},
}).subscribe();

Categories

Resources