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
72 changes: 45 additions & 27 deletions e2e/api-mocking/helpers/remoteFeatureFlagsHelper.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -268,24 +268,29 @@ describe('Remote Feature Flags Helper', () => {
mockSetupMockRequest.mockResolvedValue(undefined);
});

it('should call setupMockRequest with default configuration', async () => {
it('should call setupMockRequest with default configuration for both main and flask distributions', async () => {
await setupRemoteFeatureFlagsMock(mockServer);

expect(mockSetupMockRequest).toHaveBeenCalledTimes(3);
expect(mockSetupMockRequest).toHaveBeenCalledTimes(6);

const expectedEnvironments = ['dev', 'test', 'prod'];

expectedEnvironments.forEach((env, index) => {
expect(mockSetupMockRequest).toHaveBeenNthCalledWith(
index + 1,
mockServer,
{
requestMethod: 'GET',
url: `https://client-config.api.cx.metamask.io/v1/flags?client=mobile&distribution=main&environment=${env}`,
response: expect.any(Array),
responseCode: 200,
},
);
const expectedDistributions = ['main', 'flask'];

let callIndex = 0;
expectedDistributions.forEach((distribution) => {
expectedEnvironments.forEach((env) => {
expect(mockSetupMockRequest).toHaveBeenNthCalledWith(
callIndex + 1,
mockServer,
{
requestMethod: 'GET',
url: `https://client-config.api.cx.metamask.io/v1/flags?client=mobile&distribution=${distribution}&environment=${env}`,
response: expect.any(Array),
responseCode: 200,
},
);
callIndex++;
});
});

const callArgs = mockSetupMockRequest.mock.calls[0][1];
Expand All @@ -295,30 +300,43 @@ describe('Remote Feature Flags Helper', () => {
expect(response).toContainEqual({ rewards: false });
});

it('should call setupMockRequest with flag overrides', async () => {
it('should call setupMockRequest with flag overrides for both distributions', async () => {
await setupRemoteFeatureFlagsMock(mockServer, { rewards: true });

expect(mockSetupMockRequest).toHaveBeenCalledTimes(3);
expect(mockSetupMockRequest).toHaveBeenCalledTimes(6);
const callArgs = mockSetupMockRequest.mock.calls[0][1];
expect(callArgs.response).toContainEqual({ rewards: true });
});

it('should call setupMockRequest with custom distribution', async () => {
await setupRemoteFeatureFlagsMock(mockServer, {}, 'flask');
it('should set up mocks for both main and flask distributions across all environments', async () => {
await setupRemoteFeatureFlagsMock(mockServer);

expect(mockSetupMockRequest).toHaveBeenCalledTimes(6);

expect(mockSetupMockRequest).toHaveBeenCalledTimes(3);
const devCallArgs = mockSetupMockRequest.mock.calls[0][1];
const testCallArgs = mockSetupMockRequest.mock.calls[1][1];
const prodCallArgs = mockSetupMockRequest.mock.calls[2][1];
expect(devCallArgs.url).toBe(
'https://client-config.api.cx.metamask.io/v1/flags?client=mobile&distribution=flask&environment=dev',
// Check that we have calls for both distributions
const urls = mockSetupMockRequest.mock.calls.map((call) => call[1].url);

// Check main distribution URLs
expect(urls).toContain(
'https://client-config.api.cx.metamask.io/v1/flags?client=mobile&distribution=main&environment=dev',
);
expect(prodCallArgs.url).toBe(
'https://client-config.api.cx.metamask.io/v1/flags?client=mobile&distribution=flask&environment=prod',
expect(urls).toContain(
'https://client-config.api.cx.metamask.io/v1/flags?client=mobile&distribution=main&environment=test',
);
expect(urls).toContain(
'https://client-config.api.cx.metamask.io/v1/flags?client=mobile&distribution=main&environment=prod',
);
expect(testCallArgs.url).toBe(

// Check flask distribution URLs
expect(urls).toContain(
'https://client-config.api.cx.metamask.io/v1/flags?client=mobile&distribution=flask&environment=dev',
);
expect(urls).toContain(
'https://client-config.api.cx.metamask.io/v1/flags?client=mobile&distribution=flask&environment=test',
);
expect(urls).toContain(
'https://client-config.api.cx.metamask.io/v1/flags?client=mobile&distribution=flask&environment=prod',
);
});

it('should handle setupMockRequest errors', async () => {
Expand Down
27 changes: 15 additions & 12 deletions e2e/api-mocking/helpers/remoteFeatureFlagsHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -335,26 +335,29 @@ export const createRemoteFeatureFlagsMock = (
};

/**
* Sets up default remote feature flags mock on mockttp server
* Sets up default remote feature flags mock on mockttp server for both main and flask distributions
* This will be called automatically and can be overridden by testSpecificMock
*/
export const setupRemoteFeatureFlagsMock = async (
mockServer: Mockttp,
flagOverrides: Record<string, unknown> = {},
distribution: string = 'main',
): Promise<void> => {
const environments = ['dev', 'test', 'prod'] as const;
const mockPromises = environments.map((environment) => {
const { urlEndpoint, response, responseCode } =
createRemoteFeatureFlagsMock(flagOverrides, distribution, environment);
const distributions = ['main', 'flask'] as const;

return setupMockRequest(mockServer, {
requestMethod: 'GET',
url: urlEndpoint,
response,
responseCode,
});
});
const mockPromises = distributions.flatMap((distribution) =>
environments.map((environment) => {
const { urlEndpoint, response, responseCode } =
createRemoteFeatureFlagsMock(flagOverrides, distribution, environment);

return setupMockRequest(mockServer, {
requestMethod: 'GET',
url: urlEndpoint,
response,
responseCode,
});
}),
);

await Promise.all(mockPromises);
};
17 changes: 10 additions & 7 deletions e2e/framework/fixtures/FixtureHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,7 @@ export async function withFixtures(
languageAndLocale,
permissions = {},
endTestfn,
skipReactNativeReload = false,
} = options;

// Prepare android devices for testing to avoid having this in all tests
Expand Down Expand Up @@ -511,13 +512,15 @@ export async function withFixtures(
cleanupErrors.push(cleanupError as Error);
}

try {
// Force reload React Native to stop any lingering timers
await device.reloadReactNative();
} catch (cleanupError) {
logger.warn('React Native reload failed (non-critical):', cleanupError);
// Don't add to cleanupErrors as this is a non-critical cleanup operation
// The test should not fail if only React Native reload fails
if (!skipReactNativeReload) {
try {
// Force reload React Native to stop any lingering timers
await device.reloadReactNative();
} catch (cleanupError) {
logger.warn('React Native reload failed (non-critical):', cleanupError);
// Don't add to cleanupErrors as this is a non-critical cleanup operation
// The test should not fail if only React Native reload fails
}
}

try {
Expand Down
27 changes: 14 additions & 13 deletions e2e/framework/fixtures/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,20 @@ describe('My Test Suite', () => {

## WithFixturesOptions Reference

| Option | Type | Required | Default | Description |
| ------------------- | ----------------------- | -------- | ------- | -------------------------------------------------------------------------------------- |
| `fixture` | `FixtureBuilder` | `true` | - | The fixture object created via FixtureBuilder |
| `restartDevice` | `boolean` | `false` | `false` | Whether to restart the device before the test |
| `smartContracts` | `string[]` | `false` | - | The list of contract strings to be deployed via the first seeder |
| `disableLocalNodes` | `boolean` | `false` | `false` | Disables all local nodes for the test |
| `dapps` | `DappOptions[]` | `false` | - | Lists the dapps that should be launched before the tests |
| `localNodeOptions` | `LocalNodeOptionsInput` | `false` | Anvil | Allows overriding the use of Anvil in favor of any other node |
| `testSpecificMock` | `TestSpecificMock` | `false` | - | Allows to set mocks that are specific to the test |
| `launcArgs` | `LaunchArgs` | `false` | `-` | Allows sending arbitrary launchArgs such as the fixtureServerPort |
| `languageAndLocale` | `LanguageAndLocale` | `false` | - | Set the device Language and Locale of the device |
| `permissions` | `object` | `false` | - | Allows setting specific device permissions |
| `endTestfn` | `fn()` | `false` | - | Allows providing a function that is executed at the end of the test before the cleanup |
| Option | Type | Required | Default | Description |
| ----------------------- | ----------------------- | -------- | ------- | -------------------------------------------------------------------------------------- |
| `fixture` | `FixtureBuilder` | `true` | - | The fixture object created via FixtureBuilder |
| `restartDevice` | `boolean` | `false` | `false` | Whether to restart the device before the test |
| `smartContracts` | `string[]` | `false` | - | The list of contract strings to be deployed via the first seeder |
| `disableLocalNodes` | `boolean` | `false` | `false` | Disables all local nodes for the test |
| `dapps` | `DappOptions[]` | `false` | - | Lists the dapps that should be launched before the tests |
| `localNodeOptions` | `LocalNodeOptionsInput` | `false` | Anvil | Allows overriding the use of Anvil in favor of any other node |
| `testSpecificMock` | `TestSpecificMock` | `false` | - | Allows to set mocks that are specific to the test |
| `launcArgs` | `LaunchArgs` | `false` | `-` | Allows sending arbitrary launchArgs such as the fixtureServerPort |
| `languageAndLocale` | `LanguageAndLocale` | `false` | - | Set the device Language and Locale of the device |
| `permissions` | `object` | `false` | - | Allows setting specific device permissions |
| `endTestfn` | `fn()` | `false` | - | Allows providing a function that is executed at the end of the test before the cleanup |
| `skipReactNativeReload` | `boolean` | `false` | `false` | Skip React Native reload during cleanup to preserve app state between tests |

## Migration from Legacy Options

Expand Down
6 changes: 6 additions & 0 deletions e2e/framework/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -228,4 +228,10 @@ export interface WithFixturesOptions {
permissions?: Record<string, unknown>;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
endTestfn?: (...args: any[]) => Promise<void>;
/**
* Skip reloading React Native during cleanup to preserve app state between tests.
* Use this when tests need to maintain state across multiple `it` blocks.
* @default false
*/
skipReactNativeReload?: boolean;
}
5 changes: 5 additions & 0 deletions e2e/specs/snaps/test-snap-background-events.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ describe(FlaskBuildTests('Background Events Snap Tests'), () => {
{
fixture: new FixtureBuilder().build(),
restartDevice: true,
skipReactNativeReload: true,
},
async () => {
await loginToApp();
Expand All @@ -32,6 +33,7 @@ describe(FlaskBuildTests('Background Events Snap Tests'), () => {
await withFixtures(
{
fixture: new FixtureBuilder().build(),
skipReactNativeReload: true,
},
async () => {
const futureDate = new Date(Date.now() + 5_000).toISOString();
Expand All @@ -54,6 +56,7 @@ describe(FlaskBuildTests('Background Events Snap Tests'), () => {
await withFixtures(
{
fixture: new FixtureBuilder().build(),
skipReactNativeReload: true,
},
async () => {
await TestSnaps.fillMessage('backgroundEventDurationInput', 'PT5S');
Expand All @@ -74,6 +77,7 @@ describe(FlaskBuildTests('Background Events Snap Tests'), () => {
await withFixtures(
{
fixture: new FixtureBuilder().build(),
skipReactNativeReload: true,
},
async () => {
// Intentionally scheduling an event for 1 hour into the future, so it
Expand Down Expand Up @@ -109,6 +113,7 @@ describe(FlaskBuildTests('Background Events Snap Tests'), () => {
await withFixtures(
{
fixture: new FixtureBuilder().build(),
skipReactNativeReload: true,
},
async () => {
const pastDate = new Date(Date.now() - 5_000).toISOString();
Expand Down
8 changes: 8 additions & 0 deletions e2e/specs/snaps/test-snap-bip-32.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ describe(FlaskBuildTests('BIP-32 Snap Tests'), () => {
{
fixture: new FixtureBuilder().withMultiSRPKeyringController().build(),
restartDevice: true,
skipReactNativeReload: true,
},
async () => {
await loginToApp();
Expand All @@ -29,6 +30,7 @@ describe(FlaskBuildTests('BIP-32 Snap Tests'), () => {
await withFixtures(
{
fixture: new FixtureBuilder().withMultiSRPKeyringController().build(),
skipReactNativeReload: true,
},
async () => {
await TestSnaps.tapButton('getPublicKeyBip32Button');
Expand All @@ -44,6 +46,7 @@ describe(FlaskBuildTests('BIP-32 Snap Tests'), () => {
await withFixtures(
{
fixture: new FixtureBuilder().withMultiSRPKeyringController().build(),
skipReactNativeReload: true,
},
async () => {
await TestSnaps.fillMessage('messageSecp256k1Input', 'foo bar');
Expand All @@ -62,6 +65,7 @@ describe(FlaskBuildTests('BIP-32 Snap Tests'), () => {
await withFixtures(
{
fixture: new FixtureBuilder().withMultiSRPKeyringController().build(),
skipReactNativeReload: true,
},
async () => {
await TestSnaps.fillMessage('messageEd25519Input', 'foo bar');
Expand All @@ -80,6 +84,7 @@ describe(FlaskBuildTests('BIP-32 Snap Tests'), () => {
await withFixtures(
{
fixture: new FixtureBuilder().withMultiSRPKeyringController().build(),
skipReactNativeReload: true,
},
async () => {
await TestSnaps.fillMessage('messageEd25519Bip32Input', 'foo bar');
Expand All @@ -98,6 +103,7 @@ describe(FlaskBuildTests('BIP-32 Snap Tests'), () => {
await withFixtures(
{
fixture: new FixtureBuilder().withMultiSRPKeyringController().build(),
skipReactNativeReload: true,
},
async () => {
await TestSnaps.selectInDropdown('bip32EntropyDropDown', 'SRP 1');
Expand All @@ -117,6 +123,7 @@ describe(FlaskBuildTests('BIP-32 Snap Tests'), () => {
await withFixtures(
{
fixture: new FixtureBuilder().withMultiSRPKeyringController().build(),
skipReactNativeReload: true,
},
async () => {
await TestSnaps.selectInDropdown('bip32EntropyDropDown', 'SRP 2');
Expand All @@ -136,6 +143,7 @@ describe(FlaskBuildTests('BIP-32 Snap Tests'), () => {
await withFixtures(
{
fixture: new FixtureBuilder().withMultiSRPKeyringController().build(),
skipReactNativeReload: true,
},
async () => {
await TestSnaps.selectInDropdown('bip32EntropyDropDown', 'Invalid');
Expand Down
6 changes: 6 additions & 0 deletions e2e/specs/snaps/test-snap-bip-44.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ describe(FlaskBuildTests('BIP-44 Snap Tests'), () => {
{
fixture: new FixtureBuilder().withMultiSRPKeyringController().build(),
restartDevice: true,
skipReactNativeReload: true,
},
async () => {
await loginToApp();
Expand All @@ -29,6 +30,7 @@ describe(FlaskBuildTests('BIP-44 Snap Tests'), () => {
await withFixtures(
{
fixture: new FixtureBuilder().withMultiSRPKeyringController().build(),
skipReactNativeReload: true,
},
async () => {
await TestSnaps.tapButton('getPublicKeyBip44Button');
Expand All @@ -44,6 +46,7 @@ describe(FlaskBuildTests('BIP-44 Snap Tests'), () => {
await withFixtures(
{
fixture: new FixtureBuilder().withMultiSRPKeyringController().build(),
skipReactNativeReload: true,
},
async () => {
await TestSnaps.fillMessage('messageBip44Input', '1234');
Expand All @@ -61,6 +64,7 @@ describe(FlaskBuildTests('BIP-44 Snap Tests'), () => {
await withFixtures(
{
fixture: new FixtureBuilder().withMultiSRPKeyringController().build(),
skipReactNativeReload: true,
},
async () => {
await TestSnaps.selectInDropdown('bip44EntropyDropDown', 'SRP 1');
Expand All @@ -79,6 +83,7 @@ describe(FlaskBuildTests('BIP-44 Snap Tests'), () => {
await withFixtures(
{
fixture: new FixtureBuilder().withMultiSRPKeyringController().build(),
skipReactNativeReload: true,
},
async () => {
await TestSnaps.selectInDropdown('bip44EntropyDropDown', 'SRP 2');
Expand All @@ -97,6 +102,7 @@ describe(FlaskBuildTests('BIP-44 Snap Tests'), () => {
await withFixtures(
{
fixture: new FixtureBuilder().withMultiSRPKeyringController().build(),
skipReactNativeReload: true,
},
async () => {
await TestSnaps.selectInDropdown('bip44EntropyDropDown', 'Invalid');
Expand Down
2 changes: 2 additions & 0 deletions e2e/specs/snaps/test-snap-client-status.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ describe(FlaskBuildTests('Client Status Snap Tests'), () => {
{
fixture: new FixtureBuilder().build(),
restartDevice: true,
skipReactNativeReload: true,
},
async () => {
await loginToApp();
Expand All @@ -28,6 +29,7 @@ describe(FlaskBuildTests('Client Status Snap Tests'), () => {
await withFixtures(
{
fixture: new FixtureBuilder().build(),
skipReactNativeReload: true,
},
async () => {
await TestSnaps.tapButton('sendClientStatusButton');
Expand Down
1 change: 1 addition & 0 deletions e2e/specs/snaps/test-snap-cronjob.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ describe(FlaskBuildTests('Cronjob Snap Tests'), () => {
{
fixture: new FixtureBuilder().build(),
restartDevice: true,
skipReactNativeReload: true,
},
async () => {
await loginToApp();
Expand Down
Loading
Loading