├── shot └── banners.jpg ├── .gitignore ├── src ├── utils.js ├── converter.js └── scriptGenerator.js ├── .github └── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── package.json ├── LICENSE ├── index.js ├── README.md ├── CONTRIBUTING.md └── CODE_OF_CONDUCT.md /shot/banners.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bhadmus/postwright/HEAD/shot/banners.jpg -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .DS_Store 3 | test.json 4 | test2.json 5 | .vscode/ 6 | .idea/ -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | function sanitizeFileName(fileName) { 2 | return fileName.replace(/[^a-z0-9]/gi, "_").toLowerCase(); 3 | } 4 | 5 | module.exports = { sanitizeFileName }; 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "postwright", 3 | "version": "3.0.0", 4 | "description": "Convert Postman Collections to Playwright scripts", 5 | "main": "index.js", 6 | "bin": { 7 | "postwright": "./index.js" 8 | }, 9 | "scripts": { 10 | "test": "echo \"Error: no test specified\" && exit 1" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/bhadmus/postman-playwright.git" 15 | }, 16 | "keywords": [], 17 | "author": "", 18 | "license": "ISC", 19 | "bugs": { 20 | "url": "https://github.com/bhadmus/postman-playwright/issues" 21 | }, 22 | "homepage": "https://github.com/bhadmus/postman-playwright#readme", 23 | "dependencies": { 24 | "@playwright/test": "^1.45.3", 25 | "acorn": "^8.12.1", 26 | "acorn-walk": "^8.3.4", 27 | "commander": "12.1.0", 28 | "path": "0.12.7", 29 | "playwright": "^1.45.3" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 sanguine 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const { program } = require('commander'); 3 | const path = require('path'); 4 | const { convertPostmanToPlaywright } = require('./src/converter.js'); 5 | const packageJson = require('./package.json'); 6 | 7 | // Define the main options 8 | program 9 | .version(packageJson.version) 10 | .description('Convert Postman collections to Playwright test scripts') 11 | .option('-c, --convert ', 'Convert Postman collection to Playwright scripts') 12 | .option('-o, --output ', 'Output directory for Playwright scripts', process.cwd()) 13 | .option('-f, --format ', 'Format of the output scripts (js, ts)', 'js') // Added --format option 14 | .action((options) => { 15 | if (options.convert) { 16 | const postmanCollectionPath = path.resolve(options.convert); 17 | const outputDir = path.resolve(options.output); 18 | const format = options.format; // Get the format (js or ts) 19 | 20 | // Call the conversion function with the additional format parameter 21 | convertPostmanToPlaywright(postmanCollectionPath, outputDir, format); 22 | } 23 | }); 24 | 25 | // Define the 'convert' command for more granular control 26 | program 27 | .command('convert ') 28 | .option('-o, --output ', 'Output directory for Playwright scripts', process.cwd()) 29 | .option('-f, --format ', 'Format of the output scripts (js, ts)', 'js') // Added --format option here as well 30 | .action((postmanCollectionPath, options) => { 31 | const outputDir = path.resolve(options.output); 32 | const format = options.format; // Get the format (js or ts) 33 | 34 | // Call the conversion function with the additional format parameter 35 | convertPostmanToPlaywright(postmanCollectionPath, outputDir, format); 36 | }); 37 | 38 | // Parse the command line arguments 39 | program.parse(process.argv); 40 | 41 | // If no arguments are provided, show help 42 | if (!process.argv.slice(2).length) { 43 | program.outputHelp(); 44 | } 45 | -------------------------------------------------------------------------------- /src/converter.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs").promises; 2 | const path = require("path"); 3 | const { processCollection } = require("./scriptGenerator.js"); 4 | 5 | async function convertPostmanToPlaywright( 6 | postmanCollectionPath, 7 | outputDir = path.join(process.cwd(), 'test'), 8 | format = "js"// Default to 'js' (JavaScript) 9 | ) { 10 | try { 11 | const postmanCollection = JSON.parse( 12 | await fs.readFile(postmanCollectionPath, "utf-8") 13 | ); 14 | 15 | await fs.mkdir(outputDir, { recursive: true }); 16 | 17 | let itemCounter = 0; 18 | await processCollection(postmanCollection, outputDir, format); 19 | await createPackageJson(outputDir); 20 | 21 | // Create tsconfig.json if TypeScript is chosen 22 | if (format === "ts") { 23 | await createTsConfig(outputDir); 24 | } 25 | 26 | console.log( 27 | `Conversion complete. Playwright scripts have been generated in ${outputDir}` 28 | ); 29 | } catch (error) { 30 | console.error("Error converting Postman collection:", error.message); 31 | process.exit(1); 32 | } 33 | } 34 | 35 | async function createTsConfig(outputDir) { 36 | const tsConfigContent = { 37 | compilerOptions: { 38 | target: "ES6", 39 | module: "CommonJS", 40 | strict: true, 41 | esModuleInterop: true, 42 | skipLibCheck: true, 43 | forceConsistentCasingInFileNames: true, 44 | }, 45 | include: ["**/*.ts"], 46 | }; 47 | 48 | const tsConfigPath = path.join(outputDir, "tsconfig.json"); 49 | 50 | await fs.writeFile( 51 | tsConfigPath, 52 | JSON.stringify(tsConfigContent, null, 2), 53 | "utf8" 54 | ); 55 | console.log("tsconfig.json created."); 56 | } 57 | 58 | async function createPackageJson(outputDir){ 59 | const packageJsonPath = path.join(outputDir, 'package.json'); 60 | 61 | const packageJsonContent = { 62 | "name": "postman-to-playwright", 63 | "version": "1.0.0", 64 | "description": "Converts postman collection to playwright scripts", 65 | "main": "index.js", 66 | "scripts": { 67 | "test": "npx playwright test" 68 | }, 69 | "devDependencies": { 70 | "playwright": "latest", 71 | "@playwright/test": "latest" 72 | } 73 | 74 | } 75 | 76 | await fs.writeFile( 77 | packageJsonPath, 78 | JSON.stringify(packageJsonContent, null, 2), 79 | "utf8" 80 | ); 81 | console.log("package.json created."); 82 | } 83 | 84 | module.exports = { convertPostmanToPlaywright }; 85 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![image](shot/banners.jpg) 2 | 3 | - [Installation](#installation) 4 | - [Usage](#usage) 5 | - [What does it do presently?](#what-does-it-do-presently) 6 | 7 | ## Convert your postman collections to playwright scripts. 8 | --- 9 | 10 | This is the pilot release of this plugin. It basically converts your large postman collections into playwright scripts 11 | 12 | ### Installation 13 | - Run npm install -g postwright 14 | 15 | ### Usage 16 | 17 | - [ ] **Default Javascript** 18 | 19 | To convert to a javascript extension, do the following: 20 | 21 | - Create a folder and navigate into it then run any of the following: 22 | 23 | - `postwright -c ` 24 | - `postwright --convert ` 25 | - `postwright convert ` 26 | 27 | - You can alternatively run any of the following from any location in your terminal. 28 | - `postwright -c -o ` 29 | - `postwright --convert --output ` 30 | - `postwright -c -o -f js` 31 | - `postwright --convert --output --format js` 32 | 33 | > [!NOTE] 34 | > The default extension is Javascript so it isn't mandatory to add the flag `-f` or `--format` to specify the extension if you want to convert to Javascript. 35 | 36 | 37 | - [ ] **Convert to Typescript** 38 | 39 | To convert to a typescript extension, do the following: 40 | 41 | - Create a folder and navigate into it then run any of the following: 42 | 43 | - `postwright -c -f ts` 44 | - `postwright --convert --format ts` 45 | - `postwright convert -f ts` 46 | 47 | - You can run any of the following from any location in your terminal. 48 | - `postwright -c -o -f ts` 49 | - `postwright --convert --output --format ts` 50 | - `postwright convert --output --format ts` 51 | 52 | ### What does it do presently? 53 | --- 54 | - [ ] The current release converts your collections into playwright version. 55 | 56 | - [ ] Creates a `variables.js` or a `variable.ts` and `variables.json` files depending on which format that's specified when converting. This lets you to decide how you want to parse in saved variables 57 | 58 | - [ ] Offers basic post-response assertions which are status code and response time. It extracts the title of other tests so that you can write them manually. 59 | 60 | > [!NOTE] 61 | > This is a first release, the plugin shall be improved upon to better handle post-response tests and pre-request scripts. 62 | > If any variable is hyphenated, the `variable.js` or `variable.ts` file will be created with the hyphenated variable name and that will cause an error. Change variable names to camel case or snake case to avoid this. -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to postwright 2 | 3 | Thank you for considering contributing to the **postwright** project! This guide outlines how to get involved and contribute effectively. 4 | 5 | ## Table of Contents 6 | 7 | - [Code of Conduct](#code-of-conduct) 8 | - [How Can I Contribute?](#how-can-i-contribute) 9 | - [Reporting Issues](#reporting-issues) 10 | - [Suggesting Enhancements](#suggesting-enhancements) 11 | - [Submitting Code Changes](#submitting-code-changes) 12 | - [Setting up the Development Environment](#setting-up-the-development-environment) 13 | - [Pull Request Guidelines](#pull-request-guidelines) 14 | - [Style Guide](#style-guide) 15 | - [License](#license) 16 | 17 | ## Code of Conduct 18 | 19 | Please read our [Code of Conduct](./CODE_OF_CONDUCT.md) to understand how we expect contributors to conduct themselves while participating in this project. 20 | 21 | ## How Can I Contribute? 22 | 23 | ### Reporting Issues 24 | 25 | If you encounter a bug or have a question about the project, please: 26 | 27 | 1. **Search for existing issues** to ensure it's not a duplicate. 28 | 2. **Create a new issue** if one doesn't exist. Use a clear and descriptive title and provide as much information as possible, such as: 29 | - Steps to reproduce the issue 30 | - The expected result 31 | - The actual result 32 | - Version of `postwright` being used 33 | - Any other relevant information, such as system configurations 34 | 35 | ### Suggesting Enhancements 36 | 37 | We welcome ideas to improve the package! When suggesting an enhancement: 38 | 39 | - **Explain the current state of the problem** and why your enhancement would help. 40 | - Provide details about how the enhancement would work. 41 | - Add potential use cases and examples, if possible. 42 | 43 | ### Submitting Code Changes 44 | 45 | 1. Fork the repository. 46 | 2. Create a new branch (from `main` or `develop`) for your changes: 47 | ``` 48 | git checkout -b feature/my-feature 49 | ``` 50 | 3. Make your changes, following the [Style Guide](#style-guide). 51 | 4. Commit your changes: 52 | ``` 53 | git commit -m "Add a concise description of the changes" 54 | ``` 55 | 5. Push to your fork: 56 | ``` 57 | git push origin feature/my-feature 58 | ``` 59 | 6. Submit a pull request (PR) to the main repository and describe your changes in detail. 60 | 61 | Please keep the scope of each PR focused and atomic. If you plan on making significant changes, it's best to open an issue or start a discussion first. 62 | 63 | ## Setting up the Development Environment 64 | 65 | To contribute to `postwright`, follow these steps to set up your local environment: 66 | 67 | 1. Clone the repository: 68 | ``` 69 | git clone https://github.com/your-username/postwright.git 70 | ``` 71 | 2. Navigate to the project directory: 72 | ``` 73 | cd postwright 74 | ``` 75 | 3. Install dependencies: 76 | ``` 77 | npm install 78 | ``` 79 | 4. Run tests to ensure everything works as expected: 80 | ``` 81 | npm test 82 | ``` 83 | 84 | ## Pull Request Guidelines 85 | - Keep your PR focused; avoid including unrelated changes. 86 | - Provide a detailed description of the changes in your PR. 87 | - Update relevant documentation, if applicable. 88 | 89 | ## Style Guide 90 | 91 | Follow these guidelines to maintain code quality: 92 | 93 | - **Code Style**: Follow the coding style used in the project. Use a linter to maintain consistency (`npm run lint`). 94 | - **Commit Messages**: Use concise and clear commit messages, following [Conventional Commits](https://www.conventionalcommits.org/), e.g., `feat: add feature x`. 95 | - **Testing**: Ensure all features have corresponding tests and that all tests pass before submitting a PR. 96 | 97 | ## License 98 | 99 | By contributing, you agree that your contributions will be licensed under the [MIT License](./LICENSE). 100 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | . 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /src/scriptGenerator.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs").promises; 2 | const path = require("path"); 3 | const crypto = require("crypto"); 4 | 5 | let variables = {}; 6 | let itemCounter = 0; 7 | let convertedScripts = new Map(); 8 | let variablesJsPath; 9 | let fileName; 10 | let variableEntries; 11 | 12 | async function loadVariables(outputDir) { 13 | if (!outputDir) { 14 | console.error("Output directory is undefined"); 15 | return; 16 | } 17 | const variablesFilePath = path.join(outputDir, "variables.json"); 18 | try { 19 | const data = await fs.readFile(variablesFilePath, "utf8"); 20 | variables = JSON.parse(data); 21 | } catch (error) { 22 | if (error.code !== "ENOENT") { 23 | console.error("Error loading variables:", error); 24 | } 25 | } 26 | } 27 | 28 | async function saveVariables(outputDir) { 29 | if (!outputDir) { 30 | console.error("Output directory is undefined"); 31 | return; 32 | } 33 | 34 | // Save as JSON 35 | const variablesFilePath = path.join(outputDir, "variables.json"); 36 | await fs.writeFile( 37 | variablesFilePath, 38 | JSON.stringify(variables, null, 2), 39 | "utf8" 40 | ); 41 | } 42 | 43 | function replaceVariables(text) { 44 | return text.replace(/{{(.+?)}}/g, (_, key) => variables[key] || _); 45 | } 46 | 47 | function convertPreRequestScript(script) { 48 | if (!script) return ""; 49 | 50 | let convertedScript = " // Pre-request Script\n"; 51 | const lines = script.split("\n"); 52 | 53 | lines.forEach((line) => { 54 | if ( 55 | line.includes("pm.variables.set") || 56 | line.includes("pm.environment.set") || 57 | line.includes("pm.globals.set") 58 | ) { 59 | const match = line.match(/set\("(.+?)",\s*(.+?)\)/); 60 | if (match) { 61 | const [, key, value] = match; 62 | convertedScript += ` variables['${key}'] : ${value};\n`; 63 | variableEntries = Object.entries(variables) 64 | .map(([key, value]) => { 65 | const valueType = typeof value; 66 | const formattedValue = 67 | valueType === "string" ? `'${value}'` : JSON.stringify(value); 68 | return ` ${key}: ${formattedValue}`; 69 | }) 70 | .join(",\n"); 71 | } 72 | } else { 73 | convertedScript += ` ${line}\n`; 74 | } 75 | }); 76 | 77 | return convertedScript; 78 | } 79 | 80 | function convertPostResponseScript(script) { 81 | if (!script) return ""; 82 | 83 | const scriptHash = crypto.createHash("md5").update(script).digest("hex"); 84 | if (convertedScripts.has(scriptHash)) { 85 | return convertedScripts.get(scriptHash); 86 | } 87 | 88 | let convertedScript = " // Post-response Script (Tests)\n"; 89 | const lines = script.split("\n"); 90 | let insideTest = false; 91 | let currentTestName = ""; 92 | let jsonAssignmentFound = false; 93 | 94 | const assertionRegex = 95 | /pm\.expect\((.*?)\)\.to\.(be\.an?|have)(\.property)?\(['"](\w+)['"]\)/; 96 | const arrayAssertionRegex = 97 | /pm\.expect\((.*?)\)\.to\.be\.an\(['"]array['"]\)/; 98 | const startRegex = /pm\.expect\((.*?)\)/; 99 | const variableNameRegex = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/; 100 | lines.forEach((line) => { 101 | if (line.includes("pm.test(")) { 102 | insideTest = true; 103 | const match = line.match(/pm\.test\("(.+?)"/); 104 | if (match) { 105 | currentTestName = match[1]; 106 | convertedScript += `\n // ${currentTestName}\n`; 107 | } 108 | } else if (insideTest && line.includes("});")) { 109 | insideTest = false; 110 | } else if (insideTest) { 111 | if (line.includes("pm.response.to.have.status")) { 112 | convertedScript += ` expect(response.status()).toBe(${ 113 | line.match(/\d+/)[0] 114 | });\n`; 115 | } else if ( 116 | line.includes("pm.expect(pm.response.responseTime).to.be.below") 117 | ) { 118 | convertedScript += ` expect(responseTime).toBeLessThan(${ 119 | line.match(/\d+/)[0] 120 | });\n`; 121 | } else if (line.includes("= pm.response.json()")) { 122 | jsonAssignmentFound = true; 123 | } 124 | } 125 | }); 126 | 127 | convertedScripts.set(scriptHash, convertedScript); 128 | return convertedScript; 129 | } 130 | 131 | function generatePlaywrightTest(item, useTypeScript) { 132 | const { name, request, event } = item; 133 | const { method, url, header, body } = request; 134 | 135 | let preRequestScript = ""; 136 | let postResponseScript = ""; 137 | let variablesImport; 138 | 139 | if (event) { 140 | const preRequestEvent = event.find((e) => e.listen === "prerequest"); 141 | const testEvent = event.find((e) => e.listen === "test"); 142 | 143 | if (preRequestEvent && preRequestEvent.script) { 144 | preRequestScript = convertPreRequestScript( 145 | preRequestEvent.script.exec.join("\n") 146 | ); 147 | } 148 | 149 | if (testEvent && testEvent.script) { 150 | postResponseScript = convertPostResponseScript( 151 | testEvent.script.exec.join("\n") 152 | ); 153 | } 154 | } 155 | 156 | let requestOptions = {}; 157 | if (header && header.length > 0) { 158 | requestOptions.headers = header.reduce( 159 | (acc, h) => ({ ...acc, [h.key]: replaceVariables(h.value) }), 160 | {} 161 | ); 162 | } 163 | if (body && body.mode === "raw") { 164 | try { 165 | requestOptions.data = JSON.parse(replaceVariables(body.raw)); 166 | } catch { 167 | requestOptions.data = replaceVariables(body.raw); 168 | } 169 | } 170 | 171 | const requestUrl = 172 | url && url.raw ? replaceVariables(url.raw) : "undefined_url"; 173 | // const relativePath = path.relative(folderPath, outputDir).replace(/\\/g, "/"); 174 | 175 | variablesImport = `import { variables } from './variables.${ 176 | useTypeScript === "ts" ? "ts" : "js" 177 | }';`; 178 | 179 | return ` 180 | import { test, expect ${ 181 | useTypeScript === "ts" ? ", APIRequestContext" : "" 182 | } } from '@playwright/test'; 183 | ${variablesImport} 184 | ${ 185 | useTypeScript === "ts" 186 | ? "type TestContext = { request: APIRequestContext };" 187 | : "" 188 | } 189 | 190 | test('${name}', async ({ request }${ 191 | useTypeScript === "ts" ? ": TestContext" : "" 192 | }) => { 193 | ${preRequestScript} 194 | const startTime = Date.now(); 195 | const response = await request.${method.toLowerCase()}('${requestUrl}'${ 196 | Object.keys(requestOptions).length > 0 197 | ? `, ${JSON.stringify(requestOptions, null, 2)}` 198 | : "" 199 | }); 200 | const responseTime = Date.now() - startTime; 201 | ${postResponseScript} 202 | }); 203 | `; 204 | } 205 | 206 | async function processItem(item, parentPath = "", outputDir, useTypeScript) { 207 | if (!outputDir) { 208 | console.error("Output directory is undefined"); 209 | return; 210 | } 211 | const itemNumber = String(itemCounter++).padStart(3, "0"); 212 | 213 | if (item.item) { 214 | // This is a folder 215 | parentPath = path.join( 216 | parentPath, 217 | `${itemNumber}_${item.name.replace(/[^a-z0-9]/gi, "_").toLowerCase()}` 218 | ); 219 | await fs.mkdir(parentPath, { recursive: true }); 220 | 221 | for (const subItem of item.item) { 222 | await processItem(subItem, parentPath, outputDir, useTypeScript); 223 | } 224 | } else if (item.request) { 225 | // This is a request 226 | const testScript = generatePlaywrightTest( 227 | item, 228 | useTypeScript 229 | ); 230 | 231 | if (useTypeScript === "ts") { 232 | fileName = `${itemNumber}_${item.name 233 | .replace(/[^a-z0-9]/gi, "_") 234 | .toLowerCase()}.spec.ts`; 235 | } else { 236 | fileName = `${itemNumber}_${item.name 237 | .replace(/[^a-z0-9]/gi, "_") 238 | .toLowerCase()}.spec.js`; 239 | } 240 | const filePath = path.join(parentPath, fileName); 241 | await fs.writeFile(filePath, testScript); 242 | } 243 | } 244 | 245 | async function processCollection(collection, outputDir, useTypeScript) { 246 | if (!outputDir) { 247 | throw new Error("Output directory is undefined"); 248 | } 249 | console.log(`Processing collection. Output directory: ${outputDir}`); 250 | 251 | await loadVariables(outputDir); 252 | 253 | if (collection.variable) { 254 | collection.variable.forEach((v) => { 255 | variables[v.key] = v.value; 256 | }); 257 | } 258 | 259 | itemCounter = 0; 260 | convertedScripts.clear(); 261 | for (const item of collection.item) { 262 | await processItem(item, outputDir, outputDir, useTypeScript); 263 | } 264 | 265 | await saveVariables(outputDir); 266 | 267 | const variablesJsContent = `export const Variables = {\n${variableEntries}\n};\n`; 268 | 269 | if (useTypeScript === "ts") { 270 | variablesJsPath = path.join(outputDir, "variables.ts"); 271 | } else { 272 | variablesJsPath = path.join(outputDir, "variables.js"); 273 | } 274 | await fs.writeFile(variablesJsPath, variablesJsContent); 275 | } 276 | 277 | module.exports = { processCollection, generatePlaywrightTest }; 278 | --------------------------------------------------------------------------------