Skip to content

Commit 6098045

Browse files
authored
Merge branch 'main' into qa/e2e-cleanup
2 parents 9d74f1b + aac019d commit 6098045

704 files changed

Lines changed: 43393 additions & 12018 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.

.eslintrc.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,33 @@ module.exports = {
385385
],
386386
},
387387
},
388+
{
389+
files: ['**/*.test.{js,ts,tsx,jsx}', '**/*.spec.{js,ts,tsx,jsx}'],
390+
plugins: ['jest'],
391+
rules: {
392+
// Prevent new file-based snapshots. Inline snapshots (toMatchInlineSnapshot)
393+
// are still allowed as they keep assertions co-located with the test.
394+
'jest/no-restricted-matchers': [
395+
'error',
396+
{
397+
toMatchSnapshot:
398+
'Use toMatchInlineSnapshot() or an explicit assertion instead. File-based snapshots are being phased out.',
399+
},
400+
],
401+
},
402+
},
403+
{
404+
// Matches CODEOWNERS `**/snaps/**` and `**/Snaps/**` (@MetaMask/core-platform).
405+
// ESLint cannot read CODEOWNERS.
406+
files: [
407+
'**/snaps/**/*.{test,spec}.{js,ts,tsx,jsx}',
408+
'**/Snaps/**/*.{test,spec}.{js,ts,tsx,jsx}',
409+
],
410+
plugins: ['jest'],
411+
rules: {
412+
'jest/no-restricted-matchers': 'off',
413+
},
414+
},
388415
// ── Perps controller Core-alignment override ──
389416
// Enforces the same ESLint rules that Core's @metamask/eslint-config
390417
// applies to packages/perps-controller so that code written in mobile
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
name: CI Status Gate
2+
description: Evaluate required CI job results and fail on unexpected skips or failed jobs.
3+
4+
inputs:
5+
needs-json:
6+
description: JSON representation of the calling job's needs context.
7+
required: true
8+
requirement-context-json:
9+
description: JSON representation of get-requirements outputs.
10+
required: true
11+
e2e-job-regex:
12+
description: Regex matching E2E build/test jobs whose skipped result is allowed. Failed or cancelled E2E jobs still fail.
13+
required: false
14+
default: '^e2e-'
15+
event-name:
16+
description: GitHub event name for the current workflow run.
17+
required: true
18+
is-fork:
19+
description: Whether the current pull request originates from a fork. When true, skipped jobs are treated as allowed skips.
20+
required: false
21+
default: 'false'
22+
23+
runs:
24+
using: composite
25+
steps:
26+
- name: Evaluate CI status
27+
shell: bash
28+
env:
29+
NEEDS_JSON: ${{ inputs.needs-json }}
30+
REQUIREMENT_CONTEXT_JSON: ${{ inputs.requirement-context-json }}
31+
E2E_JOB_REGEX: ${{ inputs.e2e-job-regex }}
32+
EVENT_NAME: ${{ inputs.event-name }}
33+
IS_FORK: ${{ inputs.is-fork }}
34+
run: |
35+
set -euo pipefail
36+
37+
get_requirement() {
38+
local key="$1"
39+
jq -nr --arg key "$key" 'env.REQUIREMENT_CONTEXT_JSON | fromjson | .[$key] // "false"'
40+
}
41+
42+
sanitize_markdown_cell() {
43+
local value="$1"
44+
value="${value//$'\n'/ }"
45+
value="${value//|/\\|}"
46+
printf '%s' "$value"
47+
}
48+
49+
add_summary_row() {
50+
local job_name result decision reason
51+
job_name="$(sanitize_markdown_cell "$1")"
52+
result="$(sanitize_markdown_cell "$2")"
53+
decision="$(sanitize_markdown_cell "$3")"
54+
reason="$(sanitize_markdown_cell "$4")"
55+
56+
printf '| `%s` | `%s` | %s | %s |\n' \
57+
"$job_name" "$result" "$decision" "$reason" >> "$summary_file"
58+
}
59+
60+
mark_failure() {
61+
local message="$1"
62+
failed="true"
63+
echo "::error::$message"
64+
}
65+
66+
validate_json_type() {
67+
local variable_name="$1"
68+
local expected_type="$2"
69+
70+
if ! jq -en --arg variable_name "$variable_name" --arg expected_type "$expected_type" \
71+
'(env[$variable_name] | fromjson | type) == $expected_type' >/dev/null 2>&1; then
72+
echo "::error::$variable_name is not a valid JSON $expected_type"
73+
exit 1
74+
fi
75+
}
76+
77+
require_requirement_key() {
78+
local key="$1"
79+
80+
if ! jq -en --arg key "$key" \
81+
'env.REQUIREMENT_CONTEXT_JSON | fromjson | .[$key] != null' >/dev/null 2>&1; then
82+
echo "::error::REQUIREMENT_CONTEXT_JSON is missing or null for required key: $key"
83+
exit 1
84+
fi
85+
}
86+
87+
validate_json_type NEEDS_JSON object
88+
validate_json_type REQUIREMENT_CONTEXT_JSON object
89+
90+
for required_key in skip_everything block_merge_for_e2e_readiness; do
91+
require_requirement_key "$required_key"
92+
done
93+
94+
skip_everything="$(get_requirement skip_everything)"
95+
block_merge_for_e2e_readiness="$(get_requirement block_merge_for_e2e_readiness)"
96+
97+
if [[ "$block_merge_for_e2e_readiness" == "true" ]]; then
98+
echo "::error::The 'pr-not-ready-for-e2e' label is still applied. Remove it to trigger E2E tests before merging."
99+
exit 1
100+
fi
101+
102+
if [[ "$skip_everything" == "true" ]]; then
103+
echo "skip_everything=true; treating all jobs as passed"
104+
exit 0
105+
fi
106+
107+
failed="false"
108+
summary_file="$(mktemp)"
109+
trap 'if [[ -n "${GITHUB_STEP_SUMMARY:-}" && -f "$summary_file" ]]; then cat "$summary_file" >> "$GITHUB_STEP_SUMMARY"; fi; rm -f "$summary_file"' EXIT
110+
job_count=0
111+
112+
{
113+
echo "### CI Status Gate"
114+
echo
115+
echo "| Job | Result | Decision | Reason |"
116+
echo "| --- | --- | --- | --- |"
117+
} >> "$summary_file"
118+
119+
while IFS=$'\t' read -r job_name result; do
120+
job_count=$((job_count + 1))
121+
122+
case "$result" in
123+
success)
124+
add_summary_row "$job_name" "$result" "pass" "job succeeded"
125+
;;
126+
failure|cancelled)
127+
mark_failure "$job_name finished with result: $result"
128+
add_summary_row "$job_name" "$result" "fail" "job did not complete successfully"
129+
;;
130+
skipped)
131+
if [[ "$job_name" =~ $E2E_JOB_REGEX ]]; then
132+
add_summary_row "$job_name" "$result" "pass" "skipped E2E jobs are allowed"
133+
elif [[ "$EVENT_NAME" == "merge_group" ]]; then
134+
add_summary_row "$job_name" "$result" "pass" "merge queue skip is allowed"
135+
elif [[ "$IS_FORK" == "true" ]]; then
136+
add_summary_row "$job_name" "$result" "pass" "fork-only skip is allowed"
137+
else
138+
mark_failure "$job_name was skipped unexpectedly"
139+
add_summary_row "$job_name" "$result" "fail" "skip was not expected"
140+
fi
141+
;;
142+
*)
143+
mark_failure "$job_name has unknown result: $result"
144+
add_summary_row "$job_name" "$result" "fail" "job result is unknown"
145+
;;
146+
esac
147+
done < <(jq -nr 'env.NEEDS_JSON | fromjson | to_entries[] | [.key, (.value.result // "")] | @tsv')
148+
149+
if [[ "$job_count" -eq 0 ]]; then
150+
echo "::error::NEEDS_JSON does not contain any jobs"
151+
exit 1
152+
fi
153+
154+
if [[ "$failed" == "true" ]]; then
155+
exit 1
156+
fi
157+
158+
echo "All required jobs passed"

