优化分批执行点击菜单
This commit is contained in:
parent
0dfad65b57
commit
82fb7ebe96
78
package.json
78
package.json
@ -1,41 +1,41 @@
|
|||||||
{
|
{
|
||||||
"name": "playwright-automation",
|
"name": "playwright-automation",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "基于Playwright的自动化测试工具",
|
"description": "基于Playwright的自动化测试工具",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "playwright test",
|
"test": "playwright test",
|
||||||
"test:ui": "playwright test --ui",
|
"test:ui": "playwright test --ui",
|
||||||
"report": "playwright show-report",
|
"report": "playwright show-report",
|
||||||
"codegen": "playwright codegen",
|
"codegen": "playwright codegen",
|
||||||
"debug": "playwright test --debug",
|
"debug": "playwright test --debug",
|
||||||
"test:menu": "cross-env NODE_ENV=dev playwright test tests/e2e/menu.spec.js",
|
"test:menu": "cross-env NODE_ENV=dev playwright test tests/e2e/menu.spec.js",
|
||||||
"test:menu:ui": "cross-env NODE_ENV=dev playwright test tests/e2e/menu.spec.js --ui",
|
"test:menu:ui": "cross-env NODE_ENV=dev playwright test tests/e2e/menu.spec.js --ui",
|
||||||
"test:menu:debug": "cross-env NODE_ENV=dev playwright test tests/e2e/menu.spec.js --debug",
|
"test:menu:debug": "cross-env NODE_ENV=dev playwright test tests/e2e/menu.spec.js --debug",
|
||||||
"test:menu:clean": "cross-env NODE_ENV=dev rimraf test-data/* && npm run test:menu",
|
"test:menu:clean": "cross-env NODE_ENV=dev rimraf test-data/* && npm run test:menu",
|
||||||
"test:menu:collect": "cross-env NODE_ENV=dev playwright test tests/e2e/menu.spec.js --grep \"收集菜单数据\"",
|
"test:menu:collect": "cross-env NODE_ENV=dev playwright test tests/e2e/menu.spec.js --grep \"收集菜单数据\"",
|
||||||
"test:menu:batch": "cross-env NODE_ENV=dev playwright test tests/e2e/menu.spec.js --grep \"批量测试菜单\""
|
"test:menu:batch": "cross-env NODE_ENV=dev playwright test tests/e2e/menu.spec.js --grep \"批量测试菜单\""
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"playwright",
|
"playwright",
|
||||||
"automation",
|
"automation",
|
||||||
"testing",
|
"testing",
|
||||||
"e2e"
|
"e2e"
|
||||||
],
|
],
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@playwright/test": "^1.40.0",
|
"@playwright/test": "^1.40.0",
|
||||||
"chalk": "^4.1.2",
|
"chalk": "^4.1.2",
|
||||||
"commander": "^11.1.0",
|
"commander": "^11.1.0",
|
||||||
"dotenv": "^16.3.1",
|
"dotenv": "^16.3.1",
|
||||||
"faker": "^5.5.3"
|
"faker": "^5.5.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"allure-playwright": "^2.9.2",
|
"allure-playwright": "^2.9.2",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"dotenv-flow": "^4.1.0",
|
"dotenv-flow": "^4.1.0",
|
||||||
"eslint": "^8.54.0",
|
"eslint": "^8.54.0",
|
||||||
"eslint-plugin-playwright": "^0.18.0"
|
"eslint-plugin-playwright": "^0.18.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,13 +4,16 @@ const {defineConfig, devices} = require('@playwright/test');
|
|||||||
/**
|
/**
|
||||||
* @see https://playwright.dev/docs/test-configuration
|
* @see https://playwright.dev/docs/test-configuration
|
||||||
*/
|
*/
|
||||||
module.exports = defineConfig({
|
/**
|
||||||
testDir: './tests/e2e',
|
* @type {import('@playwright/test').PlaywrightTestConfig}
|
||||||
|
*/
|
||||||
|
const config = {
|
||||||
|
testDir: './tests',
|
||||||
/* 测试超时时间 */
|
/* 测试超时时间 */
|
||||||
timeout: 60 * 60 * 1000, // 1小时
|
timeout: parseInt(process.env.EXPECT_TIMEOUT),
|
||||||
/* 每个测试的预期状态 */
|
/* 每个测试的预期状态 */
|
||||||
expect: {
|
expect: {
|
||||||
timeout: parseInt(process.env.EXPECT_TIMEOUT || '30000', 10)
|
timeout: parseInt(process.env.EXPECT_TIMEOUT)
|
||||||
},
|
},
|
||||||
/* 测试运行并发数 */
|
/* 测试运行并发数 */
|
||||||
fullyParallel: false,
|
fullyParallel: false,
|
||||||
@ -24,7 +27,6 @@ module.exports = defineConfig({
|
|||||||
use: {
|
use: {
|
||||||
/* 基础URL */
|
/* 基础URL */
|
||||||
baseURL: process.env.BASE_URL,
|
baseURL: process.env.BASE_URL,
|
||||||
|
|
||||||
/* 收集测试追踪信息 */
|
/* 收集测试追踪信息 */
|
||||||
trace: 'on-first-retry',
|
trace: 'on-first-retry',
|
||||||
/* 自动截图 */
|
/* 自动截图 */
|
||||||
@ -34,13 +36,13 @@ module.exports = defineConfig({
|
|||||||
/* 浏览器配置 */
|
/* 浏览器配置 */
|
||||||
headless: process.env.BROWSER_HEADLESS === 'true',
|
headless: process.env.BROWSER_HEADLESS === 'true',
|
||||||
viewport: {
|
viewport: {
|
||||||
width: parseInt(process.env.VIEWPORT_WIDTH || '1920', 10),
|
width: parseInt(process.env.VIEWPORT_WIDTH),
|
||||||
height: parseInt(process.env.VIEWPORT_HEIGHT || '1080', 10)
|
height: parseInt(process.env.VIEWPORT_HEIGHT)
|
||||||
},
|
},
|
||||||
/* 浏览器启动选项 */
|
/* 浏览器启动选项 */
|
||||||
launchOptions: {
|
launchOptions: {
|
||||||
slowMo: parseInt(process.env.BROWSER_SLOW_MO || '50', 10),
|
slowMo: parseInt(process.env.BROWSER_SLOW_MO),
|
||||||
timeout: parseInt(process.env.BROWSER_TIMEOUT || '30000', 10)
|
timeout: parseInt(process.env.BROWSER_TIMEOUT)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -58,4 +60,6 @@ module.exports = defineConfig({
|
|||||||
// port: 3000,
|
// port: 3000,
|
||||||
// reuseExistingServer: !process.env.CI,
|
// reuseExistingServer: !process.env.CI,
|
||||||
// },
|
// },
|
||||||
});
|
};
|
||||||
|
|
||||||
|
module.exports = config;
|
||||||
@ -1,13 +1,13 @@
|
|||||||
const { chromium } = require('@playwright/test');
|
const {chromium} = require('@playwright/test');
|
||||||
const LongiMainPage = require('../pages/LongiMainPage');
|
const LongiMainPage = require('../pages/LongiMainPage');
|
||||||
const LongiLoginPage = require('../pages/LongiLoginPage');
|
const LongiLoginPage = require('../pages/LongiLoginPage');
|
||||||
const menuDataService = require('../services/MenuDataService');
|
const menuDataService = require('../services/LongiTestService');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 测试控制器
|
* 测试控制器
|
||||||
* 负责协调页面操作和数据管理
|
* 负责协调页面操作和数据管理
|
||||||
*/
|
*/
|
||||||
class TestController {
|
class LongiTestController {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.initializeConfig();
|
this.initializeConfig();
|
||||||
}
|
}
|
||||||
@ -18,10 +18,10 @@ class TestController {
|
|||||||
*/
|
*/
|
||||||
initializeConfig() {
|
initializeConfig() {
|
||||||
// 从环境变量获取测试相关配置
|
// 从环境变量获取测试相关配置
|
||||||
this.batchSize = parseInt(process.env.TEST_BATCH_SIZE || '5', 10);
|
this.batchSize = parseInt(process.env.TEST_BATCH_SIZE);
|
||||||
this.retryCount = parseInt(process.env.TEST_RETRY_COUNT || '3', 10);
|
this.retryCount = parseInt(process.env.TEST_RETRY_COUNT);
|
||||||
this.batchInterval = parseInt(process.env.TEST_BATCH_INTERVAL || '2000', 10);
|
this.batchInterval = parseInt(process.env.TEST_BATCH_INTERVAL);
|
||||||
this.maxRetryDelay = parseInt(process.env.TEST_MAX_RETRY_DELAY || '5000', 10);
|
this.maxRetryDelay = parseInt(process.env.TEST_MAX_RETRY_DELAY);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -33,7 +33,7 @@ class TestController {
|
|||||||
try {
|
try {
|
||||||
const browser = await chromium.launch();
|
const browser = await chromium.launch();
|
||||||
const page = await browser.newPage();
|
const page = await browser.newPage();
|
||||||
return { browser, page };
|
return {browser, page};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('创建浏览器实例失败:', error);
|
console.error('创建浏览器实例失败:', error);
|
||||||
throw new Error('无法创建浏览器实例');
|
throw new Error('无法创建浏览器实例');
|
||||||
@ -64,9 +64,9 @@ class TestController {
|
|||||||
async collectMenuData() {
|
async collectMenuData() {
|
||||||
console.log('开始收集菜单数据...');
|
console.log('开始收集菜单数据...');
|
||||||
let browser, page;
|
let browser, page;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
({ browser, page } = await this.createBrowser());
|
({browser, page} = await this.createBrowser());
|
||||||
const mainPage = new LongiMainPage(page);
|
const mainPage = new LongiMainPage(page);
|
||||||
|
|
||||||
if (!await this.performLogin(page)) {
|
if (!await this.performLogin(page)) {
|
||||||
@ -95,9 +95,9 @@ class TestController {
|
|||||||
console.log(menuBatch.map(m => m.text).join(', '));
|
console.log(menuBatch.map(m => m.text).join(', '));
|
||||||
|
|
||||||
let browser, page;
|
let browser, page;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
({ browser, page } = await this.createBrowser());
|
({browser, page} = await this.createBrowser());
|
||||||
const mainPage = new LongiMainPage(page);
|
const mainPage = new LongiMainPage(page);
|
||||||
|
|
||||||
if (!await this.performLogin(page)) {
|
if (!await this.performLogin(page)) {
|
||||||
@ -144,9 +144,9 @@ class TestController {
|
|||||||
try {
|
try {
|
||||||
const menuData = menuDataService.getMenuData();
|
const menuData = menuDataService.getMenuData();
|
||||||
const progress = menuDataService.getProgress();
|
const progress = menuDataService.getProgress();
|
||||||
|
|
||||||
if (!menuData) return null;
|
if (!menuData) return null;
|
||||||
|
|
||||||
const remainingMenus = menuData.filter(menu => !progress.includes(menu.id));
|
const remainingMenus = menuData.filter(menu => !progress.includes(menu.id));
|
||||||
if (remainingMenus.length === 0) return null;
|
if (remainingMenus.length === 0) return null;
|
||||||
|
|
||||||
@ -165,9 +165,9 @@ class TestController {
|
|||||||
try {
|
try {
|
||||||
const menuData = menuDataService.getMenuData();
|
const menuData = menuDataService.getMenuData();
|
||||||
const progress = menuDataService.getProgress();
|
const progress = menuDataService.getProgress();
|
||||||
|
|
||||||
if (!menuData) {
|
if (!menuData) {
|
||||||
return { total: 0, completed: 0, remaining: 0 };
|
return {total: 0, completed: 0, remaining: 0};
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -189,16 +189,16 @@ class TestController {
|
|||||||
try {
|
try {
|
||||||
// 清理之前的进度
|
// 清理之前的进度
|
||||||
menuDataService.saveProgress([]);
|
menuDataService.saveProgress([]);
|
||||||
|
|
||||||
let retryCount = 0;
|
let retryCount = 0;
|
||||||
let lastError = null;
|
let lastError = null;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
try {
|
try {
|
||||||
const batch = this.getNextBatch();
|
const batch = this.getNextBatch();
|
||||||
if (!batch?.length) {
|
if (!batch?.length) {
|
||||||
const progress = this.getTestProgress();
|
const progress = this.getTestProgress();
|
||||||
|
|
||||||
if (progress.completed < progress.total && retryCount < this.retryCount) {
|
if (progress.completed < progress.total && retryCount < this.retryCount) {
|
||||||
console.log(`\n还有未完成的测试,尝试重试 (${retryCount + 1}/${this.retryCount})...`);
|
console.log(`\n还有未完成的测试,尝试重试 (${retryCount + 1}/${this.retryCount})...`);
|
||||||
retryCount++;
|
retryCount++;
|
||||||
@ -207,14 +207,14 @@ class TestController {
|
|||||||
await new Promise(resolve => setTimeout(resolve, delay));
|
await new Promise(resolve => setTimeout(resolve, delay));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.getTestProgress();
|
return this.getTestProgress();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 执行当前批次
|
// 执行当前批次
|
||||||
await this.runBatchTest(batch);
|
await this.runBatchTest(batch);
|
||||||
lastError = null;
|
lastError = null;
|
||||||
|
|
||||||
// 批次间暂停
|
// 批次间暂停
|
||||||
await new Promise(resolve => setTimeout(resolve, this.batchInterval));
|
await new Promise(resolve => setTimeout(resolve, this.batchInterval));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -230,4 +230,4 @@ class TestController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = TestController;
|
module.exports = LongiTestController;
|
||||||
@ -5,7 +5,7 @@ const path = require('path');
|
|||||||
* 菜单数据服务
|
* 菜单数据服务
|
||||||
* 负责菜单数据的存储和检索
|
* 负责菜单数据的存储和检索
|
||||||
*/
|
*/
|
||||||
class MenuDataService {
|
class LongiTestService {
|
||||||
constructor() {
|
constructor() {
|
||||||
// 从环境变量获取路径配置
|
// 从环境变量获取路径配置
|
||||||
this.dataDir = path.join(process.cwd(), process.env.TEST_DATA_DIR || 'test-data');
|
this.dataDir = path.join(process.cwd(), process.env.TEST_DATA_DIR || 'test-data');
|
||||||
@ -79,4 +79,4 @@ class MenuDataService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 导出单例实例
|
// 导出单例实例
|
||||||
module.exports = new MenuDataService();
|
module.exports = new LongiTestService();
|
||||||
@ -30,7 +30,7 @@ class FileUtils {
|
|||||||
static ensureDirectoryExists(dirPath) {
|
static ensureDirectoryExists(dirPath) {
|
||||||
try {
|
try {
|
||||||
if (!fs.existsSync(dirPath)) {
|
if (!fs.existsSync(dirPath)) {
|
||||||
fs.mkdirSync(dirPath, { recursive: true });
|
fs.mkdirSync(dirPath, {recursive: true});
|
||||||
console.log(`目录已创建: ${dirPath}`);
|
console.log(`目录已创建: ${dirPath}`);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@ -51,13 +51,13 @@ class FileUtils {
|
|||||||
*/
|
*/
|
||||||
static saveToJsonFile(data, filePath, options = {}) {
|
static saveToJsonFile(data, filePath, options = {}) {
|
||||||
try {
|
try {
|
||||||
const { pretty = true, encoding = 'utf8' } = options;
|
const {pretty = true, encoding = 'utf8'} = options;
|
||||||
const indent = pretty ? 2 : 0;
|
const indent = pretty ? 2 : 0;
|
||||||
|
|
||||||
// 确保目录存在
|
// 确保目录存在
|
||||||
const dirPath = path.dirname(filePath);
|
const dirPath = path.dirname(filePath);
|
||||||
this.ensureDirectoryExists(dirPath);
|
this.ensureDirectoryExists(dirPath);
|
||||||
|
|
||||||
// 将对象转换为JSON字符串并写入文件
|
// 将对象转换为JSON字符串并写入文件
|
||||||
fs.writeFileSync(filePath, JSON.stringify(data, null, indent), encoding);
|
fs.writeFileSync(filePath, JSON.stringify(data, null, indent), encoding);
|
||||||
console.log(`数据已保存到: ${filePath}`);
|
console.log(`数据已保存到: ${filePath}`);
|
||||||
@ -77,13 +77,13 @@ class FileUtils {
|
|||||||
*/
|
*/
|
||||||
static loadFromJsonFile(filePath, options = {}) {
|
static loadFromJsonFile(filePath, options = {}) {
|
||||||
try {
|
try {
|
||||||
const { encoding = 'utf8' } = options;
|
const {encoding = 'utf8'} = options;
|
||||||
|
|
||||||
if (!fs.existsSync(filePath)) {
|
if (!fs.existsSync(filePath)) {
|
||||||
console.error(`文件不存在: ${filePath}`);
|
console.error(`文件不存在: ${filePath}`);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = fs.readFileSync(filePath, encoding);
|
const data = fs.readFileSync(filePath, encoding);
|
||||||
return JSON.parse(data);
|
return JSON.parse(data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -102,12 +102,12 @@ class FileUtils {
|
|||||||
*/
|
*/
|
||||||
static saveTextToFile(text, filePath, options = {}) {
|
static saveTextToFile(text, filePath, options = {}) {
|
||||||
try {
|
try {
|
||||||
const { encoding = 'utf8' } = options;
|
const {encoding = 'utf8'} = options;
|
||||||
|
|
||||||
// 确保目录存在
|
// 确保目录存在
|
||||||
const dirPath = path.dirname(filePath);
|
const dirPath = path.dirname(filePath);
|
||||||
this.ensureDirectoryExists(dirPath);
|
this.ensureDirectoryExists(dirPath);
|
||||||
|
|
||||||
// 写入文件
|
// 写入文件
|
||||||
fs.writeFileSync(filePath, text, encoding);
|
fs.writeFileSync(filePath, text, encoding);
|
||||||
console.log(`文本已保存到: ${filePath}`);
|
console.log(`文本已保存到: ${filePath}`);
|
||||||
@ -127,13 +127,13 @@ class FileUtils {
|
|||||||
*/
|
*/
|
||||||
static loadTextFromFile(filePath, options = {}) {
|
static loadTextFromFile(filePath, options = {}) {
|
||||||
try {
|
try {
|
||||||
const { encoding = 'utf8' } = options;
|
const {encoding = 'utf8'} = options;
|
||||||
|
|
||||||
if (!fs.existsSync(filePath)) {
|
if (!fs.existsSync(filePath)) {
|
||||||
console.error(`文件不存在: ${filePath}`);
|
console.error(`文件不存在: ${filePath}`);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return fs.readFileSync(filePath, encoding);
|
return fs.readFileSync(filePath, encoding);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`加载文本文件失败: ${filePath}`, error);
|
console.error(`加载文本文件失败: ${filePath}`, error);
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
require('../../config/env');
|
require('../../config/env');
|
||||||
|
|
||||||
const { test } = require('@playwright/test');
|
const { test } = require('@playwright/test');
|
||||||
const TestController = require('../../src/controllers/TestController');
|
const TestController = require('../../src/controllers/LongiTestController');
|
||||||
|
|
||||||
test.describe('菜单可访问性测试', () => {
|
test.describe('菜单可访问性测试', () => {
|
||||||
let controller;
|
let controller;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user