From e8cc96329b5bf5d3394246d91c1038133219490f Mon Sep 17 00:00:00 2001 From: Anton Lykhoyda Date: Fri, 25 Nov 2022 10:48:25 +0100 Subject: [PATCH] Cleanup class based emitter --- src/metamask/index.ts | 4 - src/snap/NotificationsEmitter.ts | 33 ++++----- src/snap/invokeNotification.ts | 48 ------------ src/snap/notificationEmitter.ts | 46 ------------ src/snap/waitForNotification.ts | 28 ------- src/types.ts | 16 +--- test/flask/snaps.spec.ts | 122 ++----------------------------- 7 files changed, 22 insertions(+), 275 deletions(-) delete mode 100644 src/snap/invokeNotification.ts delete mode 100644 src/snap/notificationEmitter.ts delete mode 100644 src/snap/waitForNotification.ts diff --git a/src/metamask/index.ts b/src/metamask/index.ts index 09afde97..06f35a1f 100644 --- a/src/metamask/index.ts +++ b/src/metamask/index.ts @@ -5,8 +5,6 @@ import { DappeteerPage } from "../page"; import { acceptDialog } from "../snap/acceptDialog"; import { rejectDialog } from "../snap/rejectDialog"; import { getAllNotifications, installSnap, invokeSnap } from "../snap"; -import { invokeNotification } from "../snap/invokeNotification"; -import { waitForNotification } from "../snap/waitForNotification"; import { addNetwork } from "./addNetwork"; import { addToken } from "./addToken"; import { approve } from "./approve"; @@ -58,8 +56,6 @@ export const getMetaMask = (page: DappeteerPage): Promise => { }, snaps: { invokeSnap, - waitForNotification, - invokeNotification: invokeNotification(page), getAllNotifications: getAllNotifications(page), acceptDialog: acceptDialog(page), rejectDialog: rejectDialog(page), diff --git a/src/snap/NotificationsEmitter.ts b/src/snap/NotificationsEmitter.ts index 3c77dd6d..57922b19 100644 --- a/src/snap/NotificationsEmitter.ts +++ b/src/snap/NotificationsEmitter.ts @@ -1,39 +1,32 @@ import { StrictEventEmitter } from "strict-event-emitter"; -// eslint-disable-next-line @typescript-eslint/no-require-imports -import pEvent = require("p-event"); import { DappeteerPage } from "../page"; import * as dappeteer from "../../src"; import { clickOnElement, openProfileDropdown } from "../helpers"; import { NotificationItem, NotificationList } from "./types"; +// eslint-disable-next-line @typescript-eslint/no-require-imports +import pEvent = require("p-event"); interface EventsMap { - newNotification: (notification: NotificationItem) => void; + notification: (notification: NotificationItem) => void; } class NotificationsEmitter extends StrictEventEmitter { - private notifications: NotificationList = []; private notificationsTab: DappeteerPage; - private readonly emitter: StrictEventEmitter; private readonly page: DappeteerPage; - constructor(protected metamask: dappeteer.Dappeteer) { + constructor( + protected metamask: dappeteer.Dappeteer, + private notificationTimeout: number = 30000 + ) { super(); this.page = metamask.page; - this.emitter = new StrictEventEmitter(); - this.configureEmitterListener(); - } - - private configureEmitterListener(): void { - this.emitter.on("newNotification", (notification: NotificationItem) => { - this.notifications.push(notification); - }); } private async exposeEmitNotificationToWindow(): Promise { await this.notificationsTab.exposeFunction( "emitNotification", (notification: NotificationItem) => { - this.emitter.emit("newNotification", notification); + this.emit("notification", notification); } ); } @@ -46,7 +39,9 @@ class NotificationsEmitter extends StrictEventEmitter { const newPage = await this.page.browser().newPage(); await newPage.goto(this.page.url()); - await newPage.waitForSelector(".notifications__container"); + await newPage.waitForSelector(".notifications__container", { + timeout: this.notificationTimeout, + }); this.notificationsTab = newPage; } @@ -78,11 +73,11 @@ class NotificationsEmitter extends StrictEventEmitter { } public waitForNotification(): pEvent.CancelablePromise { - return pEvent(this.emitter, "newNotification"); + return pEvent(this, "notification"); } - public getAllNotifications(): NotificationList { - return this.notifications; + public async getAllNotifications(): Promise { + return await this.metamask.snaps.getAllNotifications(); } } diff --git a/src/snap/invokeNotification.ts b/src/snap/invokeNotification.ts deleted file mode 100644 index f212a1e3..00000000 --- a/src/snap/invokeNotification.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { DappeteerPage, Serializable } from "../page"; -import { clickOnElement, openProfileDropdown } from "../helpers"; -import { invokeSnap } from "./invokeSnap"; -import { NotificationItem } from "./types"; - -async function waitForNotification( - page: DappeteerPage -): Promise { - await page.waitForSelector(".notifications__container"); - return await page.evaluate( - () => - new Promise((resolve) => { - const observer = new MutationObserver((mutations) => { - for (const mutation of mutations) { - if (mutation.addedNodes.length) { - const element = Array.from(mutation.addedNodes)[0] as HTMLElement; - observer.takeRecords(); - observer.disconnect(); - resolve({ message: element.innerText }); - } - } - }); - observer.observe(document.querySelector(".notifications__container"), { - childList: true, - }); - }) - ); -} - -export const invokeNotification = - (page: DappeteerPage) => - async ( - testPage: DappeteerPage, - snapId: string, - method: string, - params?: P - ): Promise => { - await page.bringToFront(); - await openProfileDropdown(page); - await clickOnElement(page, "Notifications"); - - const newPage = await page.browser().newPage(); - await newPage.goto(page.url()); - - await invokeSnap(testPage, snapId, method, params); - - return await waitForNotification(page); - }; diff --git a/src/snap/notificationEmitter.ts b/src/snap/notificationEmitter.ts deleted file mode 100644 index 4717937c..00000000 --- a/src/snap/notificationEmitter.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { EventEmitter } from "events"; -import { DappeteerPage } from "../page"; -import { clickOnElement, openProfileDropdown } from "../helpers"; -import { NotificationItem, NotificationList } from "./types"; - -export const notificationEmitter = async ( - page: DappeteerPage -): Promise<{ emitter: EventEmitter; notifications: NotificationList }> => { - const notifications: NotificationList = []; - const emitter: EventEmitter = new EventEmitter(); - - emitter.on("newNotification", (notification: NotificationItem) => { - notifications.push(notification); - }); - - await page.bringToFront(); - await openProfileDropdown(page); - await clickOnElement(page, "Notifications"); - - const newPage = await page.browser().newPage(); - await newPage.goto(page.url()); - - await newPage.waitForSelector(".notifications__container"); - await newPage.exposeFunction( - "emitNotification", - (notification: NotificationItem) => { - emitter.emit("newNotification", notification); - } - ); - await newPage.evaluate(() => { - const observer = new MutationObserver((mutations) => { - for (const mutation of mutations) { - if (mutation.addedNodes.length) { - const element = mutation.addedNodes[0] as HTMLElement; - window.emitNotification({ message: element.innerText }); - observer.disconnect(); - } - } - }); - observer.observe(document.querySelector(".notifications__container"), { - childList: true, - }); - }); - - return { emitter, notifications }; -}; diff --git a/src/snap/waitForNotification.ts b/src/snap/waitForNotification.ts deleted file mode 100644 index f35ab0ce..00000000 --- a/src/snap/waitForNotification.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { DappeteerPage } from "../page"; -import { NotificationItem } from "./types"; - -export async function waitForNotification( - page: DappeteerPage -): Promise { - await page.waitForSelector(".notifications__container"); - - const notification = await page.evaluate( - () => - new Promise((resolve) => { - const observer = new MutationObserver((mutations) => { - for (const mutation of mutations) { - if (mutation.addedNodes.length) { - const element = mutation.addedNodes[0] as HTMLElement; - observer.disconnect(); - resolve({ message: element.innerText }); - } - } - }); - observer.observe(document.querySelector(".notifications__container"), { - childList: true, - }); - }) - ); - - return notification; -} diff --git a/src/types.ts b/src/types.ts index 5c62110d..81822776 100644 --- a/src/types.ts +++ b/src/types.ts @@ -75,23 +75,9 @@ export type Dappeteer = { page: DappeteerPage; snaps: { /** - * Returns all notifications in Metamask notifications page + * Returns all notifications in Metamask notifications page and go back to main page */ - waitForNotification: (page: DappeteerPage) => Promise; getAllNotifications: () => Promise; - /** Invoke notification snap method. The function will open a notification - page, invoke the snap, and it will wait until notification is rendered - in the DOM. Can be used in pair with getAllNotifications method f.e: - await metamask.snaps.invokeNotification(page, "snapid", "method_name") - const notifications = await metamask.snaps.getAllNotifications() - expect(notifications[0].message).to.equal("Notification message") - */ - invokeNotification: ( - page: DappeteerPage, - snapId: string, - method: string, - params?: Params - ) => Promise; /** * Invoke Metamask snap method. Function will throw if there is an error while invoking snap. * Use generic params to override result and parameter types. diff --git a/test/flask/snaps.spec.ts b/test/flask/snaps.spec.ts index af1c5d51..9c6a90ec 100644 --- a/test/flask/snaps.spec.ts +++ b/test/flask/snaps.spec.ts @@ -1,13 +1,9 @@ // eslint-disable-next-line @typescript-eslint/no-require-imports -import pEvent = require("p-event"); import { expect } from "chai"; import * as dappeteer from "../../src"; import { DappeteerPage } from "../../src"; import { TestContext } from "../constant"; import { Snaps } from "../deploy"; -import { notificationEmitter } from "../../src/snap/notificationEmitter"; -import { NotificationItem } from "../../src/snap/types"; -import { clickOnElement, openProfileDropdown } from "../../src/helpers"; import NotificationsEmitter from "../../src/snap/NotificationsEmitter"; describe("snaps", function () { @@ -46,6 +42,7 @@ describe("snaps", function () { }); describe("should test snap methods", function () { + let Notifications: NotificationsEmitter; let testPage: DappeteerPage; let snapId: string; @@ -68,12 +65,16 @@ describe("snaps", function () { hasKeyPermissions: false, } ); - + Notifications = new NotificationsEmitter(metamask); testPage = await metamask.page.browser().newPage(); await testPage.goto("https://google.com"); return testPage; }); + after(async () => { + await Notifications.cleanup(); + }); + it("should invoke provided snap method and ACCEPT the dialog", async function (this: TestContext) { const invokeAction = metamask.snaps.invokeSnap( testPage, @@ -97,115 +98,6 @@ describe("snaps", function () { expect(await invokeAction).to.equal(false); }); - it("should invoke IN APP NOTIFICATIONS observable", async function (this: TestContext) { - const permissionSnapId = await metamask.snaps.installSnap( - this.snapServers[Snaps.PERMISSIONS_SNAP], - { - hasPermissions: true, - hasKeyPermissions: false, - } - ); - - await metamask.page.bringToFront(); - await openProfileDropdown(metamask.page); - await clickOnElement(metamask.page, "Notifications"); - - const newPage = await metamask.page.browser().newPage(); - await newPage.goto(metamask.page.url()); - - const waitForNotificationPromise = - metamask.snaps.waitForNotification(newPage); - await metamask.snaps.invokeSnap(testPage, snapId, "notify_inApp"); - await waitForNotificationPromise; - - const waitForNotificationPromise2 = - metamask.snaps.waitForNotification(newPage); - await metamask.snaps.invokeSnap( - testPage, - permissionSnapId, - "notify_inApp" - ); - await waitForNotificationPromise2; - - const notifications = await metamask.snaps.getAllNotifications(); - - expect(notifications[0].message).to.equal( - "Hello from permissions snap in App notification" - ); - expect(notifications[1].message).to.equal( - "Hello from methods snap in App notification" - ); - }); - - it("should invoke IN APP NOTIFICATIONS emitter way functional programming", async function (this: TestContext) { - const permissionSnapId = await metamask.snaps.installSnap( - this.snapServers[Snaps.PERMISSIONS_SNAP], - { - hasPermissions: true, - hasKeyPermissions: false, - } - ); - const { emitter, notifications } = await notificationEmitter( - metamask.page - ); - - const notificationPromise = pEvent( - emitter, - "newNotification" - ); - - await metamask.snaps.invokeSnap(testPage, snapId, "notify_inApp"); - await metamask.snaps.invokeSnap( - testPage, - permissionSnapId, - "notify_inApp" - ); - await notificationPromise; - - expect(notifications[0].message).to.contain( - "Hello from permissions snap in App notification" - ); - expect(notifications[1].message).to.contain( - "Hello from methods snap in App notification" - ); - }); - }); - - describe("should test snap methods - Notification class", function () { - let testPage: DappeteerPage; - let snapId: string; - let Notifications: NotificationsEmitter; - - beforeEach(function (this: TestContext) { - //skip those tests for non flask metamask - if (!this.browser.isMetaMaskFlask()) { - this.skip(); - } - }); - - before(async function (this: TestContext) { - if (!this.browser.isMetaMaskFlask()) { - this.skip(); - return; - } - snapId = await metamask.snaps.installSnap( - this.snapServers[Snaps.METHODS_SNAP], - { - hasPermissions: true, - hasKeyPermissions: false, - } - ); - Notifications = new NotificationsEmitter(metamask); - - testPage = await metamask.page.browser().newPage(); - await testPage.goto("https://google.com"); - return testPage; - }); - - after(async () => { - await Notifications.cleanup(); - }); - it("should return all notifications", async function (this: TestContext) { const permissionSnapId = await metamask.snaps.installSnap( this.snapServers[Snaps.PERMISSIONS_SNAP], @@ -226,7 +118,7 @@ describe("snaps", function () { ); await notificationPromise; - const notifications = Notifications.getAllNotifications(); + const notifications = await Notifications.getAllNotifications(); expect(notifications[0].message).to.contain( "Hello from permissions snap in App notification"