Skip to content

Commit a3381f1

Browse files
authored
test(portal-ui): add portal ui e2e gate and expand coverage (apolloconfig#5551)
* test(portal-ui): add portal ui e2e gate and optimize ci - add portal-ui-e2e workflow and Playwright e2e suite\n- expand portal UI-facing controller test coverage\n- add config service full-chain e2e scenarios and docs\n- fix license headers and add PR-linked CHANGES entry\n- optimize e2e runtime via Playwright cache and CI parallel workers\n- clarify AGENTS rule for CHANGES.md PR-link requirement * fix(portal-ui-e2e): harden startup script and credential config * fix(portal-ui-e2e): parameterize rollback response env matcher
1 parent 5c930f9 commit a3381f1

27 files changed

+3601
-0
lines changed
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
#
2+
# Copyright 2026 Apollo Authors
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
#
16+
17+
name: portal-ui-e2e
18+
19+
on:
20+
pull_request:
21+
branches: [ master ]
22+
paths:
23+
- 'apollo-portal/**'
24+
- 'apollo-assembly/**'
25+
- 'e2e/portal-e2e/**'
26+
- 'scripts/sql/**'
27+
- '.github/workflows/portal-ui-e2e.yml'
28+
29+
jobs:
30+
portal-ui-e2e:
31+
name: portal-ui-e2e
32+
runs-on: ubuntu-latest
33+
timeout-minutes: 90
34+
steps:
35+
- uses: actions/checkout@v4
36+
37+
- name: Set up JDK 17
38+
uses: actions/setup-java@v4
39+
with:
40+
distribution: temurin
41+
java-version: 17
42+
cache: maven
43+
44+
- name: Set up Node.js 20
45+
uses: actions/setup-node@v4
46+
with:
47+
node-version: 20
48+
49+
- name: Cache Playwright browsers
50+
uses: actions/cache@v4
51+
with:
52+
path: ~/.cache/ms-playwright
53+
key: ${{ runner.os }}-playwright-${{ hashFiles('e2e/portal-e2e/package-lock.json') }}
54+
restore-keys: |
55+
${{ runner.os }}-playwright-
56+
57+
- name: Build Apollo assembly
58+
run: ./mvnw -B -pl apollo-assembly -am -DskipTests package
59+
60+
- name: Start Apollo assembly
61+
run: |
62+
JAR=""
63+
for candidate in apollo-assembly/target/apollo-assembly-*.jar; do
64+
if [[ "$candidate" == *"-sources.jar" || "$candidate" == *"-javadoc.jar" ]]; then
65+
continue
66+
fi
67+
if [[ -f "$candidate" ]]; then
68+
JAR="$candidate"
69+
break
70+
fi
71+
done
72+
if [[ -z "$JAR" ]]; then
73+
echo "No runnable apollo-assembly jar found in apollo-assembly/target" >&2
74+
exit 1
75+
fi
76+
SPRING_PROFILES_ACTIVE="github,database-discovery,auth" \
77+
SPRING_SQL_CONFIG_INIT_MODE="always" \
78+
SPRING_SQL_PORTAL_INIT_MODE="always" \
79+
SPRING_CONFIG_DATASOURCE_URL="jdbc:h2:mem:apollo-config-db;mode=mysql;DB_CLOSE_ON_EXIT=FALSE;DB_CLOSE_DELAY=-1;BUILTIN_ALIAS_OVERRIDE=TRUE;DATABASE_TO_UPPER=FALSE" \
80+
SPRING_PORTAL_DATASOURCE_URL="jdbc:h2:mem:apollo-portal-db;mode=mysql;DB_CLOSE_ON_EXIT=FALSE;DB_CLOSE_DELAY=-1;BUILTIN_ALIAS_OVERRIDE=TRUE;DATABASE_TO_UPPER=FALSE" \
81+
java -jar "$JAR" > /tmp/apollo-assembly-run.log 2>&1 &
82+
echo $! > /tmp/apollo-assembly.pid
83+
84+
- name: Wait for Apollo readiness
85+
run: ./e2e/portal-e2e/scripts/wait-for-ready.sh
86+
87+
- name: Run Playwright UI tests
88+
env:
89+
BASE_URL: http://127.0.0.1:8070
90+
PLAYWRIGHT_WORKERS: 2
91+
run: |
92+
cd e2e/portal-e2e
93+
npm ci
94+
npx playwright install chromium
95+
npm run test:e2e:ci
96+
97+
- name: Upload e2e test artifacts on failure
98+
if: failure()
99+
uses: actions/upload-artifact@v4
100+
with:
101+
name: portal-ui-e2e-artifacts
102+
path: |
103+
e2e/portal-e2e/playwright-report
104+
e2e/portal-e2e/test-results
105+
/tmp/apollo-assembly-run.log
106+
107+
- name: Stop Apollo assembly
108+
if: always()
109+
run: |
110+
if [ -f /tmp/apollo-assembly.pid ]; then
111+
kill "$(cat /tmp/apollo-assembly.pid)" || true
112+
fi

AGENTS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
## Commit & Pull Request Guidelines
3030
- Use Conventional Commits format (e.g., `feat:`, `fix:`).
3131
- If a commit fixes an issue, append `Fixes #123` in the commit message.
32+
- Commit only on feature branches; never commit directly to `master` or `main`.
33+
- `CHANGES.md` entries must use a PR URL in Markdown link format; if the PR URL is not available yet, open the PR first, then add/update `CHANGES.md` in a follow-up commit.
3234
- Rebase onto `master` and squash feature work into a single commit before merge.
3335
- When merging a PR on GitHub: if it has a single commit, use rebase and merge; if it has multiple commits, use squash and merge.
3436
- Non-trivial contributions require signing the CLA.

CHANGES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,6 @@ Apollo 2.5.0
3030
* [Perf: Replace synchronized multimap with concurrent hashmap in NotificationControllerV2 for better performance](https://github.com/apolloconfig/apollo/pull/5532)
3131
* [Feature: Enable graceful shutdown for apollo-adminservice and apollo-configservice](https://github.com/apolloconfig/apollo/pull/5536)
3232
* [Feature: Support search box and fullscreen in namespace text editor](https://github.com/apolloconfig/apollo/pull/5545)
33+
* [CI: Add portal UI Playwright e2e gate on PRs with JDK 17](https://github.com/apolloconfig/apollo/pull/5551)
3334
------------------
3435
All issues and pull requests are [here](https://github.com/apolloconfig/apollo/milestone/16?closed=1)
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*
2+
* Copyright 2025 Apollo Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
*/
17+
package com.ctrip.framework.apollo.portal.controller;
18+
19+
import static org.junit.Assert.assertEquals;
20+
import static org.junit.Assert.assertSame;
21+
import static org.mockito.ArgumentMatchers.eq;
22+
import static org.mockito.Mockito.verify;
23+
import static org.mockito.Mockito.when;
24+
25+
import com.ctrip.framework.apollo.common.dto.ClusterDTO;
26+
import com.ctrip.framework.apollo.portal.entity.bo.UserInfo;
27+
import com.ctrip.framework.apollo.portal.environment.Env;
28+
import com.ctrip.framework.apollo.portal.service.ClusterService;
29+
import com.ctrip.framework.apollo.portal.spi.UserInfoHolder;
30+
import org.junit.Test;
31+
import org.junit.runner.RunWith;
32+
import org.mockito.ArgumentCaptor;
33+
import org.mockito.Captor;
34+
import org.mockito.InjectMocks;
35+
import org.mockito.Mock;
36+
import org.mockito.junit.MockitoJUnitRunner;
37+
import org.springframework.http.ResponseEntity;
38+
39+
@RunWith(MockitoJUnitRunner.class)
40+
public class ClusterControllerTest {
41+
42+
@Mock
43+
private ClusterService clusterService;
44+
45+
@Mock
46+
private UserInfoHolder userInfoHolder;
47+
48+
@InjectMocks
49+
private ClusterController clusterController;
50+
51+
@Captor
52+
private ArgumentCaptor<ClusterDTO> clusterCaptor;
53+
54+
@Test
55+
public void shouldCreateClusterWithCurrentOperator() {
56+
ClusterDTO toCreate = new ClusterDTO();
57+
toCreate.setAppId("SampleApp");
58+
toCreate.setName("sampleCluster");
59+
60+
ClusterDTO created = new ClusterDTO();
61+
created.setAppId("SampleApp");
62+
created.setName("sampleCluster");
63+
64+
when(userInfoHolder.getUser()).thenReturn(new UserInfo("apollo"));
65+
when(clusterService.createCluster(eq(Env.DEV), clusterCaptor.capture())).thenReturn(created);
66+
67+
ClusterDTO result = clusterController.createCluster("SampleApp", "DEV", toCreate);
68+
69+
assertSame(created, result);
70+
ClusterDTO captured = clusterCaptor.getValue();
71+
assertEquals("apollo", captured.getDataChangeCreatedBy());
72+
assertEquals("apollo", captured.getDataChangeLastModifiedBy());
73+
}
74+
75+
@Test
76+
public void shouldDeleteClusterByEnvAndName() {
77+
ResponseEntity<Void> response =
78+
clusterController.deleteCluster("SampleApp", "DEV", "sampleCluster");
79+
80+
assertEquals(200, response.getStatusCodeValue());
81+
verify(clusterService).deleteCluster(Env.DEV, "SampleApp", "sampleCluster");
82+
}
83+
84+
@Test
85+
public void shouldLoadClusterFromService() {
86+
ClusterDTO loaded = new ClusterDTO();
87+
loaded.setName("sampleCluster");
88+
when(clusterService.loadCluster("SampleApp", Env.DEV, "sampleCluster")).thenReturn(loaded);
89+
90+
ClusterDTO result = clusterController.loadCluster("SampleApp", "DEV", "sampleCluster");
91+
92+
assertSame(loaded, result);
93+
verify(clusterService).loadCluster("SampleApp", Env.DEV, "sampleCluster");
94+
}
95+
}

0 commit comments

Comments
 (0)