feat: e2e commands (#3042)
This commit is contained in:
parent
14a21882e8
commit
03062a2b05
@ -16,9 +16,6 @@ APP_KEY=test-key-e2e
|
||||
SOCKET_PATH=storage/gateway-e2e.sock
|
||||
__E2E__=true
|
||||
|
||||
# 指定运行测试的服务地址;当 APP_BASE_URL 不为空时,将不会在本地启动测试服务
|
||||
# APP_BASE_URL=http://localhost:20000
|
||||
|
||||
# 启用 mock-collections 插件
|
||||
APPEND_PRESET_BUILT_IN_PLUGINS=mock-collections
|
||||
|
||||
|
3
.github/workflows/nocobase-test-e2e.yml
vendored
3
.github/workflows/nocobase-test-e2e.yml
vendored
@ -62,9 +62,10 @@ jobs:
|
||||
__E2E__: true
|
||||
- run: npx playwright install --with-deps
|
||||
- name: Test with postgres
|
||||
run: yarn test:e2e
|
||||
run: yarn e2e test -x --skip-reporter
|
||||
env:
|
||||
__E2E__: true
|
||||
APP_ENV: production
|
||||
LOGGER_LEVEL: error
|
||||
DB_DIALECT: postgres
|
||||
DB_HOST: postgres
|
||||
|
@ -25,6 +25,7 @@
|
||||
"tar": "nocobase tar",
|
||||
"test": "nocobase test",
|
||||
"test:client": "vitest",
|
||||
"e2e": "nocobase e2e",
|
||||
"test:e2e": "tsx ./scripts/runE2e.setup.ts",
|
||||
"test:e2e:codegen": "tsx ./scripts/codegen.setup.ts",
|
||||
"test:e2e:server": "tsx ./scripts/nocobase.setup.ts",
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
const dotenv = require('dotenv');
|
||||
const { resolve } = require('path');
|
||||
const { existsSync } = require('fs');
|
||||
const fs = require('fs');
|
||||
const chalk = require('chalk');
|
||||
const { genTsConfigPaths } = require('../src/util');
|
||||
|
||||
@ -25,11 +25,26 @@ const env = {
|
||||
};
|
||||
|
||||
if (!process.env.APP_ENV_PATH && process.argv[2] && process.argv[2] === 'test') {
|
||||
if (existsSync(resolve(process.cwd(), '.env.test'))) {
|
||||
if (fs.existsSync(resolve(process.cwd(), '.env.test'))) {
|
||||
process.env.APP_ENV_PATH = '.env.test';
|
||||
}
|
||||
}
|
||||
|
||||
if (process.argv[2] === 'e2e') {
|
||||
// 用于存放 playwright 自动生成的相关的文件
|
||||
if (!fs.existsSync('playwright')) {
|
||||
fs.mkdirSync('playwright');
|
||||
}
|
||||
if (!fs.existsSync('.env.e2e') && fs.existsSync('.env.e2e.example')) {
|
||||
const env = fs.readFileSync('.env.e2e.example');
|
||||
fs.writeFileSync('.env.e2e', env);
|
||||
}
|
||||
if (!fs.existsSync('.env.e2e')) {
|
||||
throw new Error('Please create .env.e2e file first!');
|
||||
}
|
||||
process.env.APP_ENV_PATH = '.env.e2e';
|
||||
}
|
||||
|
||||
genTsConfigPaths();
|
||||
|
||||
dotenv.config({
|
||||
@ -42,6 +57,10 @@ for (const key in env) {
|
||||
}
|
||||
}
|
||||
|
||||
if (process.argv[2] === 'e2e' && !process.env.APP_BASE_URL) {
|
||||
process.env.APP_BASE_URL = `http://127.0.0.1:${process.env.APP_PORT}`;
|
||||
}
|
||||
|
||||
if (require('semver').satisfies(process.version, '<16')) {
|
||||
console.error(chalk.red('[nocobase cli]: Node.js version must be >= 16'));
|
||||
process.exit(1);
|
||||
|
199
packages/core/cli/src/commands/e2e.js
Normal file
199
packages/core/cli/src/commands/e2e.js
Normal file
@ -0,0 +1,199 @@
|
||||
const { Command } = require('commander');
|
||||
const { run, isPortReachable } = require('../util');
|
||||
const { execSync } = require('node:child_process');
|
||||
const axios = require('axios');
|
||||
|
||||
/**
|
||||
* 检查服务是否启动成功
|
||||
*/
|
||||
const checkServer = async (duration = 1000, max = 60 * 10) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
let count = 0;
|
||||
const timer = setInterval(async () => {
|
||||
if (count++ > max) {
|
||||
clearInterval(timer);
|
||||
return reject(new Error('Server start timeout.'));
|
||||
}
|
||||
|
||||
// if (!(await checkPort(PORT))) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
const url = `${process.env.APP_BASE_URL}/api/__health_check`;
|
||||
// console.log('url', url);
|
||||
|
||||
axios
|
||||
.get(url)
|
||||
.then((response) => {
|
||||
if (response.status === 200) {
|
||||
clearInterval(timer);
|
||||
resolve(true);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Request error:', error.message);
|
||||
});
|
||||
}, duration);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 检查 UI 是否启动成功
|
||||
* @param duration
|
||||
*/
|
||||
const checkUI = async (duration = 1000, max = 60 * 10) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
let count = 0;
|
||||
const timer = setInterval(async () => {
|
||||
if (count++ > max) {
|
||||
clearInterval(timer);
|
||||
return reject(new Error('UI start timeout.'));
|
||||
}
|
||||
|
||||
axios
|
||||
.get(`${process.env.APP_BASE_URL}/__umi/api/bundle-status`)
|
||||
.then((response) => {
|
||||
if (response.data === 'ok') {
|
||||
clearInterval(timer);
|
||||
resolve(true);
|
||||
return;
|
||||
}
|
||||
if (response.data.bundleStatus.done) {
|
||||
clearInterval(timer);
|
||||
resolve(true);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Request error:', error.message);
|
||||
});
|
||||
}, duration);
|
||||
});
|
||||
};
|
||||
|
||||
async function appReady() {
|
||||
console.log('check server...');
|
||||
await checkServer();
|
||||
console.log('server is ready, check UI...');
|
||||
await checkUI();
|
||||
console.log('UI is ready.');
|
||||
}
|
||||
|
||||
async function runApp(options = {}) {
|
||||
console.log('installing...');
|
||||
await run('nocobase', ['install', '-f']);
|
||||
if (await isPortReachable(process.env.APP_PORT)) {
|
||||
console.log('app started');
|
||||
return;
|
||||
}
|
||||
console.log('starting...');
|
||||
run('nocobase', [process.env.APP_ENV === 'production' ? 'start' : 'dev'], options);
|
||||
}
|
||||
|
||||
const commonConfig = {
|
||||
stdio: 'inherit',
|
||||
};
|
||||
|
||||
const runCodegenSync = () => {
|
||||
try {
|
||||
execSync(
|
||||
`npx playwright codegen --load-storage=playwright/.auth/codegen.auth.json ${process.env.APP_BASE_URL} --save-storage=playwright/.auth/codegen.auth.json`,
|
||||
commonConfig,
|
||||
);
|
||||
} catch (err) {
|
||||
if (err.message.includes('auth.json')) {
|
||||
execSync(
|
||||
`npx playwright codegen ${process.env.APP_BASE_URL} --save-storage=playwright/.auth/codegen.auth.json`,
|
||||
commonConfig,
|
||||
);
|
||||
} else {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const filterArgv = () => {
|
||||
const arr = process.argv.slice(4);
|
||||
const argv = [];
|
||||
for (let index = 0; index < arr.length; index++) {
|
||||
const element = arr[index];
|
||||
if (element === '--url') {
|
||||
index++;
|
||||
continue;
|
||||
}
|
||||
if (element.startsWith('--url=')) {
|
||||
continue;
|
||||
}
|
||||
if (element === '--skip-reporter') {
|
||||
continue;
|
||||
}
|
||||
argv.push(element);
|
||||
}
|
||||
return argv;
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Command} cli
|
||||
*/
|
||||
module.exports = (cli) => {
|
||||
const e2e = cli.command('e2e').hook('preAction', () => {
|
||||
if (process.env.APP_BASE_URL) {
|
||||
process.env.APP_BASE_URL = process.env.APP_BASE_URL.replace('localhost', '127.0.0.1');
|
||||
console.log('APP_BASE_URL:', process.env.APP_BASE_URL);
|
||||
}
|
||||
});
|
||||
e2e
|
||||
.command('test')
|
||||
.allowUnknownOption()
|
||||
.option('--url [url]')
|
||||
.option('--skip-reporter')
|
||||
.action(async (options) => {
|
||||
if (options.skipReporter) {
|
||||
process.env.PLAYWRIGHT_SKIP_REPORTER = true;
|
||||
}
|
||||
if (options.url) {
|
||||
process.env.APP_BASE_URL = options.url.replace('localhost', '127.0.0.1');
|
||||
} else {
|
||||
await runApp({
|
||||
stdio: 'ignore',
|
||||
});
|
||||
}
|
||||
await appReady();
|
||||
await run('npx', ['playwright', 'test', ...filterArgv()]);
|
||||
process.exit();
|
||||
});
|
||||
|
||||
e2e
|
||||
.command('codegen')
|
||||
.allowUnknownOption()
|
||||
.option('--url [url]')
|
||||
.action(async (options) => {
|
||||
if (options.url) {
|
||||
process.env.APP_BASE_URL = options.url.replace('localhost', '127.0.0.1');
|
||||
} else {
|
||||
await runApp({
|
||||
stdio: 'ignore',
|
||||
});
|
||||
}
|
||||
await appReady();
|
||||
runCodegenSync();
|
||||
});
|
||||
|
||||
e2e
|
||||
.command('start-app')
|
||||
.option('--production')
|
||||
.option('--port [port]')
|
||||
.action(async (options) => {
|
||||
if (options.production) {
|
||||
process.env.APP_ENV = 'production';
|
||||
}
|
||||
if (options.port) {
|
||||
process.env.APP_PORT = options.port;
|
||||
}
|
||||
runApp();
|
||||
});
|
||||
|
||||
e2e.command('reinstall-app').action(async (options) => {
|
||||
await run('nocobase', ['install', '-f'], options);
|
||||
});
|
||||
};
|
@ -13,6 +13,7 @@ module.exports = (cli) => {
|
||||
require('./dev')(cli);
|
||||
require('./start')(cli);
|
||||
require('./test')(cli);
|
||||
require('./e2e')(cli);
|
||||
require('./clean')(cli);
|
||||
require('./doc')(cli);
|
||||
require('./umi')(cli);
|
||||
|
@ -145,6 +145,12 @@ export class Gateway extends EventEmitter {
|
||||
async requestHandler(req: IncomingMessage, res: ServerResponse) {
|
||||
const { pathname } = parse(req.url);
|
||||
|
||||
if (pathname === '/__umi/api/bundle-status') {
|
||||
res.statusCode = 200;
|
||||
res.end('ok');
|
||||
return;
|
||||
}
|
||||
|
||||
if (pathname.startsWith('/storage/uploads/')) {
|
||||
await compress(req, res);
|
||||
return handler(req, res, {
|
||||
|
@ -22,7 +22,9 @@ export default defineConfig({
|
||||
workers: 1,
|
||||
|
||||
// Reporter to use
|
||||
reporter: [['html', { outputFolder: './playwright/tests-report' }]],
|
||||
reporter: process.env.PLAYWRIGHT_SKIP_REPORTER
|
||||
? undefined
|
||||
: [['html', { outputFolder: './playwright/tests-report' }]],
|
||||
|
||||
outputDir: './playwright/test-results',
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user