Cypress: is there an event listener for notifications in the app - dom-events

In principle, the app under test could throw at any time Error notifications (usually when something is not working as it should: server side). My problem is that my cypress test does not fail over such error messages.
Is it possible to configure a listener in cypress for such events? It would basically always listen if something like a message box pops up.
Eg. listening for:
cy.contains('[data-e2e-notification-message-text]', 'ERROR: ')

You're talking about the difference between active and passive element checking.
Generally speaking, active waiting for the notification is better
cy.contains('[data-e2e-notification-message-text]', 'ERROR: ', {timeout: 7000})
.should('not.exist')
than passive waiting
cy.on('notification', (message) => { // NOT REAL CODE - for illustration
if (message.includes('ERROR: ')) {
throw 'Notification occurred' // fail the test
}
})
because the chain of events involves asynchronous call from the backend, which can vary in timing. If the test ends before the notification arrives, you get a false positive test.
You can set up an intercept if the action is request/response style, e.g
cy.intercept(...).as('notification') // listen
cy.get(button).click() // action
cy.wait('#notification') // assert
Add a stub if the live server is slow
cy.intercept(url, { notification: 'Error: ' }) // immediately fake response
cy.get(button).click() // action
cy.contains('[data-e2e-notification-message-text]', 'ERROR: ') // assert
Or don't explicitly wait
cy.intercept(url, (req) => {
req.on('response', (res) => {
if (res.body.includes('Error:')) {
throw 'Notification of error' // fail the test
}
})
cy.get(button).click() // action
But also a chance of false positive result depending on timing.
If you did have a use case for periodic checking for an element, you could tap into the command:end event.
You can only do static (jQuery) DOM querying in an event handler (no cy.() commands).
// after every command look for notification
Cypress.on('command:end', () => {
const notification = Cypress.$('[data-e2e-notification-message-text]:contains(ERROR:)')
if (notification.length) {
throw 'Notification of error happened' // fail the test
}
})
Same caveat applies - this could be flaky if the test is faster than the notification.

Related

Detecting socket.io events in an HTML game

I'm attempting to use puppeteer to detect certain socket.io events emitted by the game, such as these:
socket.on("setStartTime", socket_onSetStartTime);
socket.on("nextTurn", socket_onNextTurn);
socket.on("livesLost", socket_onLivesLost);
socket.on("bonusAlphabetCompleted", socket_onBonusAlphabetCompleted);
socket.on("setPlayerWord", socket_onSetPlayerWord);
socket.on("failWord", socket_onFailWord);
socket.on("correctWord", socket_onCorrectWord);
socket.on("happyBirthday", socket_onHappyBirthday);
Is there a way to do this with puppeteer or something else in node?
Edit: Solution was to create a CDP session, as listed here.
Example:
const cdp = await page.target().createCDPSession();
await cdp.send("Network.enable");
await cdp.send("Page.enable");
cdp.on("Network.webSocketFrameReceived", handleResponse); // Fired when WebSocket message is received.
cdp.on("Network.webSocketFrameSent", handleResponse); // Fired when WebSocket message is sent.
function handleResponse(packet) {
//do stuff with packet
}

IndexedDB flush to disk on Chrome

I'm facing an issue with IndexedDB on Chrome where I reload my page once the transaction returns a successful write.
Problem is sometimes that data does not reflect after reload. I can solve this by giving a timeout of about 100ms before reload, which leads me to believe that the data is not flushed to disk everytime.
Firexox has an experimental readwriteflush mode which ensures data is flushed to disk before returning a success call, but can't seem to find a similar one for Chrome. Any suggestions?
Here's my insert code:
const data = {type: type, value: value};
const objectStore = StorageService.db.transaction(['localData'], 'readwrite').objectStore('localData');
// readwriteflush doesn't work in chrome
// const objectStore = StorageService.db.transaction(['localData'], 'readwriteflush').objectStore('localData');
const requestSet = objectStore.put(data);
requestSet.onerror = function (event) {
alert('Error in saving data locally');
};
requestSet.onsuccess = function (event) {
console.log('Data was successfully saved locally: ' + type);
if (callback != undefined) {
callback();
}
};
The callback has location.reload = '/'; executed in it (along with some other things), so the page reloads after the onsuccess has been returned.
After the page reloads, I cannot see any data on my IndexedDB storage, both via code and on developer tools. This does not always happen however, I've observed that this happens only when data is larger than usual.
"success" fired at a request does not indicate that the transaction has committed successfully. The transaction could later fail due to a separate failed request (e.g. a conflicting add call), I/O error, or e.g. power loss.
You need to wait for the "complete" event to be fired at the transaction. Chrome flushes to disk before firing the "complete" event.

Chrome Push Notification: This site has been updated in the background

While implementing the chrome push notification, we were fetching the latest change from our server. While doing so, the service-worker is showing an extra notification with the message
This site has been updated in the background
Already tried with the suggestion posted here
https://disqus.com/home/discussion/html5rocks/push_notifications_on_the_open_web/
But could not find anything useful till now. Is there any suggestion ?
Short Answer: You should use event.waitUntil and pass a promise to it, which returns showNotification eventually. (if you have any other nested promises, you should also return them.)
I was expriencing the same issue but after a long research I got to know that this is because delay happen between PUSH event and self.registration.showNotification(). I only missed return keyword before self.registration.showNotification()`
So you need to implement following code structure to get notification:
var APILINK = "https://xxxx.com";
self.addEventListener('push', function(event) {
event.waitUntil(
fetch(APILINK).then(function(response) {
return response.json().then(function(data) {
console.log(data);
var title = data.title;
var body = data.message;
var icon = data.image;
var tag = 'temp-tag';
var urlOpen = data.URL;
return self.registration.showNotification(title, {
body: body,
icon: icon,
tag: tag
})
});
})
);
});
Minimal senario:
self.addEventListener('push', event => {
const data = event.data.json();
event.waitUntil(
// in here we pass showNotification, but if you pass a promise, like fetch,
// then you should return showNotification inside of it. like above example.
self.registration.showNotification(data.title, {
body: data.content
})
);
});
I've run into this issue in the past. In my experience the cause is generally one of three issues:
You're not showing a notification in response to the push
message. Every time you receive a push message on the device, when
you finish handling the event a notification must be left visible on
the device. This is due to subscribing with the userVisibleOnly:
true option (although note this is not optional, and not setting it
will cause the subscription to fail.
You're not calling event.waitUntil() in response to handling the event. A promise should be passed into this function to indicate to the browser that it should wait for the promise to resolve before checking whether a notification is left showing.
For some reason you're resolving the promise passed to event.waitUntil before a notification has been shown. Note that self.registration.showNotification is a promise and async so you should be sure it has resolved before the promise passed to event.waitUntil resolves.
Generally as soon as you receive a push message from GCM (Google Cloud Messaging) you have to show a push notification in the browser. This is mentioned on the 3rd point in here:
https://developers.google.com/web/updates/2015/03/push-notificatons-on-the-open-web#what-are-the-limitations-of-push-messaging-in-chrome-42
So it might happen that somehow you are skipping the push notification though you got a push message from GCM and you are getting a push notification with some default message like "This site has been updated in the background".
This works, just copy/paste/modify. Replace the "return self.registration.showNotification()" with the below code. The first part is to handle the notification, the second part is to handle the notification's click. But don't thank me, unless you're thanking my hours of googling for answers.
Seriously though, all thanks go to Matt Gaunt over at developers.google.com
self.addEventListener('push', function(event) {
console.log('Received a push message', event);
var title = 'Yay a message.';
var body = 'We have received a push message.';
var icon = 'YOUR_ICON';
var tag = 'simple-push-demo-notification-tag';
var data = {
doge: {
wow: 'such amaze notification data'
}
};
event.waitUntil(
self.registration.showNotification(title, {
body: body,
icon: icon,
tag: tag,
data: data
})
);
});
self.addEventListener('notificationclick', function(event) {
var doge = event.notification.data.doge;
console.log(doge.wow);
});
I was trying to understand why Chrome has this requirement that the service worker must display a notification when a push notification is received. I believe the reason is that push notification service workers continue to run in the background even after a user closes the tabs for the website. So in order to prevent websites from secretly running code in the background, Chrome requires that they display some message.
What are the limitations of push messaging in Chrome?
...
You have to show a notification when you receive a push message.
...
and
Why not use Web Sockets or Server-Sent Events (EventSource)?
The advantage of using push messages is that even if your page is closed, your service worker will be woken up and be able to show a notification. Web Sockets and EventSource have their connection closed when the page or browser is closed.
If you need more things to happen at the time of receiving the push notification event, the showNotification() is returning a Promise. So you can use the classic chaining.
const itsGonnaBeLegendary = new Promise((resolve, reject) => {
self.registration.showNotification(title, options)
.then(() => {
console.log("other stuff to do");
resolve();
});
});
event.waitUntil(itsGonnaBeLegendary);
i was pushing notification twice, once in the FCM's onBackgroundMessage()
click_action: "http://localhost:3000/"
and once in self.addEventListener('notificationclick',...
event.waitUntil(clients.matchAll({
type: "window"
}).then...
so i commented click_action, ctrl+f5 to refresh browsers and now it works normal

Returning promise from reflux store

I'm working on my first react/reflux app so I may be approaching this problem in completely the wrong way. I'm trying to return a promise from a reflux store's action handler. This is the minimum code that represents how I'm trying to do this. If I display this in the browser, I get an error saying that the promise is never caught, because the result of the onLogin function is not passed back when the action is initiated. What is the best way to do this?
var Reflux = require('reflux');
var React = require('react/addons')
const Action = Reflux.createAction();
const Store = Reflux.createStore({
init: function() {
this.listenTo(Action, this.onAction);
},
onAction: function(username, password) {
var p = new Promise((resolve, reject) => {
reject('Bad password');
});
return p;
}
});
var LoginForm = React.createClass({
mixins: [Reflux.connect(Store, 'store')],
login: function() {
Action('nate', 'password1').catch(function(e) {
console.log(e); // This line is never executed
});
},
render: function() {
return (
<a onClick={this.login} href="#">login</a>
)
}
});
React.render(<LoginForm />, document.body);
Several things seem a bit confused here.
Reflux.connect(Store, 'store') is a shorthand for listening to the provided store, and automatically set the "store" property of your component state to whatever is passed in your store's this.trigger() call. However, your store never calls this.trigger so "store" in your component's state will never be updated. Returning a value from your store's action handlers doesn't trigger an update.
Stores should listen to actions to update their internal state, and typically then broadcast this state update by calling this.trigger. No component is going to get your returned promise from the store's onAction unless it explicitly calls Store.onAction (and then it doesn't matter if the actual action was invoked or not).
Async work should typically happen in the action's preEmit hook, not in the store. You should then also declare the action as async in createAction by setting the asyncResult option to true to automatically create "completed" and "failed" child actions. Check out the Reflux documentation here to learn about async events. Async actions automatically return promises, whose resolve and reject are called when the "completed" and "failed" sub-actions are called respectively. This is a bit opinionated, but that is definitely what I perceive is the intended Reflux way.

clients.openWindow() "Not allowed to open a window." on a serviceWorker Google Chrome

I'm testing under Chrome Version 42.0.2311.152m and I want to implement to open a window on a notificationclick like in this example: (source: https://developer.mozilla.org/en-US/docs/Web/API/WindowClient
)
self.addEventListener('notificationclick', function(event) {
console.log('On notification click: ', event.notification.tag);
event.notification.close();
// This looks to see if the current is already open and
// focuses if it is
event.waitUntil(clients.matchAll({
type: "window"
}).then(function(clientList) {
for (var i = 0; i < clientList.length; i++) {
var client = clientList[i];
if (client.url == '/' && 'focus' in client)
return client.focus();
}
if (clients.openWindow)
return clients.openWindow('/');
}));
});
My filestructure is like:
https://myurl.no-ip.org/app/index.html
https://myurl.no-ip.org/app/manifest.json
https://myurl.no-ip.org/app/service-worker.js
I have the issue that I always get an
InvalidAccessError
when calling clients.openWindow('/') or clients.openWindow('https://myurl.no-ip.org/app/index.html') in the service-worker.js, I receive the error:
{code: 15,
message: "Not allowed to open a window.",
name: "InvalidAccessError"}
The "return client.focus()" line is never reached because the client.url is never just '/'.
Looking at
clients.matchAll({type: "window"})
.then(function (clientList) {
console.log(clientList[0])});
I see my current WindowClient:
{focused: false,
frameType: "top-level",
url: "https://myurl.no-ip.org/app/index.html",
visibilityState: "hidden" }
The properties 'focused' and 'visibilityState' are correct and change correctly.
By doing a manual focus call
clients.matchAll({type: "window"})
.then(function (clientList) {
clientList[0].focus()});
I receive the error:
{code: 15,
message: "Not allowed to focus a window.",
name: "InvalidAccessError"}
I think the problem is that url is not just '/'. Do you have any ideas for that?
Thank you very much!
Best regards
Andi
Your code works fine for me, so I'll explain the requirements for using openWindow / focus, and how you can avoid the "Not allowed to [open|focus] a window" error message.
clients.openWindow() and windowClient.focus() are only allowed after clicking the notification (in Chrome 47 at least), and at most one of these methods can be called, for the duration of the click handler. This behavior was specified in https://github.com/slightlyoff/ServiceWorker/issues/602.
If your openWindow / focus call is rejected with error message
"Not allowed to open a window." for openWindow
"Not allowed to focus a window." for focus
then you didn't satisfy the requirements of openWindow / focus. For example (all points also apply to focus, not just openWindow).
openWindow was called while the notification wasn't clicked.
openWindow was called after the notificationclick handler returned, and you did not call event.waitUntil with a promise.
openWindow was called after the promise passed to event.waitUntil was resolved.
The promise was not resolved, but it took "too long" (10 seconds in Chrome), so the temporary permission to call openWindow expired.
It is really necessary that openWindow / focus is called at most once, and before the notificationclick handler finishes.
As I said before, the code in the question works, so I'll show another annotated example.
// serviceworker.js
self.addEventListener('notificationclick', function(event) {
// Close notification.
event.notification.close();
// Example: Open window after 3 seconds.
// (doing so is a terrible user experience by the way, because
// the user is left wondering what happens for 3 seconds.)
var promise = new Promise(function(resolve) {
setTimeout(resolve, 3000);
}).then(function() {
// return the promise returned by openWindow, just in case.
// Opening any origin only works in Chrome 43+.
return clients.openWindow('https://example.com');
});
// Now wait for the promise to keep the permission alive.
event.waitUntil(promise);
});
index.html
<button id="show-notification-btn">Show notification</button>
<script>
navigator.serviceWorker.register('serviceworker.js');
document.getElementById('show-notification-btn').onclick = function() {
Notification.requestPermission(function(result) {
// result = 'allowed' / 'denied' / 'default'
if (result !== 'denied') {
navigator.serviceWorker.ready.then(function(registration) {
// Show notification. If the user clicks on this
// notification, then "notificationclick" is fired.
registration.showNotification('Test');
});
}
});
}
</script>
PS. Service workers are still in development, so it's worth mentioning that I've verified that the above remarks are correct in Chrome 49, and that the example works in Chrome 43+ (and opening / instead of https://example.com also works in Chrome 42).
This worked for me
You should define Promise that will fire when your operation is finished.
Example bellow shows how to return chained Promise
First Promise returns list of windows. If it's not empty we are focusing one and returning Promise.resolve() - which is resolve immediately.
If no windows found we are returning next chained Promise - first on opens new window second tries to focus it.
addEventListener('notificationclick', (event) => {
console.log('---- notification clicked ----')
console.log(event)
//using notification data to constract specific path
const data = event.notification.data
console.log(data)
let url = 'https://exmaple.com'
if(data){
url += data['business'] ?
`/business/messages/${data['respondent']}` :
`/store/${data['respondent']}/questions`
}
console.log('new window url: ' + url)
event.notification.close()
//event should wait until we done
event.waitUntil(
//do we have some windows of our app?
self.clients.matchAll({includeUncontrolled: true, type: 'window'})
.then(list=>{
console.log('total clients: '+list.length)
if(list.length === 0){
//no windows of our app. We will open new one
console.log('no clients found')
return self.clients.openWindow(url)
.then((windowClient) => {
//we should focus new window and return Promise to terminate our event
return windowClient ? windowClient.focus() : Promise.resolve()
})
}
const client = list[0]
console.log(client)
//we have a window of our app. Let's focus it and return Promise
client.focus()
console.log('--window focused---')
return Promise.resolve()
}))
})