Skip to content

test: add driver.clickElementAndWaitForWindowToClose helper method#26449

Merged
davidmurdoch merged 19 commits intodevelopfrom
fix-flaky-test-window-close
Aug 19, 2024
Merged

test: add driver.clickElementAndWaitForWindowToClose helper method#26449
davidmurdoch merged 19 commits intodevelopfrom
fix-flaky-test-window-close

Conversation

@davidmurdoch
Copy link
Copy Markdown
Contributor

@davidmurdoch davidmurdoch commented Aug 15, 2024

There are lots of tests that click a button in our popup window (AKA "MetaMask Dialog") that Eventually close the popup, but they don't wait for the popup to close before continuing on with the rest of the test.

This can cause some race conditions due to the way our test infrastructure finds windows (by title). The problem is that we look for windows of the name "MetaMask Dialog", and in some cases we find 2, we start referencing the first one, then it closes (as it should), and when we try to reference it again Selenium throws (because the window no longer exists).

So this PR introduces a new method "driver.clickElementAndWaitForWindowToClose" that will click the element (just as before), but then wait until the window closes before returning.

Related issues

Fixes:
#26369
#26353
#26387

@github-actions
Copy link
Copy Markdown
Contributor

CLA Signature Action: All authors have signed the CLA. You may need to manually re-run the blocking PR check if it doesn't pass in a few minutes.

@metamaskbot metamaskbot added the team-extension-platform Extension Platform team label Aug 15, 2024
@davidmurdoch davidmurdoch changed the title try this test: add driver.withWindowUntilClose helper method Aug 15, 2024
@codecov
Copy link
Copy Markdown

codecov bot commented Aug 16, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 70.17%. Comparing base (d5592f2) to head (5613a52).
Report is 177 commits behind head on develop.

Additional details and impacted files
@@             Coverage Diff             @@
##           develop   #26449      +/-   ##
===========================================
+ Coverage    70.12%   70.17%   +0.05%     
===========================================
  Files         1428     1424       -4     
  Lines        50096    49857     -239     
  Branches     13896    13852      -44     
===========================================
- Hits         35127    34985     -142     
+ Misses       14969    14872      -97     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@metamaskbot
Copy link
Copy Markdown
Collaborator

Builds ready [00b649a]
Page Load Metrics (226 ± 231 ms)
PlatformPageMetricMin (ms)Max (ms)Average (ms)StandardDeviation (ms)MarginOfError (ms)
ChromeHomefirstPaint712641124521
domContentLoaded147127147
load461756226482231
domInteractive147127147
Bundle size diffs
  • background: 0 Bytes (0.00%)
  • ui: 0 Bytes (0.00%)
  • common: 0 Bytes (0.00%)

@HowardBraham HowardBraham force-pushed the fix-flaky-test-window-close branch from f75e65b to 97b54fe Compare August 16, 2024 05:00
1) switchToNotificationWindow
2) driver.switchToWindowWithTitle with a second parameter (that second param is now just ignored)
@HowardBraham HowardBraham force-pushed the fix-flaky-test-window-close branch from 97b54fe to d803179 Compare August 16, 2024 05:25
await driver.clickElement('#connectjsx');

// switch to metamask extension and click connect
await switchToNotificationWindow(driver, 2);
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

switchToNotificationWindow is deprecated, so they've been removed as a bit of a drive-by refactor.

@@ -1,14 +1,13 @@
import { strict as assert } from 'assert';
import { MockedEndpoint } from 'mockttp';
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

just fixing types in this file.

@@ -1,21 +1,12 @@
import { Suite } from 'mocha';
import { withFixtures, logInWithBalanceValidation } from '../../helpers';
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

just fixing types in this file.

@@ -1,20 +1,12 @@
import { Suite } from 'mocha';
import { withFixtures, logInWithBalanceValidation } from '../../helpers';
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

just fixing types in this file.


const convertETHToHexGwei = (eth) => convertToHexValue(eth * 10 ** 18);

