Skip to content

Commit d3cd107

Browse files
chore: modernize dependencies and stabilize e2e
1 parent 6776b10 commit d3cd107

File tree

16 files changed

+2350
-2341
lines changed

16 files changed

+2350
-2341
lines changed

.husky/pre-commit

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
pnpm lint-staged
1+
lint-staged
22
git update-index --again

e2e/tests/services.crud-required-fields.spec.ts

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -56,24 +56,21 @@ test('should CRUD service with required fields', async ({ page }) => {
5656
const rows = upstreamSection.locator('tr.ant-table-row');
5757
await rows.first().locator('input').first().fill('127.0.0.1');
5858
await rows.first().locator('input').nth(1).fill('80');
59-
await rows.first().locator('input').nth(2).fill('1');
59+
const weightInput = rows.first().locator('input').nth(2);
60+
await weightInput.fill('1');
61+
62+
// Editable table cells may require blur/click-outside before form submit.
63+
await weightInput.blur();
64+
await upstreamSection.click();
6065

6166
// Ensure the name field is properly filled before submitting
6267
const nameField = page.getByRole('textbox', { name: 'Name' }).first();
6368
await expect(nameField).toHaveValue(serviceName);
6469

6570
await servicesPom.getAddBtn(page).click();
66-
67-
// Wait for either success or error toast (longer timeout for CI)
68-
const alertMsg = page.getByRole('alert');
69-
await expect(alertMsg).toBeVisible({ timeout: 30000 });
70-
71-
// Check if it's a success message
72-
await expect(alertMsg).toContainText('Add Service Successfully', { timeout: 5000 });
73-
74-
// Close the toast
75-
await alertMsg.getByRole('button').click();
76-
await expect(alertMsg).toBeHidden();
71+
await uiHasToastMsg(page, {
72+
hasText: 'Add Service Successfully',
73+
});
7774
});
7875

