smartui-migration-tool
Version:
Enterprise-grade CLI tool for migrating visual testing platforms to LambdaTest SmartUI
478 lines • 20.1 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Scanner = void 0;
const fs_1 = require("fs");
const path_1 = __importDefault(require("path"));
const fast_glob_1 = __importDefault(require("fast-glob"));
const fast_xml_parser_1 = require("fast-xml-parser");
const types_1 = require("../types");
/**
* Scanner module for analyzing user's project structure and dependencies
* Responsible for detecting existing visual testing frameworks and configurations
*/
class Scanner {
constructor(projectPath) {
this.ignorePatterns = [
'node_modules/**',
'.git/**',
'dist/**',
'build/**',
'.next/**',
'coverage/**',
'.nyc_output/**',
'*.log',
'.DS_Store'
];
this.projectPath = projectPath;
}
/**
* Scans the project for existing visual testing frameworks
* @returns Promise<DetectionResult> - Analysis results
*/
async scan() {
try {
// Priority 1: Dependency Analysis (Most Reliable)
const dependencyResult = await this.analyzeDependencies();
if (dependencyResult) {
return dependencyResult;
}
// Priority 2: Configuration File Analysis (Fallback)
const configResult = await this.analyzeConfigurationFiles();
if (configResult) {
return configResult;
}
// No platform detected
throw new types_1.PlatformNotDetectedError();
}
catch (error) {
if (error instanceof types_1.PlatformNotDetectedError || error instanceof types_1.MultiplePlatformsDetectedError) {
throw error;
}
throw new Error(`Scanner error: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
/**
* Priority 1: Analyze dependencies in package.json, pom.xml, requirements.txt
*/
async analyzeDependencies() {
const detectedPlatforms = [];
// Check JavaScript/TypeScript projects
const jsResult = await this.analyzeJavaScriptDependencies();
if (jsResult)
detectedPlatforms.push(jsResult);
// Check Java projects
const javaResult = await this.analyzeJavaDependencies();
if (javaResult)
detectedPlatforms.push(javaResult);
// Check Python projects
const pythonResult = await this.analyzePythonDependencies();
if (pythonResult)
detectedPlatforms.push(pythonResult);
// Check C# projects
const csharpResult = await this.analyzeCSharpDependencies();
if (csharpResult)
detectedPlatforms.push(csharpResult);
if (detectedPlatforms.length === 0) {
return null;
}
if (detectedPlatforms.length > 1) {
throw new types_1.MultiplePlatformsDetectedError();
}
return detectedPlatforms[0];
}
/**
* Analyze JavaScript/TypeScript dependencies in package.json
*/
async analyzeJavaScriptDependencies() {
const packageJsonPath = path_1.default.join(this.projectPath, 'package.json');
try {
const packageJsonContent = await fs_1.promises.readFile(packageJsonPath, 'utf-8');
const packageJson = JSON.parse(packageJsonContent);
const dependencies = { ...packageJson.dependencies, ...packageJson.devDependencies };
// Check for Percy dependencies
if (dependencies['@percy/cypress']) {
return await this.createDetectionResult('Percy', 'Cypress', 'JavaScript/TypeScript');
}
if (dependencies['@percy/playwright']) {
return await this.createDetectionResult('Percy', 'Playwright', 'JavaScript/TypeScript');
}
if (dependencies['@percy/storybook']) {
const hasStorybookDir = await this.hasDirectory('.storybook');
if (hasStorybookDir) {
return await this.createDetectionResult('Percy', 'Storybook', 'JavaScript/TypeScript');
}
}
// Check for Applitools dependencies
if (dependencies['@applitools/eyes-cypress']) {
return await this.createDetectionResult('Applitools', 'Cypress', 'JavaScript/TypeScript');
}
if (dependencies['@applitools/eyes-playwright']) {
return await this.createDetectionResult('Applitools', 'Playwright', 'JavaScript/TypeScript');
}
// Check for Sauce Labs dependencies
if (dependencies['@saucelabs/cypress-visual-plugin']) {
return await this.createDetectionResult('Sauce Labs Visual', 'Cypress', 'JavaScript/TypeScript');
}
}
catch (error) {
// package.json doesn't exist or is invalid
}
return null;
}
/**
* Analyze Java dependencies in pom.xml
*/
async analyzeJavaDependencies() {
const pomXmlPath = path_1.default.join(this.projectPath, 'pom.xml');
try {
const pomXmlContent = await fs_1.promises.readFile(pomXmlPath, 'utf-8');
const parser = new fast_xml_parser_1.XMLParser();
const pomXml = parser.parse(pomXmlContent);
// Check for Applitools dependencies
if (this.hasMavenDependency(pomXml, 'eyes-selenium-java5')) {
return await this.createDetectionResult('Applitools', 'Selenium', 'Java');
}
// Check for Sauce Labs dependencies
if (this.hasMavenDependency(pomXml, 'java-client', 'com.saucelabs.visual')) {
return await this.createDetectionResult('Sauce Labs Visual', 'Selenium', 'Java');
}
}
catch (error) {
// pom.xml doesn't exist or is invalid
}
return null;
}
/**
* Analyze Python dependencies in requirements.txt
*/
async analyzePythonDependencies() {
const requirementsPath = path_1.default.join(this.projectPath, 'requirements.txt');
try {
const requirementsContent = await fs_1.promises.readFile(requirementsPath, 'utf-8');
const lines = requirementsContent.split('\n').map(line => line.trim());
// Check for Sauce Labs Robot Framework
if (lines.some(line => line.includes('saucelabs_visual'))) {
const hasRobotFiles = await this.hasFiles('**/*.robot');
if (hasRobotFiles) {
return await this.createDetectionResult('Sauce Labs Visual', 'Robot Framework', 'Python');
}
}
}
catch (error) {
// requirements.txt doesn't exist
}
return null;
}
/**
* Analyze C# dependencies in .csproj files
*/
async analyzeCSharpDependencies() {
const csprojFiles = await (0, fast_glob_1.default)(['**/*.csproj'], {
cwd: this.projectPath,
ignore: this.ignorePatterns
});
if (csprojFiles.length === 0) {
return null;
}
for (const csprojFile of csprojFiles) {
try {
const csprojPath = path_1.default.join(this.projectPath, csprojFile);
const csprojContent = await fs_1.promises.readFile(csprojPath, 'utf-8');
// Parse XML content
const parser = new fast_xml_parser_1.XMLParser({
ignoreAttributes: false,
attributeNamePrefix: '@_'
});
const csprojXml = parser.parse(csprojContent);
// Extract PackageReference elements
const packageReferences = this.extractPackageReferences(csprojXml);
// Check for Applitools dependencies
const hasApplitools = packageReferences.some(pkg => (pkg.include || pkg['@_Include']) && ((pkg.include || pkg['@_Include']).includes('Applitools') ||
(pkg.include || pkg['@_Include']).includes('Eyes') ||
(pkg.include || pkg['@_Include']).includes('Applitools.Eyes')));
if (hasApplitools) {
// Determine framework based on other dependencies
let framework = 'Selenium';
if (packageReferences.some(pkg => (pkg.include || pkg['@_Include']) && ((pkg.include || pkg['@_Include']).includes('Microsoft.Playwright') ||
(pkg.include || pkg['@_Include']).includes('Eyes.Playwright') ||
(pkg.include || pkg['@_Include']).includes('Playwright')))) {
framework = 'Playwright';
}
else if (packageReferences.some(pkg => (pkg.include || pkg['@_Include']) && (pkg.include || pkg['@_Include']).includes('WebDriver'))) {
framework = 'Selenium';
}
return await this.createDetectionResult('Applitools', framework, 'C#');
}
// Check for Percy dependencies
if (packageReferences.some(pkg => (pkg.include || pkg['@_Include']) && ((pkg.include || pkg['@_Include']).includes('Percy') ||
(pkg.include || pkg['@_Include']).includes('percy')))) {
let framework = 'Selenium';
if (packageReferences.some(pkg => (pkg.include || pkg['@_Include']) && ((pkg.include || pkg['@_Include']).includes('Microsoft.Playwright') ||
(pkg.include || pkg['@_Include']).includes('Eyes.Playwright') ||
(pkg.include || pkg['@_Include']).includes('Playwright')))) {
framework = 'Playwright';
}
return await this.createDetectionResult('Percy', framework, 'C#');
}
// Check for Sauce Labs dependencies
if (packageReferences.some(pkg => (pkg.include || pkg['@_Include']) && ((pkg.include || pkg['@_Include']).includes('SauceLabs') ||
(pkg.include || pkg['@_Include']).includes('Sauce')))) {
return await this.createDetectionResult('Sauce Labs Visual', 'Selenium', 'C#');
}
}
catch (error) {
// Skip invalid .csproj files
continue;
}
}
return null;
}
/**
* Extract PackageReference elements from .csproj XML
*/
extractPackageReferences(csprojXml) {
const references = [];
// Handle different .csproj structures
if (csprojXml.Project && csprojXml.Project.ItemGroup) {
const itemGroups = Array.isArray(csprojXml.Project.ItemGroup)
? csprojXml.Project.ItemGroup
: [csprojXml.Project.ItemGroup];
for (const itemGroup of itemGroups) {
if (itemGroup.PackageReference) {
const packageRefs = Array.isArray(itemGroup.PackageReference)
? itemGroup.PackageReference
: [itemGroup.PackageReference];
references.push(...packageRefs);
}
}
}
return references;
}
/**
* Priority 2: Analyze configuration files as fallback
*/
async analyzeConfigurationFiles() {
// Check for Percy config files
const percyConfigs = await (0, fast_glob_1.default)(['.percy.yml', '.percy.js', 'percy.config.js'], {
cwd: this.projectPath,
ignore: this.ignorePatterns
});
if (percyConfigs.length > 0) {
// Try to determine framework from config or project structure
const framework = await this.detectFrameworkFromStructure();
return await this.createDetectionResult('Percy', framework, 'JavaScript/TypeScript');
}
// Check for Applitools config files
const applitoolsConfigs = await (0, fast_glob_1.default)(['applitools.config.js', 'applitools.config.ts'], {
cwd: this.projectPath,
ignore: this.ignorePatterns
});
if (applitoolsConfigs.length > 0) {
const framework = await this.detectFrameworkFromStructure();
return await this.createDetectionResult('Applitools', framework, 'JavaScript/TypeScript');
}
// Check for Sauce Labs config files
const sauceConfigs = await (0, fast_glob_1.default)(['saucectl.yml', 'sauce.config.js'], {
cwd: this.projectPath,
ignore: this.ignorePatterns
});
if (sauceConfigs.length > 0) {
const framework = await this.detectFrameworkFromStructure();
return await this.createDetectionResult('Sauce Labs Visual', framework, 'JavaScript/TypeScript');
}
return null;
}
/**
* Detect framework from project structure
*/
async detectFrameworkFromStructure() {
// Check for Cypress
const cypressFiles = await (0, fast_glob_1.default)(['cypress/**/*.js', 'cypress/**/*.ts', 'cypress.json'], {
cwd: this.projectPath,
ignore: this.ignorePatterns
});
if (cypressFiles.length > 0)
return 'Cypress';
// Check for Playwright
const playwrightFiles = await (0, fast_glob_1.default)(['playwright.config.js', 'playwright.config.ts', 'tests/**/*.spec.js', 'tests/**/*.spec.ts'], {
cwd: this.projectPath,
ignore: this.ignorePatterns
});
if (playwrightFiles.length > 0)
return 'Playwright';
// Check for Storybook
const storybookDir = await this.hasDirectory('.storybook');
if (storybookDir)
return 'Storybook';
// Default to Selenium for Java projects
return 'Selenium';
}
/**
* Create a DetectionResult with file paths
*/
async createDetectionResult(platform, framework, language) {
const files = await this.collectFilePaths(platform, framework);
// Determine test type based on framework
let testType = 'e2e';
if (framework === 'Storybook') {
testType = 'storybook';
}
else if (framework === 'Appium') {
testType = 'appium';
}
return {
platform,
framework,
language,
testType,
files,
evidence: {
platform: {
source: 'legacy-scanner',
match: 'legacy-detection'
},
framework: {
files: files.source,
signatures: []
}
}
};
}
/**
* Collect relevant file paths based on platform and framework
*/
async collectFilePaths(platform, framework) {
const configPatterns = this.getConfigPatterns(platform);
const sourcePatterns = this.getSourcePatterns(framework);
const ciPatterns = this.getCIPatterns();
const packageManagerPatterns = this.getPackageManagerPatterns();
const [config, source, ci, packageManager] = await Promise.all([
(0, fast_glob_1.default)(configPatterns, { cwd: this.projectPath, ignore: this.ignorePatterns }),
(0, fast_glob_1.default)(sourcePatterns, { cwd: this.projectPath, ignore: this.ignorePatterns }),
(0, fast_glob_1.default)(ciPatterns, { cwd: this.projectPath, ignore: this.ignorePatterns }),
(0, fast_glob_1.default)(packageManagerPatterns, { cwd: this.projectPath, ignore: this.ignorePatterns })
]);
return {
config,
source,
ci,
packageManager
};
}
/**
* Get configuration file patterns based on platform
*/
getConfigPatterns(platform) {
switch (platform) {
case 'Percy':
return ['.percy.yml', '.percy.js', 'percy.config.js', 'percy.config.ts'];
case 'Applitools':
return ['applitools.config.js', 'applitools.config.ts'];
case 'Sauce Labs Visual':
return ['saucectl.yml', 'sauce.config.js', 'sauce.config.ts'];
default:
return [];
}
}
/**
* Get source file patterns based on framework
*/
getSourcePatterns(framework) {
switch (framework) {
case 'Cypress':
return ['cypress/**/*.js', 'cypress/**/*.ts', 'cypress/**/*.spec.js', 'cypress/**/*.spec.ts'];
case 'Playwright':
return ['tests/**/*.js', 'tests/**/*.ts', 'tests/**/*.spec.js', 'tests/**/*.spec.ts', 'e2e/**/*.js', 'e2e/**/*.ts', 'tests/**/*.cs', 'e2e/**/*.cs', '**/*Test.cs', '**/*Tests.cs'];
case 'Selenium':
return ['src/**/*.java', 'test/**/*.java', '**/*Test.java', '**/*Tests.java', 'src/**/*.cs', 'test/**/*.cs', '**/*Test.cs', '**/*Tests.cs'];
case 'Storybook':
return ['.storybook/**/*.js', '.storybook/**/*.ts', 'stories/**/*.js', 'stories/**/*.ts'];
case 'Robot Framework':
return ['**/*.robot'];
default:
return [];
}
}
/**
* Get CI/CD file patterns
*/
getCIPatterns() {
return [
'.github/workflows/**/*.yml',
'.github/workflows/**/*.yaml',
'.gitlab-ci.yml',
'Jenkinsfile',
'azure-pipelines.yml',
'.circleci/config.yml'
];
}
/**
* Get package manager file patterns
*/
getPackageManagerPatterns() {
return ['package.json', 'package-lock.json', 'yarn.lock', 'pnpm-lock.yaml', 'pom.xml', 'requirements.txt'];
}
/**
* Check if a directory exists
*/
async hasDirectory(dirName) {
try {
const dirPath = path_1.default.join(this.projectPath, dirName);
const stats = await fs_1.promises.stat(dirPath);
return stats.isDirectory();
}
catch {
return false;
}
}
/**
* Check if files matching pattern exist
*/
async hasFiles(pattern) {
const files = await (0, fast_glob_1.default)([pattern], {
cwd: this.projectPath,
ignore: this.ignorePatterns
});
return files.length > 0;
}
/**
* Check if Maven dependency exists in pom.xml
*/
hasMavenDependency(pomXml, artifactId, groupId) {
const dependencies = this.extractDependencies(pomXml);
return dependencies.some((dep) => {
const matchesArtifact = dep.artifactId === artifactId;
const matchesGroup = !groupId || dep.groupId === groupId;
return matchesArtifact && matchesGroup;
});
}
/**
* Extract dependencies from pom.xml structure
*/
extractDependencies(pomXml) {
const dependencies = [];
const extractFromSection = (section) => {
if (section && Array.isArray(section)) {
section.forEach((dep) => {
if (dep.artifactId) {
dependencies.push(dep);
}
});
}
};
// Check project.dependencies.dependency
if (pomXml.project?.dependencies?.dependency) {
extractFromSection(pomXml.project.dependencies.dependency);
}
// Check project.dependencyManagement.dependencies.dependency
if (pomXml.project?.dependencyManagement?.dependencies?.dependency) {
extractFromSection(pomXml.project.dependencyManagement.dependencies.dependency);
}
return dependencies;
}
}
exports.Scanner = Scanner;
//# sourceMappingURL=ScannerNew.js.map