Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
bbd7989
feat: add script to update default E2E fixture from generated report
cmd-ob Mar 3, 2026
612f6cc
feat: add force run flag to E2E selection and update workflow conditions
cmd-ob Mar 3, 2026
10e00d4
feat: enhance E2E fixture update workflow with PR number input and im…
cmd-ob Mar 3, 2026
f48691f
feat: rename and enhance command acknowledgment in E2E fixture update…
cmd-ob Mar 3, 2026
6aa7a44
feat: enhance E2E fixture update workflow with improved PR validation…
cmd-ob Mar 3, 2026
5b9d2f9
Merge branch 'main' into fixture-update
cmd-ob Mar 3, 2026
e1822d4
Merge branch 'main' of github.com:MetaMask/metamask-mobile into fixtu…
cmd-ob Mar 3, 2026
6e6d257
feat(workflow): update permissions for fixture update job
cmd-ob Mar 4, 2026
3e068a2
chore: update E2E default fixture
metamaskbot Mar 4, 2026
230aa5b
Merge branch 'main' into fixture-update
cmd-ob Mar 4, 2026
2444573
Merge branch 'main' into fixture-update
cmd-ob Mar 4, 2026
decbdeb
feat: update E2E test conditions to exclude forked repositories
cmd-ob Mar 4, 2026
1ec775f
Merge branch 'fixture-update' of github.com:MetaMask/metamask-mobile …
cmd-ob Mar 4, 2026
c7a158e
revert fixture update
cmd-ob Mar 4, 2026
ef71f92
Merge branch 'main' into fixture-update
cmd-ob Mar 4, 2026
5b78925
Merge branch 'main' into fixture-update
cmd-ob Mar 4, 2026
bfcd736
feat: update ignored keys in mobile fixture to include additional net…
cmd-ob Mar 4, 2026
dd1a479
feat: add validation for unexpected keys in fixture comparison and im…
cmd-ob Mar 4, 2026
99d58ef
Merge branch 'main' into fixture-update
cmd-ob Mar 5, 2026
5e519b7
chore: add read permission for contents in E2E fixture update workflow
cmd-ob Mar 5, 2026
838d145
Merge branch 'main' into fixture-update
cmd-ob Mar 5, 2026
ec77794
Merge branch 'main' into fixture-update
cmd-ob Mar 5, 2026
e57b228
Merge branch 'main' into fixture-update
cmd-ob Mar 5, 2026
e66ccb9
fix(workflow): prevent caching on cancelled jobs in E2E fixture update
cmd-ob Mar 6, 2026
afc3f5f
Merge branch 'main' of github.com:MetaMask/metamask-mobile into fixtu…
cmd-ob Mar 6, 2026
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
17 changes: 16 additions & 1 deletion .github/actions/smart-e2e-selection/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ outputs:
ai_performance_test_tags:
description: 'Performance test tags to run (JSON array format, empty [] means no performance tests)'
value: ${{ steps.final-outputs.outputs.ai_performance_test_tags }}
force_run:
description: 'Whether to force E2E builds/tests regardless of path filter (true when skip-smart-e2e-selection label is used)'
value: ${{ steps.final-outputs.outputs.force_run }}

runs:
using: 'composite'
Expand Down Expand Up @@ -131,6 +134,7 @@ runs:
echo "ai_performance_test_tags=[]" >> "$GITHUB_OUTPUT"
SHOULD_SKIP=false
SKIP_REASON=""
FORCE_RUN=false

if [[ "$EVENT_NAME" != "pull_request" ]]; then
SHOULD_SKIP=true
Expand All @@ -141,11 +145,14 @@ runs:
elif [[ -n "${{ steps.check-skip-label.outputs.SKIP }}" ]] && [[ "${{ steps.check-skip-label.outputs.SKIP }}" == "true" ]]; then
SHOULD_SKIP=true
SKIP_REASON="skip-smart-e2e-selection label found"
FORCE_RUN=true
echo "ai_confidence=100" >> "$GITHUB_OUTPUT"
fi