7976
await test.step('auto navigate to service detail page', async () => {

e2e/tests/stream_routes.show-disabled-error.spec.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import { exec } from 'node:child_process';
3434
import { readFile, writeFile } from 'node:fs/promises';
3535
import path from 'node:path';
36+
import { fileURLToPath } from 'node:url';
3637
import { promisify } from 'node:util';
3738

3839
import { streamRoutesPom } from '@e2e/pom/stream_routes';
@@ -51,7 +52,7 @@ type APISIXConf = {
5152
};
5253

5354
const getE2EServerDir = () => {
54-
const currentDir = new URL('.', import.meta.url).pathname;
55+
const currentDir = path.dirname(fileURLToPath(import.meta.url));
5556
return path.join(currentDir, '../server');
5657
};
5758

e2e/utils/common.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
*/
1717
import { access, readFile } from 'node:fs/promises';
1818
import path from 'node:path';
19+
import { fileURLToPath } from 'node:url';
1920

2021
import { nanoid } from 'nanoid';
2122
import selfsigned from 'selfsigned';
@@ -25,7 +26,7 @@ type APISIXConf = {
2526
deployment: { admin: { admin_key: { key: string }[] } };
2627
};
2728
export const getAPISIXConf = async () => {
28-
const currentDir = new URL('.', import.meta.url).pathname;
29+
const currentDir = path.dirname(fileURLToPath(import.meta.url));
2930
const confPath = path.join(currentDir, '../server/apisix_conf.yml');
3031
const file = await readFile(confPath, 'utf-8');
3132
const res = parse(file) as APISIXConf;

e2e/utils/env.ts

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
* limitations under the License.
1616
*/
1717
import { config } from 'dotenv';
18-
import { parseEnv } from 'znv';
1918
import { z } from 'zod';
2019

2120
import { BASE_PATH } from '../../src/config/constant';
@@ -24,12 +23,26 @@ config({
2423
path: ['./.env', './.env.local', './.env.development.local'],
2524
});
2625

27-
export const env = parseEnv(process.env, {
28-
E2E_TARGET_URL: z
29-
.string()
30-
.url()
31-
.default(`http://localhost:9180${BASE_PATH}/`)
32-
.describe(
33-
`If you want to access the test server from dev container playwright to host e2e server, try http://host.docker.internal:9180${BASE_PATH}/`
34-
),
35-
});
26+
const DEFAULT_E2E_TARGET_URL = `http://localhost:9180${BASE_PATH}/`;
27+
const E2E_TARGET_URL_HINT =
28+
`If you want to access the test server from dev container playwright to host e2e server, try http://host.docker.internal:9180${BASE_PATH}/`;
29+
30+
const rawE2ETargetUrl = process.env.E2E_TARGET_URL;
31+
const e2eTargetUrlResult = z
32+
.string()
33+
.url()
34+
.default(DEFAULT_E2E_TARGET_URL)
35+
.safeParse(rawE2ETargetUrl);
36+
37+
if (!e2eTargetUrlResult.success) {
38+
throw new Error(
39+
'Errors found while parsing environment:\n' +
40+
` [E2E_TARGET_URL]: ${E2E_TARGET_URL_HINT}\n` +
41+
` ${e2eTargetUrlResult.error.issues[0]?.message ?? 'Invalid value'}\n` +
42+
` (received ${String(rawE2ETargetUrl)})`
43+
);
44+
}
45+
46+
export const env = {
47+
E2E_TARGET_URL: e2eTargetUrlResult.data,
48+
};

e2e/utils/ui/upstreams.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,27 @@ export async function uiFillUpstreamAllFields(
238238
await tlsSection
239239
.getByRole('textbox', { name: 'Client Key', exact: true })
240240
.fill(tls.key);
241-
await tlsSection.getByRole('switch', { name: 'Verify' }).click();
241+
242+
// Mantine renders a visually hidden switch input; use name-based targeting
243+
// and force-check so this works in both upstream and route upstream forms.
244+
const verifySwitchInput = tlsSection
245+
.locator('input[name$="tls.verify"]')
246+
.first();
247+
await verifySwitchInput.evaluate((el) => {
248+
const input = el as HTMLInputElement;
249+
if (input.checked) return;
250+
251+
// Prefer native click first; this is resilient to hidden switch inputs.
252+
input.click();
253+
254+
// Fallback: force state + events in case click is ignored by the UI lib.
255+
if (!input.checked) {
256+
input.checked = true;
257+
input.dispatchEvent(new Event('input', { bubbles: true }));
258+
input.dispatchEvent(new Event('change', { bubbles: true }));
259+
}
260+
});
261+
await expect(verifySwitchInput).toBeChecked();
242262

243263
// 12. Health Check settings
244264
// Activate active health check

eslint.config.ts

Lines changed: 5 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,11 @@
1515
* limitations under the License.
1616
*/
1717
import js from '@eslint/js'
18-
import i18n from '@m6web/eslint-plugin-i18n';
1918
import headers from 'eslint-plugin-headers';
20-
import i18next from 'eslint-plugin-i18next';
2119
import * as importPlugin from 'eslint-plugin-import';
2220
import playwright from 'eslint-plugin-playwright'
23-
import react from 'eslint-plugin-react'
2421
import reactHooks from 'eslint-plugin-react-hooks'
25-
import reactRefresh from 'eslint-plugin-react-refresh'
22+
import reactRefreshPlugin from 'eslint-plugin-react-refresh'
2623
import simpleImportSort from 'eslint-plugin-simple-import-sort'
2724
import unusedImports from 'eslint-plugin-unused-imports'
2825
import globals from 'globals'
@@ -97,35 +94,6 @@ const e2eRules = tseslint.config(
9794
}
9895
);
9996

100-
const i18nRules = tseslint.config({
101-
files: ['src/**/*.{ts,tsx,js}'],
102-
plugins: {
103-
i18next: i18next,
104-
i18n: i18n,
105-
},
106-
rules: {
107-
...i18next.configs['flat/recommended'].rules,
108-
'i18n/no-unknown-key': 'error',
109-
'i18n/no-text-as-children': ['error', { ignorePattern: '^\\s?[/.]\\s?$' }],
110-
'i18n/no-text-as-attribute': ['error', { attributes: ['alt', 'title'] }],
111-
'i18n/interpolation-data': [
112-
'error',
113-
{ interpolationPattern: '\\{\\.+\\}' },
114-
],
115-
},
116-
settings: {
117-
i18n: {
118-
principalLangs: [
119-
{
120-
name: 'en',
121-
translationPath: 'src/locales/en/common.json',
122-
},
123-
],
124-
functionName: 't',
125-
},
126-
},
127-
});
128-
12997
const srcRules = tseslint.config({
13098
extends: [commonRules],
13199
files: ['src/**/*.{ts,tsx}', 'eslint.config.ts'],
@@ -136,55 +104,19 @@ const srcRules = tseslint.config({
136104
},
137105
plugins: {
138106
'react-hooks': reactHooks,
139-
'react-refresh': reactRefresh,
140-
react: react,
141-
},
142-
settings: {
143-
react: {
144-
version: 'detect',
145-
},
107+
'react-refresh': reactRefreshPlugin,
146108
},
147109
rules: {
148-
...react.configs.flat.recommended.rules,
149-
...react.configs.flat['jsx-runtime'].rules,
150110
...reactHooks.configs.recommended.rules,
111+
'react-hooks/set-state-in-effect': 'off',
112+
'react-hooks/preserve-manual-memoization': 'off',
151113
'no-console': 'warn',
152-
'react-refresh/only-export-components': [
153-
'warn',
154-
{ allowConstantExport: true },
155-
],
156-
'react/jsx-curly-brace-presence': [
157-
'error',
158-
{
159-
props: 'never',
160-
children: 'never',
161-
},
162-
],
163-
'react/no-unescaped-entities': [
164-
'error',
165-
{
166-
forbid: ['>', '}'],
167-
},
168-
],
169-
'react/no-children-prop': [
170-
'error',
171-
{
172-
allowFunctions: true,
173-
},
174-
],
175-
'react/self-closing-comp': [
176-
'error',
177-
{
178-
component: true,
179-
html: true,
180-
},
181-
],
114+
'react-refresh/only-export-components': 'off',
182115
},
183116
});
184117

185118
export default tseslint.config(
186119
{ ignores: ['dist', 'src/routeTree.gen.ts'] },
187120
e2eRules,
188-
i18nRules,
189121
srcRules
190122
);

0 commit comments

Comments
 (0)