Skip to content

Commit 62d4079

Browse files
committed
Merge branch 'main' into feat/integrate-omni-search
2 parents 8f17cbe + 7e62d28 commit 62d4079

818 files changed

Lines changed: 30907 additions & 39678 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.cursor/BUGBOT.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,11 @@ Use the rules in the [unit testing guidelines](rules/unit-testing-guidelines.mdc
2121
- Check for proper imports and framework utilities from `tests/framework/index.ts`
2222

2323
Use the rules in the [e2e-testing-guidelines](rules/e2e-testing-guidelines.mdc) to enforce the test quality and bug detection.
24+
25+
### 3. Initial Setup - Component View Tests
26+
27+
- **ALWAYS** load and reference [component-view-testing](rules/component-view-testing.mdc)
28+
- Verify test file naming pattern: `**/*.view.test.{ts,tsx,js,jsx}`
29+
- Check for proper use of presets and renderers from `app/util/test/component-view/`
30+
31+
Use the rules in the [component-view-testing](rules/component-view-testing.mdc) to enforce the test quality and bug detection.

.cursor/rules/component-view-testing.mdc

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,25 +10,29 @@ alwaysApply: false
1010

1111
Follow these rules for all component-view tests. Nested, component-specific rules may also apply.
1212

13-
- **Mock policy**
14-
- Only mock `../../../core/Engine`, `../../../core/Engine/Engine`, and `react-native-device-info`.
15-
- No mocking of hooks or selectors; drive behavior via Redux state.
16-
- Enforced by runtime guard in `app/util/test/testSetup.js` and ESLint override in `.eslintrc.js`.
17-
18-
- **Framework usage (REQUIRED)**
19-
- Use presets: `initialStateBridge`, `initialStateWallet`.
20-
- Use renderers: `renderBridgeView`, `renderWalletView`.
21-
- Use `stateFixture.ts` helpers for overrides (e.g., quotes, feature flags).
22-
- Prefer `deterministicFiat: true` for exact fiat assertions.
23-
24-
- **Navigation tests**
25-
- Use `renderScreenWithRoutes` and build `state` via the preset + minimal overrides.
26-
- Assert using route names from `app/constants/navigation/Routes.ts`.
27-
28-
- **Test structure**
29-
- AAA pattern with blank lines between sections.
30-
- Action-oriented names (no “should”).
31-
- One behavior per test; robust assertions over brittle formatting.
13+
## Mock Policy - MANDATORY
14+
15+
- Only mock `../../../core/Engine`, `../../../core/Engine/Engine`, and `react-native-device-info`.
16+
- No mocking of hooks or selectors; drive behavior via Redux state.
17+
- Enforced by runtime guard in `app/util/test/testSetup.js` and ESLint override in `.eslintrc.js`.
18+
19+
## Framework Usage - MANDATORY
20+
21+
- Use presets: `initialStateBridge`, `initialStateWallet`.
22+
- Use renderers: `renderBridgeView`, `renderWalletView`.
23+
- Use `stateFixture.ts` helpers for overrides (e.g., quotes, feature flags).
24+
- Prefer `deterministicFiat: true` for exact fiat assertions.
25+
26+
## Navigation Tests
27+
28+
- Use `renderScreenWithRoutes` and build `state` via the preset + minimal overrides.
29+
- Assert using route names from `app/constants/navigation/Routes.ts`.
30+
31+
## Test Structure - MANDATORY
32+
33+
- AAA pattern with blank lines between sections.
34+
- Action-oriented names (no "should").
35+
- One behavior per test; robust assertions over brittle formatting.
3236

3337
- **Execution**
3438
- Prefer `--coverage=false` during iteration for speed.

.github/actions/smart-e2e-selection/action.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ outputs:
4141
ai_confidence:
4242
description: 'AI confidence score (0-100)'
4343
value: ${{ steps.final-outputs.outputs.ai_confidence }}
44+
ai_performance_test_tags:
45+
description: 'Performance test tags to run (JSON array format, empty [] means no performance tests)'
46+
value: ${{ steps.final-outputs.outputs.ai_performance_test_tags }}
4447

4548
runs:
4649
using: 'composite'
@@ -119,6 +122,7 @@ runs:
119122
run: |
120123
echo "ai_e2e_test_tags=[\"ALL\"]" >> "$GITHUB_OUTPUT"
121124
echo "ai_confidence=0" >> "$GITHUB_OUTPUT"
125+
echo "ai_performance_test_tags=[]" >> "$GITHUB_OUTPUT"
122126
SHOULD_SKIP=false
123127
SKIP_REASON=""
124128
@@ -157,6 +161,13 @@ runs:
157161
echo 'ai_e2e_test_tags=["ALL"]' >> "$GITHUB_OUTPUT"
158162
fi
159163
echo "ai_confidence=${{ steps.ai-analysis.outputs.ai_confidence }}" >> "$GITHUB_OUTPUT"
164+
# Performance test tags (empty array means no performance tests)
165+
PERF_TAGS='${{ steps.ai-analysis.outputs.ai_performance_test_tags }}'
166+
if [[ -n "$PERF_TAGS" ]]; then
167+
printf 'ai_performance_test_tags=%s\n' "$PERF_TAGS" >> "$GITHUB_OUTPUT"
168+
else
169+
echo 'ai_performance_test_tags=[]' >> "$GITHUB_OUTPUT"
170+
fi
160171
161172
- name: Display AI Analysis Outputs
162173
if: always()
@@ -166,6 +177,7 @@ runs:
166177
echo "================================"
167178
echo "ai_e2e_test_tags: ${{ steps.final-outputs.outputs.ai_e2e_test_tags }}"
168179
echo "ai_confidence: ${{ steps.final-outputs.outputs.ai_confidence }}"
180+
echo "ai_performance_test_tags: ${{ steps.final-outputs.outputs.ai_performance_test_tags }}"
169181
echo "================================"
170182
171183
- name: Delete previous comments

.github/scripts/e2e-smart-selection.mjs

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,17 +32,19 @@ function appendGithubSummary(content) {
3232
}
3333

3434
function generateAnalysisSummary(analysis) {
35-
const { tagDisplay, riskLevel, confidence, reasoning } = analysis;
35+
const { tagDisplay, riskLevel, confidence, reasoning, performanceTests } = analysis;
3636

3737
let summary = '';
3838
summary += `- **Selected E2E tags**: ${tagDisplay}\n`;
39+
summary += `- **Selected Performance tags**: ${performanceTests.tagDisplay}\n`;
3940
summary += `- **Risk Level**: ${riskLevel}\n`;
4041
summary += `- **AI Confidence**: ${confidence}%\n`;
4142

4243
// Add AI reasoning in expandable section
4344
summary += '\n<details>\n';
4445
summary += '<summary>click to see 🤖 AI reasoning details</summary>\n\n';
45-
summary += `${reasoning}\n`;
46+
summary += `**E2E Test Selection:**\n${reasoning}\n\n`;
47+
summary += `**Performance Test Selection:**\n${performanceTests.reasoning}\n`;
4648
summary += '\n</details>\n';
4749

4850
return summary;
@@ -60,9 +62,11 @@ function generatePRComment(summaryContent) {
6062
}
6163

6264
function setGitHubOutputs(analysis) {
63-
const { tags, confidence } = analysis;
65+
const { tags, confidence, performanceTests } = analysis;
6466
setGithubOutputs('ai_e2e_test_tags', tags);
6567
setGithubOutputs('ai_confidence', confidence);
68+
// Performance test tags (empty array means no performance tests needed)
69+
setGithubOutputs('ai_performance_test_tags', JSON.stringify(performanceTests.selectedTags));
6670
}
6771

6872
async function main() {
@@ -99,13 +103,20 @@ async function main() {
99103

100104
// Parse results for GitHub outputs
101105
const selectedTags = parsedResult.selectedTags || [];
106+
const performanceTestsRaw = parsedResult.performanceTests || {};
107+
const perfSelectedTags = Array.isArray(performanceTestsRaw.selectedTags) ? performanceTestsRaw.selectedTags : [];
108+
const performanceTests = {
109+
selectedTags: perfSelectedTags,
110+
tagDisplay: perfSelectedTags.length > 0 ? perfSelectedTags.join(', ') : 'None (no tests recommended)',
111+
reasoning: performanceTestsRaw.reasoning || 'No performance impact detected',
112+
};
102113
const analysis = {
103114
tags: JSON.stringify(selectedTags), // JSON array format: [] or ["SmokeCore", "SmokeAccounts"]
104115
tagDisplay: selectedTags.length > 0 ? selectedTags.join(', ') : 'None (no tests recommended)',
105-
tagCount: selectedTags.length,
106116
riskLevel: parsedResult.riskLevel || '',
107117
confidence: parsedResult.confidence || '',
108118
reasoning: parsedResult.reasoning || '',
119+
performanceTests,
109120
};
110121

111122
setGitHubOutputs(analysis);

.github/scripts/e2e-split-tags-shards.mjs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@ import { extractTestResults } from './e2e-extract-test-results.mjs';
1111

1212
const env = {
1313
TEST_SUITE_TAG: process.env.TEST_SUITE_TAG,
14-
BASE_DIR: process.env.BASE_DIR || './e2e/specs',
14+
// Starting at the root drastically affects the performance of the script.
15+
// This will be reverted as soon as all specs are migrated to the new folder
16+
// structure.
17+
BASE_DIR: process.env.BASE_DIR || './',
1518
METAMASK_BUILD_TYPE: process.env.METAMASK_BUILD_TYPE || 'main',
1619
PLATFORM: process.env.PLATFORM || 'ios',
1720
SPLIT_NUMBER: Number(process.env.SPLIT_NUMBER || '1'),

.github/scripts/rc-builds.sh

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,3 +131,26 @@ if [[ -n "${GITHUB_OUTPUT:-}" ]]; then
131131
echo "bitrise-pipeline-url=https://app.bitrise.io/app/$BITRISE_APP_ID/pipelines/$BUILD_SLUG" >> "$GITHUB_OUTPUT"
132132
echo "build-number=$BUILD_NUMBER" >> "$GITHUB_OUTPUT"
133133
fi
134+
135+
# Post Slack notification if bot token is configured (fail open - non-critical)
136+
if [[ -n "${SLACK_BOT_TOKEN:-}" ]]; then
137+
echo ""
138+
echo "Posting Slack notification..."
139+
140+
# Export variables for the TypeScript script
141+
export SEMVER
142+
export BUILD_NUMBER
143+
export ANDROID_PUBLIC_URL
144+
export BITRISE_PIPELINE_URL="https://app.bitrise.io/app/$BITRISE_APP_ID/pipelines/$BUILD_SLUG"
145+
export SLACK_BOT_TOKEN
146+
147+
# Run the Slack notification script (fail open - don't fail the build if notification fails)
148+
if node ./scripts/slack-rc-notification.mjs; then
149+
echo "Slack notification sent successfully"
150+
else
151+
echo "⚠️ Slack notification failed, but continuing (non-critical)"
152+
fi
153+
else
154+
echo ""
155+
echo "Skipping Slack notification (SLACK_BOT_TOKEN not set)"
156+
fi

.github/workflows/build-rc-auto.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,13 @@ jobs:
106106
with:
107107
fetch-depth: 0
108108
ref: ${{ github.ref }}
109+
- name: Setup Node.js
110+
uses: actions/setup-node@v4
111+
with:
112+
node-version-file: '.nvmrc'
113+
cache: 'yarn'
114+
- name: Install dependencies
115+
run: yarn install --immutable
109116
- name: Trigger RC Build
110117
id: rc-build
111118
env:
@@ -116,6 +123,7 @@ jobs:
116123
BITRISE_APP_ID: ${{ secrets.BITRISE_APP_ID }}
117124
BITRISE_BUILD_TRIGGER_TOKEN: ${{ secrets.BITRISE_BUILD_TRIGGER_TOKEN }}
118125
BITRISE_API_TOKEN: ${{ secrets.BITRISE_API_TOKEN }}
126+
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
119127
run: ./.github/scripts/rc-builds.sh
120128

121129
post-rc-build-comment:

.github/workflows/build-rc-create.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,13 @@ jobs:
6161
with:
6262
fetch-depth: 0
6363
ref: release/${{ inputs.semver }}
64+
- name: Setup Node.js
65+
uses: actions/setup-node@v4
66+
with:
67+
node-version-file: '.nvmrc'
68+
cache: 'yarn'
69+
- name: Install dependencies
70+
run: yarn install --immutable
6471
- name: Trigger RC Build
6572
env:
6673
SEMVER: ${{ inputs.semver }}
@@ -70,4 +77,5 @@ jobs:
7077
BITRISE_APP_ID: ${{ secrets.BITRISE_APP_ID }}
7178
BITRISE_BUILD_TRIGGER_TOKEN: ${{ secrets.BITRISE_BUILD_TRIGGER_TOKEN }}
7279
BITRISE_API_TOKEN: ${{ secrets.BITRISE_API_TOKEN }}
80+
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
7381
run: ./.github/scripts/rc-builds.sh

.github/workflows/build.yml

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
name: Build Mobile App
2+
3+
on:
4+
workflow_call:
5+
inputs:
6+
build_name:
7+
required: true
8+
type: string
9+
platform:
10+
required: true
11+
type: string # android, ios, or both
12+
workflow_dispatch:
13+
inputs:
14+
build_name:
15+
required: true
16+
type: choice
17+
options:
18+
- main-prod
19+
- main-rc
20+
- main-test
21+
- main-e2e
22+
- main-exp
23+
- main-dev
24+
- flask-prod
25+
- flask-test
26+
- flask-e2e
27+
- flask-dev
28+
- qa-prod
29+
- qa-dev
30+
platform:
31+
required: true
32+
type: choice
33+
options: [android, ios, both]
34+
35+
permissions:
36+
contents: read
37+
id-token: write
38+
39+
jobs:
40+
# Load config
41+
prepare:
42+
runs-on: ubuntu-latest
43+
outputs:
44+
github_environment: ${{ steps.config.outputs.github_environment }}
45+
secrets_json: ${{ steps.config.outputs.secrets_json }}
46+
steps:
47+
- uses: actions/checkout@v4
48+
- uses: actions/setup-node@v4
49+
with:
50+
node-version: '20'
51+
- run: yarn install --immutable
52+
- run: node scripts/validate-build-config.js
53+
54+
- name: Load config
55+
id: config
56+
run: |
57+
node -e "
58+
const yaml = require('js-yaml');
59+
const fs = require('fs');
60+
const config = yaml.load(fs.readFileSync('.github/builds.yml', 'utf8'));
61+
const build = config.builds['${{ inputs.build_name }}'];
62+
fs.appendFileSync(process.env.GITHUB_OUTPUT, 'github_environment=' + build.github_environment + '\n');
63+
fs.appendFileSync(process.env.GITHUB_OUTPUT, 'secrets_json=' + JSON.stringify(build.secrets || {}) + '\n');
64+
"
65+
66+
# Build
67+
build:
68+
needs: [prepare]
69+
strategy:
70+
matrix:
71+
platform: ${{ inputs.platform == 'both' && fromJSON('["android", "ios"]') || fromJSON(format('["{0}"]', inputs.platform)) }}
72+
# Android uses Cirrus runner with SDK pre-installed; iOS uses GitHub-hosted macOS
73+
runs-on: ${{ matrix.platform == 'ios' && 'macos-latest' || 'ghcr.io/cirruslabs/ubuntu-runner-amd64:24.04-lg' }}
74+
environment: ${{ needs.prepare.outputs.github_environment }}
75+
steps:
76+
- uses: actions/checkout@v4
77+
- uses: actions/setup-node@v4
78+
with:
79+
node-version: '20'
80+
cache: 'yarn'
81+
- run: yarn install --immutable
82+
83+
- name: Apply build config
84+
run: |
85+
# Load env vars from builds.yml
86+
eval "$(node scripts/apply-build-config.js ${{ inputs.build_name }} --export)"
87+
88+
- name: Set secrets
89+
env:
90+
CONFIG_SECRETS: ${{ needs.prepare.outputs.secrets_json }}
91+
run: node scripts/set-secrets-from-config.js
92+
93+
# Android only: minimal env (SDK is pre-installed on Cirrus runner)
94+
- name: Set Android environment variables
95+
if: matrix.platform == 'android'
96+
run: |
97+
echo "ANDROID_HOME=/opt/android-sdk" >> "$GITHUB_ENV"
98+
echo "ANDROID_SDK_ROOT=/opt/android-sdk" >> "$GITHUB_ENV"
99+
100+
- name: Setup Java
101+
if: matrix.platform == 'android'
102+
uses: actions/setup-java@v4
103+
with:
104+
java-version: '17'
105+
distribution: 'temurin'
106+
107+
# Android qa signing (exp-test-e2e): skip for Expo dev builds (main-dev, flask-dev, qa-dev use debug keystore)
108+
- name: Configure Android signing certificates
109+
if: matrix.platform == 'android' && inputs.build_name != 'main-dev' && inputs.build_name != 'flask-dev' && inputs.build_name != 'qa-dev'
110+
uses: MetaMask/github-tools/.github/actions/configure-keystore@0259e8a920318b02a8860e178d79796eaa08de02
111+
with:
112+
aws-role-to-assume: 'arn:aws:iam::363762752069:role/metamask-mobile-build-signer-qa'
113+
aws-region: 'us-east-2'
114+
platform: 'android'
115+
target: ${{ startsWith(inputs.build_name, 'flask') && 'flask' || 'qa' }}
116+
117+
- name: Setup project dependencies with retry
118+
uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 #v3.0.2
119+
with:
120+
timeout_minutes: 10
121+
max_attempts: 3
122+
retry_wait_seconds: 30
123+
command: |
124+
echo "🚀 Setting up project..."
125+
if [ "${{ matrix.platform }}" = "ios" ]; then
126+
yarn setup:github-ci --build-ios --no-build-android
127+
else
128+
yarn setup:github-ci --no-build-ios
129+
fi
130+
131+
- name: Build ${{ matrix.platform }}
132+
run: |
133+
./scripts/build.sh ${{ matrix.platform }} ${{ inputs.build_name }}
134+
135+
# Expo development builds: upload iOS .app for simulator (Runway-style artifact)
136+
- name: Upload iOS Expo development build
137+
if: matrix.platform == 'ios' && (inputs.build_name == 'main-dev' || inputs.build_name == 'flask-dev' || inputs.build_name == 'qa-dev')
138+
uses: actions/upload-artifact@v4
139+
with:
140+
name: expo-dev-ios-${{ inputs.build_name }}
141+
path: ios/build/Build/Products/Release-iphonesimulator/

0 commit comments

Comments
 (0)