# Export skip status and reason for the comment step
# Export skip status, reason, and force run flag for downstream jobs
echo "SKIPPED=$SHOULD_SKIP" >> "$GITHUB_OUTPUT"
echo "SKIP_REASON=$SKIP_REASON" >> "$GITHUB_OUTPUT"
echo "FORCE_RUN=$FORCE_RUN" >> "$GITHUB_OUTPUT"

if [[ "$SHOULD_SKIP" == "true" ]]; then
echo "⏭️ Skipping AI analysis - $SKIP_REASON"
Expand Down Expand Up @@ -174,6 +181,13 @@ runs:
else
echo 'ai_performance_test_tags=[]' >> "$GITHUB_OUTPUT"
fi
# Force run flag (true when skip-smart-e2e-selection label is used)
FORCE_RUN='${{ steps.ai-analysis.outputs.FORCE_RUN }}'
if [[ "$FORCE_RUN" == "true" ]]; then
echo "force_run=true" >> "$GITHUB_OUTPUT"
else
echo "force_run=false" >> "$GITHUB_OUTPUT"
fi

- name: Display AI Analysis Outputs
if: always()
Expand All @@ -184,6 +198,7 @@ runs:
echo "ai_e2e_test_tags: ${{ steps.final-outputs.outputs.ai_e2e_test_tags }}"
echo "ai_confidence: ${{ steps.final-outputs.outputs.ai_confidence }}"
echo "ai_performance_test_tags: ${{ steps.final-outputs.outputs.ai_performance_test_tags }}"
echo "force_run: ${{ steps.final-outputs.outputs.force_run }}"
echo "================================"

- name: Delete previous comments
Expand Down
37 changes: 19 additions & 18 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,7 @@ jobs:
outputs:
ai_e2e_test_tags: ${{ steps.e2e-selection.outputs.ai_e2e_test_tags }}
ai_confidence: ${{ steps.e2e-selection.outputs.ai_confidence }}
force_run: ${{ steps.e2e-selection.outputs.force_run }}
steps:
- name: Checkout for action definition
uses: actions/checkout@v4
Expand Down Expand Up @@ -440,9 +441,9 @@ jobs:
if: >-
${{
github.event_name != 'merge_group' &&
needs.needs_e2e_build.outputs.android_changed == 'true' &&
!(needs.smart-e2e-selection.outputs.ai_confidence >= 80 && needs.smart-e2e-selection.outputs.ai_e2e_test_tags == '[]') &&
!github.event.pull_request.head.repo.fork
!github.event.pull_request.head.repo.fork &&
(needs.needs_e2e_build.outputs.android_changed == 'true' || needs.smart-e2e-selection.outputs.force_run == 'true') &&
!(fromJSON(needs.smart-e2e-selection.outputs.ai_confidence || '0') >= 80 && needs.smart-e2e-selection.outputs.ai_e2e_test_tags == '[]')
Comment thread
cursor[bot] marked this conversation as resolved.
}}
permissions:
contents: read
Expand All @@ -457,7 +458,7 @@ jobs:

