Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 75 additions & 12 deletions e2e/framework/fixtures/FixtureHelper.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable no-unsafe-finally */
/* eslint-disable import/no-nodejs-modules */
import FixtureServer from './FixtureServer';
import { AnvilManager, Hardfork } from '../../seeder/anvil-manager';
Expand Down Expand Up @@ -409,6 +410,8 @@ export async function withFixtures(
const dappServer: http.Server[] = [];
const fixtureServer = new FixtureServer();

let testError: Error | null = null;

try {
// Handle smart contracts
let contractRegistry;
Expand Down Expand Up @@ -457,35 +460,95 @@ export async function withFixtures(

await testSuite({ contractRegistry, mockServer, localNodes });
} catch (error) {
testError = error as Error;
logger.error('Error in withFixtures:', error);
throw error;
} finally {
const cleanupErrors: Error[] = [];

if (endTestfn) {
// Pass the mockServer to the endTestfn if it exists as we may want
// to capture events before cleanup
await endTestfn({ mockServer });
try {
// Pass the mockServer to the endTestfn if it exists as we may want
// to capture events before cleanup
await endTestfn({ mockServer });
} catch (endTestError) {
logger.error('Error in endTestfn:', endTestError);
cleanupErrors.push(endTestError as Error);
}
}

// Clean up all local nodes
if (localNodes && localNodes.length > 0) {
await handleLocalNodeCleanup(localNodes);
try {
await handleLocalNodeCleanup(localNodes);
} catch (cleanupError) {
logger.error('Error during local node cleanup:', cleanupError);
cleanupErrors.push(cleanupError as Error);
}
}

if (dapps && dapps.length > 0) {
await handleDappCleanup(dapps, dappServer);
try {
await handleDappCleanup(dapps, dappServer);
} catch (cleanupError) {
logger.error('Error during dapp cleanup:', cleanupError);
cleanupErrors.push(cleanupError as Error);
}
}

if (mockServer) {
await stopMockServer(mockServer);
try {
await stopMockServer(mockServer);
} catch (cleanupError) {
logger.error('Error during mock server cleanup:', cleanupError);
cleanupErrors.push(cleanupError as Error);
}
}

await stopFixtureServer(fixtureServer);
try {
await stopFixtureServer(fixtureServer);
} catch (cleanupError) {
logger.error('Error during fixture server cleanup:', cleanupError);
cleanupErrors.push(cleanupError as Error);
}

try {
// Force reload React Native to stop any lingering timers
await device.reloadReactNative();
} catch (cleanupError) {
logger.error('Error during React Native reload:', cleanupError);
cleanupErrors.push(cleanupError as Error);
}

// Force reload React Native to stop any lingering timers
await device.reloadReactNative();
try {
// Validate live requests
validateLiveRequests(mockServer);
} catch (cleanupError) {
logger.error('Error during live request validation:', cleanupError);
cleanupErrors.push(cleanupError as Error);
}

// Validate live requests
validateLiveRequests(mockServer);
// Handle error reporting: prioritize test error over cleanup errors
if (testError && cleanupErrors.length > 0) {
// Both test and cleanup failed - report both but throw the test error
const cleanupErrorMessages = cleanupErrors
.map((err, index) => `${index + 1}. ${err.message}`)
.join('\n');
logger.error(
`Test failed AND cleanup failed with ${cleanupErrors.length} error(s):\n${cleanupErrorMessages}`,
);
throw testError; // Preserve original test failure
} else if (testError) {
// Only test failed - normal case
throw testError;
} else if (cleanupErrors.length > 0) {
// Only cleanup failed - throw cleanup error
const errorMessages = cleanupErrors
.map((err, index) => `${index + 1}. ${err.message}`)
.join('\n');
const errorMessage = `Test cleanup failed with ${cleanupErrors.length} error(s):\n${errorMessages}`;
throw new Error(errorMessage);
}
// No errors - test passed successfully
}
}

Expand Down
7 changes: 1 addition & 6 deletions e2e/pages/Onboarding/ImportWalletView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,7 @@ class ImportWalletView {
async enterSecretRecoveryPhrase(secretRecoveryPhrase: string): Promise<void> {
await Gestures.replaceText(this.seedPhraseInput, secretRecoveryPhrase, {
elemDescription: 'Import Wallet Secret Recovery Phrase Input Box',
});
}
async clearSecretRecoveryPhraseInputBox(): Promise<void> {
await Gestures.typeText(this.seedPhraseInput, '', {
elemDescription: 'Import Wallet Secret Recovery Phrase Input Box',
clearFirst: true,
checkVisibility: device.getPlatform() === 'ios',
});
}

Expand Down
9 changes: 0 additions & 9 deletions e2e/pages/wallet/WalletView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -241,15 +241,6 @@ class WalletView {
});
}

