Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: add simple notifications test, add page objects for test #27096

Closed
wants to merge 33 commits into from
Closed
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
567c991
test: add simple notifications test, add page objects for test
cmd-ob Sep 12, 2024
89b6f49
Merge branch 'develop' into e2e/enable-notifications
cmd-ob Sep 17, 2024
f0e999c
fix mocks imports
cmd-ob Sep 17, 2024
4a1f27d
fix linting for privacy snapshot
cmd-ob Sep 17, 2024
37268cd
Merge branch 'develop' into e2e/enable-notifications
cmd-ob Sep 17, 2024
049afbb
use .ts for test file
cmd-ob Sep 17, 2024
a717b01
Merge branch 'develop' into e2e/enable-notifications
cmd-ob Sep 17, 2024
7b98884
fix: selector getKnownMethodData should return empty object if user h…
jpuri Sep 17, 2024
c9869bd
fix: Update max gas limit with value returned from eth_estimateGas ap…
jpuri Sep 17, 2024
2e25f51
test: [POM] create AccountListPage for e2e tests and migrate 4 test f…
cmd-ob Sep 18, 2024
7bf5969
chore(3210): deprecated `Application open` metric event (#27102)
DDDDDanica Sep 17, 2024
d7129ea
fix: Fixed flaky test for custom screen size (#27067)
hjetpoluru Sep 17, 2024
c1bdb49
chore: remove dead code (#27211)
cryptotavares Sep 17, 2024
8e40790
feat: upgrade notification controllers (#27224)
Prithpal-Sooriya Sep 17, 2024
f8b2c81
chore: Add currency conversion telemetry (#26876)
gambinish Sep 17, 2024
f1cf41a
fix: :pencil2: fix event property typo (#27228)
matteoscurati Sep 18, 2024
ce892c5
test: add simple notifications test, add page objects for test
cmd-ob Sep 12, 2024
4423794
update user storage id
cmd-ob Sep 18, 2024
0d821e2
Merge branch 'develop' into e2e/enable-notifications
cmd-ob Sep 18, 2024
8714172
lint fix, make test .ts
cmd-ob Sep 18, 2024
c7f7fb2
Merge branch 'develop' into e2e/enable-notifications
cmd-ob Sep 18, 2024
84e6b68
add logging to assertion, move go to notifications into header page
cmd-ob Sep 19, 2024
aa86704
Merge branch 'e2e/enable-notifications' of github.com:MetaMask/metama…
cmd-ob Sep 19, 2024
914d372
Merge branch 'develop' into e2e/enable-notifications
cmd-ob Sep 19, 2024
57f4446
remove account options menu page
cmd-ob Sep 23, 2024
15ab081
update mock var name
cmd-ob Sep 23, 2024
3b01e86
Merge branch 'develop' of github.com:MetaMask/metamask-extension into…
cmd-ob Sep 23, 2024
8420d5b
update mocks
cmd-ob Oct 7, 2024
63de77a
Merge branch 'develop' of github.com:MetaMask/metamask-extension into…
cmd-ob Oct 7, 2024
09e9f2b
remove irrelevant mocks
cmd-ob Oct 13, 2024
8cf86a7
use import onboarding flow, update mocks for account and notification…
cmd-ob Oct 18, 2024
63aa7c1
Merge branch 'develop' of github.com:MetaMask/metamask-extension into…
cmd-ob Oct 18, 2024
c50fb47
update mock, change folder name to profile-sync
cmd-ob Oct 18, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 11 additions & 10 deletions privacy-snapshot.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@
"api.web3modal.com",
"app.ens.domains",
"arbitrum-mainnet.infura.io",
"authentication.api.cx.metamask.io",
"bafkreifvhjdf6ve4jfv6qytqtux5nd4nwnelioeiqx5x2ez5yrgrzk7ypi.ipfs.dweb.link",
"bafybeidxfmwycgzcp4v2togflpqh2gnibuexjy4m4qqwxp7nh3jx5zlh4y.ipfs.dweb.link",
"bridge.api.cx.metamask.io",
"cdn.segment.com",
"cdn.segment.io",
"cdnjs.cloudflare.com",
"chainid.network",
"client-side-detection.api.cx.metamask.io",
"configuration.dev.metamask-institutional.io",
"configuration.metamask-institutional.io",
"connect.trezor.io",
Expand All @@ -24,19 +26,22 @@
"gas.api.cx.metamask.io",
"github.com",
"goerli.infura.io",
"lattice.gridplus.io",
"localhost:8000",
"localhost:8545",
"mainnet.infura.io",
"metamask.eth",
"metamask.github.io",
"min-api.cryptocompare.com",
"nft.api.cx.metamask.io",
"oidc.api.cx.metamask.io",
"on-ramp-content.api.cx.metamask.io",
"on-ramp-content.uat-api.cx.metamask.io",
"phishing-detection.api.cx.metamask.io",
"portfolio.metamask.io",
"price.api.cx.metamask.io",
"on-ramp-content.api.cx.metamask.io",
"on-ramp-content.uat-api.cx.metamask.io",
"proxy.api.cx.metamask.io",
"push.api.cx.metamask.io",
"raw.githubusercontent.com",
"registry.npmjs.org",
"responsive-rpc.test",
Expand All @@ -49,14 +54,10 @@
"test.metamask-phishing.io",
"token.api.cx.metamask.io",
"tokens.api.cx.metamask.io",
"trigger.api.cx.metamask.io",
"tx-sentinel-ethereum-mainnet.api.cx.metamask.io",
"unresponsive-rpc.url",
"www.4byte.directory",
"lattice.gridplus.io",
"unresponsive-rpc.test",
"authentication.api.cx.metamask.io",
"oidc.api.cx.metamask.io",
"price.api.cx.metamask.io",
"token.api.cx.metamask.io",
"client-side-detection.api.cx.metamask.io"
"unresponsive-rpc.url",
"user-storage.api.cx.metamask.io",
"www.4byte.directory"
]
24 changes: 24 additions & 0 deletions test/e2e/page-objects/flows/enable-notifications.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Driver } from '../../webdriver/driver';
import FirstTimeTurnOnNotificationsModal from '../pages/notifications/first-time-turn-on-modal';
import HeaderNavbar from '../pages/header-navbar';

/**
* This function enables the notifications through the header options menu from the home page
*
* Note: this flow focuses on the journey of a user who is enabling this feature for the first time
*
* @param driver - The webdriver instance.
*/
export const enableNotificationsFirstTime = async (
driver: Driver,
): Promise<void> => {
console.log(`Start enable notifications from home screen`);
const header = new HeaderNavbar(driver);
await header.goToNotifiationsList();

const turnOnNotificationsModal = new FirstTimeTurnOnNotificationsModal(
driver,
);
await turnOnNotificationsModal.check_pageIsLoaded();
await turnOnNotificationsModal.clickTurnOnNotifications();
};
18 changes: 18 additions & 0 deletions test/e2e/page-objects/pages/account-options-menu.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Driver } from '../../webdriver/driver';

class AccountOptionsMenu {
private driver: Driver;

private readonly notificationsMenuItem =
'[data-testid="notifications-menu-item"]';

constructor(driver: Driver) {
this.driver = driver;
}

async clickNotificationsMenuItem(): Promise<void> {
await this.driver.clickElement(this.notificationsMenuItem);
}
}

export default AccountOptionsMenu;
22 changes: 18 additions & 4 deletions test/e2e/page-objects/pages/header-navbar.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,43 @@
import { Driver } from '../../webdriver/driver';
import AccountOptionsMenu from './account-options-menu';

class HeaderNavbar {
private driver: Driver;

private accountMenuButton: string;

private accountOptionMenu: string;
private accountOptionsMenuButton: string;

private lockMetaMaskButton: string;

private accountOptionsMenu: AccountOptionsMenu;

constructor(driver: Driver) {
this.driver = driver;
this.accountMenuButton = '[data-testid="account-menu-icon"]';
this.accountOptionMenu = '[data-testid="account-options-menu-button"]';
this.lockMetaMaskButton = '[data-testid="global-menu-lock"]';
this.accountMenuButton = '[data-testid="account-menu-icon"]';
this.accountOptionsMenuButton =
'[data-testid="account-options-menu-button"]';
this.accountOptionsMenu = new AccountOptionsMenu(driver);
}

async openAccountMenu(): Promise<void> {
await this.driver.clickElement(this.accountMenuButton);
}

async openAccountOptionsMenu(): Promise<void> {
await this.driver.clickElement(this.accountOptionsMenuButton);
}

async lockMetaMask(): Promise<void> {
await this.driver.clickElement(this.accountOptionMenu);
await this.openAccountOptionsMenu();
await this.driver.clickElement(this.lockMetaMaskButton);
}

async goToNotifiationsList(): Promise<void> {
this.openAccountOptionsMenu();
this.accountOptionsMenu.clickNotificationsMenuItem();
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With this implementation, for notifications, we use clickNotificationsMenuItem in the AccountOptionsMenu page, but we click lockMetaMaskButton using the selectors in the header-navbar page directly. However, the notificationsMenuItem and the lockMetaMaskButton are on the same modal, which creates inconsistency. I suggest that we don't create a new page class for AccountOptionsMenu because it only has a few buttons without complex interactions on AccountOptionsMenu. We can just add the notificationsMenuItem selector in the header-navbar page and click on it directly here. This provides an easier implementation.
Screenshot 2024-09-20 at 09 35 19

}

export default HeaderNavbar;
4 changes: 4 additions & 0 deletions test/e2e/page-objects/pages/homepage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { strict as assert } from 'assert';
import { Driver } from '../../webdriver/driver';
import { DEFAULT_GANACHE_ETH_BALANCE_DEC } from '../../constants';
import HeaderNavbar from './header-navbar';
import AccountOptionsMenu from './account-options-menu';

class HomePage {
private driver: Driver;
Expand All @@ -24,9 +25,12 @@ class HomePage {

public headerNavbar: HeaderNavbar;

public accountOptionsMenu: AccountOptionsMenu;

constructor(driver: Driver) {
this.driver = driver;
this.headerNavbar = new HeaderNavbar(driver);
this.accountOptionsMenu = new AccountOptionsMenu(driver);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since we will not create accountOptionsMenu page class, this line can be deleted, accountOptionsMenu is a part of headerNavbar

this.sendButton = '[data-testid="eth-overview-send"]';
this.activityTab = '[data-testid="account-overview__activity-tab"]';
this.tokensTab = '[data-testid="account-overview__asset-tab"]';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Driver } from '../../../webdriver/driver';

class FirstTimeTurnOnNotificationsModal {
private driver: Driver;

private readonly turnOnButton =
'[data-testid="turn-on-notifications-button"]';

constructor(driver: Driver) {
this.driver = driver;
}

async check_pageIsLoaded(): Promise<void> {
try {
await this.driver.waitForSelector(this.turnOnButton);
} catch (e) {
console.log(
'Timeout while waiting for TurnOnNotificationsModal to be loaded',
e,
);
throw e;
}
console.log('TurnOnNotificationsModal is loaded');
}

async clickTurnOnNotifications(): Promise<void> {
await this.driver.clickElement(this.turnOnButton);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use clickElementAndWaitToDisappear to make sure the modal closes, that will reduce flakiness in the test execution

}
}

export default FirstTimeTurnOnNotificationsModal;
49 changes: 49 additions & 0 deletions test/e2e/page-objects/pages/notifications/notifications-list.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { Driver } from '../../../webdriver/driver';

class NotificationsListPage {
private driver: Driver;

private readonly backButton = '[data-testid="back-button"]';

private readonly notificationsSettingsButton =
'[data-testid="notifications-settings-button"]';

private readonly noNotificationsReceivedPlaceholder =
'[data-testid="notifications-list-placeholder"]';

constructor(driver: Driver) {
this.driver = driver;
}

async check_pageIsLoaded(): Promise<void> {
try {
await this.driver.waitForMultipleSelectors([
this.backButton,
this.notificationsSettingsButton,
]);
} catch (e) {
console.log(
'Timeout while waiting for notifications list page to be loaded',
e,
);
throw e;
}
console.log('Notifications list page is loaded');
}

async check_noNotificationsReceived(): Promise<void> {
chloeYue marked this conversation as resolved.
Show resolved Hide resolved
try {
await this.driver.waitForSelector(
this.noNotificationsReceivedPlaceholder,
);
} catch (e) {
console.log(
'Timed out while waiting for the empty notifications list placeholder to be displayed',
e,
);
}
console.log('Notifications list is empty as expected');
}
}

export default NotificationsListPage;
33 changes: 33 additions & 0 deletions test/e2e/tests/notifications/enable-notifications-first-time.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import {
withFixtures,
logInWithBalanceValidation,
defaultGanacheOptions,
} from '../../helpers';
import { enableNotificationsFirstTime } from '../../page-objects/flows/enable-notifications';
import FixtureBuilder from '../../fixture-builder';
import NotificationsListPage from '../../page-objects/pages/notifications/notifications-list';
import { mockNotificationServices } from './mocks';

describe('Notifications', function () {
describe('from inside MetaMask', function () {
it('enables notifications for the first time', async function () {
await withFixtures(
{
fixtures: new FixtureBuilder().build(),
ganacheOptions: defaultGanacheOptions,
title: this.test?.fullTitle(),
testSpecificMock: mockNotificationServices,
},
async ({ driver, ganacheServer }) => {
await logInWithBalanceValidation(driver, ganacheServer);
await enableNotificationsFirstTime(driver);

// should land on notifications list
const notificationsListPage = new NotificationsListPage(driver);
await notificationsListPage.check_pageIsLoaded();
await notificationsListPage.check_noNotificationsReceived();
},
);
});
});
});
26 changes: 18 additions & 8 deletions test/e2e/tests/notifications/mocks.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
import { Mockttp, RequestRuleBuilder } from 'mockttp';
import {
AuthenticationController,
UserStorageController,
} from '@metamask/profile-sync-controller';
import { AuthenticationController } from '@metamask/profile-sync-controller';
import {
NotificationServicesController,
NotificationServicesPushController,
} from '@metamask/notification-services-controller';

const AuthMocks = AuthenticationController.Mocks;
const StorageMocks = UserStorageController.Mocks;
const NotificationMocks = NotificationServicesController.Mocks;
const PushMocks = NotificationServicesPushController.Mocks;

Expand All @@ -31,8 +27,21 @@ export async function mockNotificationServices(server: Mockttp) {
mockAPICall(server, AuthMocks.getMockAuthAccessTokenResponse());

// Storage
mockAPICall(server, await StorageMocks.getMockUserStorageGetResponse());
mockAPICall(server, await StorageMocks.getMockUserStoragePutResponse());
const NOTIFICATIONS_USER_STORAGE_ENDPOINT =
'https://user-storage.api.cx.metamask.io/api/v1/userstorage/notifications';
const NOTIFICATION_USER_STORAGE_ID =
'df1d90e0a8c2c7c48a84cfc80c979b68c7e7d5624b89986a83a280ab92511bd4';
mockAPICall(server, {
url: `${NOTIFICATIONS_USER_STORAGE_ENDPOINT}/${NOTIFICATION_USER_STORAGE_ID}`,
requestMethod: 'GET',
response: NotificationMocks.createMockFullUserStorage(),
});

mockAPICall(server, {
url: `${NOTIFICATIONS_USER_STORAGE_ENDPOINT}/${NOTIFICATION_USER_STORAGE_ID}`,
requestMethod: 'PUT',
response: null,
});

// Notifications
mockAPICall(server, NotificationMocks.getMockFeatureAnnouncementResponse());
Expand All @@ -43,12 +52,13 @@ export async function mockNotificationServices(server: Mockttp) {
server,
NotificationMocks.getMockMarkNotificationsAsReadResponse(),
);

// Push Notifications
mockAPICall(server, PushMocks.getMockRetrievePushNotificationLinksResponse());
mockAPICall(server, PushMocks.getMockUpdatePushNotificationLinksResponse());
mockAPICall(server, PushMocks.getMockCreateFCMRegistrationTokenResponse());
mockAPICall(server, PushMocks.getMockDeleteFCMRegistrationTokenResponse());

mockAPICall(server, PushMocks.getMockDeleteFCMRegistrationTokenResponse());
}

function mockAPICall(server: Mockttp, response: MockResponse) {
Expand Down
1 change: 1 addition & 0 deletions ui/components/multichain/global-menu/global-menu.js
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ export const GlobalMenu = ({ closeMenu, anchorElement, isOpen }) => {
<MenuItem
iconName={IconName.Notification}
onClick={() => handleNotificationsClick()}
data-testid="notifications-menu-item"
>
<Box
display={Display.Flex}
Expand Down