e2e-smoke-tests-android:
name: 'Android E2E Smoke Tests'
if: ${{ github.event_name != 'merge_group' && needs.needs_e2e_build.outputs.android_changed == 'true' && !github.event.pull_request.head.repo.fork }}
if: ${{ github.event_name != 'merge_group' && !github.event.pull_request.head.repo.fork && (needs.needs_e2e_build.outputs.android_changed == 'true' || needs.smart-e2e-selection.outputs.force_run == 'true') }}
permissions:
contents: read
id-token: write
Expand All @@ -467,7 +468,7 @@ jobs:
changed_files: ${{ needs.needs_e2e_build.outputs.changed_files }}
selected_tags: >-
${{
(needs.smart-e2e-selection.outputs.ai_confidence >= 80 && needs.smart-e2e-selection.outputs.ai_e2e_test_tags) ||
(fromJSON(needs.smart-e2e-selection.outputs.ai_confidence || '0') >= 80 && needs.smart-e2e-selection.outputs.ai_e2e_test_tags) ||
'["ALL"]'
}}
secrets: inherit
Expand All @@ -477,9 +478,9 @@ jobs:
if: >-
${{
github.event_name != 'merge_group' &&
needs.needs_e2e_build.outputs.ios_changed == 'true' &&
!(needs.smart-e2e-selection.outputs.ai_confidence >= 80 && needs.smart-e2e-selection.outputs.ai_e2e_test_tags == '[]') &&
!github.event.pull_request.head.repo.fork
!github.event.pull_request.head.repo.fork &&
(needs.needs_e2e_build.outputs.ios_changed == 'true' || needs.smart-e2e-selection.outputs.force_run == 'true') &&
!(fromJSON(needs.smart-e2e-selection.outputs.ai_confidence || '0') >= 80 && needs.smart-e2e-selection.outputs.ai_e2e_test_tags == '[]')
}}
permissions:
contents: read
Expand All @@ -491,15 +492,15 @@ jobs:
ios-tests-ready:
name: 'iOS Tests Ready'
runs-on: ubuntu-latest
if: ${{ github.event_name != 'merge_group' && needs.needs_e2e_build.outputs.ios_changed == 'true' }}
needs: [needs_e2e_build, build-ios-apps]
if: ${{ github.event_name != 'merge_group' && (needs.needs_e2e_build.outputs.ios_changed == 'true' || needs.smart-e2e-selection.outputs.force_run == 'true') }}
needs: [needs_e2e_build, build-ios-apps, smart-e2e-selection]
steps:
- name: iOS build complete
run: echo "Dummy step to better visualize the Android and iOS E2E tests in Github workflow graph"

e2e-smoke-tests-ios:
name: 'iOS E2E Smoke Tests'
if: ${{ github.event_name != 'merge_group' && needs.needs_e2e_build.outputs.ios_changed == 'true' && !github.event.pull_request.head.repo.fork }}
if: ${{ github.event_name != 'merge_group' && !github.event.pull_request.head.repo.fork && (needs.needs_e2e_build.outputs.ios_changed == 'true' || needs.smart-e2e-selection.outputs.force_run == 'true') }}
permissions:
contents: read
id-token: write
Expand All @@ -509,7 +510,7 @@ jobs:
changed_files: ${{ needs.needs_e2e_build.outputs.changed_files }}
selected_tags: >-
${{
(needs.smart-e2e-selection.outputs.ai_confidence >= 80 && needs.smart-e2e-selection.outputs.ai_e2e_test_tags) ||
(fromJSON(needs.smart-e2e-selection.outputs.ai_confidence || '0') >= 80 && needs.smart-e2e-selection.outputs.ai_e2e_test_tags) ||
'["ALL"]'
}}
secrets: inherit
Expand All @@ -518,7 +519,7 @@ jobs:

e2e-smoke-tests-android-flask:
name: 'Android Flask E2E Smoke Tests'
if: ${{ github.event_name != 'merge_group' && needs.needs_e2e_build.outputs.android_changed == 'true' && !github.event.pull_request.head.repo.fork }}
if: ${{ github.event_name != 'merge_group' && !github.event.pull_request.head.repo.fork && (needs.needs_e2e_build.outputs.android_changed == 'true' || needs.smart-e2e-selection.outputs.force_run == 'true') }}
permissions:
contents: read
id-token: write
Expand All @@ -528,14 +529,14 @@ jobs:
changed_files: ${{ needs.needs_e2e_build.outputs.changed_files }}
selected_tags: >-
${{
(needs.smart-e2e-selection.outputs.ai_confidence >= 80 && needs.smart-e2e-selection.outputs.ai_e2e_test_tags) ||
(fromJSON(needs.smart-e2e-selection.outputs.ai_confidence || '0') >= 80 && needs.smart-e2e-selection.outputs.ai_e2e_test_tags) ||
'["ALL"]'
}}
secrets: inherit