.github/actions/setup-e2e-env/action.yml

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,9 @@ runs:
283283
node_modules
284284
.yarn/install-state.gz
285285
key: ${{ inputs.cache-prefix }}-yarn-${{ inputs.platform }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
286+
restore-keys: |
287+
${{ inputs.cache-prefix }}-yarn-${{ inputs.platform }}-${{ runner.os }}-
288+
continue-on-error: true
286289

287290
- name: Install JavaScript dependencies with retry
288291
id: yarn-install
@@ -387,19 +390,19 @@ runs:
387390
${{ runner.os }}-cocoapods-specs-
388391
continue-on-error: true
389392

390-
- name: Clear CocoaPods trunk to prevent stale specs
391-
if: ${{ inputs.platform == 'ios' }}
392-
run: pod repo remove trunk || true
393-
shell: bash
394-
395393
- name: Install CocoaPods via bundler
396394
if: ${{ inputs.platform == 'ios'}}
397395
uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 #v3.0.2
398396
with:
399397
timeout_minutes: 15
400-
max_attempts: 2
401-
retry_wait_seconds: 30
398+
max_attempts: 3
399+
retry_wait_seconds: 60
400+
on_retry_command: |
401+
echo "::warning::CocoaPods install failed, retrying after trunk cleanup..."
402+
pod repo remove trunk || true
402403
command: cd ios && bundle exec pod install --repo-update
404+
env:
405+
COCOAPODS_DISABLE_STATS: 'true'
403406

404407
- name: Install applesimutils
405408
if: ${{ inputs.platform == 'ios' }}

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

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ inputs:
2727
required: false
2828
default: 'false'
2929
base-ref:
30-
description: 'PR base branch ref passed to the AI analysis script for diff comparison'
30+
description: 'PR base branch ref passed to the AI analysis script for diff comparison. When release/*, AI selection is skipped and the full E2E suite is selected.'
3131
required: false
3232
default: ''
3333
outputs:
@@ -57,21 +57,32 @@ runs:
5757
echo "⏭️ SKIP=true due to 'skip-smart-e2e-selection' label on PR"
5858
fi
5959
60+
- name: Check release target branch (full E2E, no AI selection)
61+
id: check-release-target
62+
shell: bash
63+
run: |
64+
echo "SKIP=false" >> "$GITHUB_OUTPUT"
65+
BASE='${{ inputs.base-ref }}'
66+
if [[ -n "$BASE" && "$BASE" == release/* ]]; then
67+
echo "SKIP=true" >> "$GITHUB_OUTPUT"
68+
echo "⏭️ Base branch is release/* — skipping AI E2E selection; full E2E suite will run"
69+
fi
70+
6071
- name: Checkout for PR analysis
61-
if: steps.check-skip-label.outputs.SKIP != 'true'
72+
if: steps.check-skip-label.outputs.SKIP != 'true' && steps.check-release-target.outputs.SKIP != 'true'
6273
uses: actions/checkout@v6
6374
with:
6475
fetch-depth: 1 # Shallow clone for speed; unshallowed below for diff comparison
6576

6677
- name: Disable sparse checkout and restore all files
67-
if: steps.check-skip-label.outputs.SKIP != 'true'
78+
if: steps.check-skip-label.outputs.SKIP != 'true' && steps.check-release-target.outputs.SKIP != 'true'
6879
shell: bash
6980
run: |
7081
git sparse-checkout disable
7182
git checkout HEAD -- .
7283
7384
- name: Fetch base branch for comparison
74-
if: steps.check-skip-label.outputs.SKIP != 'true'
85+
if: steps.check-skip-label.outputs.SKIP != 'true' && steps.check-release-target.outputs.SKIP != 'true'
7586
shell: bash
7687
run: |
7788
# Unshallow the repository first (if it's shallow)
@@ -80,13 +91,13 @@ runs:
8091
git fetch origin "${{ inputs.base-ref || 'main' }}" 2>/dev/null || true
8192
8293
- name: Setup Node.js
83-
if: steps.check-skip-label.outputs.SKIP != 'true'
94+
if: steps.check-skip-label.outputs.SKIP != 'true' && steps.check-release-target.outputs.SKIP != 'true'
8495
uses: actions/setup-node@v6
8596
with:
8697
node-version-file: '.nvmrc'
8798

8899
- name: Install minimal dependencies for AI analysis
89-
if: steps.check-skip-label.outputs.SKIP != 'true'
100+
if: steps.check-skip-label.outputs.SKIP != 'true' && steps.check-release-target.outputs.SKIP != 'true'
90101
shell: bash
91102
run: |
92103
echo "📦 Installing only required packages for AI analysis..."
@@ -98,7 +109,7 @@ runs:
98109
echo "✅ AI analysis dependencies installed in /tmp/ai-deps"
99110
100111
- name: Copy AI dependencies to workspace
101-
if: steps.check-skip-label.outputs.SKIP != 'true'
112+
if: steps.check-skip-label.outputs.SKIP != 'true' && steps.check-release-target.outputs.SKIP != 'true'
102113
shell: bash
103114
run: |
104115
echo "📋 Copying AI dependencies to workspace..."
@@ -130,6 +141,11 @@ runs:
130141
echo "SKIPPED=true" >> "$GITHUB_OUTPUT"
131142
echo "SKIP_REASON=skip-smart-e2e-selection label found" >> "$GITHUB_OUTPUT"
132143
echo "ai_confidence=100" >> "$GITHUB_OUTPUT"
144+
elif [[ "${{ steps.check-release-target.outputs.SKIP }}" == "true" ]]; then
145+
echo "⏭️ Skipping AI analysis - PR targets a release branch (release/*)"
146+
echo "SKIPPED=true" >> "$GITHUB_OUTPUT"
147+
echo "SKIP_REASON=PR targets a release branch (release/*)" >> "$GITHUB_OUTPUT"
148+
echo "ai_confidence=100" >> "$GITHUB_OUTPUT"
133149
else
134150
echo "✅ Running AI analysis for PR #$PR_NUMBER"
135151
# The script will generate the GH output variables - don't fail if script errors

.github/guidelines/E2E_DECISION_TREE.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ To save infra resources while waiting for static analysis findings and potential
3333
- E2E tests are skipped and merge is blocked while the label is present, **unless** all changes are ignorable-only.
3434
- If E2E tests are needed, they should pass to be able to merge.
3535

36-
## AI test selection
36+
## Smart AI E2E test selection
3737

3838
Runs only when all of the following are true:
3939

@@ -53,3 +53,10 @@ Flakiness detection is applied to modified E2E test files in PRs:
5353
- Modified E2E test files run twice
5454
- It applies to existing test files as well as new test files added in the PR
5555
- It can be disabled by adding the label `skip-e2e-flakiness-detection`. Useful when making large refactors or when changes don't pose flakiness risk.
56+
57+
## Release branches
58+
59+
PRs to release branches (cherry-picked from main) are exempt from the following:
60+
61+
- Label `pr-not-ready-for-e2e` is not applied
62+
- Smart AI E2E selection is skipped - all E2E suites are run (if changes are not ignorable-only, e.g. only docs)

0 commit comments

Comments
 (0)