2024-08-04 08:28:19 +08:00
|
|
|
import assert from 'node:assert';
|
|
|
|
|
import { randomUUID } from 'node:crypto';
|
|
|
|
|
import {
|
|
|
|
|
copyFileSync,
|
|
|
|
|
existsSync,
|
|
|
|
|
mkdirSync,
|
|
|
|
|
readFileSync,
|
|
|
|
|
writeFileSync,
|
|
|
|
|
} from 'node:fs';
|
|
|
|
|
import { tmpdir } from 'node:os';
|
2024-08-22 20:56:34 +08:00
|
|
|
import path, { basename, dirname, join } from 'node:path';
|
2024-08-15 17:59:43 +08:00
|
|
|
import type { Rect, ReportDumpWithAttributes } from './types';
|
2024-07-23 16:25:11 +08:00
|
|
|
|
|
|
|
|
interface PkgInfo {
|
|
|
|
|
name: string;
|
|
|
|
|
version: string;
|
2024-08-15 17:59:43 +08:00
|
|
|
dir: string;
|
2024-07-23 16:25:11 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let pkg: PkgInfo | undefined;
|
|
|
|
|
export function getPkgInfo(): PkgInfo {
|
|
|
|
|
if (pkg) {
|
|
|
|
|
return pkg;
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-15 17:59:43 +08:00
|
|
|
const pkgDir = findNearestPackageJson(__dirname);
|
|
|
|
|
assert(pkgDir, 'package.json not found');
|
|
|
|
|
const pkgJsonFile = join(pkgDir, 'package.json');
|
2024-07-23 16:25:11 +08:00
|
|
|
|
|
|
|
|
if (pkgJsonFile) {
|
|
|
|
|
const { name, version } = JSON.parse(readFileSync(pkgJsonFile, 'utf-8'));
|
2024-08-15 17:59:43 +08:00
|
|
|
pkg = { name, version, dir: pkgDir };
|
2024-07-23 16:25:11 +08:00
|
|
|
return pkg;
|
|
|
|
|
}
|
2024-08-04 08:28:19 +08:00
|
|
|
return {
|
|
|
|
|
name: 'midscene-unknown-page-name',
|
|
|
|
|
version: '0.0.0',
|
2024-08-15 17:59:43 +08:00
|
|
|
dir: pkgDir,
|
2024-08-04 08:28:19 +08:00
|
|
|
};
|
2024-07-23 16:25:11 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let logDir = join(process.cwd(), './midscene_run/');
|
|
|
|
|
let logEnvReady = false;
|
|
|
|
|
export const insightDumpFileExt = 'insight-dump.json';
|
2024-07-25 13:40:46 +08:00
|
|
|
export const groupedActionDumpFileExt = 'web-dump.json';
|
2024-07-23 16:25:11 +08:00
|
|
|
|
2024-08-15 17:59:43 +08:00
|
|
|
export function getLogDir() {
|
2024-07-23 16:25:11 +08:00
|
|
|
return logDir;
|
|
|
|
|
}
|
2024-08-01 15:46:40 +08:00
|
|
|
|
2024-08-15 17:59:43 +08:00
|
|
|
export function setLogDir(dir: string) {
|
2024-07-23 16:25:11 +08:00
|
|
|
logDir = dir;
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-15 17:59:43 +08:00
|
|
|
export function getLogDirByType(type: 'dump' | 'cache' | 'report') {
|
|
|
|
|
const dir = join(getLogDir(), type);
|
|
|
|
|
if (!existsSync(dir)) {
|
|
|
|
|
mkdirSync(dir, { recursive: true });
|
|
|
|
|
}
|
|
|
|
|
return dir;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function writeDumpReport(
|
|
|
|
|
fileName: string,
|
|
|
|
|
dumpData: string | ReportDumpWithAttributes[],
|
|
|
|
|
) {
|
|
|
|
|
const { dir } = getPkgInfo();
|
|
|
|
|
const reportTplPath = join(dir, './report/index.html');
|
|
|
|
|
existsSync(reportTplPath) ||
|
|
|
|
|
assert(false, `report template not found: ${reportTplPath}`);
|
|
|
|
|
const reportPath = join(getLogDirByType('report'), `${fileName}.html`);
|
|
|
|
|
const tpl = readFileSync(reportTplPath, 'utf-8');
|
|
|
|
|
let reportContent: string;
|
2024-08-31 08:17:50 +08:00
|
|
|
if (
|
|
|
|
|
(Array.isArray(dumpData) && dumpData.length === 0) ||
|
|
|
|
|
typeof dumpData === 'undefined'
|
|
|
|
|
) {
|
|
|
|
|
reportContent = tpl.replace(
|
|
|
|
|
'{{dump}}',
|
|
|
|
|
'<script type="midscene_web_dump" type="application/json"></script>',
|
|
|
|
|
);
|
|
|
|
|
} else if (typeof dumpData === 'string') {
|
2024-08-15 17:59:43 +08:00
|
|
|
reportContent = tpl.replace(
|
|
|
|
|
'{{dump}}',
|
|
|
|
|
`<script type="midscene_web_dump" type="application/json">${dumpData}</script>`,
|
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
const dumps = dumpData.map(({ dumpString, attributes }) => {
|
|
|
|
|
const attributesArr = Object.keys(attributes || {}).map((key) => {
|
|
|
|
|
return `${key}="${encodeURIComponent(attributes![key])}"`;
|
|
|
|
|
});
|
|
|
|
|
return `<script type="midscene_web_dump" type="application/json" ${attributesArr.join(' ')}>${dumpString}</script>`;
|
|
|
|
|
});
|
|
|
|
|
reportContent = tpl.replace('{{dump}}', dumps.join('\n'));
|
|
|
|
|
}
|
|
|
|
|
writeFileSync(reportPath, reportContent);
|
|
|
|
|
|
|
|
|
|
return reportPath;
|
2024-08-01 15:46:40 +08:00
|
|
|
}
|
|
|
|
|
|
2024-08-15 17:59:43 +08:00
|
|
|
export function writeLogFile(opts: {
|
2024-08-01 15:46:40 +08:00
|
|
|
fileName: string;
|
|
|
|
|
fileExt: string;
|
|
|
|
|
fileContent: string;
|
2024-08-15 17:59:43 +08:00
|
|
|
type: 'dump' | 'cache' | 'report';
|
|
|
|
|
generateReport?: boolean;
|
2024-08-01 15:46:40 +08:00
|
|
|
}) {
|
|
|
|
|
const { fileName, fileExt, fileContent, type = 'dump' } = opts;
|
2024-08-15 17:59:43 +08:00
|
|
|
const targetDir = getLogDirByType(type);
|
2024-07-23 16:25:11 +08:00
|
|
|
// Ensure directory exists
|
|
|
|
|
if (!logEnvReady) {
|
2024-08-01 15:46:40 +08:00
|
|
|
assert(targetDir, 'logDir should be set before writing dump file');
|
2024-07-23 16:25:11 +08:00
|
|
|
|
|
|
|
|
// gitIgnore in the parent directory
|
2024-08-01 15:46:40 +08:00
|
|
|
const gitIgnorePath = join(targetDir, '../../.gitignore');
|
2024-07-23 16:25:11 +08:00
|
|
|
let gitIgnoreContent = '';
|
|
|
|
|
if (existsSync(gitIgnorePath)) {
|
|
|
|
|
gitIgnoreContent = readFileSync(gitIgnorePath, 'utf-8');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ignore the log folder
|
|
|
|
|
const logDirName = basename(logDir);
|
|
|
|
|
if (!gitIgnoreContent.includes(`${logDirName}/`)) {
|
|
|
|
|
writeFileSync(
|
|
|
|
|
gitIgnorePath,
|
2024-08-08 15:39:07 +08:00
|
|
|
`${gitIgnoreContent}\n# Midscene.js dump files\n${logDirName}/report\n${logDirName}/dump\n`,
|
2024-07-23 16:25:11 +08:00
|
|
|
'utf-8',
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
logEnvReady = true;
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-01 15:46:40 +08:00
|
|
|
const filePath = join(targetDir, `${fileName}.${fileExt}`);
|
2024-08-22 20:56:34 +08:00
|
|
|
|
|
|
|
|
const outputResourceDir = dirname(filePath);
|
|
|
|
|
if (!existsSync(outputResourceDir)) {
|
|
|
|
|
mkdirSync(outputResourceDir, { recursive: true });
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-23 16:25:11 +08:00
|
|
|
writeFileSync(filePath, fileContent);
|
2024-08-01 15:46:40 +08:00
|
|
|
|
2024-08-15 17:59:43 +08:00
|
|
|
if (opts?.generateReport) {
|
|
|
|
|
return writeDumpReport(fileName, fileContent);
|
2024-08-01 15:46:40 +08:00
|
|
|
}
|
2024-07-23 16:25:11 +08:00
|
|
|
|
|
|
|
|
return filePath;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function getTmpDir() {
|
|
|
|
|
const path = join(tmpdir(), getPkgInfo().name);
|
|
|
|
|
mkdirSync(path, { recursive: true });
|
|
|
|
|
return path;
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-28 08:49:57 +08:00
|
|
|
export function getTmpFile(fileExtWithoutDot: string) {
|
|
|
|
|
const filename = `${randomUUID()}.${fileExtWithoutDot}`;
|
2024-07-23 16:25:11 +08:00
|
|
|
return join(getTmpDir(), filename);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function overlapped(container: Rect, target: Rect) {
|
|
|
|
|
// container and the target have some part overlapped
|
|
|
|
|
return (
|
|
|
|
|
container.left < target.left + target.width &&
|
|
|
|
|
container.left + container.width > target.left &&
|
|
|
|
|
container.top < target.top + target.height &&
|
|
|
|
|
container.top + container.height > target.top
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function sleep(ms: number) {
|
|
|
|
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const commonScreenshotParam = { type: 'jpeg', quality: 75 } as any;
|
2024-08-08 15:38:26 +08:00
|
|
|
|
|
|
|
|
export function replacerForPageObject(key: string, value: any) {
|
|
|
|
|
if (value && value.constructor?.name === 'Page') {
|
|
|
|
|
return '[Page object]';
|
|
|
|
|
}
|
|
|
|
|
if (value && value.constructor?.name === 'Browser') {
|
|
|
|
|
return '[Browser object]';
|
|
|
|
|
}
|
|
|
|
|
return value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function stringifyDumpData(data: any, indents?: number) {
|
|
|
|
|
return JSON.stringify(data, replacerForPageObject, indents);
|
|
|
|
|
}
|
2024-08-15 17:59:43 +08:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Find the nearest package.json file recursively
|
|
|
|
|
* @param {string} dir - Home directory
|
|
|
|
|
* @returns {string|null} - The most recent package.json file path or null
|
|
|
|
|
*/
|
|
|
|
|
export function findNearestPackageJson(dir: string): string | null {
|
|
|
|
|
const packageJsonPath = path.join(dir, 'package.json');
|
|
|
|
|
|
|
|
|
|
if (existsSync(packageJsonPath)) {
|
|
|
|
|
return dir;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const parentDir = path.dirname(dir);
|
|
|
|
|
|
|
|
|
|
// Return null if the root directory has been reached
|
|
|
|
|
if (parentDir === dir) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return findNearestPackageJson(parentDir);
|
|
|
|
|
}
|