Skip to content

Commit 30737d1

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 60c11d7 commit 30737d1

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
@@ -1053,7 +1073,9 @@ class Driver {
10531073
*/
10541074
async switchToWindow(handle) {
10551075
await this.driver.switchTo().window(handle);
1056-
await this.windowHandles.getCurrentWindowProperties(null, handle);
1076+
if (this.windowHandles) {
1077+
await this.windowHandles.getCurrentWindowProperties(null, handle);
1078+
}
10571079
}
10581080

10591081
/**
@@ -1082,7 +1104,10 @@ class Driver {
10821104
* be resolved with an array of window handles.
10831105
*/
10841106
async getAllWindowHandles() {
1085-
return await this.windowHandles.getAllWindowHandles();
1107+
if (this.windowHandles) {
1108+
return await this.windowHandles.getAllWindowHandles();
1109+
}
1110+
return await this.driver.getAllWindowHandles();
10861111
}
10871112

10881113
/**
@@ -1153,7 +1178,7 @@ class Driver {
11531178
}
11541179

11551180
throw new Error(
1156-
`waitUntilXWindowHandles timed out polling window handles. Expected: ${x}, Actual: ${windowHandles.length}`,
1181+
`${errorMessages.waitUntilXWindowHandlesTimeout}. Expected: ${x}, Actual: ${windowHandles.length}`,
11571182
);
11581183
}
11591184

@@ -1193,7 +1218,42 @@ class Driver {
11931218
* @throws {Error} throws an error if no window with the specified title is found
11941219
*/
11951220
async switchToWindowWithTitle(title) {
1196-
return await this.windowHandles.switchToWindowWithProperty('title', title);
1221+
if (this.windowHandles) {
1222+
return await this.windowHandles.switchToWindowWithProperty(
1223+
'title',
1224+
title,
1225+
);
1226+
}
1227+
1228+
let windowHandles = await this.driver.getAllWindowHandles();
1229+
let timeElapsed = 0;
1230+
1231+
while (timeElapsed <= this.timeout) {
1232+
for (const handle of windowHandles) {
1233+
// Wait 25 x 200ms = 5 seconds for the title to match the target title
1234+
const handleTitle = await retry(
1235+
{
1236+
retries: 25,
1237+
delay: 200,
1238+
},
1239+
async () => {
1240+
await this.driver.switchTo().window(handle);
1241+
return await this.driver.getTitle();
1242+
},
1243+
);
1244+
1245+
if (handleTitle === title) {
1246+
return handle;
1247+
}
1248+
}
1249+
const delayTime = 1000;
1250+
await this.delay(delayTime);
1251+
timeElapsed += delayTime;
1252+
// refresh the window handles
1253+
windowHandles = await this.driver.getAllWindowHandles();
1254+
}
1255+
1256+
throw new Error(`No window with title: ${title}`);
11971257
}
11981258

11991259
/**
@@ -1217,6 +1277,11 @@ class Driver {
12171277
* @throws {Error} throws an error if no window with the specified URL is found
12181278
*/
12191279
async switchToWindowWithUrl(url) {
1280+
if (!this.windowHandles) {
1281+
throw new Error(
1282+
'This is only supported when the Mocha background server is enabled',
1283+
);
1284+
}
12201285
return await this.windowHandles.switchToWindowWithProperty(
12211286
'url',
12221287
new URL(url).toString(), // Make sure the URL has a trailing slash
@@ -1233,6 +1298,11 @@ class Driver {
12331298
* @throws {Error} throws an error if no window with the specified URL is found
12341299
*/
12351300
async switchToWindowIfKnown(title) {
1301+
if (!this.windowHandles) {
1302+
throw new Error(
1303+
'This is only supported when the Mocha background server is enabled',
1304+
);
1305+
}
12361306
return await this.windowHandles.switchToWindowIfKnown(title);
12371307
}
12381308

@@ -1334,46 +1404,6 @@ class Driver {
13341404
}
13351405
}
13361406

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

13791409
async verboseReportOnFailure(testTitle, error) {
@@ -1625,4 +1655,4 @@ function sanitizeTestTitle(testTitle) {
16251655
.replace(/^-+|-+$/gu, ''); // Trim leading/trailing dashes
16261656
}
16271657

1628-
module.exports = { Driver, PAGES };
1658+
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+
seleniumDriver,
30+
browser,
31+
extensionUrl,
32+
timeOut,
33+
disableServerMochaToBackground,
34+
});
2835

2936
return {
3037
driver,

0 commit comments

Comments
 (0)