/**
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

it was helpful to have proper types when refactoring to use the new withWindowUntilClose method, so I added them here (and fixed several usages of withFixtures).

@davidmurdoch davidmurdoch marked this pull request as ready for review August 16, 2024 17:40
@davidmurdoch davidmurdoch requested review from a team as code owners August 16, 2024 17:40
@davidmurdoch davidmurdoch changed the title test: add driver.withWindowUntilClose helper method test: add driver.clickElementAndWaitForWindowToClose helper method Aug 16, 2024
@metamaskbot
Copy link
Copy Markdown
Collaborator

Builds ready [d803179]
Page Load Metrics (62 ± 9 ms)
PlatformPageMetricMin (ms)Max (ms)Average (ms)StandardDeviation (ms)MarginOfError (ms)
ChromeHomefirstPaint6213295189
domContentLoaded85523126
load379162189
domInteractive85523126
Bundle size diffs
  • background: 0 Bytes (0.00%)
  • ui: 0 Bytes (0.00%)
  • common: 0 Bytes (0.00%)

danjm
danjm previously approved these changes Aug 16, 2024
@sonarqubecloud
Copy link
Copy Markdown

@metamaskbot
Copy link
Copy Markdown
Collaborator

Builds ready [5613a52]
Page Load Metrics (265 ± 270 ms)
PlatformPageMetricMin (ms)Max (ms)Average (ms)StandardDeviation (ms)MarginOfError (ms)
ChromeHomefirstPaint743011267335
domContentLoaded98829189
load432244265562270
domInteractive98829189
Bundle size diffs
  • background: 0 Bytes (0.00%)
  • ui: 0 Bytes (0.00%)
  • common: 0 Bytes (0.00%)

Comment on lines +873 to +904
async clickElementAndWaitForWindowToClose(rawLocator, retries = 3) {
const handle = await this.driver.getWindowHandle();
await this.clickElement(rawLocator, retries);
await this.waitForWindowToClose(handle);
}

/**
* Waits for the specified window handle to close before returning.
*
* @param {string} handle - The handle of the window or tab we'll wait for.
* @param {number} [timeout] - The amount of time in milliseconds to wait
* before timing out. Defaults to `this.timeout`.
* @throws {Error} throws an error if the window handle doesn't close within
* the timeout.
*/
async waitForWindowToClose(handle, timeout = this.timeout) {
const start = Date.now();
// eslint-disable-next-line no-constant-condition
while (true) {
const handles = await this.getAllWindowHandles();
if (!handles.includes(handle)) {
return;
}

const timeElapsed = Date.now() - start;
if (timeElapsed > timeout) {
throw new Error(
`waitForWindowToClose timed out waiting for window handle '${handle}' to close.`,
);
}
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

How do you feel about adding a title? It would allow for slightly easier-to-read error messages.

Suggested change
async clickElementAndWaitForWindowToClose(rawLocator, retries = 3) {
const handle = await this.driver.getWindowHandle();
await this.clickElement(rawLocator, retries);
await this.waitForWindowToClose(handle);
}
/**
* Waits for the specified window handle to close before returning.
*
* @param {string} handle - The handle of the window or tab we'll wait for.
* @param {number} [timeout] - The amount of time in milliseconds to wait
* before timing out. Defaults to `this.timeout`.
* @throws {Error} throws an error if the window handle doesn't close within
* the timeout.
*/
async waitForWindowToClose(handle, timeout = this.timeout) {
const start = Date.now();
// eslint-disable-next-line no-constant-condition
while (true) {
const handles = await this.getAllWindowHandles();
if (!handles.includes(handle)) {
return;
}
const timeElapsed = Date.now() - start;
if (timeElapsed > timeout) {
throw new Error(
`waitForWindowToClose timed out waiting for window handle '${handle}' to close.`,
);
}
}
}
async clickElementAndWaitForWindowToClose(rawLocator, retries = 3) {
const handle = await this.driver.getWindowHandle();
const title = await this.driver.getTitle();
await this.clickElement(rawLocator, retries);
await this.waitForWindowToClose(handle, title);
}
/**
* Waits for the specified window handle to close before returning.
*
* @param {string} handle - The handle of the window or tab we'll wait for.
* @param {number} [timeout] - The amount of time in milliseconds to wait
* before timing out. Defaults to `this.timeout`.
* @throws {Error} throws an error if the window handle doesn't close within
* the timeout.
*/
async waitForWindowToClose(handle, title, timeout = this.timeout) {
const start = Date.now();
// eslint-disable-next-line no-constant-condition
while (true) {
const handles = await this.getAllWindowHandles();
if (!handles.includes(handle)) {
return;
}
const timeElapsed = Date.now() - start;
if (timeElapsed > timeout) {
throw new Error(
`waitForWindowToClose timed out waiting for window handle '${handle}' with title '${title}' to close.`,
);
}
}
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I went down this route to add the title to the error message, but I found it likely to be flaky. The way it's prototyped above, where title isn't guaranteed to be the actual title of the window due to race conditions between multiple async calls to query the windows (this async nature of these calls seem to be the cause of another race condition in our tests), seems especially prone to flakiness.

That said, all of these async window interactions are potentially flaky since the windows can be changing while we are interacting with them. I'm in favor of removing as much potential flakiness as possible from the hot path.

If the async query for the current window title were to be made in the error path of waitForWindowToClose it might be slightly better... but a title fetched by getTitle isn't guaranteed to match that of the referenced window handle (since the windowHandle could finally close during the call to getTitle, in which case it would return a different title).

All this just makes me want to switch to a better browser testing framework, if there is one, and/or extend socket-background.to-mocha.ts to automatically sync all tab state (chrome.tabs.query) with the server, and implement event-driven interactions (e.g., windowHandle.on("close", ...)) and maybe even some sort of interaction lock mechanism.

@davidmurdoch davidmurdoch merged commit 97246c5 into develop Aug 19, 2024
@davidmurdoch davidmurdoch deleted the fix-flaky-test-window-close branch August 19, 2024 15:13
@github-actions github-actions bot locked and limited conversation to collaborators Aug 19, 2024
@metamaskbot metamaskbot added the release-12.5.0 Issue or pull request that will be included in release 12.5.0 label Aug 19, 2024
@gauthierpetetin gauthierpetetin added release-12.4.0 Issue or pull request that will be included in release 12.4.0 and removed release-12.5.0 Issue or pull request that will be included in release 12.5.0 labels Sep 11, 2024
@HowardBraham HowardBraham added contributor experience An issue that impacts, or planned improvement to, the contributor experience. and removed team-contributor-experience labels Oct 29, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

contributor experience An issue that impacts, or planned improvement to, the contributor experience. release-12.4.0 Issue or pull request that will be included in release 12.4.0 team-extension-platform Extension Platform team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants