Skip to content

Commit 837e3b0

Browse files
committed
test: Add E2E test for first install
Add E2E test to ensure a window opens upon first install. This test needs to use a production-like build rather than a standard E2E build, so the existing "vault decryptor" job was repurposed to be more generically for E2E tests using a production-like build. A global function `reloadExtension` is added to `stateHooks` for use by this test. This function had to be enabled for production builds because the test uses a production build.
1 parent 5c349b2 commit 837e3b0

File tree

5 files changed

+128
-6
lines changed

5 files changed

+128
-6
lines changed

.circleci/config.yml

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -174,14 +174,22 @@ workflows:
174174
- test-e2e-swap-playwright - OPTIONAL:
175175
requires:
176176
- prep-build
177-
- test-e2e-chrome-vault-decryption:
177+
- test-e2e-chrome-dist:
178178
filters:
179179
branches:
180180
only:
181181
- main
182182
- /^Version-v(\d+)[.](\d+)[.](\d+)/
183183
requires:
184184
- prep-build
185+
- test-e2e-firefox-dist:
186+
filters:
187+
branches:
188+
only:
189+
- main
190+
- /^Version-v(\d+)[.](\d+)[.](\d+)/
191+
requires:
192+
- prep-build-mv2
185193
- validate-source-maps:
186194
requires:
187195
- prep-build
@@ -213,7 +221,7 @@ workflows:
213221
- test-e2e-firefox
214222
- test-e2e-chrome-flask
215223
- test-e2e-firefox-flask
216-
- test-e2e-chrome-vault-decryption
224+
- test-e2e-chrome-dist
217225
- test-e2e-chrome-webpack
218226
- job-publish-prerelease:
219227
requires:
@@ -776,17 +784,38 @@ jobs:
776784
- store_test_results:
777785
path: test/test-results/e2e
778786

779-
test-e2e-chrome-vault-decryption:
787+
test-e2e-chrome-dist:
780788
executor: node-browsers-medium-plus
781789
steps:
782790
- run: *shallow-git-clone-and-enable-vnc
783791
- run: sudo corepack enable
784792
- attach_workspace:
785793
at: .
786794
- run:
787-
name: test:e2e:single
795+
name: Vault decryptor tests
788796
command: .circleci/scripts/test-run-e2e.sh yarn test:e2e:single test/e2e/vault-decryption-chrome.spec.ts --browser chrome
789797
no_output_timeout: 5m
798+
- run:
799+
name: First install tests
800+
command: .circleci/scripts/test-run-e2e.sh yarn test:e2e:single test/e2e/first-install.spec.ts --browser chrome
801+
no_output_timeout: 5m
802+
- store_artifacts:
803+
path: test-artifacts
804+
destination: test-artifacts
805+
- store_test_results:
806+
path: test/test-results/e2e
807+
808+
test-e2e-firefox-dist:
809+
executor: node-browsers-medium-plus
810+
steps:
811+
- run: *shallow-git-clone-and-enable-vnc
812+
- run: sudo corepack enable
813+
- attach_workspace:
814+
at: .
815+
- run:
816+
name: First install tests
817+
command: .circleci/scripts/test-run-e2e.sh yarn test:e2e:single test/e2e/first-install.spec.ts --browser firefox
818+
no_output_timeout: 5m
790819
- store_artifacts:
791820
path: test-artifacts
792821
destination: test-artifacts

test/e2e/first-install.spec.ts

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import assert from 'assert/strict';
2+
import { Browser } from 'selenium-webdriver';
3+
import { withFixtures } from './helpers';
4+
import { errorMessages } from './webdriver/driver';
5+
import StartOnboardingPage from './page-objects/pages/onboarding/start-onboarding-page';
6+
import { hasProperty, isObject } from '@metamask/utils';
7+
8+
// Window handle adjustments will need to be made for Non-MV3 Firefox
9+
// due to OffscreenDocument.
10+
const IS_FIREFOX = process.env.SELENIUM_BROWSER === Browser.FIREFOX;
11+
12+
// The number of windows to expect to be open
13+
const baseWindows =
14+
1 + // The primary window, starts out on extensions page
15+
(IS_FIREFOX ? 0 : 1); // The offscreen document, only on Chrome/MV3
16+
17+
describe('First install', function () {
18+
it('opens new window upon install, but not on subsequent reloads', async function () {
19+
await withFixtures(
20+
{
21+
disableServerMochaToBackground: true,
22+
},
23+
async ({ driver }) => {
24+
// Wait for MetaMask to automatically open a new tab
25+
await driver.waitUntilXWindowHandles(baseWindows + 1);
26+
27+
// We cannot use customized driver functions related to window handles, as they depend upon
28+
// the background websocket connection enabled only for E2E test builds. This test runs on
29+
// a production-like build, which doesn't have this websocket connection.
30+
let windowHandles = await driver.driver.getAllWindowHandles();
31+
32+
// Switch to new tab and verify it's the start onboarding page
33+
await driver.driver.switchTo().window(windowHandles[baseWindows]);
34+
const startOnboardingPage = new StartOnboardingPage(driver);
35+
await startOnboardingPage.check_pageIsLoaded();
36+
37+
await driver.executeScript('window.stateHooks.reloadExtension()');
38+
39+
// Wait for extension to reload, signified by the onboarding tab closing
40+
await driver.waitUntilXWindowHandles(baseWindows);
41+
42+
// Test to see if it re-opens
43+
try {
44+
await driver.waitUntilXWindowHandles(baseWindows + 1);
45+
} catch (error) {
46+
if (
47+
isObject(error) &&
48+
hasProperty(error, 'message') &&
49+
typeof error.message === 'string' &&
50+
error.message.startsWith(
51+
errorMessages.waitUntilXWindowHandlesTimeout,
52+
)
53+
) {
54+
// Ignore timeout error, it's expected here in the success case
55+
console.log('Onboarding tab not opened');
56+
} else {
57+
throw error;
58+
}
59+
}
60+
61+
// Ensure new tab is not open
62+
windowHandles = await driver.driver.getAllWindowHandles();
63+
assert.equal(
64+
windowHandles.length,
65+
baseWindows,
66+
`Expected ${baseWindows} windows, found ${windowHandles.length}`,
67+
);
68+
},
69+
);
70+
});
71+
});

test/e2e/webdriver/driver.js

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,14 @@ const PAGES = {
2323
POPUP: 'popup',
2424
};
2525

26+
/**
27+
* Error messages used by driver methods.
28+
*/
29+
const errorMessages = {
30+
waitUntilXWindowHandlesTimeout:
31+
'waitUntilXWindowHandles timed out polling window handles',
32+
};
33+
2634
/**
2735
* Temporary workaround to patch selenium's element handle API with methods
2836
* that match the playwright API for Elements
@@ -1097,7 +1105,7 @@ class Driver {
10971105
}
10981106

10991107
throw new Error(
1100-
`waitUntilXWindowHandles timed out polling window handles. Expected: ${x}, Actual: ${windowHandles.length}`,
1108+
`${errorMessages.waitUntilXWindowHandlesTimeout}. Expected: ${x}, Actual: ${windowHandles.length}`,
11011109
);
11021110
}
11031111

@@ -1569,4 +1577,4 @@ function sanitizeTestTitle(testTitle) {
15691577
.replace(/^-+|-+$/gu, ''); // Trim leading/trailing dashes
15701578
}
15711579

1572-
module.exports = { Driver, PAGES };
1580+
module.exports = { Driver, PAGES, errorMessages };

types/global.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,11 @@ type StateHooks = {
260260
* the `onInstalled` listener is called.
261261
*/
262262
metamaskTriggerOnInstall?: () => void;
263+
/**
264+
* Reload the extension. This is used to trigger extension reload from a page context by E2E
265+
* tests.
266+
*/
267+
reloadExtension?: () => void;
263268
};
264269

265270
export declare global {

ui/index.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,15 @@ function setupStateHooks(store) {
294294
};
295295
}
296296

297+
/**
298+
* Reload the extension.
299+
*
300+
* This is used for the `first-install` E2E test, which uses a production-like build. This
301+
* function must be present even if `process.env.IN_TEST` is false.
302+
*/
303+
window.stateHooks.reloadExtension = () => {
304+
browser.runtime.reload();
305+
};
297306
window.stateHooks.getCleanAppState = async function () {
298307
const state = clone(store.getState());
299308
// we use the manifest.json version from getVersion and not

0 commit comments

Comments
 (0)