e2e-smoke-tests-ios-flask:
name: 'iOS Flask E2E Smoke Tests'
if: ${{ github.event_name != 'merge_group' && needs.needs_e2e_build.outputs.ios_changed == 'true' && !github.event.pull_request.head.repo.fork }}
if: ${{ github.event_name != 'merge_group' && !github.event.pull_request.head.repo.fork && (needs.needs_e2e_build.outputs.ios_changed == 'true' || needs.smart-e2e-selection.outputs.force_run == 'true') }}
permissions:
contents: read
id-token: write
Expand All @@ -545,7 +546,7 @@ jobs:
changed_files: ${{ needs.needs_e2e_build.outputs.changed_files }}
selected_tags: >-
${{
(needs.smart-e2e-selection.outputs.ai_confidence >= 80 && needs.smart-e2e-selection.outputs.ai_e2e_test_tags) ||
(fromJSON(needs.smart-e2e-selection.outputs.ai_confidence || '0') >= 80 && needs.smart-e2e-selection.outputs.ai_e2e_test_tags) ||
'["ALL"]'
}}
secrets: inherit
Expand All @@ -554,11 +555,11 @@ jobs:
# TODO: Remove continue-on-error once fixture validation is stable
validate-e2e-fixtures:
name: 'Validate E2E Fixtures'
if: ${{ github.event_name != 'merge_group' && needs.needs_e2e_build.outputs.ios_changed == 'true' && !github.event.pull_request.head.repo.fork }}
if: ${{ github.event_name != 'merge_group' && !github.event.pull_request.head.repo.fork && (needs.needs_e2e_build.outputs.ios_changed == 'true' || needs.smart-e2e-selection.outputs.force_run == 'true') }}
permissions:
contents: read
id-token: write
needs: [needs_e2e_build, ios-tests-ready]
needs: [needs_e2e_build, ios-tests-ready, smart-e2e-selection]
uses: ./.github/workflows/run-e2e-workflow.yml
with:
test-suite-name: validate-e2e-fixtures
Expand Down
15 changes: 10 additions & 5 deletions .github/workflows/update-e2e-fixtures.yml
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,7 @@ jobs:
PREBUILT_IOS_APP_PATH: artifacts/main-qa-MetaMask.app

