Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
pnpm lint-staged
lint-staged
git update-index --again
21 changes: 9 additions & 12 deletions e2e/tests/services.crud-required-fields.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,24 +56,21 @@ test('should CRUD service with required fields', async ({ page }) => {
const rows = upstreamSection.locator('tr.ant-table-row');
await rows.first().locator('input').first().fill('127.0.0.1');
await rows.first().locator('input').nth(1).fill('80');
await rows.first().locator('input').nth(2).fill('1');
const weightInput = rows.first().locator('input').nth(2);
await weightInput.fill('1');

// Editable table cells may require blur/click-outside before form submit.
await weightInput.blur();
await upstreamSection.click();

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

await servicesPom.getAddBtn(page).click();

// Wait for either success or error toast (longer timeout for CI)
const alertMsg = page.getByRole('alert');
await expect(alertMsg).toBeVisible({ timeout: 30000 });

// Check if it's a success message
await expect(alertMsg).toContainText('Add Service Successfully', { timeout: 5000 });

// Close the toast
await alertMsg.getByRole('button').click();
await expect(alertMsg).toBeHidden();
await uiHasToastMsg(page, {
hasText: 'Add Service Successfully',
});
});

await test.step('auto navigate to service detail page', async () => {
Expand Down
3 changes: 2 additions & 1 deletion e2e/tests/stream_routes.show-disabled-error.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import { exec } from 'node:child_process';
import { readFile, writeFile } from 'node:fs/promises';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { promisify } from 'node:util';

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

const getE2EServerDir = () => {
const currentDir = new URL('.', import.meta.url).pathname;
const currentDir = path.dirname(fileURLToPath(import.meta.url));
return path.join(currentDir, '../server');
};

Expand Down
3 changes: 2 additions & 1 deletion e2e/utils/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
*/
import { access, readFile } from 'node:fs/promises';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

import { nanoid } from 'nanoid';
import selfsigned from 'selfsigned';
Expand All @@ -25,7 +26,7 @@ type APISIXConf = {
deployment: { admin: { admin_key: { key: string }[] } };
};
export const getAPISIXConf = async () => {
const currentDir = new URL('.', import.meta.url).pathname;
const currentDir = path.dirname(fileURLToPath(import.meta.url));
const confPath = path.join(currentDir, '../server/apisix_conf.yml');
const file = await readFile(confPath, 'utf-8');
const res = parse(file) as APISIXConf;
Expand Down
33 changes: 23 additions & 10 deletions e2e/utils/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
* limitations under the License.
*/
import { config } from 'dotenv';
import { parseEnv } from 'znv';
import { z } from 'zod';

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

export const env = parseEnv(process.env, {
E2E_TARGET_URL: z
.string()
.url()
.default(`http://localhost:9180${BASE_PATH}/`)
.describe(
`If you want to access the test server from dev container playwright to host e2e server, try http://host.docker.internal:9180${BASE_PATH}/`
),
});
const DEFAULT_E2E_TARGET_URL = `http://localhost:9180${BASE_PATH}/`;
const E2E_TARGET_URL_HINT =
`If you want to access the test server from dev container playwright to host e2e server, try http://host.docker.internal:9180${BASE_PATH}/`;

const rawE2ETargetUrl = process.env.E2E_TARGET_URL;
const e2eTargetUrlResult = z
.string()
.url()
.default(DEFAULT_E2E_TARGET_URL)
.safeParse(rawE2ETargetUrl);

if (!e2eTargetUrlResult.success) {
throw new Error(
'Errors found while parsing environment:\n' +
` [E2E_TARGET_URL]: ${E2E_TARGET_URL_HINT}\n` +
` ${e2eTargetUrlResult.error.issues[0]?.message ?? 'Invalid value'}\n` +
` (received ${String(rawE2ETargetUrl)})`
);
}

export const env = {
E2E_TARGET_URL: e2eTargetUrlResult.data,
};
22 changes: 21 additions & 1 deletion e2e/utils/ui/upstreams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,27 @@ export async function uiFillUpstreamAllFields(
await tlsSection
.getByRole('textbox', { name: 'Client Key', exact: true })
.fill(tls.key);
await tlsSection.getByRole('switch', { name: 'Verify' }).click();

// Mantine renders a visually hidden switch input; use name-based targeting
// and force-check so this works in both upstream and route upstream forms.
const verifySwitchInput = tlsSection
.locator('input[name$="tls.verify"]')
.first();
await verifySwitchInput.evaluate((el) => {
const input = el as HTMLInputElement;
if (input.checked) return;

// Prefer native click first; this is resilient to hidden switch inputs.
input.click();

// Fallback: force state + events in case click is ignored by the UI lib.
if (!input.checked) {
input.checked = true;
input.dispatchEvent(new Event('input', { bubbles: true }));
input.dispatchEvent(new Event('change', { bubbles: true }));
}
});
await expect(verifySwitchInput).toBeChecked();

// 12. Health Check settings
// Activate active health check
Expand Down
78 changes: 5 additions & 73 deletions eslint.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,11 @@
* limitations under the License.
*/
import js from '@eslint/js'
import i18n from '@m6web/eslint-plugin-i18n';
import headers from 'eslint-plugin-headers';
import i18next from 'eslint-plugin-i18next';
import * as importPlugin from 'eslint-plugin-import';
import playwright from 'eslint-plugin-playwright'
import react from 'eslint-plugin-react'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import reactRefreshPlugin from 'eslint-plugin-react-refresh'
import simpleImportSort from 'eslint-plugin-simple-import-sort'
import unusedImports from 'eslint-plugin-unused-imports'
import globals from 'globals'
Expand Down Expand Up @@ -97,35 +94,6 @@ const e2eRules = tseslint.config(
}
);

const i18nRules = tseslint.config({
files: ['src/**/*.{ts,tsx,js}'],
plugins: {
i18next: i18next,
i18n: i18n,
},
rules: {
...i18next.configs['flat/recommended'].rules,
'i18n/no-unknown-key': 'error',
'i18n/no-text-as-children': ['error', { ignorePattern: '^\\s?[/.]\\s?$' }],
'i18n/no-text-as-attribute': ['error', { attributes: ['alt', 'title'] }],
'i18n/interpolation-data': [
'error',
{ interpolationPattern: '\\{\\.+\\}' },
],
},
settings: {
i18n: {
principalLangs: [
{
name: 'en',
translationPath: 'src/locales/en/common.json',
},
],
functionName: 't',
},
},
});

const srcRules = tseslint.config({
extends: [commonRules],
files: ['src/**/*.{ts,tsx}', 'eslint.config.ts'],
Expand All @@ -136,55 +104,19 @@ const srcRules = tseslint.config({
},
plugins: {
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
react: react,
},
settings: {
react: {
version: 'detect',
},
'react-refresh': reactRefreshPlugin,
},
rules: {
...react.configs.flat.recommended.rules,
...react.configs.flat['jsx-runtime'].rules,
...reactHooks.configs.recommended.rules,
'react-hooks/set-state-in-effect': 'off',
'react-hooks/preserve-manual-memoization': 'off',
'no-console': 'warn',
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
'react/jsx-curly-brace-presence': [
'error',
{
props: 'never',
children: 'never',
},
],
'react/no-unescaped-entities': [
'error',
{
forbid: ['>', '}'],
},
],
'react/no-children-prop': [
'error',
{
allowFunctions: true,
},
],
'react/self-closing-comp': [
'error',
{
component: true,
html: true,
},
],
'react-refresh/only-export-components': 'off',
},
});

export default tseslint.config(
{ ignores: ['dist', 'src/routeTree.gen.ts'] },
e2eRules,
i18nRules,
srcRules
);
Loading