async pullToRefreshTokensList(): Promise<void> {
const tokensContainer = await this.getTokensInWallet();
await Gestures.swipe(tokensContainer as unknown as DetoxElement, 'down', {
speed: 'slow',
percentage: 0.8,
elemDescription: 'pull to refresh tokens list',
});
}

async scrollToToken(
tokenName: string,
direction: 'up' | 'down' = 'down',
Expand Down
103 changes: 97 additions & 6 deletions e2e/seeder/anvil-manager.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* eslint-disable import/no-nodejs-modules */
import { createAnvil, Anvil as AnvilType } from '@viem/anvil';
import { createServer } from 'net';
import { createAnvilClients } from './anvil-clients';
import { AnvilPort } from '../framework/fixtures/FixtureUtils';
import { AnvilNodeOptions } from '../framework/types';
Expand Down Expand Up @@ -84,6 +85,54 @@ class AnvilManager {

// Using shared port utilities from FixtureUtils

/**
* Check if a port is available
* @param {number} port - Port to check
* @returns {Promise<boolean>} True if port is available
*/
private async isPortAvailable(port: number): Promise<boolean> {
return new Promise((resolve) => {
const server = createServer();

server.listen(port, '127.0.0.1', () => {
server.once('close', () => {
resolve(true);
});
server.close();
});

server.on('error', () => {
resolve(false);
});
});
}

/**
* Find an available port starting from the given port
* @param {number} startPort - Starting port to check
* @param {number} maxRetries - Maximum number of ports to try
* @returns {Promise<number>} Available port number
*/
private async findAvailablePort(
startPort: number,
maxRetries: number = 100,
): Promise<number> {
for (let i = 0; i < maxRetries; i++) {
const port = startPort + i;
if (port > 65535) {
break; // Port number too high
}

if (await this.isPortAvailable(port)) {
return port;
}
}

throw new Error(
`No available port found starting from ${startPort} after ${maxRetries} attempts`,
);
}

/**
* Start the Anvil server with the specified options
* @param {Object} opts - Server configuration options
Expand All @@ -101,22 +150,64 @@ class AnvilManager {
* @throws {Error} If server fails to start
*/
async start(opts: AnvilNodeOptions = {}): Promise<void> {
const options = { ...defaultOptions, ...opts, port: AnvilPort() };
const { port } = options;
// First try the configured port, then find an available one if it fails
const initialPort = opts.port || AnvilPort();
let port = initialPort;

// If the initial port is busy, find an available one
if (!(await this.isPortAvailable(initialPort))) {
logger.debug(
`Port ${initialPort} is not available, searching for alternative...`,
);
port = await this.findAvailablePort(initialPort);
logger.debug(`Found available port: ${port}`);
}

const options = { ...defaultOptions, ...opts, port };
this.serverPort = port;

try {
logger.debug('Starting Anvil server...');
logger.debug(`Starting Anvil server on port ${port}...`);

// Create and start the server instance
this.server = createAnvil({
...options,
});

await this.server.start();
logger.debug(`Server started on port ${port}`);
logger.debug(`Server started successfully on port ${port}`);
} catch (error) {
logger.error('Failed to start server:', error);
logger.error(`Failed to start server on port ${port}:`, error);

// If the error is about address already in use, try to find another port
if (
error instanceof Error &&
error.message.includes('Address already in use')
) {
logger.debug(
'Attempting to find alternative port due to address conflict...',
);
try {
const alternativePort = await this.findAvailablePort(port + 1);
logger.debug(`Retrying with port ${alternativePort}...`);

const retryOptions = { ...options, port: alternativePort };
this.serverPort = alternativePort;

this.server = createAnvil(retryOptions);
await this.server.start();
logger.debug(
`Server started successfully on alternative port ${alternativePort}`,
);
return;
} catch (retryError) {
logger.error(
'Failed to start server with alternative port:',
retryError,
);
}
}

this.server = undefined;
this.serverPort = undefined;
throw error;
Expand All @@ -134,7 +225,7 @@ class AnvilManager {
}
const { walletClient, publicClient, testClient } = createAnvilClients(
this.server.options.chainId ?? 1337,
this.server.options.port ?? AnvilPort(),
this.serverPort ?? this.server.options.port ?? AnvilPort(),
);

return { walletClient, publicClient, testClient };
Expand Down
Loading
Loading