- name: Cache updated fixture
if: ${{ !cancelled() }}
uses: actions/cache/save@v4
with:
path: tests/framework/fixtures/json/default-fixture.json
Expand All @@ -263,21 +264,22 @@ jobs:
name: Commit the updated fixtures
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
Comment thread
cursor[bot] marked this conversation as resolved.
contents: write
pull-requests: write
needs:
- prepare
- is-fork-pull-request
- update-fixtures
if: ${{ needs.is-fork-pull-request.outputs.IS_FORK == 'false' }}
if: ${{ !cancelled() && needs.is-fork-pull-request.outputs.IS_FORK == 'false' }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
token: ${{ secrets.ACTIONS_WRITE_TOKEN }}

- name: Checkout pull request
run: gh pr checkout "${PR_NUMBER}"
env:
GITHUB_TOKEN: ${{ secrets.ACTIONS_WRITE_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ needs.prepare.outputs.PR_NUMBER }}

- name: Restore updated fixture
Expand Down Expand Up @@ -318,7 +320,7 @@ jobs:
fi
env:
HAS_CHANGES: ${{ steps.fixture-changes.outputs.HAS_CHANGES }}
GITHUB_TOKEN: ${{ secrets.ACTIONS_WRITE_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ needs.prepare.outputs.PR_NUMBER }}

check-status:
Expand Down Expand Up @@ -346,6 +348,9 @@ jobs:
if: ${{ !cancelled() && needs.is-fork-pull-request.outputs.IS_FORK == 'false' }}
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
contents: read
pull-requests: write
Comment thread
cursor[bot] marked this conversation as resolved.
needs:
- is-fork-pull-request
- check-status
Expand Down
18 changes: 18 additions & 0 deletions scripts/update-e2e-fixture.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/bin/bash
# Updates the default E2E fixture from the generated report.
# Run this after executing the fixture-validation.spec.ts test locally.

set -e

REPORT_FILE="tests/reports/updated-default-fixture.json"
TARGET_FILE="tests/framework/fixtures/json/default-fixture.json"

if [ ! -f "$REPORT_FILE" ]; then
echo "Error: $REPORT_FILE not found."
echo "Run the fixture validation test first:"
echo " yarn detox test tests/regression/fixtures/fixture-validation.spec.ts -c <config>"
exit 1
fi

cp "$REPORT_FILE" "$TARGET_FILE"
echo "Updated $TARGET_FILE from $REPORT_FILE"
26 changes: 26 additions & 0 deletions tests/framework/fixtures/fixture-validation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -453,5 +453,31 @@ describe('fixture-validation', () => {
const diff = computeSchemaDiff(state, state);
expect(hasSchemaDifferences(diff)).toBe(false);
});

it('fails validation when candidate has new unexpected keys', () => {
const fixture = readFixtureFile('default-fixture.json');
const state = fixture.state as Record<string, unknown>;
const engine = state.engine as Record<string, unknown>;
const backgroundState = engine.backgroundState as Record<string, unknown>;

const candidateWithNewKey = {
...state,
engine: {
...engine,
backgroundState: {
...backgroundState,
SomeNewController: { foo: 'bar' },
},
},
};

const diff = computeSchemaDiff(state, candidateWithNewKey);
expect(hasSchemaDifferences(diff)).toBe(true);
expect(
diff.newKeys.some((k) =>
k.startsWith('engine.backgroundState.SomeNewController'),
),
).toBe(true);
});
});
});
9 changes: 9 additions & 0 deletions tests/framework/fixtures/fixture-validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,15 @@ export function getMobileFixtureIgnoredKeys(): string[] {
'card.geoLocation',
'fiatOrders.detectedGeolocation',

// ── Networks present in app defaults but not in fixture (added by controller at runtime) ──
'engine.backgroundState.NetworkController.networkConfigurationsByChainId.0x2105', // Base
'engine.backgroundState.NetworkController.networkConfigurationsByChainId.0xa4b1', // Arbitrum
'engine.backgroundState.NetworkController.networkConfigurationsByChainId.0xa', // Optimism
'engine.backgroundState.NetworkController.networkConfigurationsByChainId.0x89', // Polygon
'engine.backgroundState.NetworkController.networkConfigurationsByChainId.0x38', // BNB Chain
'engine.backgroundState.NetworkController.networkConfigurationsByChainId.0x279f', // Monad Testnet
'engine.backgroundState.NetworkController.networkConfigurationsByChainId.0x18c7', // MegaETH Testnet

// ── Dynamic network client IDs, port-dependent URLs, and display names ──
'engine.backgroundState.NetworkController.networkConfigurationsByChainId.*.name',
'engine.backgroundState.NetworkController.networkConfigurationsByChainId.*.nativeCurrency',
Expand Down
6 changes: 3 additions & 3 deletions tests/regression/fixtures/fixture-validation.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,16 +163,16 @@ describe(FixtureValidation('Fixture Validation — Post-Onboarding'), () => {
'utf-8',
);

// TODO: Change console.warn to throw once fixture validation is stable
console.warn(
throw new Error(
`Committed fixture is out of date.\n` +
` New keys: ${diff.newKeys.length}\n` +
` Missing keys: ${diff.missingKeys.length}\n` +
` Type mismatches: ${diff.typeMismatches.length}\n` +
` Auto-updated values: ${autoUpdateMismatches.length}\n\n` +
`Updated fixture written to: ${fixturePath}\n` +
`Structural changes and auto-updatable keys were applied.\n` +
`Other value mismatches require manual review.`,
`Other value mismatches require manual review.\n\n` +
`To fix: commit the updated fixture, or add new keys to getMobileFixtureIgnoredKeys() in fixture-validation.ts.`,
Comment thread
cursor[bot] marked this conversation as resolved.
);
} else if (diff.valueMismatches.length > 0) {
console.log(
Expand Down
Loading