Skip to content

Commit 14b1007

Browse files
e2e improve metamask tests stability (#426)
1 parent 5c03125 commit 14b1007

File tree

5 files changed

+69
-50
lines changed

5 files changed

+69
-50
lines changed

tests/config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ export const TEST_CONFIG = {
22
/* Retry on CI only */
33
retries: process.env.CI ? 1 : 0,
44
/* Parallel tests on CI only. */
5-
workers: 10,
5+
workers: 5,
66
/* Timeout for each test */
77
timeout: 120_000,
88
/* Timeout for locators */

tests/support/metaMask/handleMetaMaskSnap.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { getPageAndWaitForLoad } from '../template/getPageAndWaitForLoad';
33
import { waitUntilStable } from '../template/waitUntilStable';
44

55
const RETRY_DELAY_BASE_MS = 500;
6-
const CLICK_TIMEOUT_MS = 2500;
6+
const CLICK_TIMEOUT_MS = 6000;
77

88
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
99

Lines changed: 49 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { errors, Page } from '@playwright/test';
2+
import { WaitForMetaMaskLoadOptions } from '../template/types';
23
import { waitUntilStable } from '../template/waitUntilStable';
34

45
const DEFAULT_TIMEOUT = 10_000;
5-
const DEFAULT_POST_DELAY_MS = 300;
6+
const DEFAULT_CONCURRENCY = 3;
67

7-
// Core loading indicators commonly found in MetaMask screens
88
const BASE_LOADING_SELECTORS: readonly string[] = [
99
'.loading-logo',
1010
'.loading-spinner',
@@ -20,71 +20,73 @@ const BASE_LOADING_SELECTORS: readonly string[] = [
2020
'.spinner'
2121
];
2222

23-
type WaitForMetaMaskLoadOptions = {
24-
// Per-selector timeout while waiting to become hidden (defaults to 10s).
25-
selectorTimeoutMs?: number;
26-
// Additional page selectors that should also be hidden before continuing.
27-
extraLoadingSelectors?: string[];
28-
// Milliseconds to sleep after the page looks ready (defaults to 300ms).
29-
postDelayMs?: number;
30-
// Skip the initial stable wait if you’ve already done it.
31-
skipInitialStabilityWait?: boolean;
32-
};
33-
34-
// Waits for MetaMask UI to become usable:
35-
// 1) (optionally) waits for DOM/network to settle
36-
// 2) waits for known loading indicators to disappear (best-effort)
37-
// 3) small post-delay to avoid flakiness on slow CI
23+
/**
24+
* Waits for MetaMask UI to be usable:
25+
* 1) (optional) wait for page stability
26+
* 2) wait for known loading indicators to hide (best-effort)
27+
* 3) brief post delay (conditional)
28+
*/
3829
export async function waitForMetaMaskLoad(
3930
page: Page,
40-
options: WaitForMetaMaskLoadOptions = {}
41-
): Promise<Page> {
31+
options: WaitForMetaMaskLoadOptions
32+
): Promise<void> {
4233
const {
4334
selectorTimeoutMs = DEFAULT_TIMEOUT,
4435
extraLoadingSelectors = [],
45-
postDelayMs = DEFAULT_POST_DELAY_MS,
46-
skipInitialStabilityWait = false
36+
skipInitialStabilityWait = false,
37+
concurrency = DEFAULT_CONCURRENCY // how many selectors to wait on in parallel
4738
} = options;
4839

4940
try {
5041
if (!skipInitialStabilityWait) {
5142
await waitUntilStable(page);
5243
}
5344

54-
const selectors = [...BASE_LOADING_SELECTORS, ...extraLoadingSelectors];
45+
const selectors = Array.from(
46+
new Set([...BASE_LOADING_SELECTORS, ...extraLoadingSelectors])
47+
);
5548

56-
await waitForSelectorsHidden(page, selectors, selectorTimeoutMs);
49+
await waitSelectorsHiddenWithConcurrency(
50+
page,
51+
selectors,
52+
selectorTimeoutMs,
53+
concurrency
54+
);
5755
} catch (err) {
58-
// Don’t fail the test — UI might still be interactive
5956
const msg = err instanceof Error ? err.message : String(err);
6057
console.warn(`[waitForMetaMaskLoad] Non-fatal warning: ${msg}`);
6158
}
62-
63-
await page.waitForTimeout(postDelayMs);
64-
return page;
6559
}
6660

67-
// Wait until each selector is hidden; ignore timeouts (selector may not exist on this screen).
68-
async function waitForSelectorsHidden(
61+
async function waitSelectorsHiddenWithConcurrency(
6962
page: Page,
7063
selectors: string[],
71-
perSelectorTimeoutMs: number
72-
): Promise<void> {
73-
await Promise.all(
74-
selectors.map(async (selector) => {
75-
try {
76-
await waitUntilStable(page);
77-
await page.waitForSelector(selector, {
78-
state: 'hidden',
79-
timeout: perSelectorTimeoutMs
80-
});
81-
} catch (err) {
82-
if (err instanceof errors.TimeoutError) {
83-
// OK: selector may never appear on this view; continue
84-
return;
64+
perSelectorTimeoutMs: number,
65+
concurrency: number
66+
) {
67+
// One stability wait per batch, not per selector
68+
await waitUntilStable(page);
69+
70+
// Process selectors with a small concurrency to reduce polling in CI/CD
71+
let i = 0;
72+
while (i < selectors.length) {
73+
const batch = selectors.slice(i, i + Math.max(1, concurrency));
74+
await Promise.all(
75+
batch.map(async (selector) => {
76+
try {
77+
await page.waitForSelector(selector, {
78+
state: 'hidden',
79+
timeout: perSelectorTimeoutMs
80+
});
81+
} catch (err) {
82+
if (err instanceof errors.TimeoutError) {
83+
// OK if a selector never appears on this screen
84+
return;
85+
}
86+
throw err;
8587
}
86-
throw err;
87-
}
88-
})
89-
);
88+
})
89+
);
90+
i += batch.length;
91+
}
9092
}

tests/support/template/getPageAndWaitForLoad.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,14 @@ export async function getPageAndWaitForLoad(
3636
} catch (error) {
3737
retries++;
3838
const message = error instanceof Error ? error.message : 'Unknown error';
39+
const openedPageUrls = context
40+
.pages()
41+
.map((p) => p.url())
42+
.filter(Boolean);
3943
console.warn(
40-
`[getPageAndWaitForLoad] Retry ${retries}/3 after error: ${message}`
44+
`[getPageAndWaitForLoad] Retry ${retries}/3 after error: ${message}. Open pages (${
45+
openedPageUrls.length
46+
}): ${openedPageUrls.join(', ')}`
4147
);
4248

4349
if (retries >= maxRetries) throw error;

tests/support/template/types.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ export interface AuthenticateWithKeystoreType {
1010
keystorePassword: string;
1111
}
1212

13+
export interface ConnectPasskeyWalletType {
14+
page: Page;
15+
}
16+
1317
export interface InMemoryProviderType {
1418
page: Page;
1519
loginMethod: InMemoryProviderLoginMethodType;
@@ -171,3 +175,10 @@ export type GetPageAndWaitForLoad = (
171175
urlSubstring: string | RegExp,
172176
options?: GetPageAndWaitForLoadOptions
173177
) => Promise<Page>;
178+
179+
export type WaitForMetaMaskLoadOptions = {
180+
selectorTimeoutMs?: number;
181+
extraLoadingSelectors?: string[];
182+
skipInitialStabilityWait?: boolean;
183+
concurrency?: number;
184+
};

0 commit comments

Comments
 (0)