Skip to content

Commit 8d2edc3

Browse files
committed
test: Support window switching without mocha server
Our E2E driver class now allows the methods `switchWindow` and `switchToWindowWithTitle` methods to be used in tests that have the Mocha background server disabled (such as those that use a production build, or those that involve the extension resetting). Previously the `switchToWindowWithTitleWithoutSocket` driver method served this purpose, but the test author had to know to use this alternative method. Plus it didn't allow switching to a window handle, or any of the other switch methods that internally use `switchWindow`. There are a couple of switch methods still not supported, but in these cases an error will be thrown so that the failure reason will be clear. This unblocks #31435
1 parent 4be7602 commit 8d2edc3

File tree

5 files changed

+98
-63
lines changed

5 files changed

+98
-63
lines changed

test/e2e/helpers.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,10 @@ async function withFixtures(options, testSuite) {
273273

274274
await setManifestFlags(manifestFlags);
275275

276-
const wd = await buildWebDriver(driverOptions);
276+
const wd = await buildWebDriver({
277+
...driverOptions,
278+
disableServerMochaToBackground,
279+
});
277280
driver = wd.driver;
278281
extensionId = wd.extensionId;
279282
webDriver = driver.driver;

test/e2e/tests/update-modal/update-modal.spec.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,9 +100,7 @@ describe('Update modal', function (this: Suite) {
100100
const updateModal = new UpdateModal(driver);
101101
await updateModal.check_pageIsLoaded();
102102
await updateModal.confirm();
103-
await driver.switchToWindowByTitleWithoutSocket(
104-
WINDOW_TITLES.ExtensionUpdating,
105-
);
103+
await driver.switchToWindowWithTitle(WINDOW_TITLES.ExtensionUpdating);
106104
},
107105
);
108106
});

