Skip to content
Merged
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 playwright/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "testsolar-oss-playwright",
"version": "0.2.22",
"version": "0.2.23",
"description": "TestSolar support for playwright tool",
"main": "main.js",
"scripts": {
Expand Down
81 changes: 59 additions & 22 deletions playwright/src/playwrightx/executor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ export async function runTestCase(runParamFile: string): Promise<void> {
const testSelectors = data.TestSelectors || [];
let projPath = data.ProjectPath;
const taskId = data.TaskId;

// 默认true, 有值则为false
const fileMode = process.env.TESTSOLAR_TTP_FILEMODE == "1";

// 创建附件目录
const attachmentsPath = path.join(projPath, "attachments");
Expand Down Expand Up @@ -93,29 +96,63 @@ export async function runTestCase(runParamFile: string): Promise<void> {
}
});

// 按照文件对用例进行分组
const caseLists = groupTestCasesByPath(newSelectors);

// 对每个文件生成命令行
for (const [casePath, testcases] of Object.entries(caseLists)) {
// 上报用例运行状态
createRunningTestResults(casePath, testcases, reporter);

// 执行命令并解析用例生成的 JSON 文件
log.info(`当前进程ID: ${process.pid}`)
const jsonName = casePath.replace(/\//g, "_") + "_pid_" + process.pid + ".json";
const { command, testIdentifiers } = generateCommands(casePath, testcases, jsonName);
const testResults = await executeCommands(
projPath,
command,
testIdentifiers,
jsonName,
attachmentsPath,
);
if (fileMode) {
// fileMode: 直接运行文件,不解析具体测试用例
log.info("TESTSOLAR_TTP_FILEMODE is set, running files directly");

// 按照文件对用例进行分组,但在fileMode下每个文件运行所有测试
const caseLists = groupTestCasesByPath(newSelectors);

// 对每个文件生成命令行
for (const [casePath, testcases] of Object.entries(caseLists)) {
// 上报用例运行状态
createRunningTestResults(casePath, testcases, reporter);

// 执行命令并解析用例生成的 JSON 文件
log.info(`当前进程ID: ${process.pid}`)
const jsonName = casePath.replace(/\//g, "_") + "_pid_" + process.pid + ".json";
// 在fileMode下,只运行文件,不指定具体测试用例
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { command, testIdentifiers } = generateCommands(casePath, [], jsonName);
// 注意:在fileMode下,testIdentifiers不会被使用,但仍需要解构以保持函数接口一致性
const testResults = await executeCommands(
projPath,
command,
[casePath], // 在fileMode下,使用文件路径作为标识符
jsonName,
attachmentsPath,
);

const results = createTestResults(testResults, [casePath]);
for (const result of results) {
await reporter.reportTestResult(result);
}
}
} else {
// 按照文件对用例进行分组
const caseLists = groupTestCasesByPath(newSelectors);

// 对每个文件生成命令行
for (const [casePath, testcases] of Object.entries(caseLists)) {
// 上报用例运行状态
createRunningTestResults(casePath, testcases, reporter);

const results = createTestResults(testResults, testIdentifiers);
for (const result of results) {
await reporter.reportTestResult(result);
// 执行命令并解析用例生成的 JSON 文件
log.info(`当前进程ID: ${process.pid}`)
const jsonName = casePath.replace(/\//g, "_") + "_pid_" + process.pid + ".json";
const { command, testIdentifiers } = generateCommands(casePath, testcases, jsonName);
const testResults = await executeCommands(
projPath,
command,
testIdentifiers,
jsonName,
attachmentsPath,
);

const results = createTestResults(testResults, testIdentifiers);
for (const result of results) {
await reporter.reportTestResult(result);
}
}
}
}
Expand Down
51 changes: 35 additions & 16 deletions playwright/src/playwrightx/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
filterTestcases,
getTestcasePrefix,
parsePlaywrightReport,
scanPlaywrightTestFiles,
} from "./utils";

import {
Expand Down Expand Up @@ -38,24 +39,35 @@ export async function collectTestCases(
}
const filePath = path.join(projPath, "load.json");

// 默认false
const fileMode = process.env.TESTSOLAR_TTP_FILEMODE == "1";

// 执行命令获取output.json文件内容
const command = `npx playwright test --list --reporter=json > ${filePath}`;
log.info("Run Command: ", command);
const { stdout, stderr } = await executeCommand(command);
log.info("stdout:", stdout);
log.info("stderr:", stderr);

//TODO 解析output.json文件内容, 待完善,重跑用例加上数据驱动
const fileContent = fs.readFileSync(filePath, "utf-8");
const testData = JSON.parse(fileContent);

// 解析所有用例
const loadCaseResult = parseTestcase(projPath, testData);
let loadCaseResult;
if (fileMode) {
log.info("TESTSOLAR_TTP_FILEMODE is set, using file paths directly without parsing");
// 扫描Playwright测试文件
loadCaseResult = scanPlaywrightTestFiles(projPath);
} else {
// 如果环境变量未设置,则按原来的方式解析用例
// 执行命令获取output.json文件内容
const command = `npx playwright test --list --reporter=json > ${filePath}`;
log.info("Run Command: ", command);
const { stdout, stderr } = await executeCommand(command);
log.info("stdout:", stdout);
log.info("stderr:", stderr);

//TODO 解析output.json文件内容, 待完善,重跑用例加上数据驱动
const fileContent = fs.readFileSync(filePath, "utf-8");
const testData = JSON.parse(fileContent);

// 解析所有用例
loadCaseResult = parseTestcase(projPath, testData);
}
log.info("PlayWright testtool parse all testcases: \n", loadCaseResult);

// 如果用例为空,则通过解析json来获取错误信息
if (loadCaseResult.length === 0) {
if (loadCaseResult.length === 0 && !fileMode) {
const fileContent = fs.readFileSync(filePath, "utf-8");
const errors = parsePlaywrightReport(fileContent);
result.LoadErrors.push(...errors); // 使用LoadResult中定义的属性名
}
Expand Down Expand Up @@ -85,8 +97,15 @@ export async function collectTestCases(

// 提取用例数据
filterResult.forEach((filteredTestCase: string) => {
const [path, descAndName] = filteredTestCase.split("?");
const test = new TestCase(`${testcasePrefix}${path}?${descAndName}`, {});
let test;
if (fileMode) {
// 去掉前缀 projectPath,获取相对路径
const relativePath = path.relative(projPath, filteredTestCase);
test = new TestCase(`${testcasePrefix}${relativePath}`, {});
} else {
const [path, descAndName] = filteredTestCase.split("?");
test = new TestCase(`${testcasePrefix}${path}?${descAndName}`, {});
}
result.Tests.push(test);
});
} catch (error: unknown) {
Expand Down
60 changes: 56 additions & 4 deletions playwright/src/playwrightx/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -371,9 +371,12 @@ export function generateCommands(
const hash = createHash('md5').update(input).digest('hex').substring(0, 10);
const outputOption = `--output=test-results-${hash}`;

// 获取 grep 模式
// 检查是否为 fileMode
const fileMode = process.env.TESTSOLAR_TTP_FILEMODE == "1";

// 获取 grep 模式(在 fileMode 下不使用 grep)
let grepPattern = "";
if (testCases.length > 0) {
if (testCases.length > 0 && !fileMode) {
grepPattern = `--grep="${decodeURIComponent(testCases.join("|"))}"`;
}

Expand All @@ -382,14 +385,24 @@ export function generateCommands(
if (useEnvJsonFile) {
// 使用环境变量设置 JSON 输出
if (testCases.length === 0) {
command = `export PLAYWRIGHT_JSON_OUTPUT_NAME=${jsonName} && npx playwright test --reporter=json ${traceOption} ${workersOption} ${outputOption} ${extraArgs}`;
// 在 fileMode 下,即使没有具体测试用例,也要指定文件路径
if (fileMode) {
command = `export PLAYWRIGHT_JSON_OUTPUT_NAME=${jsonName} && npx playwright test ${casePath} --reporter=json ${traceOption} ${workersOption} ${outputOption} ${extraArgs}`;
} else {
command = `export PLAYWRIGHT_JSON_OUTPUT_NAME=${jsonName} && npx playwright test --reporter=json ${traceOption} ${workersOption} ${outputOption} ${extraArgs}`;
}
} else {
command = `export PLAYWRIGHT_JSON_OUTPUT_NAME=${jsonName} && npx playwright test ${casePath} ${grepPattern} --reporter=json ${traceOption} ${workersOption} ${outputOption} ${extraArgs}`;
}
} else {
// 使用原始的重定向方式
if (testCases.length === 0) {
command = `npx playwright test --reporter=json ${traceOption} ${workersOption} ${outputOption} ${extraArgs} > ${jsonName}`;
// 在 fileMode 下,即使没有具体测试用例,也要指定文件路径
if (fileMode) {
command = `npx playwright test ${casePath} --reporter=json ${traceOption} ${workersOption} ${outputOption} ${extraArgs} > ${jsonName}`;
} else {
command = `npx playwright test --reporter=json ${traceOption} ${workersOption} ${outputOption} ${extraArgs} > ${jsonName}`;
}
} else {
command = `npx playwright test ${casePath} ${grepPattern} --reporter=json ${traceOption} ${workersOption} ${outputOption} ${extraArgs} > ${jsonName}`;
}
Expand Down Expand Up @@ -871,6 +884,45 @@ export function parsePlaywrightReport(jsonData: string): LoadError[] {
}
}

// 扫描目录中的Playwright测试文件(排除node_modules)
export function scanPlaywrightTestFiles(directory: string): string[] {
const testCases: string[] = [];
// 匹配Playwright测试文件的模式
const testPattern = /test\(['"`]/;

function readDirRecursive(dir: string) {
// 如果路径中包含node_modules,则跳过该目录
if (dir.includes("node_modules")) {
return;
}

try {
const files = fs.readdirSync(dir);

files.forEach((file) => {
const fullPath = path.join(dir, file);
const stat = fs.statSync(fullPath);

if (stat.isDirectory()) {
readDirRecursive(fullPath);
} else if (file.endsWith(".spec.js") || file.endsWith(".spec.ts") ||
file.endsWith(".test.js") || file.endsWith(".test.ts") ||
file.endsWith(".e2e.js") || file.endsWith(".e2e.ts")) {
const content = fs.readFileSync(fullPath, "utf-8");
if (testPattern.test(content)) {
testCases.push(fullPath);
}
}
});
} catch (error) {
console.error(`Error reading directory ${dir}:`, error);
}
}

readDirRecursive(directory);
return testCases;
}

export async function createRunningTestResults(
path: string,
names: string[],
Expand Down
Loading