tnicola / cypress-parallel

Reduce up to 40% your Cypress suite execution time parallelizing the test run on the same machine.
MIT License
560 stars 117 forks source link

need to run specific test specs on respective thread. #177

Open mukeshnagpal90 opened 7 months ago

mukeshnagpal90 commented 7 months ago

I have two folders containing spec files: folder1 having specs with user1 folder2 having specs with user2 i want to run these specs on two threads where thread1 run folder1 specs i.e. user1 specs and thread2 specs should run on thread2.

Please suggest and provide me solution.

mukeshnagpal90 commented 7 months ago

Hi Team,

can anyone help me with the above task?

mukeshnagpal90 commented 7 months ago

Hi Team,

Please help me in resolving the above issue.

ketteq-neon commented 7 months ago

Hi Team,

Please have a look into the query.

khaja38 commented 6 months ago

Hey hi, if you goto node_module/cypress-parallel/test-suites.js you will find a method/function distributeTestsByWeight(testSuitePaths) which will guide you.

  1. It will sort the weights json as per weights in descending order
  2. Then it will initiate the threads object array with the size we've mentioned in the cmd
  3. Later, it will assign the threads with the spec with highest weight
  4. if threads are filled, it will sort the threads in ascending order according to total weight assigned to thread, now it will repeat the step 3, 4 iteratively.

For eg, if you have 7 specs with weights 14, 12, 10, 8, 6, 4, 2 and threads = 4

t1 = [14], t2 = [12], t3 =[10], t4 = [8]
in later stages...
t1 = [8, 6], t2 = [10], t3 =[12], t4=[14]
t1 = [10, 4], t2 = [8, 6], t3=[12], t4=[14]
t1=[12, 2], t2=[10, 4], t3=[8, 6], t4=[14]
The above will be the final thread list format.

So, play with it a bit, you will be able to understand.

MosesWescombe commented 2 months ago

I had the same issue. I made a node.js script that will combine tests in folders into files. I never managed to get the tests to pass when running in parallel due to complex state management, but running it this way did manage to get the folders to act like 'threads'.

My scripts looked like:

"combine": "rm -rf ./cypress/e2e/execution && node cypress/combine-threads.js",
"cy:parallel": "yarn run combine && cypress-parallel -s cypress:run:chrome -t 4 -m false -n ../../node_modules/cypress-multi-reporters -d './cypress/e2e/execution' -a '\"--config baseUrl=... --env REACT_API_URL=..."' && rm -rf ./cypress/e2e/execution"

Where -d is defining where my .cy.ts files are. The script compiles them into this location. Just make sure all of the contents of each test file are contained within describe() as the files are simply pasted together.

var path = require('path');

var e2eDir = './cypress/e2e';
var executionDir = './cypress/e2e/execution';

// Helper function to adjust import paths
function adjustImportPaths(content, originalDirPath) {
    const importRegex = /^import\s+{([^}]+)}\s+from\s+['"](.+?)['"];$/gm;
    const importsMap = new Map();

    content = content.replace(importRegex, (match, imports, from) => {
        // Check if the import is from a package or a local file
        const isPackageImport = from.startsWith('.') ? false : true;

        let normalizedFrom = from;
        if (!isPackageImport) {
            // It's a local import, adjust the path
            normalizedFrom = path.relative(executionDir, path.resolve(originalDirPath, from)).replace(/\\/g, '/');
        }

        const importItems = imports
            .split(',')
            .map((item) => item.trim())
            .filter((item) => item);

        if (importsMap.has(normalizedFrom)) {
            const existingImports = importsMap.get(normalizedFrom);
            importItems.forEach((item) => {
                if (!existingImports.includes(item)) {
                    existingImports.push(item);
                }
            });
        } else {
            importsMap.set(normalizedFrom, importItems);
        }

        return ''; // Remove the original import statement
    });

    // Reconstruct combined import statements at the beginning of the content
    const combinedImports = Array.from(
        importsMap,
        ([from, imports]) => `import { ${imports.join(', ')} } from '${from}';`
    ).join('\n');
    return combinedImports + '\n' + content;
}

// Function to combine files from a single directory
function combineFilesFromDirectory(dirPath, targetFileName) {
    let combinedContent = '';
    const importStatements = new Set();
    const files = fs.readdirSync(dirPath);

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

        if (stat.isFile() && filePath.endsWith('.cy.ts')) {
            let content = fs.readFileSync(filePath, 'utf8');

            // Adjust the import paths in the content
            content = adjustImportPaths(content, dirPath);

            // Extract and remove import statements from content
            content = content.replace(/^import .+ from .+;$/gm, (importStatement) => {
                importStatements.add(importStatement);
                return '';
            });

            combinedContent += `\n${content}`;
        }
    });

    // Prepend combined import statements to the top
    combinedContent = Array.from(importStatements).join('\n') + combinedContent;

    if (!fs.existsSync(executionDir)) {
        fs.mkdirSync(executionDir, { recursive: true });
    }

    fs.writeFileSync(path.join(executionDir, targetFileName), combinedContent);
}

// Function to iterate through each subdirectory of e2e and combine files
function processDirectories(parentDirectory) {
    const directories = fs
        .readdirSync(parentDirectory, { withFileTypes: true })
        .filter((dirent) => dirent.isDirectory())
        .map((dirent) => dirent.name);

    directories.forEach((dir) => {
        const dirPath = path.join(parentDirectory, dir);
        const targetFileName = `${dir}.cy.ts`;
        combineFilesFromDirectory(dirPath, targetFileName);
        console.log(`Combined file created for ${dirPath}: ${targetFileName}`);
    });
}

processDirectories(e2eDir);