test/e2e/vault-decryption-chrome.spec.ts

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -169,10 +169,8 @@ describe('Vault Decryptor Page', function () {
169169
},
170170
async ({ driver }) => {
171171
// we don't need to use navigate since MM will automatically open a new window in prod build
172-
await driver.waitUntilXWindowHandles(2);
173-
174-
// we cannot use the customized driver functions as there is no socket for window communications in prod builds
175-
await driver.switchToWindowByTitleWithoutSocket(
172+
await driver.waitAndSwitchToWindowWithTitle(
173+
2,
176174
WINDOW_TITLES.ExtensionInFullScreenView,
177175
);
178176

@@ -229,10 +227,9 @@ describe('Vault Decryptor Page', function () {
229227
},
230228
async ({ driver }) => {
231229
// we don't need to use navigate since MM will automatically open a new window in prod build
232-
await driver.waitUntilXWindowHandles(2);
233230

234-
// we cannot use the customized driver functions as there is no socket for window communications in prod builds
235-
await driver.switchToWindowByTitleWithoutSocket(
231+
await driver.waitAndSwitchToWindowWithTitle(
232+
2,
236233
WINDOW_TITLES.ExtensionInFullScreenView,
237234
);
238235

test/e2e/webdriver/driver.js

Lines changed: 81 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ const {
1212
const cssToXPath = require('css-to-xpath');
1313
const { sprintf } = require('sprintf-js');
1414
const lodash = require('lodash');
15+
const { retry } = require('../../../development/lib/retry');
1516
const { quoteXPathText } = require('../../helpers/quoteXPathText');
1617
const { isManifestV3 } = require('../../../shared/modules/mv3.utils');
1718
const { WindowHandles } = require('../background-socket/window-handles');
@@ -118,27 +119,46 @@ until.foundElementCountIs = function foundElementCountIs(locator, n) {
118119
});
119120
};
120121

122+
/**
123+
* Error messages used by driver methods.
124+
*/
125+
const errorMessages = {
126+
waitUntilXWindowHandlesTimeout:
127+
'waitUntilXWindowHandles timed out polling window handles',
128+
};
129+
121130
/**
122131
* This is MetaMask's custom E2E test driver, wrapping the Selenium WebDriver.
123132
* For Selenium WebDriver API documentation, see:
124133
* https://www.selenium.dev/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_WebDriver.html
125134
*/
126135
class Driver {
127136
/**
128-
* @param {!ThenableWebDriver} driver - A {@code WebDriver} instance
129-
* @param {string} browser - The type of browser this driver is controlling
130-
* @param {string} extensionUrl
131-
* @param {number} timeout - Defaults to 10000 milliseconds (10 seconds)
137+
* @param {object} args - Constructor arguments.
138+
* @param {!ThenableWebDriver} args.driver - A {@code WebDriver} instance
139+
* @param {string} args.browser - The type of browser this driver is controlling
140+
* @param {string} args.extensionUrl
141+
* @param {number} args.timeout - Defaults to 10000 milliseconds (10 seconds)
142+
* @param {boolean} args.disableServerMochaToBackground - Determines whether the background mocha
143+
* server is used.
132144
*/
133-
constructor(driver, browser, extensionUrl, timeout = 10 * 1000) {
145+
constructor({
146+
driver,
147+
browser,
148+
extensionUrl,
149+
timeout = 10 * 1000,
150+
disableServerMochaToBackground,
151+
}) {
134152
this.driver = driver;
135153
this.browser = browser;
136154
this.extensionUrl = extensionUrl;
137155
this.timeout = timeout;
138156
this.exceptions = [];
139157
this.errors = [];
140158
this.eventProcessingStack = [];
141-
this.windowHandles = new WindowHandles(this.driver);
159+
this.windowHandles = disableServerMochaToBackground
160+
? null
161+
: new WindowHandles(this.driver);
142162

143163
// The following values are found in
144164
// https://github.com/SeleniumHQ/selenium/blob/trunk/javascript/node/selenium-webdriver/lib/input.js#L50-L110
@@ -1060,7 +1080,9 @@ class Driver {
10601080
*/
10611081
async switchToWindow(handle) {
10621082
await this.driver.switchTo().window(handle);
1063-
await this.windowHandles.getCurrentWindowProperties(null, handle);
1083+
if (this.windowHandles) {
1084+
await this.windowHandles.getCurrentWindowProperties(null, handle);
1085+
}
10641086
}
10651087

10661088
/**
@@ -1089,7 +1111,10 @@ class Driver {
10891111
* be resolved with an array of window handles.
10901112
*/
10911113
async getAllWindowHandles() {
1092-
return await this.windowHandles.getAllWindowHandles();
1114+
if (this.windowHandles) {
1115+
return await this.windowHandles.getAllWindowHandles();
1116+
}
1117+
return await this.driver.getAllWindowHandles();
10931118
}
10941119

10951120
/**
@@ -1160,7 +1185,7 @@ class Driver {
11601185
}
11611186

11621187
throw new Error(
1163-
`waitUntilXWindowHandles timed out polling window handles. Expected: ${x}, Actual: ${windowHandles.length}`,
1188+
`${errorMessages.waitUntilXWindowHandlesTimeout}. Expected: ${x}, Actual: ${windowHandles.length}`,
11641189
);
11651190
}
11661191

@@ -1200,7 +1225,42 @@ class Driver {
12001225
* @throws {Error} throws an error if no window with the specified title is found
12011226
*/
12021227
async switchToWindowWithTitle(title) {
1203-
return await this.windowHandles.switchToWindowWithProperty('title', title);
1228+
if (this.windowHandles) {
1229+
return await this.windowHandles.switchToWindowWithProperty(
1230+
'title',
1231+
title,
1232+
);
1233+
}
1234+
1235+
let windowHandles = await this.driver.getAllWindowHandles();
1236+
let timeElapsed = 0;
1237+
1238+
while (timeElapsed <= this.timeout) {
1239+
for (const handle of windowHandles) {
1240+
// Wait 25 x 200ms = 5 seconds for the title to match the target title
1241+
const handleTitle = await retry(
1242+
{
1243+
retries: 25,
1244+
delay: 200,
1245+
},
1246+
async () => {
1247+
await this.driver.switchTo().window(handle);
1248+
return await this.driver.getTitle();
1249+
},
1250+
);
1251+
1252+
if (handleTitle === title) {
1253+
return handle;
1254+
}
1255+
}
1256+
const delayTime = 1000;
1257+
await this.delay(delayTime);
1258+
timeElapsed += delayTime;
1259+
// refresh the window handles
1260+
windowHandles = await this.driver.getAllWindowHandles();
1261+
}
1262+
1263+
throw new Error(`No window with title: ${title}`);
12041264
}
12051265

12061266
/**
@@ -1224,6 +1284,11 @@ class Driver {
12241284
* @throws {Error} throws an error if no window with the specified URL is found
12251285
*/
12261286
async switchToWindowWithUrl(url) {
1287+
if (!this.windowHandles) {
1288+
throw new Error(
1289+
'This is only supported when the Mocha background server is enabled',
1290+
);
1291+
}
12271292
return await this.windowHandles.switchToWindowWithProperty(
12281293
'url',
12291294
new URL(url).toString(), // Make sure the URL has a trailing slash
@@ -1240,6 +1305,11 @@ class Driver {
12401305
* @throws {Error} throws an error if no window with the specified URL is found
12411306
*/
12421307
async switchToWindowIfKnown(title) {
1308+
if (!this.windowHandles) {
1309+
throw new Error(
1310+
'This is only supported when the Mocha background server is enabled',
1311+
);
1312+
}
12431313
return await this.windowHandles.switchToWindowIfKnown(title);
12441314
}
12451315

@@ -1341,46 +1411,6 @@ class Driver {
13411411
}
13421412
}
13431413

1344-
/**
1345-
* Switches to a window by its title without using the background socket.
1346-
* To be used in specs that run against production builds.
1347-
*
1348-
* @param {string} title - The target window title to switch to
1349-
* @returns {Promise<void>}
1350-
* @throws {Error} Will throw an error if the target window is not found
1351-
*/
1352-
async switchToWindowByTitleWithoutSocket(title) {
1353-
const windowHandles = await this.driver.getAllWindowHandles();
1354-
let targetWindowFound = false;
1355-
1356-
// Iterate through each window handle
1357-
for (const handle of windowHandles) {
1358-
await this.driver.switchTo().window(handle);
1359-
let currentTitle = await this.driver.getTitle();
1360-
1361-
// Wait 25 x 200ms = 5 seconds for the title to match the target title
1362-
for (let i = 0; i < 25; i++) {
1363-
if (currentTitle === title) {
1364-
targetWindowFound = true;
1365-
console.log(`Switched to ${title} window`);
1366-
break;
1367-
}
1368-
await this.driver.sleep(200);
1369-
currentTitle = await this.driver.getTitle();
1370-
}
1371-
1372-
// If the target window is found, break out of the outer loop
1373-
if (targetWindowFound) {
1374-
break;
1375-
}
1376-
}
1377-
1378-
// If target window is not found, throw an error
1379-
if (!targetWindowFound) {
1380-
throw new Error(`${title} window not found`);
1381-
}
1382-
}
1383-
13841414
// Error handling
13851415

13861416
async verboseReportOnFailure(testTitle, error) {
@@ -1632,4 +1662,4 @@ function sanitizeTestTitle(testTitle) {
16321662
.replace(/^-+|-+$/gu, ''); // Trim leading/trailing dashes
16331663
}
16341664

1635-
module.exports = { Driver, PAGES };
1665+
module.exports = { Driver, PAGES, errorMessages };

test/e2e/webdriver/index.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ async function buildWebDriver({
1010
port,
1111
timeOut,
1212
proxyPort,
13+
disableServerMochaToBackground,
1314
} = {}) {
1415
const browser = process.env.SELENIUM_BROWSER;
1516

@@ -24,7 +25,13 @@ async function buildWebDriver({
2425
constrainWindowSize,
2526
proxyPort,
2627
});
27-
const driver = new Driver(seleniumDriver, browser, extensionUrl, timeOut);
28+
const driver = new Driver({
29+
driver: seleniumDriver,
30+
browser,
31+
extensionUrl,
32+
timeOut,
33+
disableServerMochaToBackground,
34+
});
2835

2936
return {
3037
driver,

0 commit comments

Comments
 (0)