├── .editorconfig ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── config.yml │ └── feature_request.md ├── dependabot.yml └── workflows │ ├── build.yml │ └── release.yml ├── .gitignore ├── .gitmodules ├── .node-inspectorrc ├── .npmrc ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── debug.bat ├── docs ├── README.md ├── SUMMARY.md ├── configuration.md ├── debugging.md ├── install.md ├── refactoring.md ├── syntax │ ├── README.md │ ├── custom-messages.md │ ├── custom-screenshot-hook.md │ ├── data-stores.md │ ├── execution-hooks.md │ └── step-implementation.md └── usage.md ├── eslint.config.js ├── examples ├── .gitignore ├── README.md ├── env │ └── default │ │ ├── default.properties │ │ └── js.properties ├── manifest.json ├── specs │ └── example.spec └── tests │ ├── step_implementation.js │ └── vowels.js ├── index.bat ├── index.js ├── js.json ├── package-lock.json ├── package.json ├── scripts └── install.js ├── skel ├── js.properties └── step_implementation.js ├── src ├── custom-message-registry.js ├── custom-screenshot-registry.js ├── data-store-factory.js ├── executor.js ├── file-util.js ├── gauge-global.js ├── gauge.js ├── hook-registry.js ├── impl-loader.js ├── logger.js ├── message-processor.js ├── refactor.js ├── req-manager.js ├── response-factory.js ├── screenshot.js ├── serviceHandlers.js ├── static-loader.js ├── step-parser.js ├── step-registry.js ├── table.js ├── test.js └── vm.js ├── test ├── custom-message-registry-test.js ├── custom-screenshot-registry-test.js ├── data-store-factory-test.js ├── executor-test.js ├── file-util-test.js ├── gauge-global-test.js ├── hook-registry-test.js ├── lsp-server-test.js ├── message-processor-test.js ├── package-test.js ├── refactor-test.js ├── screenshot-test.js ├── static-loader-test.js ├── step-parser-test.js ├── step-registry-test.js ├── table.js ├── test.js ├── testdata │ ├── custom │ │ ├── inner-dir │ │ │ ├── foo.js │ │ │ └── foo.txt │ │ └── step-impl.js │ └── tests │ │ ├── inner-dir │ │ ├── foo.js │ │ └── foo.txt │ │ └── step-impl.js └── vm-test.js └── update_version.py /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | 7 | [*.{js,json}] 8 | charset = utf-8 9 | indent_style = space 10 | indent_size = 2 11 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.js text eol=lf -------------------------------------------------------------------------------- /.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 (or project) to reproduce the behavior: 15 | 1. Initialise a gauge project 16 | 2. Use the following piece of code 17 | 3. Run the gauge command 18 | 5. See error 19 | 20 | ``` 21 | $ gauge init js 22 | $ gauge run specs 23 | ``` 24 | 25 | **Logs** 26 | 27 | ``` 28 | Paste any log or error messages here 29 | ``` 30 | 31 | **Expected behavior** 32 | A clear and concise description of what you expected to happen. 33 | 34 | **Screenshots** 35 | If applicable, add screenshots to help explain your problem. 36 | 37 | 38 | **Versions:** 39 | - Taiko: [e.g. 1.0.23 (use `taiko --version`)] 40 | - OS [e.g. 10.15.6 (19G2021) please be specific ] 41 | - Node.js [e.g. v.12.18.14] 42 | 43 | 44 | 45 | ``` 46 | gauge -v 47 | ``` 48 | 49 | 50 | **Additional context** 51 | Add any other context about the problem here. 52 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Gauge Community Support 4 | url: https://spectrum.chat/gauge 5 | about: Please ask and answer questions here. 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 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: monthly 7 | groups: 8 | github-actions: 9 | patterns: 10 | - "*" 11 | - package-ecosystem: npm 12 | directory: "/" 13 | schedule: 14 | interval: monthly 15 | allow: 16 | - dependency-type: all 17 | groups: 18 | npm: 19 | patterns: 20 | - "*" 21 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | jobs: 11 | tests: 12 | name: Run UTs on ${{ matrix.os }} 13 | runs-on: ${{ matrix.os }} 14 | strategy: 15 | matrix: 16 | os: [ubuntu-latest, windows-latest, macos-latest] 17 | 18 | steps: 19 | - uses: actions/checkout@v4 20 | 21 | - name: Set up Nodejs 22 | uses: actions/setup-node@v4 23 | with: 24 | node-version: 22 25 | 26 | - name: Setup submodule 27 | run: | 28 | git submodule init 29 | git submodule update 30 | 31 | - name: unit-tests 32 | run: | 33 | npm install 34 | npm test 35 | 36 | functional-tests: 37 | needs: tests 38 | name: FTs ${{ matrix.os }} 39 | runs-on: ${{ matrix.os }} 40 | strategy: 41 | matrix: 42 | os: [windows-latest, ubuntu-latest] 43 | steps: 44 | - uses: actions/checkout@v4 45 | 46 | - name: Set up Go 47 | uses: actions/setup-go@v5 48 | with: 49 | check-latest: true 50 | go-version: '1.24' 51 | 52 | - name: Set up Nodejs 53 | uses: actions/setup-node@v4 54 | with: 55 | node-version: 22 56 | 57 | - name: Setup java 58 | uses: actions/setup-java@v4 59 | with: 60 | distribution: temurin 61 | java-version: '17' 62 | 63 | - uses: getgauge/setup-gauge@master 64 | with: 65 | gauge-version: master 66 | 67 | - name: Install JS 68 | run: | 69 | npm install 70 | npm run installPlugin 71 | 72 | - name: Prep FTs 73 | run: | 74 | git clone https://github.com/getgauge/gauge-tests 75 | cd gauge-tests 76 | gauge install 77 | 78 | - name: Run FTs (linux) 79 | if: matrix.os != 'windows-latest' 80 | run: | 81 | cd gauge-tests 82 | ./gradlew clean jsFT 83 | 84 | - name: Run FTs (windows) 85 | if: matrix.os == 'windows-latest' 86 | run: | 87 | cd gauge-tests 88 | .\gradlew.bat clean jsFT 89 | 90 | lsp-tests: 91 | needs: tests 92 | name: LSP Tests ${{ matrix.os }} 93 | runs-on: ${{ matrix.os }} 94 | strategy: 95 | matrix: 96 | os: [windows-latest, ubuntu-latest] 97 | steps: 98 | - uses: actions/checkout@v4 99 | 100 | - name: Use Node.js 101 | uses: actions/setup-node@v4 102 | with: 103 | node-version: 22 104 | 105 | - name: Set up Go 106 | uses: actions/setup-go@v5 107 | with: 108 | check-latest: true 109 | go-version: '1.24' 110 | 111 | - uses: getgauge/setup-gauge@master 112 | with: 113 | gauge-version: master 114 | 115 | - name: Install JS 116 | run: | 117 | npm install 118 | npm run installPlugin 119 | 120 | - name: Prep LSP tests 121 | run: | 122 | git clone https://github.com/getgauge/gauge-lsp-tests 123 | cd gauge-lsp-tests 124 | npm install 125 | gauge install 126 | 127 | - name: Run LSP tests 128 | run: | 129 | cd gauge-lsp-tests 130 | gauge run --tags='!knownIssue & (actions_on_project_load | actions_on_file_edit)' --env=js-wd 131 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release on PR Merge 2 | 3 | on: deployment 4 | 5 | jobs: 6 | deploy: 7 | if: github.event.deployment.environment == 'production' 8 | runs-on: ubuntu-latest 9 | env: 10 | GITHUB_TOKEN: '${{ secrets.GAUGEBOT_GITHUB_TOKEN }}' 11 | CI: true 12 | 13 | steps: 14 | - uses: actions/checkout@v4 15 | - name: Set up Nodejs 16 | uses: actions/setup-node@v4 17 | with: 18 | node-version: 22 19 | 20 | - name: Setup git 21 | run: | 22 | git config --global user.name "$(git --no-pager log --format=format:'%an' -n 1)" 23 | git config --global user.email "$(git --no-pager log --format=format:'%ae' -n 1)" 24 | 25 | - name: Build artifacts 26 | run: | 27 | npm install 28 | npm run package 29 | 30 | - name: update 31 | run: | 32 | if [ -z "$version" ]; then 33 | version=$(cd deploy && ls gauge-js* | head -1 | sed "s/\.[^\.]*$//" | sed "s/gauge-js-//" | sed "s/-[a-z]*\.[a-z0-9_]*$//"); 34 | fi 35 | echo "VERSION=$version" >> $GITHUB_ENV 36 | 37 | echo "---------------------------" 38 | echo "Updating release v$version" 39 | echo "---------------------------" 40 | echo -e "Gauge JS v$version\n\n" > desc.txt 41 | release_description=$(ruby -e "$(curl -sSfL https://github.com/getgauge/gauge/raw/master/build/create_release_text.rb)" getgauge gauge-js) 42 | echo "$release_description" >> desc.txt 43 | gh release create --title "Gauge JS v${version}" --notes-file ./desc.txt "v${version}" deploy/gauge-js* 44 | 45 | - name: Update metadata in gauge-repository 46 | run: | 47 | git clone https://github.com/getgauge/gauge-repository.git 48 | cd gauge-repository 49 | python update_metadata.py js $VERSION 50 | commit_message=$(echo -e "Update js to v$VERSION") 51 | git commit -am "$commit_message" 52 | git push "https://$GITHUB_ACTOR:$GITHUB_TOKEN@github.com/getgauge/gauge-repository.git" master 53 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .DS_Store 3 | 4 | # Logs 5 | logs 6 | *.log 7 | npm-debug.log* 8 | 9 | # Runtime data 10 | pids 11 | *.pid 12 | *.seed 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directory 30 | # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git 31 | node_modules 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | 39 | *~ 40 | \#*\# 41 | .\#* 42 | 43 | # Ignore build and deploy dirs 44 | /build/ 45 | /deploy/ 46 | 47 | # Screenshot 48 | screenshot-test.png 49 | 50 | # Docs 51 | /docs/_book 52 | 53 | .vscode -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "gauge-proto"] 2 | path = gauge-proto 3 | url = https://github.com/getgauge/gauge-proto.git 4 | -------------------------------------------------------------------------------- /.node-inspectorrc: -------------------------------------------------------------------------------- 1 | { 2 | "debug-brk": true, 3 | "web-port": 8765, 4 | "debug-port": 5678, 5 | "save-live-edit": true, 6 | "preload": false, 7 | "hidden": ["node_modules/"], 8 | "stack-trace-limit": 10 9 | } 10 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=true -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at gauge-coc@thoughtworks.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Issues 2 | 3 | If you find any issues or have any feature requests, please file them in the [issue tracker](https://github.com/getgauge/gauge-js/issues). 4 | 5 | If you are filing issues, please provide the version of `gauge` core and `gauge-js` plugin that you have installed. You can find it by doing: 6 | 7 | ```sh 8 | $ gauge -v 9 | ``` 10 | 11 | ## Develop 12 | 13 | **Download** 14 | 15 | ```sh 16 | $ git clone git://github.com/getgauge/gauge-js --recursive 17 | ``` 18 | 19 | **Setup**: 20 | 21 | - Preferably use [EditorConfig](http://editorconfig.org/) with your text editor. 22 | 23 | **Install npm dependencies:** 24 | 25 | ```sh 26 | $ npm install 27 | ``` 28 | 29 | **Run tests:** 30 | 31 | ```sh 32 | $ npm test 33 | ``` 34 | 35 | ### Create package 36 | 37 | ```sh 38 | $ npm run package 39 | ``` 40 | 41 | ### Installing from source 42 | 43 | ```sh 44 | $ npm run installPlugin 45 | ``` 46 | 47 | ### Code Style 48 | 49 | - Indent: 2 spaces 50 | - Line ending: LF 51 | 52 | 53 | ## Bump up gauge-js version 54 | 55 | * Update the value `version` property in`js.json` file. 56 | 57 | Ex: 58 | ```diff 59 | }, 60 | - "version": "2.3.9", 61 | + "version": "2.3.10", 62 | "gaugeVersionSupport": { 63 | ``` 64 | 65 | * Update the value of `version` property in `package.json`. 66 | 67 | Ex: 68 | ```diff 69 | "name": "gauge-js", 70 | - "version": "2.3.9", 71 | + "version": "2.3.10", 72 | ``` -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Gauge 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gauge-JS 2 | 3 | [![Actions Status](https://github.com/getgauge/gauge-js/workflows/build/badge.svg)](https://github.com/getgauge/gauge-js/actions) 4 | [![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-v1.4%20adopted-ff69b4.svg)](CODE_OF_CONDUCT.md) 5 | 6 | This project adds Javascript [language plugin](https://gauge.org/plugins/) for [gauge](https://gauge.org). 7 | 8 | ## Getting started 9 | 10 | ### Pre-requisite 11 | 12 | - [Gauge](https://gauge.org/index.html) 13 | 14 | ### Installation 15 | ``` 16 | gauge install js 17 | ``` 18 | 19 | ### Create a gauge-js project 20 | ``` 21 | gauge init js 22 | ``` 23 | 24 | ### Run tests 25 | ``` 26 | gauge run specs 27 | ``` 28 | 29 | ## Documentation 30 | 31 | For other details refer the documentation [here](https://docs.gauge.org) 32 | 33 | ## Demos and examples 34 | 35 | ### Plain Javascript 36 | 37 | Run the following command to create a [sample](https://github.com/getgauge/template-js-simple) gauge template 38 | 39 | ``` 40 | $ gauge init js_simple 41 | ``` 42 | 43 | ### Taiko 44 | 45 | Run the following command to create a [sample](https://github.com/getgauge/template-js) [Taiko](https://github.com/getgauge/taiko) template 46 | 47 | ``` 48 | $ gauge init js 49 | ``` 50 | 51 | 52 | ### Alternate Installation options 53 | 54 | #### Install specific version 55 | ``` 56 | gauge install js --version 57 | ``` 58 | 59 | ### Install from zip file 60 | * Download the plugin from [Releases](https://github.com/getgauge/gauge-js/releases) 61 | ``` 62 | gauge install js --file gauge-js-.zip 63 | ``` 64 | 65 | #### Build from Source 66 | The plugin is authored in [Javascript](https://en.wikipedia.org/wiki/JavaScript). 67 | Gauge is authored in golang. These are independent processes talking to each other over TCP on port GAUGE_INTERNAL_PORT (env variable) using [Protobuf](https://github.com/getgauge/gauge-proto). 68 | 69 | ##### Pre-Requisites 70 | * [Node.js](https://nodejs.org/en/) - Version >= 18 71 | 72 | ##### Compiling 73 | ``` 74 | npm install 75 | ``` 76 | 77 | ##### Run tests: 78 | ``` 79 | npm test 80 | ``` 81 | 82 | ##### Installing from source 83 | ``` 84 | npm run installPlugin 85 | ``` 86 | 87 | ##### Create package 88 | ``` 89 | npm run package 90 | ``` 91 | 92 | You can then install the offline zip archive created using 93 | ``` 94 | gauge install js --file gauge-js-.zip 95 | ``` 96 | 97 | ## Copyright 98 | 99 | Copyright 2018 ThoughtWorks, Inc. 100 | -------------------------------------------------------------------------------- /debug.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | node-debug %1 %2 3 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Gauge JS 2 | 3 | JavaScript Runner for [Gauge](http://www.getgauge.io). 4 | 5 | ## Table of Contents 6 | 7 | - [Install](install.md) 8 | - [Usage](usage.md) 9 | - [Syntax](syntax/README.md) 10 | - [Step implentation](syntax/step-implementation.md) 11 | - [Execution hooks](syntax/execution-hooks.md) 12 | - [Async Operations](syntax/async-operations.md) 13 | - [Custom messages](syntax/custom-messages.md) 14 | - [Data stores](syntax/data-stores.md) 15 | - [Custom screenshot hook](syntax/custom-screenshot-hook.md) 16 | - [Refactoring](refactoring.md) 17 | - [Debugging](debugging.md) 18 | - [Configuration](configuration.md) 19 | -------------------------------------------------------------------------------- /docs/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | - [Install](install.md) 4 | - [Usage](usage.md) 5 | - [Syntax](syntax/README.md) 6 | - [Step implentation](syntax/step-implementation.md) 7 | - [Execution hooks](syntax/execution-hooks.md) 8 | - [Custom messages](syntax/custom-messages.md) 9 | - [Data stores](syntax/data-stores.md) 10 | - [Custom screenshot hook](syntax/custom-screenshot-hook.md) 11 | - [Refactoring](refactoring.md) 12 | - [Debugging](debugging.md) 13 | - [Configuration](configuration.md) 14 | -------------------------------------------------------------------------------- /docs/configuration.md: -------------------------------------------------------------------------------- 1 | # Configuration 2 | 3 | JavaScript specific configuration changes can be made in the `env/default/js.properties` file. 4 | 5 | | Property | Default | Description | 6 | |--------------------------|--------------|----------------------------------------------------------------------------------| 7 | | **`test_timeout`** | `1000` | Specify test timeout in milliseconds. If any async test takes more time than specified by this option, `gauge-js` will fail that test. Default value is `1000ms`.| 8 | 9 | Example: 10 | 11 | ```js 12 | test_timeout=1500 13 | ``` 14 | 15 | | Property | Default | Description | 16 | |------------------|--------------------|--------------------------------------------------| 17 | | **`test_match`** | `**/tests/**/*.js` | Specify test step implemetation location pattern.| 18 | 19 | Example: 20 | 21 | ```js 22 | test_match='**/some_folder/**/*.test.js, **/some_other_folder/**/*.steps.ts' 23 | ``` 24 | 25 | | Property | Default | Description | 26 | |--------------------------|--------------|--------------------------------------------------------------------------------------------------| 27 | | **`DEBUG`** | `false` | Set this to `true` to start with the debugger. Read [Debugging](#debugging) for more information.| 28 | 29 | Example: 30 | 31 | ```js 32 | DEBUG=true 33 | ``` 34 | -------------------------------------------------------------------------------- /docs/debugging.md: -------------------------------------------------------------------------------- 1 | # Debugging 2 | 3 | `gauge-js` supports debugging your test implementation code using [node-inspector](https://github.com/node-inspector/node-inspector). 4 | 5 | ## Requirements 6 | 7 | - Ensure you have the latest Chrome browser and `node-inspector` installed. Please consult the [node-inspector documentation](https://github.com/node-inspector/node-inspector) for installation instructions. 8 | - Ensure that the binaries `node-debug` and `node-inspector` are available on `PATH`. 9 | 10 | ## Starting gauge-js with debugger 11 | 12 | You can do either of these: 13 | 14 | - Set the `DEBUG` key to `true` in `env//js.properties` file in your `gauge` project. 15 | - Set the environment variable `DEBUG=true` when calling `gauge`. Like: `DEBUG=true gauge specs/`. This needs `gauge v0.3.2` or newer. 16 | 17 | ## How it works 18 | 19 | Setting the debug option will launch the runner code through `node-debug`. It will start `node-inspector`, launch Chrome DevTools and pause on the first line of execution. You will need to continue execution to let `gauge` carry on with its execution. 20 | 21 | You can set `debugger;` inside step implementation or hook callbacks to pause execution in the debugger. This retains the `gauge` context and gives you a full blown debugger to debug your test implementations. 22 | 23 | Example: 24 | 25 | ```js 26 | gauge.step("There are vowels.", function (num) { 27 | debugger; 28 | assert.equal(num, 5); 29 | }); 30 | ``` 31 | 32 | This will pause the debugger when this step's callback is executed by `gauge-js`. 33 | 34 | ## Caveats 35 | 36 | - The debugger exposes entire gauge-js runner code. 37 | - You need to be quick enough to hit continue in the browser when `node-inspector` launches. If this takes too long, `gauge` will timeout connecting to the API. A workaround for this is to increase the `runner_connection_timeout` property to an acceptable value. 38 | -------------------------------------------------------------------------------- /docs/install.md: -------------------------------------------------------------------------------- 1 | # Install Gauge JS plugin 2 | 3 | Before installing gauge-js, make sure Gauge `v0.3.0` or above is installed. 4 | 5 | ```sh 6 | $ gauge -v 7 | ``` 8 | 9 | ### Install through gauge (Recommended) 10 | 11 | ```sh 12 | $ gauge --install js 13 | ``` 14 | 15 | ### Installing from zip file 16 | 17 | - Download `gauge-js-.zip` from the [releases](https://github.com/getgauge-contrib/gauge-js/releases/latest) page. 18 | - Install plugin from downloaded file: 19 | 20 | ```sh 21 | $ gauge --install js --file /gauge-js-.zip` 22 | ``` 23 | -------------------------------------------------------------------------------- /docs/refactoring.md: -------------------------------------------------------------------------------- 1 | # Refactoring 2 | 3 | `gauge-js` supports refactoring your specifications and step implementations. Refactoring can be done using the following command signature: 4 | 5 | ```sh 6 | $ gauge --refactor "Existing step text" "New step text" 7 | ``` 8 | 9 | The JS runner plugin will alter the step text and callback signature in your step implementations. It does not change the callback body. 10 | -------------------------------------------------------------------------------- /docs/syntax/README.md: -------------------------------------------------------------------------------- 1 | # Gauge JS syntax 2 | 3 | - [Step implentation](step-implementation.md) 4 | - [Execution hooks](execution-hooks.md) 5 | - [Custom messages](custom-messages.md) 6 | - [Data stores](data-stores.md) 7 | - [Custom screenshot hook](custom-screenshot-hook.md) 8 | - [Refactoring](refactoring.md) 9 | -------------------------------------------------------------------------------- /docs/syntax/custom-messages.md: -------------------------------------------------------------------------------- 1 | # Custom messages 2 | 3 | **Syntax: `gauge.message()`** 4 | 5 | Use the `gauge.message()` function to send custom messages to `gauge` in your step implementations. This method takes only one string as an argument. You can call it multiple times to send multiple messages within the same step. 6 | 7 | Example: 8 | 9 | ```js 10 | gauge.step("Vowels in English language are .", function (vowelsGiven) { 11 | gauge.message("Vowels are " + vowelsGiven); 12 | }); 13 | ``` 14 | -------------------------------------------------------------------------------- /docs/syntax/custom-screenshot-hook.md: -------------------------------------------------------------------------------- 1 | # Custom screenshot hook 2 | 3 | You can specify a custom function to grab a screenshot on step failure. By default, `gauge-js` takes screenshot of the current screen using the `gauge_screenshot` binary. 4 | 5 | This custom function should be set on the `gauge.screenshotFn` property in test implementation code and it should return a base64 encoded string of the image data that `gauge-js` will use as image content on failure. 6 | 7 | ```js 8 | gauge.screenshotFn = function () { 9 | return "base64encodedstring"; 10 | }; 11 | ``` 12 | -------------------------------------------------------------------------------- /docs/syntax/data-stores.md: -------------------------------------------------------------------------------- 1 | # Data Stores 2 | 3 | Step implementations can share custom data across scenarios, specifications and suites using data stores. 4 | 5 | There are 3 different types of data stores based on the lifecycle of when it gets cleared. 6 | 7 | ## Scenario store 8 | 9 | This data store keeps values added to it in the lifecycle of the scenario execution. Values are cleared after every scenario executes. 10 | 11 | **Store a value:** 12 | 13 | ```js 14 | gauge.dataStore.scenarioStore.put(key, value); 15 | ``` 16 | 17 | **Retrieve a value:** 18 | 19 | ```js 20 | gauge.dataStore.scenarioStore.get(key); 21 | ``` 22 | 23 | ## Specification store 24 | 25 | This data store keeps values added to it in the lifecycle of the specification execution. Values are cleared after every specification executes. 26 | 27 | **Store a value:** 28 | 29 | ```js 30 | gauge.dataStore.specStore.put(key, value); 31 | ``` 32 | 33 | **Retrieve a value:** 34 | 35 | ```js 36 | gauge.dataStore.specStore.get(key); 37 | ``` 38 | 39 | ## Suite store 40 | 41 | This data store keeps values added to it in the lifecycle of the entire suite's execution. Values are cleared after entire suite executes. 42 | 43 | **Store a value:** 44 | 45 | ```js 46 | gauge.dataStore.suiteStore.put(key, value); 47 | ``` 48 | 49 | **Retrieve a value:** 50 | 51 | ```js 52 | gauge.dataStore.suiteStore.get(key); 53 | ``` 54 | 55 | **Note:** Suite Store is not advised to be used when executing specs in parallel. The values are not retained between parallel streams of execution. 56 | -------------------------------------------------------------------------------- /docs/syntax/execution-hooks.md: -------------------------------------------------------------------------------- 1 | # Execution Hooks 2 | 3 | gauge-js supports tagged [execution hooks](https://docs.gauge.org/writing-specifications.html#execution-hooks). These methods are available for each type of hook: 4 | 5 | **"Before" hooks:** 6 | 7 | - **`gauge.hooks.beforeSuite(fn, [opts]) { ... }`** - Executed before the test suite begins 8 | - **`gauge.hooks.beforeSpec(fn, [opts]) { ... }`** - Executed before each specification 9 | - **`gauge.hooks.beforeScenario(fn, [opts]) { ... }`** - Executed before each scenario 10 | - **`gauge.hooks.beforeStep(fn, [opts]) { ... }`**- Execute before each step 11 | 12 | **"After" hooks:** 13 | 14 | - **`gauge.hooks.afterSuite(fn, [opts]) { ... }`** - Executed after the test suite ends 15 | - **`gauge.hooks.afterSpec(fn, [opts]) { ... }`** - Executed after each specification 16 | - **`gauge.hooks.afterScenario(fn, [opts]) { ... }`** - Executed after each scenario 17 | - **`gauge.hooks.afterStep(fn, [opts]) { ... }`**- Execute after each step 18 | 19 | Here's an example of a hook that is executed before each scenario: 20 | 21 | ```js 22 | gauge.hooks.beforeScenario(function () { 23 | assert.equal(vowels.join(""), "aeiou"); 24 | }); 25 | ``` 26 | 27 | ## Hook options 28 | 29 | Each hook takes an optional 2nd argument as an object. It can contain the following properties: 30 | 31 | - *`tags`*: Default: `[]`. 32 | An array of strings for the tags for which to execute the current callback. These are only useful at specification or scenario level. If not specified, the provided callback is executed on each occurrence of the hook. 33 | - *`operator`*: Valid values: `"AND"`, `"OR"`. Default: `"AND"`. 34 | This controls whether the current callback is executed when all of the tags match (in case of `"AND"`), or if any of the tags match (in case of `OR`). 35 | 36 | Example of a tagged execution hook implementation: 37 | 38 | ```js 39 | gauge.hooks.beforeScenario(function () { 40 | assert.equal(vowels[0], "a"); 41 | }, { tags: [ "single word" ]}); 42 | ``` 43 | 44 | ## Async operations in execution hooks 45 | 46 | ```js 47 | gauge.hooks.beforeStep(function (context, done) { 48 | setTimeout(function() { 49 | done(); 50 | }, 1000); 51 | }); 52 | ``` 53 | -------------------------------------------------------------------------------- /docs/syntax/step-implementation.md: -------------------------------------------------------------------------------- 1 | # Step implementation 2 | 3 | **Syntax: `gauge.step(, fn, [done]) { ... }`** 4 | 5 | Use the `gauge.step()` method to implement your steps. For example: 6 | 7 | ```js 8 | gauge.step("Vowels in English language are .", function (vowelsGiven) { 9 | assert.equal(vowelsGiven, "aeiou"); 10 | }); 11 | ``` 12 | 13 | ## Multiple step names 14 | 15 | To implement the same function for multiple step names (aka, step aliases), pass an `array` of `strings` as the first argument to `gauge.step()`. For example: 16 | 17 | ```js 18 | gauge.step(["Create a user ", "Create another user "], function (username) { 19 | // do cool stuff 20 | }); 21 | ``` 22 | 23 | ## Async operations in step implementation 24 | 25 | If test code involves asynchronous operations, invoke the optional callback when the test is done. Including this optional parameter (`done` in the following example) in step function or execution hook makes runner to wait for the completion of the async operation. 26 | 27 | ```js 28 | gauge.step("Vowels in English language are ", function (vowels, done) { 29 | setTimeout(function () { 30 | done(); 31 | }, 1000); 32 | }); 33 | ``` 34 | 35 | ### Handling assertions and errors in async tests 36 | 37 | If an asynchronous test code can throw an error, or if you use assertions in an asynchronous test, wrap the assertions in a `try-catch` block and pass the error to `done()`: 38 | 39 | ```js 40 | gauge.step("I am run async and I may fail", function (done) { 41 | setTimeout(function () { 42 | try { 43 | assert.equal(true, false); 44 | } catch (e) { 45 | done(e); 46 | } 47 | }, 1000); 48 | }); 49 | ``` 50 | 51 | ### Continue on failure 52 | 53 | To have a particular step implementation not break execution, pass an `options` object with a boolean `continueOnFailure` property as the second argument to `gauge.step()`. Like this: 54 | 55 | ```js 56 | gauge.step("Vowels in English language are .", { continueOnFailure: true}, function (vowelsGiven) { 57 | assert.equal(vowelsGiven, "aeio"); // This will cause the step to fail, but it will not break execution 58 | }); 59 | ``` 60 | -------------------------------------------------------------------------------- /docs/usage.md: -------------------------------------------------------------------------------- 1 | # Usage 2 | 3 | If you are new to Gauge, please consult the [Gauge documentation](https://docs.gauge.org) to know about how Gauge works. 4 | 5 | ## Initialize 6 | 7 | To initialize a project with `gauge-js`, in an empty directory run: 8 | 9 | ```sh 10 | $ gauge --init js 11 | ``` 12 | 13 | ## Execute test cases 14 | 15 | ```sh 16 | $ gauge specs/ 17 | ``` 18 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import mocha from "eslint-plugin-mocha"; 2 | 3 | export default [ 4 | { 5 | plugins: { 6 | mocha 7 | }, 8 | languageOptions: { 9 | ecmaVersion: 11, 10 | globals: { 11 | node: true, 12 | es6: true, 13 | mocha: true 14 | } 15 | }, 16 | rules: { 17 | curly: "error", 18 | indent: [ 19 | "error", 20 | 2 21 | ], 22 | "linebreak-style": [ 23 | "error", 24 | "unix" 25 | ], 26 | "mocha/no-exclusive-tests": "error", 27 | "no-console": "off", 28 | quotes: [ 29 | "error", 30 | "double" 31 | ], 32 | semi: [ 33 | "error", 34 | "always" 35 | ] 36 | } 37 | } 38 | ]; 39 | -------------------------------------------------------------------------------- /examples/.gitignore: -------------------------------------------------------------------------------- 1 | reports/ 2 | bin/ 3 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | Examples showing different features of the Gauge JS plugin. 2 | 3 | This directory is a Gauge project created using `gauge init js`. Each test implementation file in the `tests/` sub-directory highlights each feature: 4 | 5 | - `module.exports`: [tests/vowels.js](tests/vowels.js) 6 | - `require`: [tests/step_implementations.js](tests/step_implementations.js) 7 | -------------------------------------------------------------------------------- /examples/env/default/default.properties: -------------------------------------------------------------------------------- 1 | # default.properties 2 | # properties set here will be available to the test execution as environment variables 3 | 4 | # sample_key = sample_value 5 | 6 | #The path to the gauge reports directory. Should be either relative to the project directory or an absolute path 7 | gauge_reports_dir = reports 8 | 9 | #Set as false if gauge reports should not be overwritten on each execution. A new time-stamped directory will be created on each execution. 10 | overwrite_reports = true 11 | 12 | # Set to false to disable screenshots on failure in reports. 13 | screenshot_on_failure = true 14 | 15 | # The path to the gauge logs directory. Should be either relative to the project directory or an absolute path 16 | logs_directory = logs -------------------------------------------------------------------------------- /examples/env/default/js.properties: -------------------------------------------------------------------------------- 1 | #js.properties 2 | #settings related to gauge-js. 3 | 4 | test_timeout = 1000 5 | 6 | # Change this to true to enable debugging support 7 | DEBUG = false 8 | -------------------------------------------------------------------------------- /examples/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "Language": "js", 3 | "Plugins": [ 4 | "html-report" 5 | ] 6 | } -------------------------------------------------------------------------------- /examples/specs/example.spec: -------------------------------------------------------------------------------- 1 | Specification Heading 2 | ===================== 3 | 4 | This is an executable specification file. This file follows markdown syntax. 5 | Every heading in this file denotes a scenario. Every bulleted point denotes a step. 6 | 7 | To execute this specification, run 8 | 9 | gauge specs 10 | 11 | 12 | * Vowels in English language are "aeiou". 13 | 14 | Vowel counts in single word 15 | --------------------------- 16 | 17 | tags: single word 18 | 19 | * The word "gauge" has "3" vowels. 20 | 21 | 22 | Vowel counts in multiple word 23 | ----------------------------- 24 | 25 | This is the second scenario in this specification 26 | 27 | Here's a step that takes a table 28 | 29 | * Almost all words have vowels 30 | |Word |Vowel Count| 31 | |------|-----------| 32 | |Gauge |3 | 33 | |Mingle|2 | 34 | |Snap |1 | 35 | |GoCD |1 | 36 | |Rhythm|0 | 37 | -------------------------------------------------------------------------------- /examples/tests/step_implementation.js: -------------------------------------------------------------------------------- 1 | /* globals gauge*/ 2 | 3 | "use strict"; 4 | 5 | /** 6 | * Loads the `assert` module provided by NodeJS 7 | */ 8 | import assert from "node:assert"; 9 | 10 | /** 11 | * Loads the local `vowels.js` module present in this directory 12 | */ 13 | import vowels from "./vowels.js"; 14 | 15 | 16 | // -------------------------- 17 | // Gauge step implementations 18 | // -------------------------- 19 | 20 | gauge.step("Vowels in English language are .", function(vowelsGiven) { 21 | assert.equal(vowelsGiven, vowels.vowelList.join("")); 22 | }); 23 | 24 | gauge.step("The word has vowels.", function(word, number) { 25 | assert.equal(number, vowels.numVowels(word)); 26 | }); 27 | 28 | gauge.step("Almost all words have vowels ", function(table) { 29 | table.rows.forEach(function (row) { 30 | assert.equal(vowels.numVowels(row.cells[0]), parseInt(row.cells[1])); 31 | }); 32 | }); 33 | 34 | // --------------- 35 | // Execution Hooks 36 | // --------------- 37 | 38 | gauge.hooks.beforeScenario(function () { 39 | assert.equal(vowels.vowelList.join(""), "aeiou"); 40 | }); 41 | 42 | gauge.hooks.beforeScenario(function () { 43 | assert.equal(vowels.vowelList[0], "a"); 44 | }, { tags: [ "single word" ]}); 45 | -------------------------------------------------------------------------------- /examples/tests/vowels.js: -------------------------------------------------------------------------------- 1 | const vowelList = ["a", "e", "i", "o", "u"]; 2 | 3 | const numVowels = function (word) { 4 | const vowelArr = word.split("").filter(function (elem) { 5 | return vowelList.indexOf(elem) > -1; 6 | }); 7 | return vowelArr.length; 8 | }; 9 | 10 | export default { 11 | vowelList: vowelList, 12 | numVowels: numVowels 13 | }; 14 | -------------------------------------------------------------------------------- /index.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | node index.js %1 3 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | 3 | const minNodeVersion = 18; 4 | const version = process.versions.node.split("."); 5 | if (Number.parseInt(version[0]) < minNodeVersion) { 6 | throw new Error( 7 | `gauge-js requires Node.js version ${minNodeVersion}+. Current version: ${process.versions.node}`, 8 | ); 9 | } 10 | 11 | import child_process from "node:child_process"; 12 | import fs from "node:fs"; 13 | import path from "node:path"; 14 | import { fileURLToPath } from "node:url"; 15 | const __filename = fileURLToPath(import.meta.url); 16 | const __dirname = path.dirname(__filename); 17 | 18 | 19 | const skeldir = path.join(__dirname, "skel"); 20 | const srcdir = path.join(process.env.GAUGE_PROJECT_ROOT, "tests"); 21 | const envdir = path.join(process.env.GAUGE_PROJECT_ROOT, "env", "default"); 22 | const testCode = "step_implementation.js"; 23 | const jsPropertyFile = "js.properties"; 24 | 25 | if (process.argv[2] === "--init") { 26 | console.log("Initialising Gauge JavaScript project"); 27 | fs.mkdir(srcdir, 484, (err) => { 28 | if (err && err.code !== "EEXIST") { 29 | console.error(err); 30 | } else { 31 | fs.createReadStream(path.join(skeldir, testCode)).pipe( 32 | fs.createWriteStream(path.join(srcdir, testCode)), 33 | ); 34 | } 35 | }); 36 | 37 | fs.mkdir(path.dirname(envdir), 484, (err) => { 38 | if (err && err.code !== "EEXIST") { 39 | console.error(err); 40 | } else { 41 | fs.mkdir(envdir, 484, (err) => { 42 | if (err && err.code !== "EEXIST") { 43 | console.error(err); 44 | } else { 45 | fs.createReadStream(path.join(skeldir, jsPropertyFile)).pipe( 46 | fs.createWriteStream(path.join(envdir, jsPropertyFile)), 47 | ); 48 | } 49 | }); 50 | } 51 | }); 52 | } else if (process.argv[2] === "--start") { 53 | let args = ["./src/gauge.js", "--run"]; 54 | if (process.env.gauge_nodejs_args) { 55 | args = process.env.gauge_nodejs_args.split(" ").concat(args); 56 | } 57 | const cmd = process.env.gauge_nodejs_bin || "node"; 58 | const runner = child_process.spawn(cmd, args, { 59 | env: process.env, 60 | silent: false, 61 | stdio: "inherit", 62 | }); 63 | process.on("beforeExit", (code) => { 64 | try { 65 | if (!runner.killed) { 66 | runner.kill("SIGINT"); 67 | } 68 | } finally { 69 | process.exit(code); 70 | } 71 | }); 72 | runner.on("error", (err) => { 73 | console.trace(err.stack); 74 | }); 75 | } 76 | -------------------------------------------------------------------------------- /js.json: -------------------------------------------------------------------------------- 1 | { 2 | "run": { 3 | "windows": [ 4 | "index.bat", 5 | "--start" 6 | ], 7 | "darwin": [ 8 | "./index.js", 9 | "--start" 10 | ], 11 | "linux": [ 12 | "./index.js", 13 | "--start" 14 | ] 15 | }, 16 | "description": "Javascript support for gauge", 17 | "lib": "libs", 18 | "postInstall": { 19 | "windows": [ 20 | "npm", 21 | "install", 22 | "--omit=dev", 23 | "--silent" 24 | ], 25 | "darwin": [ 26 | "npm", 27 | "install", 28 | "--omit=dev", 29 | "--silent" 30 | ], 31 | "linux": [ 32 | "npm", 33 | "install", 34 | "--omit=dev", 35 | "--silent" 36 | ] 37 | }, 38 | "gRPCSupport": true, 39 | "lspLangId": "javascript", 40 | "init": { 41 | "windows": [ 42 | "index.bat", 43 | "--init" 44 | ], 45 | "darwin": [ 46 | "./index.js", 47 | "--init" 48 | ], 49 | "linux": [ 50 | "./index.js", 51 | "--init" 52 | ] 53 | }, 54 | "version": "5.0.1", 55 | "gaugeVersionSupport": { 56 | "minimum": "1.0.7", 57 | "maximum": "" 58 | }, 59 | "install": { 60 | "windows": [], 61 | "darwin": [], 62 | "linux": [] 63 | }, 64 | "id": "js" 65 | } 66 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gauge-js", 3 | "version": "5.0.1", 4 | "type": "module", 5 | "engines": { 6 | "node": ">=18" 7 | }, 8 | "description": "JavaScript runner for Gauge", 9 | "main": "index.js", 10 | "scripts": { 11 | "lint": "eslint index.js src/ skel/ test/ scripts/", 12 | "test": "npm run lint && mocha --recursive", 13 | "installPlugin": "node scripts/install.js", 14 | "package": "node scripts/install.js --package" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "https://github.com/getgauge/gauge-js.git" 19 | }, 20 | "keywords": [ 21 | "gauge", 22 | "JavaScript" 23 | ], 24 | "author": "ThoughtWorks, Inc", 25 | "license": "MIT", 26 | "bugs": { 27 | "url": "https://github.com/getgauge/gauge-js/issues" 28 | }, 29 | "homepage": "https://github.com/getgauge/gauge-js", 30 | "dependencies": { 31 | "@grpc/grpc-js": "^1.13.4", 32 | "@grpc/proto-loader": "^0.7.15", 33 | "escodegen": "^2.0.0", 34 | "esprima": "^4.0.1", 35 | "estraverse": "^5.3.0", 36 | "klaw-sync": "^7.0.0", 37 | "protobufjs": "^7.5.3", 38 | "q": "^1.5.1" 39 | }, 40 | "devDependencies": { 41 | "archiver": "^7.0.1", 42 | "chai": "^5.2.0", 43 | "check-if-windows": "^1.0.0", 44 | "eslint": "^9.28.0", 45 | "eslint-plugin-mocha": "^11.1.0", 46 | "fs-extra": "^11.3.0", 47 | "mocha": "^11.5.0", 48 | "mock-tmp": "^0.0.4", 49 | "sinon": "^20.0.0" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /scripts/install.js: -------------------------------------------------------------------------------- 1 | import { readFileSync } from "node:fs"; 2 | import fs from "fs-extra"; 3 | import path from "node:path"; 4 | import archiver from "archiver"; 5 | import child_process from "node:child_process"; 6 | const CWD = process.cwd(); 7 | 8 | const localPath = (relativePath) => 9 | relativePath ? path.resolve(CWD, relativePath) : path.resolve(CWD); 10 | 11 | const plugin = JSON.parse(readFileSync(localPath("./js.json"), "utf8")); 12 | 13 | const cleanDir = (dirPath) => { 14 | try { 15 | fs.removeSync(dirPath); 16 | } catch (err) { 17 | console.error("Error removing directory: %s", err.message); 18 | } 19 | }; 20 | 21 | const createDir = (dirPath) => { 22 | try { 23 | fs.ensureDirSync(dirPath); 24 | } catch (err) { 25 | console.error("Error creating directory: %s", err.message); 26 | } 27 | }; 28 | 29 | const recreateDir = (dirPath) => { 30 | cleanDir(dirPath); 31 | createDir(dirPath); 32 | }; 33 | 34 | const prepareFiles = () => { 35 | const buildDir = localPath("build"), 36 | copyList = [ 37 | "gauge-proto", 38 | "src", 39 | "skel", 40 | "index.js", 41 | "index.bat", 42 | "debug.bat", 43 | "js.json", 44 | "package.json", 45 | "package-lock.json", 46 | ".node-inspectorrc", 47 | "README.md", 48 | ]; 49 | try { 50 | console.log("Installing dependencies..."); 51 | fs.removeSync("./node_modules"); 52 | child_process.execSync("npm install --omit=dev", {cwd: localPath()}); 53 | } catch (err) { 54 | console.error("Error installing dependencies: %s", err.toString()); 55 | console.error(err.stack); 56 | } 57 | copyList.push("node_modules"); 58 | 59 | recreateDir(buildDir); 60 | 61 | try { 62 | console.log("Updating git submodules..."); 63 | child_process.execSync("git submodule update --init --recursive", { 64 | cwd: localPath(), 65 | }); 66 | } catch (err) { 67 | console.error("Error updating submodules: %s", err.toString()); 68 | console.error(err.stack); 69 | } 70 | 71 | copyList.forEach((item) => { 72 | try { 73 | fs.copySync(localPath(item), path.join(buildDir, item), { 74 | clobber: true, 75 | filter: (f) => !/(\/.git|^\/build)/.test(f.split(localPath())[1]), 76 | }); 77 | } catch (err) { 78 | console.error( 79 | "Failed to copy %s to build directory: %s", 80 | item, 81 | err.message, 82 | ); 83 | console.error(err.stack); 84 | } 85 | }); 86 | 87 | try { 88 | fs.removeSync(path.join(buildDir, "gauge-proto", ".git")); 89 | } catch (err) { 90 | console.error("Failed to remove .git in gauge-proto: %s", err.message); 91 | console.error(err.stack); 92 | } 93 | }; 94 | 95 | const createPackage = (callback) => { 96 | const zip = archiver("zip"), 97 | deployDir = localPath("deploy"), 98 | buildDir = localPath("build"), 99 | packageFile = `gauge-${plugin.id}-${plugin.version}.zip`; 100 | 101 | callback = callback || (() => { 102 | }); 103 | 104 | recreateDir(deployDir); 105 | prepareFiles(); 106 | 107 | const packageStream = fs.createWriteStream(path.join(deployDir, packageFile)); 108 | 109 | zip.on("error", (err) => { 110 | throw err; 111 | }); 112 | 113 | packageStream.on("close", () => { 114 | console.log("Created: %s", path.join("deploy", packageFile)); 115 | console.log( 116 | "To install this plugin, run:\n\t$ gauge install js --file %s", 117 | path.join("deploy", packageFile), 118 | ); 119 | typeof callback == "function" && callback(path.join(deployDir, packageFile)); 120 | }); 121 | 122 | zip.pipe(packageStream); 123 | 124 | zip.directory(buildDir, "/").finalize(); 125 | }; 126 | 127 | const installPluginFiles = () => { 128 | createPackage((packageFilePath) => { 129 | let log; 130 | 131 | try { 132 | log = child_process.execSync( 133 | `gauge uninstall ${plugin.id} --version "${plugin.version}"`, 134 | ); 135 | console.log(log.toString()); 136 | } catch (err) { 137 | console.error("Could not uninstall existing plugin: %s", err.message); 138 | } 139 | 140 | try { 141 | log = child_process.execSync( 142 | `gauge install ${plugin.id} --file "${packageFilePath}"`, 143 | ); 144 | console.log(log.toString()); 145 | } catch (err) { 146 | console.error("Failed to install plugin: %s", err.message); 147 | console.error(err.stack); 148 | process.exit(1); 149 | } 150 | }); 151 | }; 152 | 153 | if (process.argv[2] === "--package") { 154 | createPackage(false); 155 | } else { 156 | installPluginFiles(false); 157 | } 158 | -------------------------------------------------------------------------------- /skel/js.properties: -------------------------------------------------------------------------------- 1 | #js.properties 2 | #settings related to gauge-js. 3 | 4 | test_timeout = 1000 5 | 6 | # Change this to true to enable debugging support 7 | DEBUG = false 8 | 9 | # Comma seperated list of dirs. path should be relative to project root. 10 | STEP_IMPL_DIR = tests -------------------------------------------------------------------------------- /skel/step_implementation.js: -------------------------------------------------------------------------------- 1 | /* globals step,beforeScenario*/ 2 | 3 | "use strict"; 4 | 5 | import assert from "node:assert"; 6 | 7 | var vowels = ["a", "e", "i", "o", "u"]; 8 | 9 | var numberOfVowels = function (word) { 10 | var vowelArr = word.split("").filter(function (elem) { return vowels.indexOf(elem) > -1; }); 11 | return vowelArr.length; 12 | }; 13 | 14 | // -------------------------- 15 | // Gauge step implementations 16 | // -------------------------- 17 | 18 | step("Vowels in English language are .", function (vowelsGiven) { 19 | assert.equal(vowelsGiven, vowels.join("")); 20 | }); 21 | 22 | step("The word has vowels.", function (word, number) { 23 | assert.equal(number, numberOfVowels(word)); 24 | }); 25 | 26 | step("Almost all words have vowels
", function (table) { 27 | table.rows.forEach(function (row) { 28 | assert.equal(numberOfVowels(row.cells[0]), parseInt(row.cells[1])); 29 | }); 30 | }); 31 | 32 | // --------------- 33 | // Execution Hooks 34 | // --------------- 35 | 36 | beforeScenario(function () { 37 | assert.equal(vowels.join(""), "aeiou"); 38 | }); 39 | 40 | beforeScenario(function () { 41 | assert.equal(vowels[0], "a"); 42 | }, { tags: ["single word"] }); 43 | -------------------------------------------------------------------------------- /src/custom-message-registry.js: -------------------------------------------------------------------------------- 1 | var CustomMessageRegistry = function () { 2 | this.messages = []; 3 | }; 4 | 5 | CustomMessageRegistry.prototype.add = function (msg) { 6 | this.messages.push(msg); 7 | return this.messages; 8 | }; 9 | 10 | CustomMessageRegistry.prototype.get = function () { 11 | return this.messages; 12 | }; 13 | 14 | CustomMessageRegistry.prototype.clear = function () { 15 | this.messages = []; 16 | return this.messages; 17 | }; 18 | 19 | export default new CustomMessageRegistry(); 20 | -------------------------------------------------------------------------------- /src/custom-screenshot-registry.js: -------------------------------------------------------------------------------- 1 | import screenshot from "./screenshot.js"; 2 | 3 | var ScreenshotFactory = function () { 4 | this.screenshots = []; 5 | }; 6 | 7 | ScreenshotFactory.prototype.add = function () { 8 | var bytePromise = screenshot.capture(); 9 | this.screenshots.push(bytePromise); 10 | }; 11 | 12 | ScreenshotFactory.prototype.get = function () { 13 | return Promise.all(this.screenshots); 14 | }; 15 | 16 | ScreenshotFactory.prototype.clear = function () { 17 | this.screenshots = []; 18 | }; 19 | 20 | export default new ScreenshotFactory(); 21 | -------------------------------------------------------------------------------- /src/data-store-factory.js: -------------------------------------------------------------------------------- 1 | var DataStore = function () { 2 | this.store = {}; 3 | }; 4 | 5 | DataStore.prototype.put = function (key, val) { 6 | this.store[key] = val; 7 | }; 8 | 9 | DataStore.prototype.get = function (key) { 10 | return this.store[key] !== undefined ? this.store[key] : null; 11 | }; 12 | 13 | DataStore.prototype.clear = function () { 14 | this.store = {}; 15 | }; 16 | 17 | var DataStoreFactory = function () { 18 | 19 | this.suiteStore = new DataStore(); 20 | this.specStore = new DataStore(); 21 | this.scenarioStore = new DataStore(); 22 | 23 | }; 24 | 25 | export default new DataStoreFactory(); 26 | -------------------------------------------------------------------------------- /src/executor.js: -------------------------------------------------------------------------------- 1 | import Q from "q"; 2 | import Table from "./table.js"; 3 | import factory from "./response-factory.js"; 4 | import Test from "./test.js"; 5 | import screenshot from "./screenshot.js"; 6 | import stepRegistry from "./step-registry.js"; 7 | import hookRegistry from "./hook-registry.js"; 8 | import customScreenshotRegistry from "./custom-screenshot-registry.js"; 9 | import customMessageRegistry from "./custom-message-registry.js"; 10 | import logger from "./logger.js"; 11 | 12 | 13 | /* If test_timeout env variable is not available set the default to 1000ms */ 14 | var timeout = process.env.test_timeout || 1000; 15 | 16 | // Source: http://stackoverflow.com/a/26034767/575242 17 | var hasIntersection = function (arr1, arr2) { 18 | var intArr = arr1.filter(function (elem) { return arr2.indexOf(elem) > -1; }); 19 | return intArr.length; 20 | }; 21 | 22 | var filterHooks = function (hooks, tags) { 23 | return hooks.filter(function (hook) { 24 | var hookTags = (hook.options && hook.options.tags) ? hook.options.tags : []; 25 | var hookOperator = (hook.options && hook.options.operator) ? hook.options.operator : "AND"; 26 | if (!hookTags.length) { 27 | return true; 28 | } 29 | var matched = hasIntersection(tags, hookTags); 30 | switch (hookOperator) { 31 | case "AND": 32 | return matched === hookTags.length; 33 | case "OR": 34 | return matched > 0; 35 | } 36 | return false; 37 | }); 38 | }; 39 | 40 | 41 | var executeStep = function (executeStepRequest) { 42 | var deferred = Q.defer(); 43 | 44 | var parsedStepText = executeStepRequest.parsedStepText; 45 | 46 | var parameters = executeStepRequest.parameters.map(function (item) { 47 | return item.value ? item.value : item.table? new Table(item.table) : ""; 48 | }); 49 | 50 | var step = stepRegistry.get(parsedStepText); 51 | new Test(step.fn, parameters, timeout).run().then( 52 | function (result) { 53 | var screenshotPromises = customScreenshotRegistry.get(); 54 | var msgs = customMessageRegistry.get(); 55 | customScreenshotRegistry.clear(); 56 | customMessageRegistry.clear(); 57 | screenshotPromises.then(function (screenshots) { 58 | var response = factory.createExecutionStatusResponse(false, result.duration, false, msgs, "", step.options.continueOnFailure, screenshots); 59 | deferred.resolve(response); 60 | }); 61 | }, 62 | 63 | function (result) { 64 | var screenshotPromises = customScreenshotRegistry.get(); 65 | var msgs = customMessageRegistry.get(); 66 | customScreenshotRegistry.clear(); 67 | customMessageRegistry.clear(); 68 | screenshotPromises.then(function (screenshots) { 69 | var errorResponse = factory.createExecutionStatusResponse(true, result.duration, result.exception, msgs, "", step.options.continueOnFailure, screenshots); 70 | if (process.env.screenshot_on_failure !== "false") { 71 | screenshot.capture().then(function (screenshotFile) { 72 | errorResponse.executionResult.failureScreenshotFile = screenshotFile; 73 | deferred.reject(errorResponse); 74 | }).catch(function(error){ 75 | logger.error("\nFailed to capture screenshot on failure.\n" + error); 76 | deferred.reject(errorResponse); 77 | }); 78 | }else{ 79 | deferred.reject(errorResponse); 80 | } 81 | }); 82 | } 83 | ); 84 | 85 | return deferred.promise; 86 | }; 87 | 88 | var executeHook = function (hookLevel, currentExecutionInfo) { 89 | var deferred = Q.defer(), 90 | tags = [], 91 | timestamp = Date.now(); 92 | 93 | if (currentExecutionInfo) { 94 | var specTags = currentExecutionInfo.currentSpec ? currentExecutionInfo.currentSpec.tags : []; 95 | var sceanarioTags = currentExecutionInfo.currentScenario ? currentExecutionInfo.currentScenario.tags : []; 96 | tags = specTags.concat(sceanarioTags); 97 | } 98 | 99 | var hooks = hookRegistry.get(hookLevel); 100 | var filteredHooks = hooks.length ? filterHooks(hooks, tags) : []; 101 | 102 | if (!filteredHooks.length) { 103 | deferred.resolve(factory.createExecutionStatusResponse(false, Date.now() - timestamp)); 104 | return deferred.promise; 105 | } 106 | 107 | var number = 0; 108 | var onPass = function (result) { 109 | if (number === filteredHooks.length - 1) { 110 | var response = factory.createExecutionStatusResponse(false, result.duration); 111 | deferred.resolve(response); 112 | } 113 | number++; 114 | }; 115 | 116 | var onError = function (result) { 117 | var errorResponse = factory.createExecutionStatusResponse(true, result.duration, result.exception); 118 | if (process.env.screenshot_on_failure !== "false") { 119 | screenshot.capture().then(function (screenshotFile) { 120 | errorResponse.executionResult.failureScreenshotFile = screenshotFile; 121 | deferred.reject(errorResponse); 122 | }).catch(function(error){ 123 | logger.error("\nFailed to capture screenshot on failure.\n" + error); 124 | deferred.reject(errorResponse); 125 | }); 126 | } else { 127 | deferred.reject(errorResponse); 128 | } 129 | }; 130 | 131 | for (var i = 0; i < filteredHooks.length; i++) { 132 | new Test(filteredHooks[i].fn, [currentExecutionInfo], timeout).run().then(onPass, onError); 133 | } 134 | 135 | return deferred.promise; 136 | }; 137 | 138 | 139 | export default { 140 | step: executeStep, 141 | hook: executeHook 142 | }; 143 | -------------------------------------------------------------------------------- /src/file-util.js: -------------------------------------------------------------------------------- 1 | import fs from "node:fs"; 2 | import path from "node:path"; 3 | import klawSync from "klaw-sync"; 4 | import logger from "./logger.js"; 5 | 6 | function isJSFile(file) { 7 | return path.extname(file) === ".js"; 8 | } 9 | 10 | function collectFilesIn(dir) { 11 | return klawSync(dir, { nodir:true, filter: function (item) { return item.stats.isDirectory() || isJSFile(item.path); } }).map(function (item) { 12 | return item.path; 13 | }); 14 | } 15 | 16 | function getImplDirs() { 17 | const projectRoot = process.env.GAUGE_PROJECT_ROOT; 18 | if (process.env.STEP_IMPL_DIR) { 19 | return process.env.STEP_IMPL_DIR.split(",").map(function (dir) { 20 | return path.join(projectRoot, dir.trim()); 21 | }); 22 | } 23 | return [path.join(projectRoot, "tests")]; 24 | } 25 | 26 | 27 | function getListOfFiles() { 28 | return getImplDirs().reduce(function (files, dir) { 29 | if (!fs.existsSync(dir)) { 30 | logger.info("Failed to load implementations from " + dir); 31 | return files; 32 | } 33 | return files.concat(collectFilesIn(dir)); 34 | }, []); 35 | } 36 | 37 | function isSameFilePath(filePath1, filePath2) { 38 | return path.relative(filePath1, filePath2) === ""; 39 | } 40 | 41 | function getFileName(dir, counter = 0) { 42 | const tmpl = counter && "step_implementation_" + counter + ".js" || "step_implementation.js"; 43 | const fileName = path.join(dir, tmpl); 44 | if (!fs.existsSync(fileName)) { 45 | return fileName; 46 | } 47 | return getFileName(dir, ++counter); 48 | } 49 | 50 | function isInImplDir(filePath) { 51 | return getImplDirs().findIndex((implDir) => { 52 | if (path.normalize(filePath).startsWith(path.normalize(implDir))) { 53 | return true; 54 | } 55 | }) !== -1; 56 | } 57 | 58 | function parseJsonFileSyncSafe(filePath, encoding) { 59 | try { 60 | return JSON.parse(fs.readFileSync(filePath, encoding || "utf8")); 61 | } catch (e) { 62 | return {}; 63 | } 64 | } 65 | 66 | export default { 67 | getImplDirs: getImplDirs, 68 | getListOfFiles: getListOfFiles, 69 | isSameFilePath: isSameFilePath, 70 | getFileName: getFileName, 71 | isJSFile: isJSFile, 72 | isInImplDir: isInImplDir, 73 | parseJsonFileSyncSafe: parseJsonFileSyncSafe, 74 | }; 75 | -------------------------------------------------------------------------------- /src/gauge-global.js: -------------------------------------------------------------------------------- 1 | import hookRegistry from "./hook-registry.js"; 2 | import customMessageRegistry from "./custom-message-registry.js"; 3 | import dataStore from "./data-store-factory.js"; 4 | import stepRegistry from "./step-registry.js"; 5 | import customScreenshotFactory from "./custom-screenshot-registry.js"; 6 | 7 | const gauge = {hooks: {}, dataStore: dataStore}; 8 | 9 | export const step = function (stepName, options, stepFunction) { 10 | if (!stepName || !stepName.length) { 11 | throw new Error("Step text cannot be empty"); 12 | } 13 | if (typeof options === "function" && !!options.call && !!options.apply) { 14 | stepFunction = options; 15 | options = {continueOnFailure: false}; 16 | } 17 | 18 | const filepath = process.env.GAUGE_STEPFILEPATH; 19 | if (typeof stepName === "object" && !!stepName.length) { 20 | stepRegistry.addAlias(stepName, stepFunction, filepath, {}, options); 21 | } else if (typeof stepName === "string") { 22 | stepRegistry.add(stepName, stepFunction, filepath, {}, options); 23 | } 24 | }; 25 | 26 | const hooks = {}; 27 | 28 | hookRegistry.types.forEach(function (type) { 29 | hooks[type] = function (fn, options) { 30 | hookRegistry.add(type, fn, options); 31 | }; 32 | }); 33 | 34 | gauge.message = function (msg) { 35 | if (typeof msg === "string") { 36 | customMessageRegistry.add(msg); 37 | } 38 | }; 39 | 40 | gauge.screenshotFn = null; 41 | gauge.customScreenshotWriter = null; 42 | 43 | gauge.screenshot = function() { 44 | customScreenshotFactory.add(); 45 | }; 46 | 47 | export default { 48 | gauge: gauge, 49 | step: step, 50 | hooks: hooks, 51 | }; 52 | -------------------------------------------------------------------------------- /src/gauge.js: -------------------------------------------------------------------------------- 1 | import path from "node:path"; 2 | import { fileURLToPath } from "node:url"; 3 | import protoLoader from "@grpc/proto-loader"; 4 | import protobuf from "protobufjs"; 5 | import gaugeGlobal from "./gauge-global.js"; 6 | const __filename = fileURLToPath(import.meta.url); 7 | const __dirname = path.dirname(__filename); 8 | import loader from "./static-loader.js"; 9 | const PROTO_PATH = `${__dirname}/../gauge-proto/services.proto`; 10 | import grpc from "@grpc/grpc-js"; 11 | 12 | const packageDefinition = protoLoader.loadSync(PROTO_PATH, { 13 | keepCase: true, 14 | longs: String, 15 | enums: String, 16 | defaults: true, 17 | oneofs: true, 18 | }); 19 | 20 | const servicesProto = 21 | grpc.loadPackageDefinition(packageDefinition).gauge.messages; 22 | 23 | import logger from "./logger.js"; 24 | import ServiceHandlers from "./serviceHandlers.js"; 25 | 26 | function run() { 27 | global.gauge = gaugeGlobal.gauge; 28 | protobuf 29 | .load(path.resolve("gauge-proto/messages.proto")) 30 | .then((root) => { 31 | const errorType = root.lookupEnum( 32 | "gauge.messages.StepValidateResponse.ErrorType", 33 | ); 34 | const fileStatus = root.lookupEnum( 35 | "gauge.messages.CacheFileRequest.FileStatus", 36 | ); 37 | return { errorType: errorType, fileStatus: fileStatus }; 38 | }) 39 | .catch((e) => { 40 | logger.error(`Failed while loading runner.\n${e}`); 41 | process.exit(); 42 | }) 43 | .then((types) => { 44 | loader.load(); 45 | const server = new grpc.Server(); 46 | server.addService( 47 | servicesProto.Runner.service, 48 | new ServiceHandlers(server, types), 49 | ); 50 | server.bindAsync( 51 | "127.0.0.1:0", 52 | grpc.ServerCredentials.createInsecure(), 53 | (err, port) => { 54 | if (!err) { 55 | logger.info(`Listening on port:${port}`); 56 | } else { 57 | logger.error(err); 58 | process.exit(); 59 | } 60 | }, 61 | ); 62 | }) 63 | .catch((e) => { 64 | logger.error(`${e.message}\n${e.stack}`); 65 | }); 66 | } 67 | 68 | if (process.argv[2] === "--run") { 69 | run(); 70 | } 71 | 72 | export default { 73 | run: run, 74 | }; 75 | -------------------------------------------------------------------------------- /src/hook-registry.js: -------------------------------------------------------------------------------- 1 | var HookRegistry = function () { 2 | this.registry = {}; 3 | }; 4 | 5 | var InvalidHookException = function (message) { 6 | this.message = message; 7 | this.name = "InvalidHookException"; 8 | }; 9 | 10 | HookRegistry.prototype.types = [ 11 | "beforeSuite", 12 | "afterSuite", 13 | "beforeSpec", 14 | "afterSpec", 15 | "beforeScenario", 16 | "afterScenario", 17 | "beforeStep", 18 | "afterStep" 19 | ]; 20 | 21 | HookRegistry.prototype.add = function (hookName, hookFn, options) { 22 | if (!hookName) { 23 | throw new InvalidHookException("Need a hook name"); 24 | } 25 | 26 | if (this.types.indexOf(hookName) < 0) { 27 | throw new InvalidHookException("Invalid hook name: " + hookName); 28 | } 29 | 30 | this.registry[hookName] = this.registry[hookName] || []; 31 | this.registry[hookName].push({"fn": hookFn, "options": options}); 32 | }; 33 | 34 | HookRegistry.prototype.get = function (hookName) { 35 | return this.registry[hookName] ? this.registry[hookName] : []; 36 | }; 37 | 38 | HookRegistry.prototype.clear = function () { 39 | this.registry = {}; 40 | }; 41 | 42 | export default new HookRegistry(); 43 | -------------------------------------------------------------------------------- /src/impl-loader.js: -------------------------------------------------------------------------------- 1 | import fileUtil from "./file-util.js"; 2 | import VM from "./vm.js"; 3 | var loaded = false; 4 | 5 | function loadImpl(stepRegistry) { 6 | return new Promise((resolve)=>{ 7 | if(loaded) {return resolve();} 8 | stepRegistry.clear(); 9 | var vm = new VM(); 10 | fileUtil.getListOfFiles().forEach(function(filePath) { 11 | process.env.GAUGE_STEPFILEPATH = filePath; 12 | vm.contextify(filePath, process.env.GAUGE_PROJECT_ROOT); 13 | vm.runFile(filePath); 14 | }); 15 | loaded = true; 16 | resolve(); 17 | }); 18 | } 19 | 20 | export default { 21 | load: loadImpl 22 | }; 23 | -------------------------------------------------------------------------------- /src/logger.js: -------------------------------------------------------------------------------- 1 | function _print(level, message, isError = false) { 2 | var log = isError ? console.error : console.log; 3 | log(JSON.stringify({ logLevel: level, message: message })); 4 | } 5 | 6 | function debug(message) { 7 | _print("debug", message); 8 | } 9 | 10 | function info(message) { 11 | _print("info", message); 12 | } 13 | 14 | function error(message) { 15 | _print("error", message, true); 16 | } 17 | 18 | function fatal(message) { 19 | _print("fatal", message, true); 20 | process.exit(1); 21 | } 22 | 23 | export default { 24 | debug: debug, info: info, error: error, fatal: fatal 25 | }; 26 | -------------------------------------------------------------------------------- /src/message-processor.js: -------------------------------------------------------------------------------- 1 | import fs from "node:fs"; 2 | import path from "node:path"; 3 | import factory from "./response-factory.js"; 4 | import stepRegistry from "./step-registry.js"; 5 | import customMessageRegistry from "./custom-message-registry.js"; 6 | import executor from "./executor.js"; 7 | import refactor from "./refactor.js"; 8 | import dataStore from "./data-store-factory.js"; 9 | import impl_loader from "./impl-loader.js"; 10 | import loader from "./static-loader.js"; 11 | import inspector from "node:inspector"; 12 | import fileUtil from "./file-util.js"; 13 | import customScreenshotRegistry from "./custom-screenshot-registry.js"; 14 | import logger from "./logger.js"; 15 | 16 | const config = fileUtil.parseJsonFileSyncSafe("./package.json", "utf8"); 17 | 18 | const ATTACH_DEBUGGER_EVENT = "Runner Ready for Debugging"; 19 | 20 | const GAUGE_PROJECT_ROOT = process.env.GAUGE_PROJECT_ROOT; 21 | 22 | export const processCustomMessages = function (response) { 23 | const msgs = customMessageRegistry.get(); 24 | response.executionResult.message = response.executionResult.message.concat(msgs); 25 | customMessageRegistry.clear(); 26 | return response; 27 | }; 28 | 29 | export const processScreenshots = function (response) { 30 | const screenshotPromises = customScreenshotRegistry.get(); 31 | return screenshotPromises.then(function (screenshotFiles) { 32 | response.executionResult.screenshotFiles = response.executionResult.screenshotFiles.concat(screenshotFiles); 33 | customScreenshotRegistry.clear(); 34 | }); 35 | }; 36 | 37 | export function executionResponse(isFailed, executionTime) { 38 | return factory.createExecutionStatusResponse(isFailed, executionTime); 39 | } 40 | 41 | export function successExecutionStatus() { 42 | return executionResponse(false, 0); 43 | } 44 | 45 | export function executeStep(request, callback) { 46 | const promise = executor.step(request); 47 | promise.then( 48 | function (value) { 49 | callback(value); 50 | }, 51 | function (reason) { 52 | callback(reason); 53 | } 54 | ); 55 | } 56 | 57 | export function executeHook(hookName, currentExecutionInfo, callback) { 58 | const promise = executor.hook(hookName, currentExecutionInfo); 59 | promise.then( 60 | function (response) { 61 | processCustomMessages(response); 62 | processScreenshots(response).then(function () { 63 | callback(response); 64 | }); 65 | }, 66 | function (reason) { 67 | processCustomMessages(reason); 68 | processScreenshots(reason).then(function () { 69 | callback(reason); 70 | }); 71 | } 72 | ); 73 | } 74 | 75 | export function startExecution(executionStartingRequest, callback) { 76 | impl_loader.load(stepRegistry).then(() => { 77 | executeHook("beforeSuite", executionStartingRequest.currentExecutionInfo, callback); 78 | }); 79 | } 80 | 81 | export function executeBeforeSuiteHook(executionStartingRequest, callback) { 82 | if (process.env.DEBUGGING) { 83 | const port = parseInt(process.env.DEBUG_PORT); 84 | logger.info(ATTACH_DEBUGGER_EVENT); 85 | inspector.open(port, "127.0.0.1", true); 86 | const inspectorWaitTime = 1000; 87 | setTimeout(function () { startExecution(executionStartingRequest, callback); }, inspectorWaitTime); 88 | } else { 89 | startExecution(executionStartingRequest, callback); 90 | } 91 | } 92 | 93 | export function executeBeforeSpecHook(specExecutionStartingRequest, callback) { 94 | executeHook("beforeSpec", specExecutionStartingRequest.currentExecutionInfo, callback); 95 | } 96 | 97 | export function executeBeforeScenarioHook(scenarioExecutionStartingRequest, callback) { 98 | executeHook("beforeScenario", scenarioExecutionStartingRequest.currentExecutionInfo, callback); 99 | } 100 | 101 | export function executeBeforeStepHook(stepExecutionStartingRequest, callback) { 102 | customMessageRegistry.clear(); 103 | executeHook("beforeStep", stepExecutionStartingRequest.currentExecutionInfo, callback); 104 | } 105 | 106 | export function executeAfterSuiteHook(executionEndingRequest, callback) { 107 | executeHook("afterSuite", executionEndingRequest.currentExecutionInfo, function (data) { 108 | dataStore.suiteStore.clear(); 109 | callback(data); 110 | }); 111 | if (process.env.DEBUGGING) { 112 | inspector.close(); 113 | } 114 | } 115 | 116 | export function executeAfterSpecHook(specExecutionEndingRequest, callback) { 117 | executeHook("afterSpec", specExecutionEndingRequest.currentExecutionInfo, function (data) { 118 | dataStore.specStore.clear(); 119 | callback(data); 120 | }); 121 | } 122 | 123 | export function executeAfterScenarioHook(scenarioExecutionEndingRequest, callback) { 124 | executeHook("afterScenario", scenarioExecutionEndingRequest.currentExecutionInfo, function (data) { 125 | dataStore.scenarioStore.clear(); 126 | callback(data); 127 | }); 128 | } 129 | 130 | export function executeAfterStepHook(stepExecutionEndingRequest, callback) { 131 | executeHook("afterStep", stepExecutionEndingRequest.currentExecutionInfo, callback); 132 | } 133 | 134 | export const getParamsList = function (params) { 135 | return params.map(function (p, i) { 136 | return "arg" + i.toString(); 137 | }).join(", "); 138 | }; 139 | 140 | export const generateImplStub = function (stepValue) { 141 | let argCount = 0; 142 | const stepText = stepValue.stepValue.replace(/{}/g, function () { 143 | return ""; 144 | }); 145 | return "step(\"" + stepText + "\", async function(" + getParamsList(stepValue.parameters) + ") {\n\t" + 146 | "throw 'Unimplemented Step';\n" + 147 | "});"; 148 | }; 149 | 150 | export const getSuggestionFor = function (request, validated) { 151 | if (validated.reason !== "notfound") { 152 | return ""; 153 | } 154 | return generateImplStub(request.stepValue); 155 | }; 156 | 157 | export const stepValidateResponse = function (stepValidateRequest, errorType) { 158 | const validated = stepRegistry.validate(stepValidateRequest.stepText); 159 | const suggestion = getSuggestionFor(stepValidateRequest, validated); 160 | return factory.createStepValidateResponse(errorType, validated, suggestion); 161 | }; 162 | 163 | export const stepNamesResponse = function () { 164 | return factory.createStepNamesResponse(stepRegistry.getStepTexts()); 165 | }; 166 | 167 | export const stepNameResponse = function (stepNameRequest) { 168 | const stepValue = stepNameRequest.stepValue; 169 | const response = factory.createStepNameResponse(); 170 | const step = stepRegistry.get(stepValue); 171 | if (step) { 172 | response.stepNameResponse.stepName = step.aliases; 173 | response.stepNameResponse.hasAlias = step.hasAlias; 174 | response.stepNameResponse.isStepPresent = true; 175 | response.stepNameResponse.fileName = step.fileLocations[0].filePath; 176 | response.stepNameResponse.span = step.fileLocations[0].span; 177 | } 178 | return response; 179 | }; 180 | 181 | export const stepPositions = function (stepPositionsRequest) { 182 | const filepath = stepPositionsRequest.filePath; 183 | return factory.createStepPositionsResponse(stepRegistry.getStepPositions(filepath)); 184 | }; 185 | 186 | export const implementationFiles = function () { 187 | return factory.createImplementationFileListResponse(fileUtil.getListOfFiles()); 188 | }; 189 | 190 | export const implementStubResponse = function (stubImplementationCodeRequest) { 191 | const response = factory.createFileDiff(); 192 | let filePath = stubImplementationCodeRequest.implementationFilePath; 193 | const codes = stubImplementationCodeRequest.codes; 194 | 195 | const reducer = function (accumulator, currentValue) { 196 | return accumulator + "\n" + currentValue; 197 | }; 198 | let content = codes.reduce(reducer); 199 | 200 | let fileLineCount = 0; 201 | if (fs.existsSync(filePath)) { 202 | let fileContent = fs.readFileSync(filePath, "utf8").replace("\r\n", "\n"); 203 | if (fileContent.trim().split("\n").length == fileContent.split("\n").length) { 204 | fileLineCount = fileContent.split("\n").length; 205 | content = "\n\n" + content; 206 | } else { 207 | fileLineCount = fileContent.split("\n").length; 208 | content = "\n" + content; 209 | } 210 | } else { 211 | filePath = fileUtil.getFileName(fileUtil.getImplDirs(GAUGE_PROJECT_ROOT)[0]); 212 | } 213 | 214 | const span = {start: fileLineCount, end: fileLineCount, startChar: 0, endChar: 0}; 215 | const textDiffs = [{span: span, content: content}]; 216 | response.fileDiff.filePath = filePath; 217 | response.fileDiff.textDiffs = textDiffs; 218 | return response; 219 | }; 220 | 221 | export const refactorResponse = function (request) { 222 | let response = factory.createRefactorResponse(); 223 | response = refactor(request, response); 224 | return response; 225 | }; 226 | 227 | export const cacheFileResponse = function (cacheFileRequest, fileStatus) { 228 | const filePath = cacheFileRequest.filePath; 229 | if (!fileUtil.isJSFile(filePath) || !fileUtil.isInImplDir(filePath)) { 230 | return; 231 | } 232 | let CHANGED, OPENED, CLOSED, CREATED; 233 | if (config.hasPureJsGrpc) { 234 | CHANGED = fileStatus.values.CHANGED; 235 | OPENED = fileStatus.values.OPENED; 236 | CLOSED = fileStatus.values.CLOSED; 237 | CREATED = fileStatus.values.CREATED; 238 | } else { 239 | CHANGED = fileStatus.valuesById[fileStatus.values.CHANGED]; 240 | OPENED = fileStatus.valuesById[fileStatus.values.OPENED]; 241 | CLOSED = fileStatus.valuesById[fileStatus.values.CLOSED]; 242 | CREATED = fileStatus.valuesById[fileStatus.values.CREATED]; 243 | } 244 | if (cacheFileRequest.status === CREATED) { 245 | if (!stepRegistry.isFileCached(filePath)) { 246 | loader.reloadFile(filePath, fs.readFileSync(filePath, "utf8")); 247 | } 248 | } else if (cacheFileRequest.status === CHANGED || cacheFileRequest.status === OPENED || ( 249 | cacheFileRequest.status === undefined && config.hasPureJsGrpc 250 | )) { 251 | loader.reloadFile(filePath, cacheFileRequest.content); 252 | } else if (cacheFileRequest.status === CLOSED && 253 | fs.existsSync(filePath)) { 254 | loader.reloadFile(filePath, fs.readFileSync(filePath, "utf8")); 255 | } else { 256 | loader.unloadFile(filePath); 257 | } 258 | }; 259 | 260 | export const implementationGlobPatternResponse = function () { 261 | const globPatterns = []; 262 | fileUtil.getImplDirs().forEach((dir) => { 263 | globPatterns.push(dir.split(path.sep).join("/") + "/**/*.js"); 264 | }); 265 | const response = factory.createImplementationFileGlobPatternResponse(globPatterns); 266 | return response; 267 | }; 268 | 269 | export default { 270 | stepNamesResponse: stepNamesResponse, 271 | cacheFileResponse: cacheFileResponse, 272 | stepPositions: stepPositions, 273 | implementationFiles: implementationFiles, 274 | implementStubResponse: implementStubResponse, 275 | stepValidateResponse: stepValidateResponse, 276 | refactorResponse: refactorResponse, 277 | stepNameResponse: stepNameResponse, 278 | implementationGlobPatternResponse: implementationGlobPatternResponse, 279 | successExecutionStatus, 280 | executeBeforeSuiteHook, 281 | executeAfterSuiteHook, 282 | executeBeforeSpecHook, 283 | executeAfterSpecHook, 284 | executeBeforeScenarioHook, 285 | executeAfterScenarioHook, 286 | executeBeforeStepHook, 287 | executeAfterStepHook, 288 | executeStep 289 | }; 290 | -------------------------------------------------------------------------------- /src/refactor.js: -------------------------------------------------------------------------------- 1 | import fs from "node:fs"; 2 | import esprima from "esprima"; 3 | import estraverse from "estraverse"; 4 | import escodegen from "escodegen"; 5 | import stepRegistry from "./step-registry.js"; 6 | import stepParser from "./step-parser.js"; 7 | 8 | var isArrowFunction = function (node) { 9 | return node.type === "ArrowFunctionExpression"; 10 | }; 11 | 12 | var isFunction = function (node) { 13 | return (node.type === "FunctionExpression" || isArrowFunction(node)); 14 | }; 15 | 16 | var getParamName = function (index, params) { 17 | var name = "arg" + index; 18 | if (!params.includes(name)) { 19 | return name; 20 | } 21 | return getParamName(++index, params); 22 | }; 23 | 24 | var processNode = function (node, req) { 25 | var li = node.arguments.length - 1; 26 | node.arguments[0].value = req.newStepValue.parameterizedStepValue; 27 | node.arguments[0].raw = "\"" + req.newStepValue.parameterizedStepValue + "\""; 28 | var asyncparams = node.arguments[li].params.slice(req.oldStepValue.parameters.length); 29 | var oldParams = node.arguments[li].params.slice(0, req.oldStepValue.parameters.length); 30 | var newParams = []; 31 | for (var i = 0, paramsPositionsSize = req.paramPositions.length; i < paramsPositionsSize; i++) { 32 | var oldPosition = req.paramPositions[i].oldPosition || 0; 33 | var newPosition = req.paramPositions[i].newPosition || 0; 34 | if (oldPosition < 0) { 35 | var paramName = getParamName(i, oldParams.map(function (p) { return p.name; })); 36 | newParams.splice(newPosition, 0, paramName); 37 | } else { 38 | newParams.splice(newPosition, 0, oldParams[oldPosition].name); 39 | } 40 | } 41 | node.arguments[li].params = newParams.map(function (param) { 42 | return { 43 | type: "Identifier", 44 | name: param 45 | }; 46 | }); 47 | node.arguments[li].params = node.arguments[li].params.concat(asyncparams); 48 | return node; 49 | }; 50 | 51 | 52 | var generateSignature = function (oldFunction, newfunction) { 53 | var signature = newfunction.params.map(function (param) { return escodegen.generate(param); }).join(", "); 54 | if (isArrowFunction(oldFunction)) { 55 | signature = "(" + signature + ") => "; 56 | } else { 57 | signature = "function (" + signature + ") "; 58 | } 59 | if (oldFunction.async) { 60 | signature = "async " + signature; 61 | } 62 | return signature; 63 | }; 64 | 65 | var getParamDiff = function (oldFunction, newFunction) { 66 | return { 67 | content: generateSignature(oldFunction, newFunction), 68 | span: { 69 | start: oldFunction.loc.start.line, 70 | startChar: oldFunction.loc.start.column, 71 | end: oldFunction.body.loc.start.line, 72 | endChar: oldFunction.body.loc.start.column, 73 | } 74 | }; 75 | }; 76 | 77 | var getSignatureDiff = function (newStep, oldStep) { 78 | return { 79 | content: newStep.raw, 80 | span: { 81 | start: oldStep.loc.start.line, 82 | end: oldStep.loc.end.line, 83 | startChar: oldStep.loc.start.column, 84 | endChar: oldStep.loc.end.column, 85 | } 86 | }; 87 | }; 88 | 89 | var createDiff = function (oldNode, newNode) { 90 | var oldStep = oldNode.arguments[0]; 91 | var newStep = newNode.arguments[0]; 92 | var oldFunction = oldNode.arguments[oldNode.arguments.length - 1]; 93 | var newFunction = newNode.arguments[oldNode.arguments.length - 1]; 94 | return [getSignatureDiff(newStep, oldStep), getParamDiff(oldFunction, newFunction)]; 95 | }; 96 | 97 | var refactor_content = function (content, info, req) { 98 | var ast = esprima.parse(content, { loc: true }); 99 | var diffs = null; 100 | estraverse.replace(ast, { 101 | enter: function (node) { 102 | if (stepParser.isStepNode(node) && node.arguments[0].value === info.stepText) { 103 | if (!node.arguments[node.arguments.length - 1] || !isFunction(node.arguments[node.arguments.length - 1])) { 104 | throw new Error("anonymous function expected!"); 105 | } 106 | var oldNode = JSON.parse(JSON.stringify(node)); 107 | processNode(node, req); 108 | diffs = createDiff(oldNode, node); 109 | } 110 | return node; 111 | } 112 | }); 113 | return { content: escodegen.generate(ast), diffs: diffs }; 114 | }; 115 | 116 | var refactor = function (refactorRequest, response) { 117 | var info = stepRegistry.get(refactorRequest.oldStepValue.stepValue); 118 | try { 119 | var content = fs.readFileSync(info.fileLocations[0].filePath, "utf8"); 120 | var refactorInfo = refactor_content(content, info, refactorRequest); 121 | if (refactorRequest.saveChanges) { 122 | fs.writeFileSync(info.fileLocations[0].filePath, refactorInfo.content, "utf8"); 123 | } 124 | var change = { 125 | "fileName": info.fileLocations[0].filePath, 126 | "fileContent": refactorInfo.content, 127 | "diffs": refactorInfo.diffs 128 | }; 129 | response.refactorResponse.fileChanges.push(change); 130 | } catch (e) { 131 | response.refactorResponse.success = false; 132 | response.refactorResponse.error = e.message; 133 | return response; 134 | } 135 | response.refactorResponse.success = true; 136 | response.refactorResponse.filesChanged.push(info.fileLocations[0].filePath); 137 | return response; 138 | }; 139 | 140 | export default refactor; 141 | -------------------------------------------------------------------------------- /src/req-manager.js: -------------------------------------------------------------------------------- 1 | import fs from "node:fs"; 2 | import mod from "node:module"; 3 | import path from "node:path"; 4 | import logger from "./logger.js"; 5 | 6 | const require = mod.createRequire(import.meta.url); 7 | 8 | const Req = function(filepath, root) { 9 | this.Module = mod.Module; 10 | this.root = root; 11 | this.filepath = filepath || null; 12 | this.nativeModules = [ 13 | "assert", 14 | "buffer", 15 | "child_process", 16 | "constants", 17 | "crypto", 18 | "tls", 19 | "dgram", 20 | "dns", 21 | "http", 22 | "https", 23 | "net", 24 | "querystring", 25 | "url", 26 | "domain", 27 | "events", 28 | "fs", 29 | "path", 30 | "os", 31 | "punycode", 32 | "stream", 33 | "string_decoder", 34 | "timers", 35 | "tty", 36 | "util", 37 | "sys", 38 | "vm", 39 | "zlib", 40 | ]; 41 | }; 42 | 43 | Req.prototype.load = function(modname) { 44 | const cachedModule = this.Module._cache[this.filepath]; 45 | if (cachedModule) { 46 | return cachedModule.exports; 47 | } 48 | 49 | return ((self, mod, modname) => { 50 | if (!modname.startsWith("./") && self.nativeModules.indexOf(modname) < 0) { 51 | modname = path.normalize(modname); 52 | } 53 | const m = new mod.Module(self.filepath, mod.Module); 54 | m.paths = [ 55 | path.dirname(self.filepath), 56 | path.join(self.root, "node_modules"), 57 | ].concat(require.resolve.paths("").filter((p) => p.indexOf(".gauge") < 0)); 58 | try { 59 | if (modname === path.basename(modname)) { 60 | return m.require(modname); 61 | } 62 | const relativePath = path.join(path.dirname(self.filepath), modname); 63 | if (fs.existsSync(relativePath)) { 64 | return m.require(relativePath); 65 | } 66 | if (fs.existsSync(relativePath.concat(".js"))) { 67 | return m.require(relativePath.concat(".js")); 68 | } 69 | return m.require(modname); 70 | } catch (e) { 71 | logger.error( 72 | `Unable to require module '${modname}' in ${self.filepath}\n${e.stack}`, 73 | ); 74 | return null; 75 | } 76 | })(this, mod, modname); 77 | }; 78 | 79 | const reqman = (filepath, root) => { 80 | const req = new Req(filepath, root); 81 | return { 82 | mod: req.Module, 83 | exports: req.Module.exports || {}, 84 | fn: ( 85 | (req) => (modname) => 86 | req.load(modname) 87 | )(req), 88 | }; 89 | }; 90 | 91 | export default reqman; 92 | -------------------------------------------------------------------------------- /src/response-factory.js: -------------------------------------------------------------------------------- 1 | export const createStepNamesResponse = function (steps) { 2 | 3 | return { 4 | stepNamesResponse: { 5 | steps: steps 6 | } 7 | }; 8 | 9 | }; 10 | 11 | export const createStepNameResponse = function () { 12 | 13 | return { 14 | stepNameResponse: { 15 | isStepPresent: false, 16 | stepName: [], 17 | hasAlias: false, 18 | fileName: "", 19 | span: {} 20 | } 21 | }; 22 | 23 | }; 24 | 25 | export const createRefactorResponse = function () { 26 | 27 | return { 28 | refactorResponse: { 29 | success: false, 30 | error: "", 31 | filesChanged: [], 32 | fileChanges: [] 33 | } 34 | }; 35 | 36 | }; 37 | 38 | export const createStepValidateResponse = function (errorType, validated, suggestion) { 39 | 40 | if (validated.valid) { 41 | return { 42 | stepValidateResponse: { 43 | isValid: true 44 | } 45 | }; 46 | } 47 | 48 | var errortype, 49 | errmsg = "Invalid step."; 50 | 51 | switch (validated.reason) { 52 | case "duplicate": 53 | errortype = errorType.values.DUPLICATE_STEP_IMPLEMENTATION; 54 | errmsg = "Duplicate step implementation found in file: " + validated.file; 55 | break; 56 | case "notfound": 57 | errortype = errorType.values.STEP_IMPLEMENTATION_NOT_FOUND; 58 | break; 59 | } 60 | 61 | return { 62 | stepValidateResponse: { 63 | isValid: false, 64 | errorType: errortype, 65 | errorMessage: errmsg, 66 | suggestion: suggestion 67 | } 68 | }; 69 | }; 70 | 71 | export const createExecutionStatusResponse = function (isFailed, executionTime, err, msg, failureScreenshotFile, recoverable, screenshotFiles) { 72 | return { 73 | executionResult: { 74 | failed: isFailed, 75 | recoverableError: recoverable, 76 | executionTime: executionTime || 0, 77 | stackTrace: err && err.stack ? err.stack : "", 78 | errorMessage: err ? (err instanceof Error ? err.toString() : JSON.stringify(err)) : "", 79 | message: msg || [], 80 | failureScreenshotFile: failureScreenshotFile || "", 81 | screenshotFiles: screenshotFiles || [] 82 | } 83 | }; 84 | 85 | }; 86 | 87 | export const createStepPositionsResponse = function (stepPositions) { 88 | 89 | return { 90 | stepPositionsResponse: { 91 | stepPositions: stepPositions, 92 | error: "" 93 | } 94 | }; 95 | 96 | }; 97 | 98 | export const createImplementationFileListResponse = function (files) { 99 | return { 100 | implementationFileListResponse: { 101 | implementationFilePaths: files 102 | } 103 | }; 104 | }; 105 | 106 | export const createImplementationFileGlobPatternResponse = function (globPatterns) { 107 | return { 108 | implementationFileGlobPatternResponse: { 109 | globPatterns: globPatterns 110 | } 111 | }; 112 | }; 113 | 114 | export const createFileDiff = function () { 115 | 116 | return { 117 | fileDiff: {} 118 | }; 119 | }; 120 | 121 | export default { createStepNamesResponse, createStepNameResponse, createRefactorResponse, createStepValidateResponse, createExecutionStatusResponse, createStepPositionsResponse, createImplementationFileListResponse, createImplementationFileGlobPatternResponse, createFileDiff }; 122 | -------------------------------------------------------------------------------- /src/screenshot.js: -------------------------------------------------------------------------------- 1 | import path from "node:path"; 2 | import fs from "node:fs"; 3 | import child_process from "node:child_process"; 4 | import logger from "./logger.js"; 5 | 6 | const SCREENSHOTS_DIR_ENV = "gauge_screenshots_dir"; 7 | 8 | var defaultScreenshotWriter = function () { 9 | return new Promise((resolve) => { 10 | const filePath = getScreenshotFileName(); 11 | var proc = child_process.spawnSync("gauge_screenshot", [filePath]); 12 | if (proc.error) { 13 | logger.error(proc.error.toString()); 14 | } 15 | resolve(path.basename(filePath)); 16 | }); 17 | }; 18 | 19 | function getScreenshotFileName() { 20 | return path.join(process.env[SCREENSHOTS_DIR_ENV], "screenshot-" + process.hrtime.bigint() + ".png"); 21 | } 22 | 23 | function isCustumScreenshotFun(funcName) { 24 | return global.gauge && global.gauge[funcName] && typeof global.gauge[funcName] === "function"; 25 | } 26 | 27 | function getScreenshotFunc() { 28 | if (isCustumScreenshotFun("customScreenshotWriter")) { 29 | return () => { 30 | return new Promise((resolve, reject) => { 31 | const screenshotFile = global.gauge.customScreenshotWriter(); 32 | if (screenshotFile.constructor.name === "Promise") { 33 | screenshotFile.then((file) => { 34 | resolve(path.basename(file)); 35 | }).catch(reject); 36 | } else { 37 | resolve(path.basename(screenshotFile)); 38 | } 39 | }); 40 | }; 41 | } else if (isCustumScreenshotFun("screenshotFn")) { 42 | return () => { 43 | return new Promise((resolve, reject) => { 44 | logger.error("[DEPRECATED] gauge.screenshotFn will be removed soon, use gauge.customScreenshotWriter instead."); 45 | const res = global.gauge.screenshotFn(); 46 | const screenshotFile = getScreenshotFileName(); 47 | if (res.constructor.name == "Promise") { 48 | res.then((data) => { 49 | writeScreenshotToFile(data, screenshotFile, resolve); 50 | }) 51 | .catch(reject); 52 | } else { 53 | writeScreenshotToFile(res, screenshotFile, resolve); 54 | } 55 | }); 56 | }; 57 | } 58 | return defaultScreenshotWriter; 59 | } 60 | 61 | function writeScreenshotToFile(data, screenshotFile, resolver) { 62 | try { 63 | let options = typeof data === "string" ? 64 | [screenshotFile, data, "base64"] : 65 | [screenshotFile, data]; 66 | 67 | fs.writeFileSync.apply(null, options); 68 | resolver(path.basename(screenshotFile)); 69 | } catch(e) { 70 | logger.error(e); 71 | } 72 | } 73 | function capture() { 74 | var screenshotFn = getScreenshotFunc(); 75 | return screenshotFn(); 76 | } 77 | 78 | export default { capture: capture }; 79 | -------------------------------------------------------------------------------- /src/serviceHandlers.js: -------------------------------------------------------------------------------- 1 | import processors from "./message-processor.js"; 2 | 3 | class ServiceHandlers { 4 | constructor(server, options) { 5 | this.server = server; 6 | this.options = options; 7 | } 8 | 9 | initializeSuiteDataStore(call, callback) { 10 | var res = processors.successExecutionStatus(call.request); 11 | callback(null, res); 12 | } 13 | 14 | startExecution(call, callback) { 15 | function responseCallback(response) { 16 | callback(null, response); 17 | } 18 | processors.executeBeforeSuiteHook(call.request, responseCallback); 19 | } 20 | 21 | initializeSpecDataStore(call, callback) { 22 | var res = processors.successExecutionStatus(call.request); 23 | callback(null, res); 24 | } 25 | 26 | startSpecExecution(call, callback) { 27 | function responseCallback(response) { 28 | callback(null, response); 29 | } 30 | processors.executeBeforeSpecHook(call.request, responseCallback); 31 | } 32 | 33 | initializeScenarioDataStore(call, callback) { 34 | var res = processors.successExecutionStatus(call.request); 35 | callback(null, res); 36 | } 37 | 38 | startScenarioExecution(call, callback) { 39 | function responseCallback(response) { 40 | callback(null, response); 41 | } 42 | processors.executeBeforeScenarioHook(call.request, responseCallback); 43 | } 44 | 45 | startStepExecution(call, callback) { 46 | function responseCallback(response) { 47 | callback(null, response); 48 | } 49 | processors.executeBeforeStepHook(call.request, responseCallback); 50 | } 51 | executeStep(call, callback) { 52 | function responseCallback(response) { 53 | callback(null, response); 54 | } 55 | processors.executeStep(call.request, responseCallback); 56 | } 57 | 58 | finishStepExecution(call, callback) { 59 | function responseCallback(response) { 60 | callback(null, response); 61 | } 62 | processors.executeAfterStepHook(call.request, responseCallback); 63 | } 64 | 65 | finishScenarioExecution(call, callback) { 66 | function responseCallback(response) { 67 | callback(null, response); 68 | } 69 | processors.executeAfterScenarioHook(call.request, responseCallback); 70 | } 71 | 72 | finishSpecExecution(call, callback) { 73 | function responseCallback(response) { 74 | callback(null, response); 75 | } 76 | processors.executeAfterSpecHook(call.request, responseCallback); 77 | } 78 | 79 | finishExecution(call, callback) { 80 | function responseCallback(response) { 81 | callback(null, response); 82 | } 83 | processors.executeAfterSuiteHook(call.request, responseCallback); 84 | } 85 | 86 | getStepNames(call, callback) { 87 | var res = processors.stepNamesResponse(call.request); 88 | callback(null, res.stepNamesResponse); 89 | } 90 | 91 | cacheFile(call, callback) { 92 | processors.cacheFileResponse(call.request, this.options.fileStatus); 93 | callback(null, {}); 94 | } 95 | 96 | getStepPositions(call, callback) { 97 | var res = processors.stepPositions(call.request); 98 | callback(null, res.stepPositionsResponse); 99 | } 100 | 101 | getImplementationFiles(call, callback) { 102 | var res = processors.implementationFiles(call.request); 103 | callback(null, res.implementationFileListResponse); 104 | } 105 | 106 | implementStub(call, callback) { 107 | var res = processors.implementStubResponse(call.request); 108 | callback(null, res.fileDiff); 109 | } 110 | 111 | validateStep(call, callback) { 112 | var res = processors.stepValidateResponse(call.request, this.options.errorType); 113 | callback(null, res.stepValidateResponse); 114 | } 115 | 116 | refactor(call, callback) { 117 | var res = processors.refactorResponse(call.request); 118 | callback(null, res.refactorResponse); 119 | } 120 | 121 | getStepName(call, callback) { 122 | var res = processors.stepNameResponse(call.request); 123 | callback(null, res.stepNameResponse); 124 | } 125 | 126 | getGlobPatterns(call, callback) { 127 | var res = processors.implementationGlobPatternResponse(call.request); 128 | callback(null, res.implementationFileGlobPatternResponse); 129 | } 130 | 131 | kill(_call, callback) { 132 | callback(null, {}); 133 | setTimeout(() => { 134 | this.server.forceShutdown(); 135 | process.exit(0); 136 | }, 100); 137 | } 138 | } 139 | 140 | export default ServiceHandlers; 141 | -------------------------------------------------------------------------------- /src/static-loader.js: -------------------------------------------------------------------------------- 1 | import fs from "node:fs"; 2 | import esprima from "esprima"; 3 | import estraverse from "estraverse"; 4 | import fileUtil from "./file-util.js"; 5 | import stepRegistry from "./step-registry.js"; 6 | import stepParser from "./step-parser.js"; 7 | import logger from "./logger.js"; 8 | 9 | function hasAliases(node) { 10 | return node.type === "ArrayExpression" && !!node.elements.length; 11 | } 12 | 13 | function addStep(step, info) { 14 | stepRegistry.add(step.value, null, info.filePath, info.span, null); 15 | } 16 | 17 | function addAliases(aliases, info) { 18 | stepRegistry.addAlias(aliases.map(function (alias) { 19 | return alias.value; 20 | }), null, info.filePath, info.span, null); 21 | } 22 | 23 | function processNode(node, filePath) { 24 | var stepNode = node.arguments[0]; 25 | var span = { 26 | start: node.loc.start.line, 27 | end: node.loc.end.line, 28 | startChar: node.loc.start.column, 29 | endChar: node.loc.end.column 30 | }; 31 | try { 32 | if (hasAliases(stepNode)) { 33 | addAliases(stepNode.elements, { filePath: filePath, span: span }); 34 | } else if (stepNode.type === "Literal") { 35 | addStep(stepNode, { filePath: filePath, span: span }); 36 | } 37 | } catch (e) { 38 | logger.info(e); 39 | } 40 | } 41 | 42 | function traverser(filePath) { 43 | return function (node) { 44 | if (stepParser.isStepNode(node)) { 45 | processNode(node, filePath); 46 | } 47 | }; 48 | } 49 | 50 | var loadFile = function (filePath, ast) { 51 | estraverse.traverse(ast, { enter: traverser(filePath) }); 52 | }; 53 | 54 | function createAst(content) { 55 | try { 56 | return esprima.parse(content, { loc: true }); 57 | } catch (e) { 58 | logger.error(e.message); 59 | return ""; 60 | } 61 | } 62 | 63 | function loadFiles(projectRoot) { 64 | fileUtil.getListOfFiles(projectRoot).forEach(function (filePath) { 65 | var ast = createAst(fs.readFileSync(filePath, "utf8")); 66 | if (ast) { 67 | loadFile(filePath, ast); 68 | } 69 | }); 70 | } 71 | 72 | function unloadFile(filePath) { 73 | stepRegistry.deleteSteps(filePath); 74 | } 75 | 76 | function reloadFile(filePath, content) { 77 | var ast = createAst(content); 78 | if (ast) { 79 | unloadFile(filePath); 80 | loadFile(filePath, ast); 81 | } 82 | } 83 | 84 | export default { 85 | load: loadFiles, 86 | reloadFile: reloadFile, 87 | unloadFile: unloadFile 88 | }; 89 | -------------------------------------------------------------------------------- /src/step-parser.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns the generalised form of a step description. 3 | * Example: 4 | * Generalised form of the step "Say to " is "Say {} to {}". 5 | * 6 | * @param {[String]} stepName Description of the step. 7 | * @return {[String]} Generalised form of the step. 8 | */ 9 | export const generalise = function(stepName) { 10 | return stepName.replace(/(<.*?>)/g, "{}"); 11 | }; 12 | 13 | export const getParams = function(step) { 14 | var matches = step.match(/(<.*?>)/g); 15 | return (matches === null) ? [] : matches.map(function(item) { return item.substring(1, item.length-1); }); 16 | }; 17 | 18 | export const isStepNode = function(node) { 19 | var isGaugeStepFunction = function (node) { 20 | return node.callee.object && node.callee.object.name === "gauge" && node.callee.property && node.callee.property.name === "step"; 21 | }; 22 | var isGlobalStepFunction = function (node) { 23 | return node.callee && node.callee.name === "step"; 24 | }; 25 | return (node && node.type === "CallExpression" && (isGaugeStepFunction(node) || isGlobalStepFunction(node))); 26 | }; 27 | 28 | export default { generalise, getParams, isStepNode }; 29 | -------------------------------------------------------------------------------- /src/step-registry.js: -------------------------------------------------------------------------------- 1 | import fileUtil from "./file-util.js"; 2 | import stepParser from "./step-parser.js"; 3 | 4 | var StepRegistry = function () { 5 | this.registry = {}; 6 | }; 7 | 8 | /** 9 | * Add a step to the registry 10 | * 11 | * @param stepText Name of the step. 12 | * @param stepFunction Function to be executed for this step. 13 | * @param filePath Filepath where the function is defined. 14 | * @param span Location in file where the function is defined. 15 | * @param options Optional parameters defined with the step. 16 | * @returns The step added. 17 | */ 18 | StepRegistry.prototype.add = function (stepText, stepFunction, filePath, span, options) { 19 | if (!stepText.length) { 20 | throw new Error("Step text cannot be empty."); 21 | } 22 | var generalisedText = stepParser.generalise(stepText); 23 | if (this.exists(generalisedText)) { 24 | this.registry[generalisedText].fileLocations.push({ filePath: filePath, span: span }); 25 | return this.registry[generalisedText]; 26 | } 27 | 28 | this.registry[generalisedText] = { 29 | fn: stepFunction, 30 | stepText: stepText, 31 | generalisedText: generalisedText, 32 | fileLocations: [ 33 | { 34 | filePath: filePath, 35 | span: span 36 | } 37 | ], 38 | count: function () { 39 | return this.fileLocations.length; 40 | }, 41 | hasAlias: false, 42 | aliases: [stepText], 43 | options: options 44 | }; 45 | return this.registry[generalisedText]; 46 | }; 47 | 48 | /** 49 | * Add a step to the registry 50 | * 51 | * @param stepTexts Step Names with all alias. 52 | * @param stepFunction Function to be executed for this step. 53 | * @param filePath Filepath where the function is defined. 54 | * @param span Location in file where the function is defined. 55 | * @param options Optional parameters defined with the step. 56 | */ 57 | StepRegistry.prototype.addAlias = function (stepTexts, stepFunction, filePath, span, options) { 58 | stepTexts.forEach((stepText) => { 59 | var step = this.add(stepText, stepFunction, filePath, span, options); 60 | step.hasAlias = true; 61 | step.aliases = stepTexts; 62 | }, this); 63 | }; 64 | 65 | /** 66 | * Get the function associated with a step. 67 | * 68 | * @param stepName Name of the step. 69 | * @returns The step corresponding to the StepName. 70 | */ 71 | StepRegistry.prototype.get = function (stepName) { 72 | return this.registry[stepName]; 73 | }; 74 | 75 | StepRegistry.prototype.getStepTexts = function () { 76 | var reg = this.registry; 77 | return Object.keys(reg).map(function (key) { 78 | return reg[key].stepText; 79 | }); 80 | }; 81 | 82 | StepRegistry.prototype.getStepPositions = function (filePath) { 83 | var stepPositions = []; 84 | for (var step in this.registry) { 85 | for (var i = 0; i < this.registry[step].fileLocations.length; i++) { 86 | if (fileUtil.isSameFilePath(this.registry[step].fileLocations[i].filePath, filePath)) { 87 | stepPositions.push({ stepValue: step, span: this.registry[step].fileLocations[i].span }); 88 | } 89 | } 90 | } 91 | return stepPositions; 92 | }; 93 | 94 | StepRegistry.prototype.isRecoverable = function (stepName) { 95 | var step = this.registry[stepName]; 96 | if (!step) { 97 | return false; 98 | } 99 | return step.options.continueOnFailure; 100 | }; 101 | 102 | /** 103 | * Checks if a given step exists. 104 | * 105 | * @param stepName Name of the step. 106 | * @return boolean true if the step exists. false if it is not. 107 | */ 108 | StepRegistry.prototype.exists = function (stepName) { 109 | return stepName in this.registry; 110 | }; 111 | 112 | /** 113 | * Checks if a given step is valid 114 | */ 115 | StepRegistry.prototype.validate = function (stepName) { 116 | var step = this.get(stepName); 117 | if (!step) { 118 | return { valid: false, reason: "notfound", file: null }; 119 | } 120 | if (step.fileLocations.length > 1) { 121 | return { valid: false, reason: "duplicate", file: step.fileLocations[0].filePath }; 122 | } 123 | return { valid: true, reason: null, file: null }; 124 | }; 125 | 126 | StepRegistry.prototype.clear = function () { 127 | this.registry = {}; 128 | }; 129 | 130 | StepRegistry.prototype.deleteSteps = function (filePath) { 131 | var filterFunc = function (location) { 132 | return !fileUtil.isSameFilePath(location.filePath, filePath); 133 | }; 134 | for (var stepText in this.registry) { 135 | this.registry[stepText].fileLocations = this.registry[stepText].fileLocations.filter(filterFunc); 136 | if (this.registry[stepText].count() === 0) { 137 | delete this.registry[stepText]; 138 | } 139 | } 140 | }; 141 | 142 | StepRegistry.prototype.isFileCached = function (filePath) { 143 | var filterFunc = function (location) { 144 | return fileUtil.isSameFilePath(location.filePath, filePath); 145 | }; 146 | for (var stepText in this.registry) { 147 | if(this.registry[stepText].fileLocations.find(filterFunc)) { 148 | return true; 149 | } 150 | } 151 | return false; 152 | }; 153 | 154 | export default new StepRegistry(); 155 | -------------------------------------------------------------------------------- /src/table.js: -------------------------------------------------------------------------------- 1 | var Table = function (protoTable) { 2 | Object.assign(this, protoTable); 3 | 4 | this.entries = async function (callback) { 5 | for (const row of this.rows) { 6 | let entry = {}; 7 | row.cells.forEach( 8 | (cell, index) => (entry[this.headers.cells[index]] = cell) 9 | ); 10 | if (callback.constructor.name === "AsyncFunction") { 11 | await callback(entry); 12 | } else { 13 | callback(entry); 14 | } 15 | } 16 | }; 17 | }; 18 | 19 | export default Table; 20 | -------------------------------------------------------------------------------- /src/test.js: -------------------------------------------------------------------------------- 1 | import Q from "q"; 2 | import path from "node:path"; 3 | 4 | var Test = function (fn, params, ms) { 5 | this.fn = fn; 6 | this.params = params; 7 | this.async = fn.length > params.length; 8 | this.start = new Date(); 9 | this.finished = false; 10 | this.timedOut = false; 11 | this.ms = ms || 1000; 12 | }; 13 | 14 | var done = function (err) { 15 | var self = this; 16 | if (self.finished || self.timedOut) { 17 | return; 18 | } 19 | self.duration = new Date() - self.start; 20 | self.finished = true; 21 | clearTimeout(self.timer); 22 | 23 | if (err) { 24 | self.deferred.reject({ 25 | exception: err, 26 | duration: self.duration 27 | }); 28 | return; 29 | } else { 30 | self.deferred.resolve({ 31 | duration: self.duration 32 | }); 33 | return; 34 | } 35 | }; 36 | 37 | var resetTimeout = function () { 38 | var self = this; 39 | if (self.timer) { 40 | clearTimeout(self.timer); 41 | } 42 | self.timer = setTimeout(function () { 43 | const errorMsg = self.async ? "Timed out. Number of parameters in the step do not match the number of arguments in the step definition." : "Timed out"; 44 | done.apply(self, [new Error(errorMsg)]); 45 | self.timedOut = true; 46 | }, self.ms); 47 | }; 48 | 49 | var absoluteToRelativePath = function(stack) { 50 | for (var i = 0; i< stack.length; i++) { 51 | stack[i] = stack[i].replace(process.env.GAUGE_PROJECT_ROOT + path.sep, ""); 52 | } 53 | return stack; 54 | }; 55 | 56 | var chopStackTrace = function (stack, pattern) { 57 | var limit = stack.findIndex(function (frame) { 58 | return frame.match(pattern); 59 | }); 60 | stack = limit > 0 ? stack.slice(0, limit) : stack; 61 | return absoluteToRelativePath(stack).join("\n"); 62 | }; 63 | 64 | var runFn = function () { 65 | var self = this; 66 | try { 67 | if (!process.env.DEBUGGING){ 68 | resetTimeout.call(self); 69 | } 70 | var res = self.fn.apply({}, self.params); 71 | if (Object.prototype.toString.call(res) === "[object Promise]") { 72 | res.then(function () { 73 | done.call(self); 74 | }).catch(function (e) { 75 | e.stack = e.stack && chopStackTrace(e.stack.split("\n"), /at Test.runFn/); 76 | done.apply(self, [e ? e : new Error("Undefined error thrown")]); 77 | }); 78 | return; 79 | } 80 | done.call(self); 81 | } catch (e) { 82 | e.stack = e.stack && chopStackTrace(e.stack.split("\n"), /at Test.runFn/); 83 | done.apply(self, [e ? e : new Error("Undefined error thrown")]); 84 | } 85 | }; 86 | 87 | var runFnAsync = function () { 88 | var self = this; 89 | self.params.push(function (err) { done.call(self, err); }); 90 | if(!process.env.DEBUGGING){ 91 | resetTimeout.call(self); 92 | } 93 | try { 94 | self.fn.apply({}, self.params); 95 | } catch (e) { 96 | e.stack = e.stack && chopStackTrace(e.stack.split("\n"), /at Test.runFnAsync/); 97 | done.apply(self, [e ? e : new Error("Undefined error thrown")]); 98 | } 99 | }; 100 | 101 | Test.prototype.run = function () { 102 | this.deferred = Q.defer(); 103 | if (this.async) { 104 | runFnAsync.call(this); 105 | } 106 | else { 107 | runFn.call(this); 108 | } 109 | return this.deferred.promise; 110 | }; 111 | 112 | export default Test; 113 | -------------------------------------------------------------------------------- /src/vm.js: -------------------------------------------------------------------------------- 1 | import vm from "node:vm"; 2 | import fs from "node:fs"; 3 | import path from "node:path"; 4 | import reqman from "./req-manager.js"; 5 | import gaugeGlobal from "./gauge-global.js"; 6 | import logger from "./logger.js"; 7 | 8 | var VM = function () { 9 | var self = this; 10 | 11 | self.options = { 12 | filename: "test", 13 | dirname: ".", 14 | filepath: path.join(".", "test.js"), 15 | displayErrors: true, 16 | timeout: parseInt(process.env.test_timeout) || 1000, 17 | root: process.env.GAUGE_PROJECT_ROOT ? process.env.GAUGE_PROJECT_ROOT : process.cwd() 18 | }; 19 | }; 20 | 21 | VM.prototype.contextify = function (filePath, root) { 22 | var self = this; 23 | 24 | filePath = filePath || self.options.filepath; 25 | root = root || self.options.root; 26 | 27 | self.setFile(filePath); 28 | self.require = reqman(filePath, root); 29 | var sandbox = { 30 | isVM: true, 31 | console: console, 32 | __dirname: path.dirname(path.resolve(filePath)), 33 | __filename: path.resolve(filePath), 34 | require: self.require.fn, 35 | module: self.require.mod, 36 | exports: self.require.exports, 37 | process: process, 38 | gauge: gaugeGlobal.gauge, 39 | step: gaugeGlobal.step, 40 | setImmediate: setImmediate, 41 | setInterval: setInterval, 42 | setTimeout: setTimeout, 43 | clearImmediate: clearImmediate, 44 | clearInterval: clearInterval, 45 | clearTimeout: clearTimeout, 46 | gauge_runner_root: process.cwd(), 47 | gauge_project_root: self.options.root 48 | }; 49 | for (var type in gaugeGlobal.hooks) { 50 | sandbox[type] = gaugeGlobal.hooks[type]; 51 | } 52 | 53 | self.context = vm.createContext(sandbox); 54 | }; 55 | 56 | VM.prototype.run = function (code) { 57 | try { 58 | vm.runInContext("(function () { process.chdir(gauge_project_root); })()", this.context, this.options); 59 | vm.runInContext(code + "\n//# sourceURL=" + this.options.filepath, this.context, this.options); 60 | } catch (e) { 61 | logger.fatal("Error executing " + this.options.filename + "\n" + e.stack); 62 | } 63 | }; 64 | 65 | VM.prototype.setFile = function (filePath) { 66 | this.options.filepath = filePath; 67 | this.options.filename = path.relative(this.options.root, filePath); 68 | this.options.dirname = path.dirname(filePath); 69 | }; 70 | 71 | VM.prototype.runFile = function (filePath) { 72 | this.setFile(filePath); 73 | this.run(fs.readFileSync(filePath, "utf8")); 74 | }; 75 | 76 | export default VM; 77 | -------------------------------------------------------------------------------- /test/custom-message-registry-test.js: -------------------------------------------------------------------------------- 1 | import { assert } from "chai"; 2 | import customMessageRegistry from "../src/custom-message-registry.js"; 3 | 4 | describe("Custom Message registry", function () { 5 | 6 | afterEach(function () { 7 | customMessageRegistry.clear(); 8 | }); 9 | 10 | it("Should return empty array after CustomMessageRegistry.clear", function () { 11 | customMessageRegistry.add("Hello"); 12 | customMessageRegistry.add("World!"); 13 | var list = customMessageRegistry.clear(); 14 | 15 | assert.deepEqual(customMessageRegistry.get(), []); 16 | assert.deepEqual(list, []); 17 | }); 18 | 19 | it("Should store and retrieve customMessages for valid customMessage types", function (done) { 20 | var list = []; 21 | 22 | for (var i=0; i<10; i++) { 23 | list.push("Hello " + i); 24 | customMessageRegistry.add("Hello " + i); 25 | } 26 | 27 | assert.deepEqual(list, customMessageRegistry.get()); 28 | 29 | assert.notDeepEqual([], customMessageRegistry.get()); 30 | 31 | done(); 32 | }); 33 | 34 | }); 35 | -------------------------------------------------------------------------------- /test/custom-screenshot-registry-test.js: -------------------------------------------------------------------------------- 1 | import { assert } from "chai"; 2 | import sinon from "sinon"; 3 | import screenshot from "../src/screenshot.js"; 4 | import customScreenshotRegistry from "../src/custom-screenshot-registry.js"; 5 | 6 | const sandbox = sinon.createSandbox(); 7 | 8 | describe("Custom Screenshot Registry", () => { 9 | beforeEach(() => { 10 | sandbox.stub(screenshot, "capture") 11 | .returns(Promise.resolve("screenshot-file.png")); 12 | }); 13 | 14 | afterEach(() => { 15 | sandbox.restore(); 16 | customScreenshotRegistry.clear(); 17 | }); 18 | 19 | it("should add a screenshot", (done) => { 20 | customScreenshotRegistry.add(); 21 | customScreenshotRegistry.get().then((screenshots) => { 22 | assert.deepEqual(screenshots, ["screenshot-file.png"]); 23 | done(); 24 | }); 25 | }); 26 | 27 | it("should clear the screenshots", (done) => { 28 | customScreenshotRegistry.add(); 29 | customScreenshotRegistry.clear(); 30 | customScreenshotRegistry.get().then((screenshots) => { 31 | assert.deepEqual(screenshots, []); 32 | done(); 33 | }); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /test/data-store-factory-test.js: -------------------------------------------------------------------------------- 1 | import { assert } from "chai"; 2 | import dataStoreFactory from "../src/data-store-factory.js"; 3 | 4 | describe("DataStoreFactory", function () { 5 | describe("get data from datastore", function () { 6 | it("should return the data as user set in suite datastore", function () { 7 | dataStoreFactory.suiteStore.put("text", "message"); 8 | let data = dataStoreFactory.suiteStore.get("text"); 9 | assert.equal(data, "message"); 10 | }); 11 | 12 | it("should return the data as user set in spec datastore", function () { 13 | dataStoreFactory.specStore.put("text", "message"); 14 | let data = dataStoreFactory.specStore.get("text"); 15 | assert.equal(data, "message"); 16 | }); 17 | 18 | it("should return the data as user set in scenario datastore", function () { 19 | dataStoreFactory.scenarioStore.put("text", "message"); 20 | let data = dataStoreFactory.scenarioStore.get("text"); 21 | assert.equal(data, "message"); 22 | }); 23 | 24 | it("should return null if no value is set in datastore", function () { 25 | dataStoreFactory.scenarioStore.put("text"); 26 | let data = dataStoreFactory.scenarioStore.get("text"); 27 | assert.equal(data, null); 28 | }); 29 | 30 | it("should return the same for falsy values", function () { 31 | dataStoreFactory.scenarioStore.put("text", false); 32 | let data = dataStoreFactory.scenarioStore.get("text"); 33 | assert.equal(data, false); 34 | dataStoreFactory.scenarioStore.put("text", 0); 35 | data = dataStoreFactory.scenarioStore.get("text"); 36 | assert.equal(data, 0); 37 | }); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /test/executor-test.js: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import sinon from "sinon"; 3 | import executor from "../src/executor.js"; 4 | import stepRegistry from "../src/step-registry.js"; 5 | 6 | 7 | describe("Executing steps", function () { 8 | var executeStepRequest = null; 9 | var executeStepFailingRequest = null; 10 | this.timeout(10000); 11 | var originalGlobalGauge; 12 | 13 | before(function () { 14 | var opts = { continueOnFailure: false }; 15 | stepRegistry.clear(); 16 | stepRegistry.add("Say to ", function () { }, "executor-test.js", 3, opts); 17 | stepRegistry.add("failing test", function () { throw new Error("error message"); }, "executor-test.js", 6, opts); 18 | sinon.spy(stepRegistry, "get"); 19 | originalGlobalGauge = global.gauge; 20 | global.gauge = { 21 | screenshotFn: function() { 22 | return Promise.resolve("screenshot"); 23 | } 24 | }; 25 | executeStepRequest = { 26 | actualStepText: "Say \"hello\" to \"gauge\"", 27 | parsedStepText: "Say {} to {}", 28 | parameters: [ 29 | { parameterType: 1, value: "hello", name: "", table: null }, 30 | { parameterType: 1, value: "gauge", name: "", table: null } 31 | ] 32 | }; 33 | executeStepFailingRequest = { 34 | actualStepText: "failing test", 35 | parsedStepText: "failing test", 36 | parameters: [] 37 | }; 38 | }); 39 | 40 | after(function (done) { 41 | stepRegistry.get.restore(); 42 | global.gauge = originalGlobalGauge; 43 | done(); 44 | }); 45 | 46 | it("Should resolve promise when test function passes", function (done) { 47 | var promise = executor.step(executeStepRequest); 48 | promise.then( 49 | function (value) { 50 | expect(value.executionResult.failed).to.equal(false); 51 | done(); 52 | } 53 | ).done(); 54 | }); 55 | 56 | it("Should reject the promise when test function fails", function (done) { 57 | var promise = executor.step(executeStepFailingRequest); 58 | promise.then( 59 | function () { }, 60 | function (reason) { 61 | expect(reason.executionResult.failed).to.equal(true); 62 | expect(reason.executionResult.errorMessage).to.eql("Error: error message"); 63 | expect(reason.executionResult.stackTrace).to.contains("executor-test.js"); 64 | done(); 65 | } 66 | ).done(); 67 | }); 68 | 69 | }); 70 | -------------------------------------------------------------------------------- /test/file-util-test.js: -------------------------------------------------------------------------------- 1 | import path from "node:path"; 2 | import { fileURLToPath } from "node:url"; 3 | import { assert } from "chai"; 4 | import isWindows from "check-if-windows"; 5 | import mock from "mock-tmp"; 6 | import fileUtil from "../src/file-util.js"; 7 | 8 | const __filename = fileURLToPath(import.meta.url); 9 | const __dirname = path.dirname(__filename); 10 | 11 | describe("File util functions", () => { 12 | describe("isSameFilePath", () => { 13 | it("Should return true when given paths are same windows", function() { 14 | if (!isWindows) { 15 | this.skip(); 16 | } else { 17 | const path1 = "c:/Users/user_name/test_js/tests/step_implementation.js"; 18 | const path2 = "C:/Users/user_name/test_js/tests/step_implementation.js"; 19 | 20 | assert.isTrue(fileUtil.isSameFilePath(path1, path2)); 21 | } 22 | }); 23 | 24 | it("Should return true when given paths are with different fileseperator ", function() { 25 | if (!isWindows) { 26 | this.skip(); 27 | } else { 28 | const path1 = 29 | "c:\\Users\\user_name\\test_js\\tests\\step_implementation.js"; 30 | const path2 = "c:/Users/user_name/test_js/tests/step_implementation.js"; 31 | 32 | assert.isTrue(fileUtil.isSameFilePath(path1, path2)); 33 | } 34 | }); 35 | 36 | it("Should return true when given paths are same and has space ", () => { 37 | const path1 = "/Users/test_js/tests/Step implementation.js"; 38 | const path2 = "/Users/test_js/tests/Step implementation.js"; 39 | 40 | assert.isTrue(fileUtil.isSameFilePath(path1, path2)); 41 | }); 42 | 43 | it("Should return true when given paths are same on linux ", () => { 44 | const path1 = "/Users/test_js/tests/Step_implementation.js"; 45 | const path2 = "/Users/test_js/tests/Step_implementation.js"; 46 | 47 | assert.isTrue(fileUtil.isSameFilePath(path1, path2)); 48 | }); 49 | 50 | it("Should return false when given paths are not different", () => { 51 | const path1 = "/Users/test_js/tests/Step_implementation.js"; 52 | const path2 = "/Users/test_js/tests1/step_implementation.js"; 53 | 54 | assert.isFalse(fileUtil.isSameFilePath(path1, path2)); 55 | }); 56 | }); 57 | 58 | describe("getListOfFiles", () => { 59 | it("should get all the js file", () => { 60 | process.env.GAUGE_PROJECT_ROOT = path.join(__dirname, "testdata"); 61 | const files = fileUtil.getListOfFiles(); 62 | assert.equal(files.length, 2); 63 | }); 64 | 65 | it("should get only js file", () => { 66 | process.env.GAUGE_PROJECT_ROOT = path.join(__dirname, "testdata"); 67 | const files = fileUtil.getListOfFiles(); 68 | assert.equal(files.length, 2); 69 | }); 70 | 71 | it("should get scan only tests dir by default", () => { 72 | process.env.GAUGE_PROJECT_ROOT = path.join( 73 | __dirname, 74 | "testdata", 75 | "custom", 76 | ); 77 | const files = fileUtil.getListOfFiles(); 78 | assert.equal(files.length, 0); 79 | }); 80 | 81 | it("should get scan only dir specified as STEP_IMPL_DIR env", () => { 82 | process.env.STEP_IMP_DIR = "custom"; 83 | process.env.GAUGE_PROJECT_ROOT = path.join(__dirname, "testdata"); 84 | const files = fileUtil.getListOfFiles(); 85 | assert.equal(files.length, 2); 86 | }); 87 | }); 88 | 89 | describe("getFileName", () => { 90 | afterEach(() => { 91 | mock.reset(); 92 | }); 93 | 94 | it("should give default file name does not exist", () => { 95 | const tmp = mock({ 96 | tests: {}, 97 | }); 98 | 99 | const file = fileUtil.getFileName(path.join(tmp, "tests")); 100 | assert.equal(path.basename(file), "step_implementation.js"); 101 | }); 102 | 103 | it("should give file name with increment if default exists", () => { 104 | let tmp = mock({ 105 | tests: { 106 | "step_implementation.js": "foo", 107 | }, 108 | }); 109 | 110 | let file = fileUtil.getFileName(path.join(tmp, "tests")); 111 | assert.equal(path.basename(file), "step_implementation_1.js"); 112 | 113 | mock.reset(); 114 | 115 | tmp = mock({ 116 | tests: { 117 | "step_implementation.js": "foo", 118 | "step_implementation_1.js": "something", 119 | }, 120 | }); 121 | 122 | file = fileUtil.getFileName(path.join(tmp, "tests")); 123 | assert.equal(path.basename(file), "step_implementation_2.js"); 124 | }); 125 | }); 126 | 127 | describe("isInImplDir", () => { 128 | afterEach(() => { 129 | process.env.GAUGE_PROJECT_ROOT = process.cwd(); 130 | mock.reset(); 131 | }); 132 | 133 | it("should be true if file is under implementation dir", () => { 134 | const tmp = mock({ 135 | tests: { 136 | "step_impl.js": "file content", 137 | }, 138 | }); 139 | process.env.GAUGE_PROJECT_ROOT = tmp; 140 | assert.isTrue( 141 | fileUtil.isInImplDir(path.join(tmp, "tests", "step_impl.js")), 142 | ); 143 | }); 144 | 145 | it("should be true if file in nested dir under implementation dir", () => { 146 | const tmp = mock({ 147 | tests: { 148 | "inner-dir": { 149 | "step_impl.js": "file content", 150 | }, 151 | }, 152 | }); 153 | process.env.GAUGE_PROJECT_ROOT = tmp; 154 | assert.isTrue( 155 | fileUtil.isInImplDir( 156 | path.join(tmp, "tests", "inner-dir", "step_impl.js"), 157 | ), 158 | ); 159 | }); 160 | 161 | it("should be false if file is not under implementation dir", () => { 162 | const tmp = mock({ 163 | tests: { 164 | "inner-dir": { 165 | "step_impl.js": "file content", 166 | }, 167 | }, 168 | }); 169 | process.env.GAUGE_PROJECT_ROOT = tmp; 170 | assert.isFalse(fileUtil.isInImplDir(path.join(tmp, "step_impl.js"))); 171 | }); 172 | }); 173 | 174 | describe("isJSFile", () => { 175 | it("should check for js file extensions", () => { 176 | assert.isTrue(fileUtil.isJSFile("step_impl.js")); 177 | assert.isFalse(fileUtil.isJSFile("step_impl.java")); 178 | }); 179 | }); 180 | 181 | describe("parseJsonFileSyncSafe", () => { 182 | it("should parse a json file that exists", () => { 183 | assert.equal(fileUtil.parseJsonFileSyncSafe("./package.json").name, "gauge-js"); 184 | }); 185 | 186 | it("should default to empty object if json file does not exist", () => { 187 | assert.deepStrictEqual(fileUtil.parseJsonFileSyncSafe("./not-here.json"), {}); 188 | }); 189 | }); 190 | }); 191 | -------------------------------------------------------------------------------- /test/gauge-global-test.js: -------------------------------------------------------------------------------- 1 | import { assert } from "chai"; 2 | import sinon from "sinon"; 3 | import stepRegistry from "../src/step-registry.js"; 4 | import stepParser from "../src/step-parser.js"; 5 | import { step } from "../src/gauge-global.js"; 6 | 7 | describe("Calling global gauge.step()", function() { 8 | 9 | beforeEach(function() { 10 | stepRegistry.clear(); 11 | }); 12 | 13 | it("should throw error if steptext is empty", function (done) { 14 | var dumb = function () {}; 15 | assert.throw(function () { step(); }); 16 | assert.throw(function () { step("", dumb); }); 17 | assert.throw(function () { step([], dumb); }); 18 | assert.throw(function () { step(["", ""], dumb); }); 19 | done(); 20 | }); 21 | 22 | it("should add test function to step registry", function(done) { 23 | sinon.spy(stepRegistry, "add"); 24 | sinon.spy(stepParser, "generalise"); 25 | 26 | var sampleFunction = function() {}; 27 | 28 | step("Step <1>", sampleFunction); 29 | 30 | assert(stepRegistry.add.calledOnce); 31 | assert(stepParser.generalise.calledOnce); 32 | assert.equal("Step <1>", stepParser.generalise.getCall(0).args[0]); 33 | assert.equal("Step <1>", stepRegistry.add.getCall(0).args[0]); 34 | assert.deepEqual(sampleFunction, stepRegistry.add.getCall(0).args[1]); 35 | 36 | stepRegistry.add.restore(); 37 | stepParser.generalise.restore(); 38 | done(); 39 | }); 40 | 41 | it("should support step aliases", function(done) { 42 | var sampleFunction = function(stepnum) { console.log(stepnum); }; 43 | sinon.spy(stepRegistry, "addAlias"); 44 | 45 | step(["Step ","Another step "], sampleFunction); 46 | 47 | assert(stepRegistry.addAlias.calledOnce); 48 | 49 | var list = stepRegistry.registry; 50 | 51 | assert(list["Step {}"]); 52 | assert(list["Another step {}"]); 53 | 54 | assert.equal(list["Step {}"].stepText, "Step "); 55 | assert.equal(list["Step {}"].hasAlias, true); 56 | assert.deepEqual(list["Step {}"].aliases, ["Step ", "Another step "]); 57 | assert.deepEqual(list["Step {}"].fn, sampleFunction); 58 | 59 | assert.equal(list["Another step {}"].stepText, "Another step "); 60 | assert.equal(list["Another step {}"].hasAlias, true); 61 | assert.deepEqual(list["Another step {}"].aliases, ["Step ", "Another step "]); 62 | assert.deepEqual(list["Another step {}"].fn, sampleFunction); 63 | 64 | stepRegistry.addAlias.restore(); 65 | done(); 66 | }); 67 | 68 | }); 69 | -------------------------------------------------------------------------------- /test/hook-registry-test.js: -------------------------------------------------------------------------------- 1 | import { assert } from "chai"; 2 | import hookRegistry from "../src/hook-registry.js"; 3 | import sinon from "sinon"; 4 | 5 | describe("Hook registry", function () { 6 | 7 | before(function () { 8 | sinon.spy(hookRegistry, "get"); 9 | }); 10 | 11 | afterEach(function () { 12 | hookRegistry.clear(); 13 | }); 14 | 15 | after(function () { 16 | hookRegistry.get.restore(); 17 | }); 18 | 19 | it("Should have pre-defined hook types", function () { 20 | 21 | assert.deepEqual(hookRegistry.types, [ 22 | "beforeSuite", 23 | "afterSuite", 24 | "beforeSpec", 25 | "afterSpec", 26 | "beforeScenario", 27 | "afterScenario", 28 | "beforeStep", 29 | "afterStep" 30 | ]); 31 | }); 32 | 33 | describe("Clear", function () { 34 | 35 | beforeEach(function () { 36 | hookRegistry.add("beforeSpec", function () {}, {}); 37 | hookRegistry.add("beforeSpec", function () {}, {}); 38 | hookRegistry.clear(); 39 | }); 40 | 41 | it("Should return empty object after HookRegistry.clear", function () { 42 | assert.deepEqual(hookRegistry.registry, {}); 43 | }); 44 | 45 | it("Should return empty array when no hooks have set for a hook name", function () { 46 | assert.deepEqual(hookRegistry.get("beforeSuite"), []); 47 | }); 48 | 49 | }); 50 | 51 | describe("Adding and removing", function () { 52 | var hookfn = function () { assert(1 + 1, 2); }, 53 | hookopts = { tags: ["hello world"]}; 54 | 55 | it("Should store and retrieve hooks for valid hook types", function (done) { 56 | var got; 57 | 58 | hookRegistry.types.forEach(function (hook) { 59 | hookRegistry.add(hook, hookfn, hookopts); 60 | got = hookRegistry.get(hook); 61 | 62 | assert.equal(hookfn, got[0].fn); 63 | assert.deepEqual(hookopts, got[0].options); 64 | }); 65 | 66 | done(); 67 | }); 68 | 69 | it("Should retrieve all hooks when calling HookRegistry.get without arguments", function (done) { 70 | var list = {}; 71 | 72 | hookRegistry.types.forEach(function (hook) { 73 | list[hook] = list[hook] || []; 74 | list[hook].push({fn: hookfn, options: hookopts}); 75 | hookRegistry.add(hook, hookfn, hookopts); 76 | }); 77 | 78 | assert.deepEqual(hookRegistry.registry, list); 79 | done(); 80 | 81 | }); 82 | 83 | it("Should throw error when trying to add hook for invalid hook type", function () { 84 | var add = function () { hookRegistry.add("blah", hookfn, hookopts); }; 85 | assert.throw(add); 86 | }); 87 | 88 | }); 89 | 90 | }); 91 | -------------------------------------------------------------------------------- /test/lsp-server-test.js: -------------------------------------------------------------------------------- 1 | import path from "node:path"; 2 | import protobuf from "protobufjs"; 3 | import mock from "mock-tmp"; 4 | import { assert } from "chai"; 5 | import ServiceHandlers from "../src/serviceHandlers.js"; 6 | import registry from "../src/step-registry.js"; 7 | import loader from "../src/static-loader.js"; 8 | 9 | describe("ServiceHandlers", function () { 10 | var options = null; 11 | before(function (done) { 12 | protobuf.load("gauge-proto/messages.proto").then(function (root) { 13 | options = { 14 | message: root.lookupType("gauge.messages.Message"), 15 | fileStatus: root.lookupEnum("gauge.messages.CacheFileRequest.FileStatus"), 16 | errorType: root.lookupEnum("gauge.messages.StepValidateResponse.ErrorType") 17 | }; 18 | done(); 19 | }).catch(e => console.log(e)); 20 | }); 21 | 22 | it(".getGlobPatterns should give the file glob patterns", function (done) { 23 | process.env.GAUGE_PROJECT_ROOT = ""; 24 | var handler = new ServiceHandlers(null, options); 25 | handler.getGlobPatterns({ request: {} }, function (err, res) { 26 | assert.isNull(err); 27 | assert.ok(res.globPatterns.includes("tests/**/*.js")); 28 | done(); 29 | }); 30 | }); 31 | 32 | it(".getImplementationFiles should get implementation files", function (done) { 33 | process.env.GAUGE_PROJECT_ROOT = mock({ 34 | "tests": { 35 | "example.js": "file content" 36 | }, 37 | }); 38 | var handler = new ServiceHandlers(null, options); 39 | handler.getImplementationFiles({ request: {} }, function (err, res) { 40 | assert.isNull(err); 41 | var files = res.implementationFilePaths; 42 | assert.equal(files.length, 1); 43 | assert.equal(path.basename(files[0]), "example.js"); 44 | done(); 45 | }); 46 | }); 47 | 48 | it(".getStepName should get step info", function (done) { 49 | registry.add("foo ", null, "example.js", { start: 0, end: 0, statCahr: 0, endChar: 0 }, {}); 50 | var handler = new ServiceHandlers(null, options); 51 | handler.getStepName({ request: { stepValue: "foo {}" } }, function (err, res) { 52 | assert.isNull(err); 53 | assert.ok(res.isStepPresent); 54 | assert.ok(res.stepName.includes("foo ")); 55 | done(); 56 | }); 57 | }); 58 | 59 | it(".getStepNames should give all steps", function (done) { 60 | registry.add("foo ", null, "example.js", { start: 0, end: 0, statCahr: 0, endChar: 0 }, {}); 61 | registry.add("foo", null, "example.js", { start: 0, end: 0, statCahr: 0, endChar: 0 }, {}); 62 | registry.add("bar", null, "example.js", { start: 0, end: 0, statCahr: 0, endChar: 0 }, {}); 63 | var handler = new ServiceHandlers(null, options); 64 | handler.getStepNames({ request: {} }, function (err, res) { 65 | assert.isNull(err); 66 | assert.ok(res.steps.length, 3); 67 | assert.ok(res.steps.includes("foo ")); 68 | assert.ok(res.steps.includes("foo")); 69 | assert.ok(res.steps.includes("foo")); 70 | assert.notOk(res.steps.includes("jackperalta")); 71 | done(); 72 | }); 73 | }); 74 | 75 | it(".getStepPositions should give all step positions in a given file", function () { 76 | registry.add("foo ", null, "nothing.js", { start: 1, end: 2, statCahr: 0, endChar: 0 }, {}); 77 | registry.add("foo", null, "example.js", { start: 1, end: 3, statCahr: 0, endChar: 0 }, {}); 78 | registry.add("bar", null, "example.js", { start: 4, end: 6, statCahr: 0, endChar: 0 }, {}); 79 | var handler = new ServiceHandlers(null, options); 80 | process.env.GAUGE_PROJECT_ROOT = ""; 81 | handler.getStepPositions({ request: { filePath: "nothing.js" } }, function (err, res) { 82 | assert.isNull(err); 83 | assert.ok(res.stepPositions.length, 1); 84 | assert.ok(res.stepPositions[0].stepValue, "foo {}"); 85 | }); 86 | 87 | handler.getStepPositions({ request: { filePath: "example.js" } }, function (err, res) { 88 | assert.isNull(err); 89 | assert.ok(res.stepPositions.length, 2); 90 | }); 91 | }); 92 | 93 | it(".implementStub should add stub in file when does not exists", function () { 94 | process.env.GAUGE_PROJECT_ROOT = process.cwd(); 95 | var handler = new ServiceHandlers(null, options); 96 | const req = { request: { implementationFilePath: "New File", codes: ["foo", "bar"] } }; 97 | handler.implementStub(req, function (err, res) { 98 | assert.isNull(err); 99 | assert.equal(path.basename(res.filePath), "step_implementation.js"); 100 | assert.equal(res.textDiffs[0].content, "foo\nbar"); 101 | }); 102 | }); 103 | 104 | it(".implementStub should add stub in existing file", function () { 105 | process.env.GAUGE_PROJECT_ROOT = mock({ 106 | "tests": { 107 | "example.js": "something is here" 108 | } 109 | }); 110 | var handler = new ServiceHandlers(null, options); 111 | const req = { request: { implementationFilePath: path.join(process.env.GAUGE_PROJECT_ROOT, "tests", "example.js"), codes: ["foo", "bar"] } }; 112 | handler.implementStub(req, function (err, res) { 113 | assert.isNull(err); 114 | assert.equal(path.basename(res.filePath), "example.js"); 115 | assert.equal(res.textDiffs[0].content, "\n\nfoo\nbar"); 116 | }); 117 | }); 118 | 119 | it(".refactor should refactor step", function () { 120 | var content = "step('shhh',function(){\n\tconsole.log('hello')\n})"; 121 | process.env.GAUGE_PROJECT_ROOT = mock({ "tests": { "example.js": content } }); 122 | loader.reloadFile(path.join(process.env.GAUGE_PROJECT_ROOT, "tests", "example.js"), content); 123 | 124 | var handler = new ServiceHandlers(null, options); 125 | const req = { 126 | request: { 127 | saveChanges: false, 128 | newStepValue: { 129 | stepValue: "foo", 130 | parameterizedStepValue: "foo", 131 | parameters: [] 132 | }, 133 | oldStepValue: { 134 | stepValue: "shhh", 135 | parameterizedStepValue: "shhh", 136 | parameters: [] 137 | }, 138 | paramPositions: [] 139 | } 140 | }; 141 | handler.refactor(req, function (err, res) { 142 | assert.isNull(err); 143 | assert.ok(res.success); 144 | }); 145 | }); 146 | 147 | it(".validateStep should validate a step", function () { 148 | registry.add("foo", null, "example.js", { start: 1, end: 3, statCahr: 0, endChar: 0 }, {}); 149 | var handler = new ServiceHandlers(null, options); 150 | const req = { 151 | request: { 152 | stepText: "foo", 153 | stepValue: { 154 | stepValue: "foo", 155 | parameterizedText: "foo", 156 | parameters: [] 157 | } 158 | } 159 | }; 160 | handler.validateStep(req, function (err, res) { 161 | assert.isNull(err); 162 | assert.ok(res.isValid); 163 | }); 164 | }); 165 | 166 | it(".cacheFile should update registry with new steps", function () { 167 | process.env.GAUGE_PROJECT_ROOT = process.cwd(); 168 | var filePath = path.join(process.cwd(), "tests", "example.js"); 169 | var fileContent = "step('shhh',function(){\n\tconsole.log('hello')\n})"; 170 | loader.reloadFile(filePath, fileContent); 171 | var handler = new ServiceHandlers(null, options); 172 | 173 | const req = { 174 | request: { 175 | filePath: filePath, 176 | content: "step('foo',function(){\n\tconsole.log('hello')\n})", 177 | status: options.fileStatus.valuesById[options.fileStatus.values.CHANGED] 178 | } 179 | }; 180 | handler.cacheFile(req, function (err) { 181 | assert.isNull(err); 182 | assert.isUndefined(registry.get("shhh")); 183 | assert.isDefined(registry.get("foo")); 184 | }); 185 | }); 186 | 187 | afterEach(function () { 188 | registry.clear(); 189 | mock.reset(); 190 | }); 191 | }); 192 | -------------------------------------------------------------------------------- /test/message-processor-test.js: -------------------------------------------------------------------------------- 1 | import { assert } from "chai"; 2 | import sinon from "sinon"; 3 | import protobuf from "protobufjs"; 4 | import stepRegistry from "../src/step-registry.js"; 5 | import hookRegistry from "../src/hook-registry.js"; 6 | import loader from "../src/static-loader.js"; 7 | import dataStore from "../src/data-store-factory.js"; 8 | 9 | import { 10 | executeBeforeSuiteHook, 11 | executeBeforeSpecHook, 12 | executeBeforeScenarioHook, 13 | stepValidateResponse, 14 | stepNameResponse, 15 | stepPositions, 16 | cacheFileResponse, 17 | implementationGlobPatternResponse, 18 | } from "../src/message-processor.js"; 19 | 20 | import mock from "mock-tmp"; 21 | import path from "node:path"; 22 | 23 | describe("Step Validate Request Processing", function () { 24 | let stepValidateRequests = []; 25 | let errorType = null; 26 | this.timeout(10000); 27 | before(function (done) { 28 | stepRegistry.clear(); 29 | stepRegistry.add("Say {} to {}", function () { 30 | }); 31 | sinon.spy(stepRegistry, "validate"); 32 | protobuf.load("gauge-proto/messages.proto").then(function (root) { 33 | errorType = root.lookupEnum("gauge.messages.StepValidateResponse.ErrorType"); 34 | stepValidateRequests = [ 35 | { 36 | stepText: "A context step which takes two params {} and {}", 37 | numberOfParameters: 2, 38 | stepValue: { 39 | stepValue: "A context step which takes two params {} and {}", 40 | parameterizedStepValue: "A context step which takes two params and ", 41 | parameters: ["hello", "blah"] 42 | } 43 | },{ 44 | stepText: "Say {} to {}", 45 | numberOfParameters: 2, 46 | stepValue: { 47 | parameterizedStepValue: "Say \"hi\" to \"gauge\"", 48 | parameters: ["hi", "gauge"] 49 | } 50 | } 51 | ]; 52 | done(); 53 | }); 54 | }); 55 | 56 | after(function () { 57 | stepRegistry.validate.restore(); 58 | }); 59 | 60 | it("Should check if step exists in step registry when a StepValidateRequest is received", function (done) { 61 | 62 | stepValidateResponse(stepValidateRequests[0], errorType); 63 | 64 | assert(stepRegistry.validate.calledOnce); 65 | assert.equal("A context step which takes two params {} and {}", stepRegistry.validate.getCall(0).args[0]); 66 | done(); 67 | 68 | }); 69 | 70 | it("StepValidateRequest should get back StepValidateResponse when step does exists", function () { 71 | const expectedResponse = { stepValidateResponse:{ isValid: true} }; 72 | const response = stepValidateResponse(stepValidateRequests[1], errorType); 73 | assert.ok(response.stepValidateResponse.isValid); 74 | assert.deepEqual(response, expectedResponse); 75 | }); 76 | 77 | it("StepValidateRequest should get back StepValidateResponse when step does not exist", function ( ) { 78 | const stub = "step(\"A context step which takes two params and \", async function(arg0, arg1) {\n\t" + 79 | "throw 'Unimplemented Step';\n});"; 80 | const expectedResponse = { 81 | stepValidateResponse:{ 82 | isValid: false, 83 | errorType: errorType.values.STEP_IMPLEMENTATION_NOT_FOUND, 84 | errorMessage: "Invalid step.", 85 | suggestion: stub 86 | } 87 | }; 88 | const response = stepValidateResponse(stepValidateRequests[0], errorType); 89 | 90 | assert.deepEqual(response, expectedResponse); 91 | }); 92 | }); 93 | 94 | describe("StepNameRequest Processing", function () { 95 | let stepNameRequest = []; 96 | this.timeout(10000); 97 | before(function () { 98 | const filePath = "example.js"; 99 | stepRegistry.clear(); 100 | const content = "\"use strict\";\n" + 101 | "var assert = require(\"assert\");\n" + 102 | "var vowels = require(\"./vowels\");\n" + 103 | "step([\"A context step which gets executed before every scenario\", \"A context step.\"], function() {\n" + 104 | " console.log('in context step');\n" + 105 | "});\n"; 106 | 107 | loader.reloadFile(filePath, content); 108 | stepNameRequest ={ 109 | stepValue: "A context step which gets executed before every scenario", 110 | }; 111 | 112 | }); 113 | 114 | it("StepNameRequest should get back StepNameResponse with fileName and span", function () { 115 | 116 | const response = stepNameResponse(stepNameRequest); 117 | 118 | assert.equal(true, response.stepNameResponse.isStepPresent); 119 | assert.equal(response.stepNameResponse.fileName, "example.js"); 120 | assert.deepEqual(response.stepNameResponse.span, { start: 4, end: 6, startChar: 0, endChar: 2 }); 121 | 122 | }); 123 | 124 | it("StepNameRequest should respond with all aliases for a given step", function () { 125 | 126 | const response = stepNameResponse(stepNameRequest); 127 | 128 | assert.equal(response.stepNameResponse.isStepPresent, true); 129 | assert.equal(response.stepNameResponse.hasAlias, true); 130 | assert.deepEqual(response.stepNameResponse.stepName, ["A context step which gets executed before every scenario", "A context step."]); 131 | }); 132 | }); 133 | 134 | describe("StepPositionsRequest Processing", function () { 135 | const filePath = "example.js"; 136 | this.timeout(10000); 137 | 138 | before(function () { 139 | stepRegistry.clear(); 140 | const content = "\"use strict\";\n" + 141 | "var assert = require(\"assert\");\n" + 142 | "var vowels = require(\"./vowels\");\n" + 143 | "step(\"Vowels in English language are .\", function(vowelsGiven) {\n" + 144 | " assert.equal(vowelsGiven, vowels.vowelList.join(\"\"));\n" + 145 | "});\n" + 146 | "step(\"The word has vowels.\", function(word, number) {\n" + 147 | " assert.equal(number, vowels.numVowels(word));\n" + 148 | "});"; 149 | 150 | loader.reloadFile(filePath, content); 151 | }); 152 | 153 | it("StepPositionsRequest should get back StepPositionsResponse with stepValue and lineNumber", function () { 154 | const stepPositionsRequest = { 155 | filePath: filePath 156 | }; 157 | const response = stepPositions(stepPositionsRequest); 158 | 159 | assert.equal("", response.stepPositionsResponse.error); 160 | assert.equal(2, response.stepPositionsResponse.stepPositions.length); 161 | assert.equal(1, response.stepPositionsResponse.stepPositions.filter(function (stepPosition) { 162 | return stepPosition.stepValue === "Vowels in English language are {}." && stepPosition.span.start === 4; 163 | }).length); 164 | assert.equal(1, response.stepPositionsResponse.stepPositions.filter(function (stepPosition) { 165 | return stepPosition.stepValue === "The word {} has {} vowels." && stepPosition.span.start === 7; 166 | }).length); 167 | }); 168 | }); 169 | 170 | describe("CacheFileRequest Processing", function () { 171 | let fileStatus = null; 172 | let filePath; 173 | const fileContent = "\"use strict\";\n" + 174 | "var assert = require(\"assert\");\n" + 175 | "var vowels = require(\"./vowels\");\n" + 176 | "step(\"Vowels in English language are .\", function(vowelsGiven) {\n" + 177 | " assert.equal(vowelsGiven, vowels.vowelList.join(\"\"));\n" + 178 | "});"; 179 | this.timeout(10000); 180 | 181 | const getCacheFileRequestMessage = function (filePath, status) { 182 | return { 183 | filePath: filePath, 184 | status: status 185 | }; 186 | }; 187 | 188 | before(function (done) { 189 | protobuf.load("gauge-proto/messages.proto").then(function (root) { 190 | fileStatus = root.lookupEnum("gauge.messages.CacheFileRequest.FileStatus"); 191 | done(); 192 | }); 193 | }); 194 | 195 | beforeEach(function() { 196 | process.env.GAUGE_PROJECT_ROOT = mock({ "dummy" : {}}); 197 | filePath = path.join(process.env.GAUGE_PROJECT_ROOT, "tests", "example.js"); 198 | }); 199 | 200 | afterEach(function () { 201 | process.env.GAUGE_PROJECT_ROOT = process.cwd(); 202 | mock.reset(); 203 | stepRegistry.clear(); 204 | }); 205 | 206 | it("should reload files on create", function () { 207 | process.env.GAUGE_PROJECT_ROOT = mock({ 208 | "tests": { 209 | "example.js": fileContent 210 | } 211 | }); 212 | const cacheFileRequest = getCacheFileRequestMessage(path.join(process.env.GAUGE_PROJECT_ROOT, "tests", "example.js"), fileStatus.valuesById[fileStatus.values.CREATED]); 213 | cacheFileResponse(cacheFileRequest, fileStatus); 214 | assert.isNotEmpty(stepRegistry.get("Vowels in English language are {}.")); 215 | }); 216 | 217 | it("should not reload files on create if file is cached already", function () { 218 | const tmp = mock({ 219 | "tests": { 220 | "example.js": "" 221 | } 222 | }); 223 | const filePath = path.join(tmp, "tests", "example.js"); 224 | const cacheFileRequest = getCacheFileRequestMessage(filePath, fileStatus.valuesById[fileStatus.values.CREATED]); 225 | loader.reloadFile(filePath, fileContent); 226 | assert.isNotEmpty(stepRegistry.get("Vowels in English language are {}.")); 227 | 228 | cacheFileResponse(cacheFileRequest, fileStatus); 229 | assert.isNotEmpty(stepRegistry.get("Vowels in English language are {}.")); 230 | }); 231 | 232 | it("should unload file on delete.", function () { 233 | const cacheFileRequest = getCacheFileRequestMessage(filePath, fileStatus.valuesById[fileStatus.values.DELETED]); 234 | loader.reloadFile(filePath, fileContent); 235 | assert.isNotEmpty(stepRegistry.get("Vowels in English language are {}.")); 236 | 237 | cacheFileResponse(cacheFileRequest, fileStatus); 238 | assert.isUndefined(stepRegistry.get("Vowels in English language are {}.")); 239 | }); 240 | 241 | it("should reload file from disk on closed.", function () { 242 | const tmp = mock({ 243 | "tests": { 244 | "example.js": fileContent 245 | } 246 | }); 247 | const filePath = path.join(tmp, "tests", "example.js"); 248 | const cacheFileRequest = getCacheFileRequestMessage(filePath, fileStatus.valuesById[fileStatus.values.CLOSED]); 249 | loader.reloadFile(filePath, fileContent); 250 | 251 | cacheFileResponse(cacheFileRequest, fileStatus); 252 | assert.isNotEmpty(stepRegistry.get("Vowels in English language are {}.")); 253 | }); 254 | 255 | it("should unload file from disk on closed and file does not exists.", function () { 256 | const tmp = mock({ 257 | "tests": {} 258 | }); 259 | process.env.GAUGE_PROJECT_ROOT = tmp; 260 | const filePath = path.join(tmp, "tests", "example.js"); 261 | const cacheFileRequest = getCacheFileRequestMessage(filePath, fileStatus.valuesById[fileStatus.values.CLOSED]); 262 | loader.reloadFile(filePath, fileContent); 263 | 264 | cacheFileResponse(cacheFileRequest, fileStatus); 265 | assert.isUndefined(stepRegistry.get("Vowels in English language are {}.")); 266 | }); 267 | 268 | it("should load changed content on file opened", function () { 269 | const cacheFileRequest = getCacheFileRequestMessage(filePath, fileStatus.valuesById[fileStatus.values.OPENED]); 270 | cacheFileRequest.content = fileContent; 271 | 272 | cacheFileResponse(cacheFileRequest, fileStatus); 273 | assert.isNotEmpty(stepRegistry.get("Vowels in English language are {}.")); 274 | }); 275 | 276 | it("should load changed content on file changed", function () { 277 | const cacheFileRequest = getCacheFileRequestMessage(filePath, fileStatus.valuesById[fileStatus.values.OPENED]); 278 | cacheFileRequest.content = fileContent; 279 | 280 | cacheFileResponse(cacheFileRequest, fileStatus); 281 | assert.isNotEmpty(stepRegistry.get("Vowels in English language are {}.")); 282 | }); 283 | }); 284 | 285 | describe("ImplementationFileGlobPatternRequest Processing", function () { 286 | this.timeout(10000); 287 | let implementationFileGlobPatternRequest; 288 | const projectRoot = "exampleProject"; 289 | 290 | before(function () { 291 | process.env.GAUGE_PROJECT_ROOT = projectRoot; 292 | stepRegistry.clear(); 293 | implementationFileGlobPatternRequest = {}; 294 | }); 295 | 296 | after(function () { 297 | process.env.GAUGE_PROJECT_ROOT = process.cwd(); 298 | process.env.STEP_IMPL_DIR = ""; 299 | }); 300 | 301 | it("should return glob pattern for default test directory", function () { 302 | const response = implementationGlobPatternResponse(implementationFileGlobPatternRequest); 303 | const expectedGlobPattern = [projectRoot + "/tests/**/*.js"]; 304 | assert.deepEqual(response.implementationFileGlobPatternResponse.globPatterns, expectedGlobPattern); 305 | }); 306 | 307 | it("should return glob patterns when multiple test directories present", function () { 308 | process.env.STEP_IMPL_DIR = "test1, test2"; 309 | const response = implementationGlobPatternResponse(implementationFileGlobPatternRequest); 310 | const expectedGlobPatterns = [projectRoot + "/test1/**/*.js", projectRoot + "/test2/**/*.js"]; 311 | assert.deepEqual(response.implementationFileGlobPatternResponse.globPatterns, expectedGlobPatterns); 312 | }); 313 | }); 314 | 315 | describe("BeforeSpecHook", function () { 316 | this.timeout(10000); 317 | var beforeDir; 318 | 319 | before(function () { 320 | stepRegistry.clear(); 321 | hookRegistry.clear(); 322 | dataStore.suiteStore.clear(); 323 | process.env.STEP_IMPL_DIR = "test1"; 324 | beforeDir = process.cwd(); 325 | process.env.GAUGE_PROJECT_ROOT = mock( { 326 | test1: { 327 | "example.js":` 328 | beforeSuite( () => { 329 | gauge.dataStore.suiteStore.put("executedBeforeSuiteHook", true); 330 | }); 331 | beforeSpec( () => { 332 | gauge.dataStore.specStore.put("executedBeforeSpecHook", true); 333 | }) 334 | beforeScenario( () => { 335 | gauge.dataStore.scenarioStore.put("executedBeforeScenarioHook", true); 336 | }) 337 | ` 338 | } 339 | }); 340 | }); 341 | 342 | after(function () { 343 | process.chdir(beforeDir); // Go back to where we were; vm.js changes the process working directory. 344 | process.env.GAUGE_PROJECT_ROOT = process.cwd(); 345 | process.env.STEP_IMPL_DIR = ""; 346 | mock.reset(); 347 | }); 348 | 349 | it("should execute before suite hook and return a success response", function (done) { 350 | function callback(response) { 351 | assert.notOk(response.executionResult.failed); 352 | assert.ok(dataStore.suiteStore.get("executedBeforeSuiteHook")); 353 | done(); 354 | } 355 | executeBeforeSuiteHook({executionStartingRequest: {} }, callback); 356 | }); 357 | 358 | it("should execute before spec hook and return a success response", function (done) { 359 | function callback(response) { 360 | assert.notOk(response.executionResult.failed); 361 | assert.ok(dataStore.specStore.get("executedBeforeSpecHook")); 362 | done(); 363 | } 364 | executeBeforeSpecHook({specExecutionStartingRequest: {} }, callback); 365 | }); 366 | 367 | it("should execute before scenario hook and return a success response", function (done) { 368 | function callback(response) { 369 | assert.notOk(response.executionResult.failed); 370 | assert.ok(dataStore.scenarioStore.get("executedBeforeScenarioHook")); 371 | done(); 372 | } 373 | executeBeforeScenarioHook({scenarioExecutionStartingRequest: {} }, callback); 374 | }); 375 | }); 376 | -------------------------------------------------------------------------------- /test/package-test.js: -------------------------------------------------------------------------------- 1 | import { assert } from "chai"; 2 | import { readFileSync } from "node:fs"; 3 | 4 | describe("Package", function () { 5 | 6 | const packageJSON = JSON.parse(readFileSync("./package.json")); 7 | const jsJSON = JSON.parse(readFileSync("./js.json")); 8 | 9 | describe("version", function () { 10 | 11 | it("should be same in package.json and js.json", function () { 12 | assert.equal(packageJSON.version, jsJSON.version); 13 | }); 14 | 15 | }); 16 | 17 | describe("name", function () { 18 | 19 | it("should be gauge- where equals in js.json", function () { 20 | assert.equal(packageJSON.name, "gauge-" + jsJSON.id); 21 | }); 22 | 23 | }); 24 | 25 | }); 26 | -------------------------------------------------------------------------------- /test/refactor-test.js: -------------------------------------------------------------------------------- 1 | import { assert } from "chai"; 2 | import stepRegistry from "../src/step-registry.js"; 3 | import refactor from "../src/refactor.js"; 4 | import factory from "../src/response-factory.js"; 5 | import fs from "node:fs"; 6 | import sinon from "sinon"; 7 | 8 | var sandbox, request, response; 9 | var contentInput, contentOutput, info; 10 | 11 | describe("Refactor", function () { 12 | this.timeout(10000); 13 | before(function () { 14 | sandbox = sinon.createSandbox(); 15 | 16 | sandbox.stub(fs, "readFileSync").callsFake(function () { 17 | return contentInput; 18 | }); 19 | 20 | sandbox.stub(fs, "writeFileSync").callsFake(function (file, data) { 21 | contentOutput = data; 22 | }); 23 | 24 | sandbox.stub(stepRegistry, "get").callsFake(function () { 25 | return info; 26 | }); 27 | }); 28 | 29 | beforeEach(function () { 30 | contentOutput = contentInput = info = request = response = null; 31 | response = factory.createRefactorResponse(); 32 | }); 33 | 34 | after(function () { 35 | sandbox.restore(); 36 | }); 37 | 38 | it("Should refactor step text without changing function ref", function () { 39 | var output = []; 40 | output.push("var vowels=[\n 'a',\n 'e',\n 'i',\n 'o',\n 'u'\n];"); 41 | output.push("hakunaMatata('What a wonderful phrase!');"); 42 | output.push("gauge.step('The word has vowels.', function (word, number) {\n});"); 43 | output.push("var myfn = function (number) {\n};"); 44 | output.push("gauge.step('There are vowels.', myfn);"); 45 | contentInput = output.join("\n"); 46 | 47 | request = { 48 | oldStepValue: { 49 | stepValue: "The word {} has {} vowels.", 50 | parameterizedStepValue: "The word has vowels.", 51 | parameters: ["word", "number"] 52 | }, 53 | newStepValue: { 54 | stepValue: "This English word {} has {} vowels.", 55 | parameterizedStepValue: "This English word has vowels.", 56 | parameters: ["word", "number"] 57 | }, 58 | paramPositions: [{ 59 | oldPosition: 0, 60 | newPosition: 0 61 | }, { 62 | oldPosition: 1, 63 | newPosition: 1 64 | }], 65 | saveChanges: true 66 | }; 67 | 68 | info = { 69 | fn: function (word, number) { console.log(word, number); }, 70 | stepText: "The word has vowels.", 71 | generalisedText: "The word {} has {} vowels.", 72 | fileLocations: [{ filePath: "test/data/refactor-output.js" }] 73 | }; 74 | 75 | response = refactor(request, response, fs); 76 | assert.strictEqual(response.refactorResponse.error, ""); 77 | assert.strictEqual(response.refactorResponse.success, true); 78 | assert.strictEqual(response.refactorResponse.filesChanged.length, 1); 79 | assert.strictEqual(response.refactorResponse.filesChanged[0], "test/data/refactor-output.js"); 80 | assert.strictEqual(response.refactorResponse.fileChanges.length, 1); 81 | assert.strictEqual(response.refactorResponse.fileChanges[0].fileName, "test/data/refactor-output.js"); 82 | assert.strictEqual(contentOutput, "var vowels = [\n 'a',\n 'e',\n 'i',\n 'o',\n 'u'\n];\nhakunaMatata('What a wonderful phrase!');\ngauge.step('This English word has vowels.', function (word, number) {\n});\nvar myfn = function (number) {\n};\ngauge.step('There are vowels.', myfn);"); 83 | assert.strictEqual(response.refactorResponse.fileChanges[0].fileContent, contentOutput); 84 | assert.deepInclude(response.refactorResponse.fileChanges[0].diffs, { 85 | content: "\"This English word has vowels.\"", 86 | span: { 87 | start: 9, 88 | startChar: 11, 89 | end: 9, 90 | endChar: 49 91 | } 92 | }); 93 | assert.deepInclude(response.refactorResponse.fileChanges[0].diffs[1], { 94 | content: "function (word, number) ", 95 | span: { 96 | start: 9, 97 | startChar: 51, 98 | end: 9, 99 | endChar: 75 100 | } 101 | }); 102 | }); 103 | 104 | it("Should not save changes when request save changes is false", function () { 105 | var output = []; 106 | output.push("var vowels=[\n 'a',\n 'e',\n 'i',\n 'o',\n 'u'\n];"); 107 | output.push("hakunaMatata('What a wonderful phrase!');"); 108 | output.push("gauge.step('The word has vowels.', function (word, number) {\n});"); 109 | output.push("var myfn = function (number) {\n};"); 110 | output.push("gauge.step('There are vowels.', myfn);"); 111 | contentInput = output.join("\n"); 112 | 113 | request = { 114 | oldStepValue: { 115 | stepValue: "The word {} has {} vowels.", 116 | parameterizedStepValue: "The word has vowels.", 117 | parameters: ["word", "number"] 118 | }, 119 | newStepValue: { 120 | stepValue: "This English word {} has {} vowels.", 121 | parameterizedStepValue: "This English word has vowels.", 122 | parameters: ["word", "number"] 123 | }, 124 | paramPositions: [{ 125 | oldPosition: 0, 126 | newPosition: 0 127 | }, { 128 | oldPosition: 1, 129 | newPosition: 1 130 | }], 131 | saveChanges: false 132 | }; 133 | 134 | info = { 135 | fn: function (word, number) { console.log(word, number); }, 136 | stepText: "The word has vowels.", 137 | generalisedText: "The word {} has {} vowels.", 138 | fileLocations: [{ filePath: "test/data/refactor-output.js" }] 139 | }; 140 | 141 | response = refactor(request, response); 142 | assert.strictEqual(response.refactorResponse.error, ""); 143 | assert.strictEqual(response.refactorResponse.success, true); 144 | assert.strictEqual(response.refactorResponse.filesChanged.length, 1); 145 | assert.strictEqual(response.refactorResponse.filesChanged[0], "test/data/refactor-output.js"); 146 | assert.strictEqual(response.refactorResponse.fileChanges.length, 1); 147 | assert.strictEqual(response.refactorResponse.fileChanges[0].fileName, "test/data/refactor-output.js"); 148 | assert.strictEqual(response.refactorResponse.fileChanges[0].fileContent, "var vowels = [\n 'a',\n 'e',\n 'i',\n 'o',\n 'u'\n];\nhakunaMatata('What a wonderful phrase!');\ngauge.step('This English word has vowels.', function (word, number) {\n});\nvar myfn = function (number) {\n};\ngauge.step('There are vowels.', myfn);"); 149 | assert.deepInclude(response.refactorResponse.fileChanges[0].diffs, { 150 | content: "\"This English word has vowels.\"", 151 | span: { 152 | start: 9, 153 | startChar: 11, 154 | end: 9, 155 | endChar: 49 156 | } 157 | }); 158 | assert.deepInclude(response.refactorResponse.fileChanges[0].diffs[1], { 159 | content: "function (word, number) ", 160 | span: { 161 | start: 9, 162 | startChar: 51, 163 | end: 9, 164 | endChar: 75 165 | } 166 | }); 167 | assert.notExists(contentOutput); 168 | }); 169 | 170 | it("Should refactor global step text without changing function ref", function () { 171 | var output = []; 172 | output.push("var vowels=[\n 'a',\n 'e',\n 'i',\n 'o',\n 'u'\n];"); 173 | output.push("hakunaMatata('What a wonderful phrase!');"); 174 | output.push("gauge.step('The word has vowels.', function (word, number) {\n});"); 175 | output.push("var myfn = function (number) {\n};"); 176 | output.push("step('There are vowels.', myfn);"); 177 | contentInput = output.join("\n"); 178 | 179 | request = { 180 | oldStepValue: { 181 | stepValue: "The word {} has {} vowels.", 182 | parameterizedStepValue: "The word has vowels.", 183 | parameters: ["word", "number"] 184 | }, 185 | newStepValue: { 186 | stepValue: "This English word {} has {} vowels.", 187 | parameterizedStepValue: "This English word has vowels.", 188 | parameters: ["word", "number"] 189 | }, 190 | paramPositions: [{ 191 | oldPosition: 0, 192 | newPosition: 0 193 | }, { 194 | oldPosition: 1, 195 | newPosition: 1 196 | }], 197 | saveChanges: true 198 | }; 199 | 200 | info = { 201 | fn: function (word, number) { console.log(word, number); }, 202 | stepText: "The word has vowels.", 203 | generalisedText: "The word {} has {} vowels.", 204 | fileLocations: [{ filePath: "test/data/refactor-output.js" }] 205 | }; 206 | 207 | response = refactor(request, response, fs); 208 | assert.strictEqual(response.refactorResponse.error, ""); 209 | assert.strictEqual(response.refactorResponse.success, true); 210 | assert.strictEqual(response.refactorResponse.filesChanged.length, 1); 211 | assert.deepInclude(response.refactorResponse.fileChanges[0].diffs, { 212 | content: "\"This English word has vowels.\"", 213 | span: { 214 | start: 9, 215 | startChar: 11, 216 | end: 9, 217 | endChar: 49 218 | } 219 | }); 220 | assert.deepInclude(response.refactorResponse.fileChanges[0].diffs[1], { 221 | content: "function (word, number) ", 222 | span: { 223 | start: 9, 224 | startChar: 51, 225 | end: 9, 226 | endChar: 75 227 | } 228 | }); 229 | }); 230 | 231 | it("Should perform refactoring when param names are changed", function () { 232 | contentInput = "gauge.step('The word has vowels.', function (word, number) {\n});"; 233 | request = { 234 | oldStepValue: { 235 | stepValue: "The word {} has {} vowels.", 236 | parameterizedStepValue: "The word has vowels.", 237 | parameters: ["word", "number"] 238 | }, 239 | newStepValue: { 240 | stepValue: "This English word {} has {} vowels.", 241 | parameterizedStepValue: "This English word has vowels.", 242 | parameters: ["word_en", "numbers"] 243 | }, 244 | paramPositions: [{ 245 | oldPosition: -1, 246 | newPosition: 0 247 | }, { 248 | oldPosition: -1, 249 | newPosition: 1 250 | }], 251 | saveChanges: true 252 | }; 253 | 254 | info = { 255 | fn: function (word, number) { console.log(word, number); }, 256 | stepText: "The word has vowels.", 257 | generalisedText: "The word {} has {} vowels.", 258 | fileLocations: [{ filePath: "test/data/refactor-output.js" }] 259 | }; 260 | 261 | response = refactor(request, response); 262 | 263 | assert.strictEqual(contentOutput, "gauge.step('This English word has vowels.', function (arg0, arg1) {\n});"); 264 | assert.deepInclude(response.refactorResponse.fileChanges[0].diffs, { 265 | content: "\"This English word has vowels.\"", 266 | span: { 267 | start: 1, 268 | startChar: 11, 269 | end: 1, 270 | endChar: 49 271 | } 272 | }); 273 | assert.deepInclude(response.refactorResponse.fileChanges[0].diffs, { 274 | content: "function (arg0, arg1) ", 275 | span: { 276 | start: 1, 277 | startChar: 51, 278 | end: 1, 279 | endChar: 75 280 | } 281 | }); 282 | }); 283 | 284 | it("Should perform refactoring for global step when param names are changed", function () { 285 | contentInput = "step('The word has vowels.', function (word, number) {\n});"; 286 | request = { 287 | oldStepValue: { 288 | stepValue: "The word {} has {} vowels.", 289 | parameterizedStepValue: "The word has vowels.", 290 | parameters: ["word", "number"] 291 | }, 292 | newStepValue: { 293 | stepValue: "This English word {} has {} vowels.", 294 | parameterizedStepValue: "This English word has vowels.", 295 | parameters: ["word_en", "numbers"] 296 | }, 297 | paramPositions: [{ 298 | oldPosition: -1, 299 | newPosition: 0 300 | }, { 301 | oldPosition: -1, 302 | newPosition: 1 303 | }], 304 | saveChanges: true 305 | }; 306 | 307 | info = { 308 | fn: function (word, number) { console.log(word, number); }, 309 | stepText: "The word has vowels.", 310 | generalisedText: "The word {} has {} vowels.", 311 | fileLocations: [{ filePath: "test/data/refactor-output.js" }] 312 | }; 313 | 314 | response = refactor(request, response); 315 | 316 | assert.strictEqual(contentOutput, "step('This English word has vowels.', function (arg0, arg1) {\n});"); 317 | assert.deepInclude(response.refactorResponse.fileChanges[0].diffs, { 318 | content: "\"This English word has vowels.\"", 319 | span: { 320 | start: 1, 321 | startChar: 5, 322 | end: 1, 323 | endChar: 43 324 | } 325 | }); 326 | assert.deepInclude(response.refactorResponse.fileChanges[0].diffs, { 327 | content: "function (arg0, arg1) ", 328 | span: { 329 | start: 1, 330 | startChar: 45, 331 | end: 1, 332 | endChar: 69 333 | } 334 | }); 335 | }); 336 | 337 | it("Should perform refactoring when params are removed", function () { 338 | contentInput = "gauge.step('The word has vowels.', function (word, number) {\n});"; 339 | request = { 340 | oldStepValue: { 341 | stepValue: "The word {} has {} vowels.", 342 | parameterizedStepValue: "The word has vowels.", 343 | parameters: ["word", "number"] 344 | }, 345 | newStepValue: { 346 | stepValue: "This English word {} has {} vowels.", 347 | parameterizedStepValue: "This English word has vowels.", 348 | parameters: ["numbers"] 349 | }, 350 | paramPositions: [{ 351 | oldPosition: -1, 352 | newPosition: 0 353 | }], 354 | saveChanges: true 355 | }; 356 | 357 | info = { 358 | fn: function (word, number) { console.log(word, number); }, 359 | stepText: "The word has vowels.", 360 | generalisedText: "The word {} has {} vowels.", 361 | fileLocations: [{ filePath: "test/data/refactor-output.js" }] 362 | }; 363 | 364 | response = refactor(request, response); 365 | 366 | assert.strictEqual(contentOutput, "gauge.step('This English word has vowels.', function (arg0) {\n});"); 367 | assert.deepInclude(response.refactorResponse.fileChanges[0].diffs, { 368 | content: "\"This English word has vowels.\"", 369 | span: { 370 | start: 1, 371 | startChar: 11, 372 | end: 1, 373 | endChar: 49 374 | } 375 | }); 376 | assert.deepInclude(response.refactorResponse.fileChanges[0].diffs, { 377 | content: "function (arg0) ", 378 | span: { 379 | start: 1, 380 | startChar: 51, 381 | end: 1, 382 | endChar: 75 383 | } 384 | }); 385 | }); 386 | 387 | it("Should perform refactoring for global when params are removed", function () { 388 | contentInput = "step('The word has vowels.', function (word, number) {\n});"; 389 | request = { 390 | oldStepValue: { 391 | stepValue: "The word {} has {} vowels.", 392 | parameterizedStepValue: "The word has vowels.", 393 | parameters: ["word", "number"] 394 | }, 395 | newStepValue: { 396 | stepValue: "This English word {} has {} vowels.", 397 | parameterizedStepValue: "This English word has vowels.", 398 | parameters: ["numbers"] 399 | }, 400 | paramPositions: [{ 401 | oldPosition: -1, 402 | newPosition: 0 403 | }], 404 | saveChanges: true 405 | }; 406 | 407 | info = { 408 | fn: function (word, number) { console.log(word, number); }, 409 | stepText: "The word has vowels.", 410 | generalisedText: "The word {} has {} vowels.", 411 | fileLocations: [{ filePath: "test/data/refactor-output.js" }] 412 | }; 413 | 414 | response = refactor(request, response); 415 | 416 | assert.strictEqual(contentOutput, "step('This English word has vowels.', function (arg0) {\n});"); 417 | assert.deepInclude(response.refactorResponse.fileChanges[0].diffs, { 418 | content: "\"This English word has vowels.\"", 419 | span: { 420 | start: 1, 421 | startChar: 5, 422 | end: 1, 423 | endChar: 43 424 | } 425 | }); 426 | assert.deepInclude(response.refactorResponse.fileChanges[0].diffs, { 427 | content: "function (arg0) ", 428 | span: { 429 | start: 1, 430 | startChar: 45, 431 | end: 1, 432 | endChar: 69 433 | } 434 | }); 435 | }); 436 | 437 | it("Should perform refactoring when params are reordered", function () { 438 | contentInput = "gauge.step('The word has vowels.', function (word, number) {\n});"; 439 | request = { 440 | oldStepValue: { 441 | stepValue: "The word {} has {} vowels.", 442 | parameterizedStepValue: "The word has vowels.", 443 | parameters: ["word", "number"] 444 | }, 445 | newStepValue: { 446 | stepValue: "There are {} vowels in the word {}", 447 | parameterizedStepValue: "There are vowels in the word .", 448 | parameters: ["number", "word"] 449 | }, 450 | paramPositions: [{ 451 | oldPosition: 0, 452 | newPosition: 1 453 | }, { 454 | oldPosition: 1, 455 | newPosition: 0 456 | }], 457 | saveChanges: true 458 | }; 459 | 460 | info = { 461 | fn: function (word, number) { console.log(word, number); }, 462 | stepText: "The word has vowels.", 463 | generalisedText: "The word {} has {} vowels.", 464 | fileLocations: [{ filePath: "test/data/refactor-output.js" }] 465 | }; 466 | 467 | response = refactor(request, response); 468 | 469 | assert.strictEqual(contentOutput, "gauge.step('There are vowels in the word .', function (number, word) {\n});"); 470 | assert.deepInclude(response.refactorResponse.fileChanges[0].diffs, { 471 | content: "\"There are vowels in the word .\"", 472 | span: { 473 | start: 1, 474 | startChar: 11, 475 | end: 1, 476 | endChar: 49 477 | } 478 | }); 479 | assert.deepInclude(response.refactorResponse.fileChanges[0].diffs, { 480 | content: "function (number, word) ", 481 | span: { 482 | start: 1, 483 | startChar: 51, 484 | end: 1, 485 | endChar: 75 486 | } 487 | }); 488 | }); 489 | 490 | it("Should perform refactoring for global step when params are reordered", function () { 491 | contentInput = "step('The word has vowels.', function (word, number) {\n});"; 492 | request = { 493 | oldStepValue: { 494 | stepValue: "The word {} has {} vowels.", 495 | parameterizedStepValue: "The word has vowels.", 496 | parameters: ["word", "number"] 497 | }, 498 | newStepValue: { 499 | stepValue: "There are {} vowels in the word {}", 500 | parameterizedStepValue: "There are vowels in the word .", 501 | parameters: ["number", "word"] 502 | }, 503 | paramPositions: [{ 504 | oldPosition: 0, 505 | newPosition: 1 506 | }, { 507 | oldPosition: 1, 508 | newPosition: 0 509 | }], 510 | saveChanges: true 511 | }; 512 | 513 | info = { 514 | fn: function (word, number) { console.log(word, number); }, 515 | stepText: "The word has vowels.", 516 | generalisedText: "The word {} has {} vowels.", 517 | fileLocations: [{ filePath: "test/data/refactor-output.js" }] 518 | }; 519 | 520 | response = refactor(request, response); 521 | 522 | assert.strictEqual(contentOutput, "step('There are vowels in the word .', function (number, word) {\n});"); 523 | assert.deepInclude(response.refactorResponse.fileChanges[0].diffs, { 524 | content: "\"There are vowels in the word .\"", 525 | span: { 526 | start: 1, 527 | startChar: 5, 528 | end: 1, 529 | endChar: 43 530 | } 531 | }); 532 | assert.deepInclude(response.refactorResponse.fileChanges[0].diffs, { 533 | content: "function (number, word) ", 534 | span: { 535 | start: 1, 536 | startChar: 45, 537 | end: 1, 538 | endChar: 69 539 | } 540 | }); 541 | }); 542 | 543 | it("Should perform refactoring when new params are added", function () { 544 | contentInput = "gauge.step('The word has vowels.', function (word, number) {\n});"; 545 | request = { 546 | oldStepValue: { 547 | stepValue: "The word {} has {} vowels.", 548 | parameterizedStepValue: "The word has vowels.", 549 | parameters: ["word", "number"] 550 | }, 551 | newStepValue: { 552 | stepValue: "The word {} has {} vowels and ends with {}.", 553 | parameterizedStepValue: "The word has vowels and ends with .", 554 | parameters: ["word", "number", "end_letter"] 555 | }, 556 | paramPositions: [{ 557 | oldPosition: 0, 558 | newPosition: 0 559 | }, { 560 | oldPosition: 1, 561 | newPosition: 1 562 | }, { 563 | oldPosition: -1, 564 | newPosition: 2 565 | }], 566 | saveChanges: true 567 | }; 568 | 569 | info = { 570 | fn: function (word, number) { console.log(word, number); }, 571 | stepText: "The word has vowels.", 572 | generalisedText: "The word {} has {} vowels.", 573 | fileLocations: [{ filePath: "test/data/refactor-output.js" }] 574 | }; 575 | 576 | response = refactor(request, response); 577 | 578 | assert.strictEqual(contentOutput, "gauge.step('The word has vowels and ends with .', function (word, number, arg2) {\n});"); 579 | assert.deepInclude(response.refactorResponse.fileChanges[0].diffs, { 580 | content: "\"The word has vowels and ends with .\"", 581 | span: { 582 | start: 1, 583 | startChar: 11, 584 | end: 1, 585 | endChar: 49 586 | } 587 | }); 588 | assert.deepInclude(response.refactorResponse.fileChanges[0].diffs, { 589 | content: "function (word, number, arg2) ", 590 | span: { 591 | start: 1, 592 | startChar: 51, 593 | end: 1, 594 | endChar: 75 595 | } 596 | }); 597 | }); 598 | 599 | it("Should perform refactoring for global step when new params are added", function () { 600 | contentInput = "step('The word has vowels.', function (word, number) {\n});"; 601 | request = { 602 | oldStepValue: { 603 | stepValue: "The word {} has {} vowels.", 604 | parameterizedStepValue: "The word has vowels.", 605 | parameters: ["word", "number"] 606 | }, 607 | newStepValue: { 608 | stepValue: "The word {} has {} vowels and ends with {}.", 609 | parameterizedStepValue: "The word has vowels and ends with .", 610 | parameters: ["word", "number", "end_letter"] 611 | }, 612 | paramPositions: [{ 613 | oldPosition: 0, 614 | newPosition: 0 615 | }, { 616 | oldPosition: 1, 617 | newPosition: 1 618 | }, { 619 | oldPosition: -1, 620 | newPosition: 2 621 | }], 622 | saveChanges: true 623 | }; 624 | 625 | info = { 626 | fn: function (word, number) { console.log(word, number); }, 627 | stepText: "The word has vowels.", 628 | generalisedText: "The word {} has {} vowels.", 629 | fileLocations: [{ filePath: "test/data/refactor-output.js" }] 630 | }; 631 | 632 | response = refactor(request, response); 633 | 634 | assert.strictEqual(contentOutput, "step('The word has vowels and ends with .', function (word, number, arg2) {\n});"); 635 | assert.deepInclude(response.refactorResponse.fileChanges[0].diffs, { 636 | content: "\"The word has vowels and ends with .\"", 637 | span: { 638 | start: 1, 639 | startChar: 5, 640 | end: 1, 641 | endChar: 43 642 | } 643 | }); 644 | assert.deepInclude(response.refactorResponse.fileChanges[0].diffs, { 645 | content: "function (word, number, arg2) ", 646 | span: { 647 | start: 1, 648 | startChar: 45, 649 | end: 1, 650 | endChar: 69 651 | } 652 | }); 653 | }); 654 | 655 | it("Should perform refactoring while retaining callbacks for async step implementation calls", function () { 656 | contentInput = "gauge.step('The word has vowels.', function (word, number, done) {\n});"; 657 | request = { 658 | oldStepValue: { 659 | stepValue: "The word {} has {} vowels.", 660 | parameterizedStepValue: "The word has vowels.", 661 | parameters: ["word", "number"] 662 | }, 663 | newStepValue: { 664 | stepValue: "This English word {} has {} vowels.", 665 | parameterizedStepValue: "This English word has vowels.", 666 | parameters: ["word", "numbers"] 667 | }, 668 | paramPositions: [{ 669 | oldPosition: 0, 670 | newPosition: 0 671 | }, { 672 | oldPosition: -1, 673 | newPosition: 1 674 | }], 675 | saveChanges: true 676 | }; 677 | 678 | info = { 679 | fn: function (word, number, done) { console.log(word, number, done); }, 680 | stepText: "The word has vowels.", 681 | generalisedText: "The word {} has {} vowels.", 682 | fileLocations: [{ filePath: "test/data/refactor-output.js" }] 683 | }; 684 | 685 | response = refactor(request, response); 686 | 687 | assert.strictEqual(contentOutput, "gauge.step('This English word has vowels.', function (word, arg1, done) {\n});"); 688 | assert.deepInclude(response.refactorResponse.fileChanges[0].diffs, { 689 | content: "\"This English word has vowels.\"", 690 | span: { 691 | start: 1, 692 | startChar: 11, 693 | end: 1, 694 | endChar: 49 695 | } 696 | }); 697 | assert.deepInclude(response.refactorResponse.fileChanges[0].diffs, { 698 | content: "function (word, arg1, done) ", 699 | span: { 700 | start: 1, 701 | startChar: 51, 702 | end: 1, 703 | endChar: 81 704 | } 705 | }); 706 | }); 707 | 708 | it("Should perform refactoring while retaining callbacks for async for global step implementation calls", function () { 709 | contentInput = "step('The word has vowels.', function (word, number, done) {\n});"; 710 | request = { 711 | oldStepValue: { 712 | stepValue: "The word {} has {} vowels.", 713 | parameterizedStepValue: "The word has vowels.", 714 | parameters: ["word", "number"] 715 | }, 716 | newStepValue: { 717 | stepValue: "This English word {} has {} vowels.", 718 | parameterizedStepValue: "This English word has vowels.", 719 | parameters: ["word", "numbers"] 720 | }, 721 | paramPositions: [{ 722 | oldPosition: 0, 723 | newPosition: 0 724 | }, { 725 | oldPosition: -1, 726 | newPosition: 1 727 | }], 728 | saveChanges: true 729 | }; 730 | 731 | info = { 732 | fn: function (word, number, done) { console.log(word, number, done); }, 733 | stepText: "The word has vowels.", 734 | generalisedText: "The word {} has {} vowels.", 735 | fileLocations: [{ filePath: "test/data/refactor-output.js" }] 736 | }; 737 | 738 | response = refactor(request, response); 739 | 740 | assert.strictEqual(contentOutput, "step('This English word has vowels.', function (word, arg1, done) {\n});"); 741 | assert.deepInclude(response.refactorResponse.fileChanges[0].diffs, { 742 | content: "\"This English word has vowels.\"", 743 | span: { 744 | start: 1, 745 | startChar: 5, 746 | end: 1, 747 | endChar: 43 748 | } 749 | }); 750 | assert.deepInclude(response.refactorResponse.fileChanges[0].diffs, { 751 | content: "function (word, arg1, done) ", 752 | span: { 753 | start: 1, 754 | startChar: 45, 755 | end: 1, 756 | endChar: 75 757 | } 758 | }); 759 | }); 760 | 761 | it("Should perform refactoring when new params are interchanged", function () { 762 | contentInput = "step('The word has vowels.', function (word, number) {\n});"; 763 | request = { 764 | oldStepValue: { 765 | stepValue: "The word {} has {} vowels.", 766 | parameterizedStepValue: "The word has vowels.", 767 | parameters: ["word", "number"] 768 | }, 769 | newStepValue: { 770 | stepValue: "There are {} of vowels in {}.", 771 | parameterizedStepValue: "There are of vowels in .", 772 | parameters: ["number", "word"] 773 | }, 774 | paramPositions: [{ 775 | oldPosition: 0, 776 | newPosition: 1 777 | }, { 778 | oldPosition: 1, 779 | newPosition: 0 780 | }], 781 | saveChanges: true 782 | }; 783 | 784 | info = { 785 | fn: function (word, number) { console.log(word, number); }, 786 | stepText: "The word has vowels.", 787 | generalisedText: "The word {} has {} vowels.", 788 | fileLocations: [{ filePath: "test/data/refactor-output.js" }] 789 | }; 790 | 791 | response = refactor(request, response); 792 | 793 | assert.strictEqual(contentOutput, "step('There are of vowels in .', function (number, word) {\n});"); 794 | assert.deepInclude(response.refactorResponse.fileChanges[0].diffs, { 795 | content: "\"There are of vowels in .\"", 796 | span: { 797 | start: 1, 798 | startChar: 5, 799 | end: 1, 800 | endChar: 43 801 | } 802 | }); 803 | assert.deepInclude(response.refactorResponse.fileChanges[0].diffs, { 804 | content: "function (number, word) ", 805 | span: { 806 | start: 1, 807 | startChar: 45, 808 | end: 1, 809 | endChar: 69 810 | } 811 | }); 812 | }); 813 | }); 814 | -------------------------------------------------------------------------------- /test/screenshot-test.js: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import screenshot from "../src/screenshot.js"; 3 | import child_process from "node:child_process"; 4 | import fs from "node:fs"; 5 | import path from "node:path"; 6 | import sinon from "sinon"; 7 | const sandbox = sinon.createSandbox(); 8 | 9 | 10 | function screenshotFunction() { 11 | return Buffer.from("screentshot").toString("base64"); 12 | } 13 | 14 | function asyncScreenshotFunction() { 15 | return Promise.resolve(Buffer.from("screentshot").toString("base64")); 16 | } 17 | 18 | describe("screentshot.capture", function () { 19 | const screenshotsDir = path.join(".gauge", "screenshots"); 20 | 21 | this.beforeEach(() => { 22 | process.env.gauge_screenshots_dir = screenshotsDir; 23 | }); 24 | afterEach(() => { 25 | sandbox.restore(); 26 | }); 27 | describe("with default screenshot writer", () => { 28 | it("should capture screentshot 5769768", function (done) { 29 | let screenShotFile = "screenshot-21432453.png"; 30 | const spawnSyncStub = sandbox.stub(child_process, "spawnSync").returns({}); 31 | sandbox.stub(process.hrtime, "bigint").returns(21432453); 32 | screenshot.capture().then(function (file) { 33 | const filePath = path.join(screenshotsDir, screenShotFile); 34 | const expectedArgs = ["gauge_screenshot", [filePath]]; 35 | const actualArgs = spawnSyncStub.getCall(0).args; 36 | 37 | expect(file).to.be.equal(screenShotFile); 38 | expect(actualArgs).to.be.deep.equal(expectedArgs); 39 | done(); 40 | }); 41 | }); 42 | }); 43 | 44 | describe("with custom screenshot grabber function", () => { 45 | afterEach(function () { 46 | global.gauge = { screenshotFn: null }; 47 | }); 48 | it("Should handle rejected promise", function (done) { 49 | global.gauge = { 50 | screenshotFn: function () { 51 | return Promise.reject("Failed to take screenshot"); 52 | } 53 | }; 54 | 55 | screenshot.capture().catch(function (file) { 56 | expect(file).to.be.equal("Failed to take screenshot"); 57 | done(); 58 | }); 59 | }); 60 | describe("when data is in base64 string", () => { 61 | it("Should capture screentshot with async function", function (done) { 62 | sandbox.stub(process.hrtime, "bigint").returns(6767787989089); 63 | const screenShotFile = "screenshot-6767787989089.png"; 64 | global.gauge = { screenshotFn: asyncScreenshotFunction }; 65 | const writeFileSyncStub = sandbox.stub(fs, "writeFileSync"); 66 | 67 | screenshot.capture().then(function (file) { 68 | const filePath = path.join(screenshotsDir, screenShotFile); 69 | const expectedArgs = [filePath, Buffer.from("screentshot").toString("base64"), "base64"]; 70 | const actualArgs = writeFileSyncStub.getCall(0).args; 71 | expect(file).to.be.equal(screenShotFile); 72 | expect(actualArgs).to.be.deep.equal(expectedArgs); 73 | done(); 74 | }); 75 | }); 76 | 77 | it("Should capture screentshot with sync function", function (done) { 78 | sandbox.stub(process.hrtime, "bigint").returns(6767787989089); 79 | const screenShotFile = "screenshot-6767787989089.png"; 80 | global.gauge = { screenshotFn: screenshotFunction }; 81 | const writeFileSyncStub = sandbox.stub(fs, "writeFileSync"); 82 | 83 | screenshot.capture().then(function (file) { 84 | const filePath = path.join(screenshotsDir, screenShotFile); 85 | const expectedArgs = [filePath, Buffer.from("screentshot").toString("base64"), "base64"]; 86 | const actualArgs = writeFileSyncStub.getCall(0).args; 87 | expect(file).to.be.equal(screenShotFile); 88 | expect(actualArgs).to.be.deep.equal(expectedArgs); 89 | done(); 90 | }); 91 | }); 92 | 93 | describe("when data is in bytes", () => { 94 | it("Should capture screentshot with function returning a promise", function (done) { 95 | sandbox.stub(process.hrtime, "bigint").returns(6767787989089); 96 | const screenShotFile = "screenshot-6767787989089.png"; 97 | global.gauge = { screenshotFn: () => Promise.resolve(Buffer.from("screentshot")) }; 98 | const writeFileSyncStub = sandbox.stub(fs, "writeFileSync"); 99 | 100 | screenshot.capture().then(function (file) { 101 | const filePath = path.join(screenshotsDir, screenShotFile); 102 | const expectedArgs = [filePath, Buffer.from("screentshot")]; 103 | const actualArgs = writeFileSyncStub.getCall(0).args; 104 | expect(file).to.be.equal(screenShotFile); 105 | expect(actualArgs).to.be.deep.equal(expectedArgs); 106 | done(); 107 | }); 108 | }); 109 | 110 | it("Should capture screentshot with function returning screenshot data", function (done) { 111 | sandbox.stub(process.hrtime, "bigint").returns(6767787989089); 112 | const screenShotFile = "screenshot-6767787989089.png"; 113 | global.gauge = { screenshotFn: ()=> Buffer.from("screentshot") }; 114 | const writeFileSyncStub = sandbox.stub(fs, "writeFileSync"); 115 | 116 | screenshot.capture().then(function (file) { 117 | const filePath = path.join(screenshotsDir, screenShotFile); 118 | const expectedArgs = [filePath, Buffer.from("screentshot")]; 119 | const actualArgs = writeFileSyncStub.getCall(0).args; 120 | expect(file).to.be.equal(screenShotFile); 121 | expect(actualArgs).to.be.deep.equal(expectedArgs); 122 | done(); 123 | }); 124 | }); 125 | }); 126 | }); 127 | 128 | describe("with custom screenshot writer function", () => { 129 | afterEach(() => { 130 | global.gauge = { customScreenshotWriter: null }; 131 | }); 132 | it("Should capture screentshot with function returning a promise", function (done) { 133 | const screenShotFile = "screenshot-file-1.png"; 134 | global.gauge = { 135 | customScreenshotWriter: function () { 136 | return Promise.resolve(screenShotFile); 137 | } 138 | }; 139 | 140 | screenshot.capture().then(function (file) { 141 | expect(file).to.be.equal(screenShotFile); 142 | done(); 143 | }); 144 | }); 145 | 146 | it("Should capture screentshot with function returning screenshot file name", function (done) { 147 | const screenShotFile = "screenshot-file-2.png"; 148 | global.gauge = { 149 | customScreenshotWriter: function () { 150 | return screenShotFile; 151 | } 152 | }; 153 | 154 | screenshot.capture().then(function (file) { 155 | expect(file).to.be.equal(screenShotFile); 156 | done(); 157 | }); 158 | }); 159 | 160 | it("Should handle rejected promise", function (done) { 161 | global.gauge = { 162 | customScreenshotWriter: function () { 163 | return Promise.reject("Failed to take screenshot"); 164 | } 165 | }; 166 | 167 | screenshot.capture().catch(function (file) { 168 | expect(file).to.be.equal("Failed to take screenshot"); 169 | done(); 170 | }); 171 | }); 172 | }); 173 | }); 174 | }); 175 | -------------------------------------------------------------------------------- /test/static-loader-test.js: -------------------------------------------------------------------------------- 1 | import { assert } from "chai"; 2 | import loader from "../src/static-loader.js"; 3 | import stepRegistry from "../src/step-registry.js"; 4 | 5 | describe("Static loader", function () { 6 | beforeEach(function () { 7 | stepRegistry.clear(); 8 | }); 9 | 10 | it("Should load the steps from provided js contents", function (done) { 11 | var filepath = "step_implementation.js"; 12 | var source = "step('vsdvsv', function () {\n" + 13 | "\tassert.equal(+number, numberOfVowels(word));\n" + 14 | "});"; 15 | 16 | loader.reloadFile(filepath, source); 17 | var step = stepRegistry.get("vsdvsv"); 18 | assert.isDefined(step); 19 | assert.isNull(step.fn); 20 | assert.deepEqual(step.fileLocations, [{ filePath: filepath, span: { start: 1, end: 3, startChar: 0, endChar: 2 } }]); 21 | assert.equal(step.stepText, "vsdvsv"); 22 | assert.equal(step.generalisedText, "vsdvsv"); 23 | assert.isNull(step.options); 24 | done(); 25 | }); 26 | 27 | it("Should load the aliases steps from provided js contents", function (done) { 28 | var filepath = "step_implementation.js"; 29 | var source = "step(['vsdvsv', 'oohooo'], function () {\n" + 30 | "\tassert.equal(+number, numberOfVowels(word));\n" + 31 | "});"; 32 | 33 | loader.reloadFile(filepath, source); 34 | var steps = stepRegistry.getStepTexts(); 35 | assert.equal(steps.length, 2); 36 | assert.deepEqual(steps, ["vsdvsv", "oohooo"]); 37 | steps.forEach((stepText) => { 38 | var step = stepRegistry.get(stepText); 39 | assert.equal(step.hasAlias, true); 40 | assert.deepEqual(step.aliases, ["vsdvsv", "oohooo"]); 41 | }); 42 | done(); 43 | }); 44 | 45 | it("Should reload the steps from for a given file and content", function (done) { 46 | var filepath = "step_implementation.js"; 47 | 48 | var sourceV1 = "step('vsdvsv', function () {\n" + 49 | "\tconsole.log('it does not do anything')\n" + 50 | "});"; 51 | 52 | var sourceV2 = "step('black magic', function () {\n" + 53 | "\tconsole.log('lets start the magic!');\n" + 54 | "});"; 55 | 56 | loader.reloadFile(filepath, sourceV1); 57 | 58 | var step = stepRegistry.get("vsdvsv"); 59 | assert.isDefined(step); 60 | 61 | loader.reloadFile(filepath, sourceV2); 62 | 63 | var oldStep = stepRegistry.get("vsdvsv"); 64 | assert.isUndefined(oldStep); 65 | 66 | var newStep = stepRegistry.get("black magic"); 67 | assert.isDefined(newStep); 68 | assert.isNull(newStep.fn); 69 | assert.deepEqual(newStep.fileLocations, [{ filePath: filepath, span: { start: 1, end: 3, startChar: 0, endChar: 2 } }]); 70 | assert.equal(newStep.stepText, "black magic"); 71 | assert.equal(newStep.generalisedText, "black magic"); 72 | assert.isNull(newStep.options); 73 | 74 | done(); 75 | }); 76 | 77 | it("Should not reload the steps if content has parse error", function (done) { 78 | var filepath = "step_implementation.js"; 79 | 80 | var sourceV1 = "step('vsdvsv', function () {\n" + 81 | "\tconsole.log('it does not do anything')\n" + 82 | "});"; 83 | 84 | var sourceV2 = "step('black magic', function () {\n" + 85 | "\tconsole.log('lets start the magic!');\n" + 86 | "\\ // syntax error\n" + 87 | "});"; 88 | 89 | loader.reloadFile(filepath, sourceV1); 90 | 91 | var step = stepRegistry.get("vsdvsv"); 92 | assert.isDefined(step); 93 | 94 | loader.reloadFile(filepath, sourceV2); 95 | 96 | var oldStep = stepRegistry.get("vsdvsv"); 97 | assert.isDefined(oldStep); 98 | 99 | var newStep = stepRegistry.get("black magic"); 100 | assert.isUndefined(newStep); 101 | done(); 102 | }); 103 | 104 | it("Should not add the steps with no value", function (done) { 105 | var filepath = "step_implementation.js"; 106 | 107 | var source = "step('', function () {\n" + 108 | "\tconsole.log('it does not do anything')\n" + 109 | "});"; 110 | 111 | loader.reloadFile(filepath, source); 112 | 113 | var steps = Object.keys(stepRegistry.registry); 114 | assert.isOk(steps.length == 0); 115 | done(); 116 | }); 117 | 118 | it("Should not add the step aliases with no value in", function (done) { 119 | var filepath = "step_implementation.js"; 120 | 121 | var source = "step(['hello', ''], function () {\n" + 122 | "\tconsole.log('it does not do anything')\n" + 123 | "});"; 124 | 125 | loader.reloadFile(filepath, source); 126 | 127 | var steps = Object.keys(stepRegistry.registry); 128 | assert.isOk(steps.length == 1); 129 | done(); 130 | }); 131 | }); 132 | -------------------------------------------------------------------------------- /test/step-parser-test.js: -------------------------------------------------------------------------------- 1 | import { assert } from "chai"; 2 | import stepParser from "../src/step-parser.js"; 3 | 4 | describe("Parsing steps", function() { 5 | 6 | it("Should generalise a step.", function(done) { 7 | assert.equal("Say {} to {}", stepParser.generalise("Say to ")); 8 | assert.equal("A step without any paramaeters", stepParser.generalise("A step without any paramaeters")); 9 | done(); 10 | }); 11 | 12 | it("Should parse the parameters.", function(done) { 13 | assert.deepEqual(["greeting", "user"], stepParser.getParams("Say to ")); 14 | assert.deepEqual([], stepParser.getParams("A step without any parameters")); 15 | done(); 16 | }); 17 | 18 | }); 19 | -------------------------------------------------------------------------------- /test/step-registry-test.js: -------------------------------------------------------------------------------- 1 | import { assert } from "chai"; 2 | import stepRegistry from "../src/step-registry.js"; 3 | 4 | describe("Store and retrieve steps", function () { 5 | var sampleFunction; 6 | 7 | beforeEach(function () { 8 | sampleFunction = function () { }; 9 | stepRegistry.clear(); 10 | }); 11 | 12 | it("Should store and retrive steps", function (done) { 13 | stepRegistry.add("Sample Step <1>", sampleFunction); 14 | assert.equal(sampleFunction, stepRegistry.get("Sample Step {}").fn); 15 | assert.equal("Sample Step {}", stepRegistry.get("Sample Step {}").generalisedText); 16 | assert.equal("Sample Step <1>", stepRegistry.get("Sample Step {}").stepText); 17 | done(); 18 | }); 19 | 20 | it("Should return true for implemented step", function (done) { 21 | stepRegistry.add("Say {} to {}", function () { }); 22 | assert.equal(true, stepRegistry.exists("Say {} to {}")); 23 | done(); 24 | }); 25 | 26 | it("Should return false for unimplemented step", function (done) { 27 | assert.equal(false, stepRegistry.exists("Say {} to {}")); 28 | done(); 29 | }); 30 | 31 | it("Should clear registry", function (done) { 32 | stepRegistry.add("Sample Step <1>", sampleFunction); 33 | stepRegistry.clear(); 34 | assert.deepEqual({}, stepRegistry.registry); 35 | done(); 36 | }); 37 | 38 | it("Should delete steps for given filepath", function (done) { 39 | var filepath = "impl.js"; 40 | stepRegistry.add("Sample Step <1>", sampleFunction, filepath, 2); 41 | stepRegistry.deleteSteps(filepath); 42 | assert.deepEqual({}, stepRegistry.registry); 43 | done(); 44 | }); 45 | 46 | it("Should delete only fileLocation steps for duplicate steps", function (done) { 47 | var filepath = "impl.js"; 48 | var anotherFilePath = "impl2.js"; 49 | stepRegistry.add("Sample Step <1>", sampleFunction, filepath, { start: 2, end: 4 }, {}); 50 | stepRegistry.add("Sample Step <1>", sampleFunction, anotherFilePath, { start: 2, end: 4 }, {}); 51 | assert.equal(2, stepRegistry.get("Sample Step {}").count()); 52 | 53 | stepRegistry.deleteSteps(filepath); 54 | var step = stepRegistry.get("Sample Step {}"); 55 | assert.equal(1, step.count()); 56 | assert.deepEqual({ filePath: anotherFilePath, span: { start: 2, end: 4 } }, step.fileLocations[0]); 57 | done(); 58 | }); 59 | 60 | it("Should add steps with aliases", function (done) { 61 | stepRegistry.addAlias(["Sample Step <1>", "Sample alias step <1>"], sampleFunction); 62 | 63 | assert.equal(sampleFunction, stepRegistry.get("Sample Step {}").fn); 64 | assert.equal(stepRegistry.get("Sample Step {}").generalisedText, "Sample Step {}"); 65 | assert.equal(stepRegistry.get("Sample Step {}").stepText, "Sample Step <1>"); 66 | assert.equal(stepRegistry.get("Sample Step {}").hasAlias, true); 67 | assert.deepEqual(stepRegistry.get("Sample Step {}").aliases, ["Sample Step <1>", "Sample alias step <1>"]); 68 | 69 | assert.equal(sampleFunction, stepRegistry.get("Sample alias step {}").fn); 70 | assert.equal(stepRegistry.get("Sample alias step {}").generalisedText, "Sample alias step {}"); 71 | assert.equal(stepRegistry.get("Sample alias step {}").stepText, "Sample alias step <1>"); 72 | assert.equal(stepRegistry.get("Sample alias step {}").hasAlias, true); 73 | assert.deepEqual(stepRegistry.get("Sample alias step {}").aliases, ["Sample Step <1>", "Sample alias step <1>"]); 74 | 75 | done(); 76 | }); 77 | 78 | it("Should check if given filepath is already cached", function (done) { 79 | var filepath = "impl.js"; 80 | stepRegistry.add("Sample Step <1>", sampleFunction, filepath, 2); 81 | assert.isTrue(stepRegistry.isFileCached(filepath)); 82 | assert.isNotTrue(stepRegistry.isFileCached("some_impl.js")); 83 | done(); 84 | }); 85 | }); 86 | -------------------------------------------------------------------------------- /test/table.js: -------------------------------------------------------------------------------- 1 | import Table from "../src/table.js"; 2 | import { expect } from "chai"; 3 | import util from "node:util"; 4 | const setTimeoutPromise = util.promisify(setTimeout); 5 | 6 | describe("ProtoTable parsing", function() { 7 | 8 | var protoTable = { 9 | headers: { 10 | cells: [ "Product", "Description" ] 11 | }, 12 | rows: [ 13 | { cells: [ "Gauge", "Test automation with ease" ] }, 14 | { cells: [ "Mingle", "Agile project management" ] }, 15 | { cells: [ "Snap", "Hosted continuous integration" ] }, 16 | { cells: [ "Gocd", "Continuous delivery platform" ] } 17 | ] 18 | }; 19 | 20 | var table = new Table(protoTable); 21 | 22 | let getRowData = function(entry) { 23 | const rowData = setTimeoutPromise(500, entry).then((value) => { 24 | return {cells: [value["Product"], value["Description"]]}; 25 | }); 26 | return rowData; 27 | 28 | }; 29 | 30 | it("Should get headers", function () { 31 | expect(table.headers).to.deep.equal(protoTable.headers); 32 | }); 33 | 34 | it("Should get rows", function () { 35 | expect(table.rows).to.deep.equal(protoTable.rows); 36 | }); 37 | 38 | describe("Table entries", function () { 39 | 40 | it("Should have correct number of entries", function () { 41 | var result = []; 42 | table.entries(entry => result.push(entry)); 43 | expect(result.length).to.equal(4); 44 | }); 45 | 46 | it("Should have correct entry object", function () { 47 | var result = []; 48 | table.entries(entry => result.push(entry)); 49 | expect(result[1]).to.deep.equal({ 50 | "Product": "Mingle", 51 | "Description": "Agile project management" 52 | }); 53 | }); 54 | 55 | it("Should process an asynchronous callback action", async function () { 56 | let data = []; 57 | 58 | await table.entries(async (entry) => data.push(await getRowData(entry))); 59 | 60 | expect(data).to.deep.equal(protoTable.rows); 61 | }).timeout(10000); 62 | 63 | }); 64 | 65 | }); 66 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | import Test from "../src/test.js"; 2 | import { expect } from "chai"; 3 | import sinon from "sinon"; 4 | 5 | describe("Test function execution", function () { 6 | 7 | it("should set async to true if passed in test has additional param", function () { 8 | var asyncTestFunction = function (param1, param2, done) { 9 | done(param1 + param2); 10 | }; 11 | var syncTestFunction = function (param1, param2) { 12 | return param1 + param2; 13 | }; 14 | var params = ["param1", "param2"]; 15 | 16 | var asyncTest = new Test(asyncTestFunction, params); 17 | var syncTest = new Test(syncTestFunction, params); 18 | 19 | expect(asyncTest.async).to.equal(true); 20 | expect(syncTest.async).to.equal(false); 21 | }); 22 | 23 | it("should call test function with params", function () { 24 | var testFunction = sinon.spy(function (arg1, arg2) { 25 | return arg1 + arg2; 26 | }); 27 | var params = [1, 2]; 28 | 29 | new Test(testFunction, params).run(); 30 | 31 | expect(testFunction.calledWith(1, 2)).to.equal(true); 32 | }); 33 | 34 | it("should pass the done function when additional param is present", function () { 35 | var testFunction = sinon.spy(function (arg1, arg2, done) { 36 | done(); 37 | }); 38 | var params = [1, 2]; 39 | 40 | new Test(testFunction, params).run(); 41 | 42 | expect(testFunction.getCall(0).args[2]).to.be.a("function"); 43 | }); 44 | 45 | it("should return a promise", function () { 46 | var result = new Test(function () { }, []).run(); 47 | expect(result.then).to.be.a("function"); 48 | }); 49 | 50 | describe("when a test function fails", function () { 51 | it("should reject the promise", function (done) { 52 | var exception = new Error("Error!"); 53 | var testFunction = sinon.stub().throws(exception); 54 | var result = new Test(testFunction, []).run(); 55 | result.then( 56 | function () { }, 57 | function (reason) { 58 | expect(reason.exception).to.equal(exception); 59 | expect(reason.duration).to.be.a("number"); 60 | done(); 61 | }).done(); 62 | }); 63 | 64 | it("should reject the promise when if test function times out", function (done) { 65 | var asyncFn = function (gaugeDone) { 66 | return gaugeDone; 67 | }; 68 | var result = new Test(asyncFn, []).run(); 69 | result.then( 70 | function () { }, 71 | function () { 72 | done(); 73 | } 74 | ).done(); 75 | 76 | }); 77 | 78 | }); 79 | 80 | describe("when test function runs without any error", function () { 81 | it("should resolve the promise", function (done) { 82 | var result = new Test(function () { }, []).run(); 83 | result.then(function (value) { 84 | expect(value.duration).to.be.a("number"); 85 | done(); 86 | }).done(); 87 | }); 88 | }); 89 | 90 | describe("when test function executes asynchronously", function () { 91 | it("should resolve the promise only when async execution finishes", function (done) { 92 | var asyncComplete = false; 93 | 94 | var testFunction = function (gaugeDone) { 95 | setTimeout(function () { 96 | asyncComplete = true; 97 | gaugeDone(); 98 | }, 900); 99 | }; 100 | 101 | var result = new Test(testFunction, []).run(); 102 | 103 | result.then(function () { 104 | expect(asyncComplete).to.equal(true); 105 | done(); 106 | }).done(); 107 | 108 | }); 109 | }); 110 | 111 | describe("when test fails stracktrace", function () { 112 | it("should not contain internal stack", function (done) { 113 | var testFunction = function () { 114 | throw new Error("failed"); 115 | }; 116 | 117 | var result = new Test(testFunction, []).run(); 118 | 119 | result.then(function () { }).catch(function (reason) { 120 | expect(reason.exception.stack).to.not.contains("at Test.runFn"); 121 | expect(reason.exception.stack.split("\n").length).to.be.eql(2); 122 | done(); 123 | }).done(); 124 | }); 125 | 126 | it("should contain relative path", function (done) { 127 | var testFunction = function () { 128 | throw new Error("failed"); 129 | }; 130 | var result = new Test(testFunction, []).run(); 131 | result.then(function () { }).catch(function (reason) { 132 | expect(reason.exception.stack).to.not.contains(process.env.GAUGE_PROJECT_ROOT); 133 | done(); 134 | }).done(); 135 | }); 136 | }); 137 | }); 138 | -------------------------------------------------------------------------------- /test/testdata/custom/inner-dir/foo.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getgauge/gauge-js/f14314a7b80205073ac1ef8a28494df3d860ed51/test/testdata/custom/inner-dir/foo.js -------------------------------------------------------------------------------- /test/testdata/custom/inner-dir/foo.txt: -------------------------------------------------------------------------------- 1 | foo -------------------------------------------------------------------------------- /test/testdata/custom/step-impl.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getgauge/gauge-js/f14314a7b80205073ac1ef8a28494df3d860ed51/test/testdata/custom/step-impl.js -------------------------------------------------------------------------------- /test/testdata/tests/inner-dir/foo.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getgauge/gauge-js/f14314a7b80205073ac1ef8a28494df3d860ed51/test/testdata/tests/inner-dir/foo.js -------------------------------------------------------------------------------- /test/testdata/tests/inner-dir/foo.txt: -------------------------------------------------------------------------------- 1 | foo -------------------------------------------------------------------------------- /test/testdata/tests/step-impl.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getgauge/gauge-js/f14314a7b80205073ac1ef8a28494df3d860ed51/test/testdata/tests/step-impl.js -------------------------------------------------------------------------------- /test/vm-test.js: -------------------------------------------------------------------------------- 1 | import { assert } from "chai"; 2 | import nodevm from "node:vm"; 3 | import sinon from "sinon"; 4 | import fs from "node:fs"; 5 | import VM from "../src/vm.js"; 6 | import path from "node:path"; 7 | import hookRegistry from "../src/hook-registry.js"; 8 | 9 | describe("VM", function () { 10 | 11 | var sandbox; 12 | 13 | before( function () { 14 | sandbox = sinon.createSandbox(); 15 | 16 | sandbox.stub( fs, "readFileSync").callsFake(function () { 17 | return "var x = 'oh, my!';"; 18 | }); 19 | }); 20 | 21 | after( function () { 22 | sandbox.restore(); 23 | }); 24 | 25 | it("should instantiate with sane defaults", function (done) { 26 | sinon.spy(nodevm, "createContext"); 27 | var vm = new VM(); 28 | vm.contextify(); 29 | 30 | assert(nodevm.createContext.calledOnce); 31 | assert.isDefined(vm.context); 32 | assert.deepEqual(vm.options, { 33 | dirname: ".", 34 | filename: "test.js", 35 | filepath: path.join(".", "test.js"), 36 | displayErrors: true, 37 | timeout: 1000, 38 | root: process.cwd() 39 | }); 40 | 41 | nodevm.createContext.restore(); 42 | done(); 43 | }); 44 | 45 | describe("should expose", function() { 46 | it("global.gauge", function () { 47 | var vm = new VM(); 48 | vm.contextify(); 49 | assert.doesNotThrow(function () { vm.run("var ohai = gauge.step"); }); 50 | }); 51 | 52 | it("__dirname", function () { 53 | var vm = new VM(); 54 | vm.contextify(path.join(process.cwd(), "some", "file.js")); 55 | assert.doesNotThrow(function () { 56 | vm.run(` 57 | var path = require('path'); 58 | var expected = path.join(process.cwd(), 'some'); 59 | if (__dirname !== expected) { 60 | throw new Error('__dirname "' + __dirname + '" did not match "' + expected + '"'); 61 | } 62 | `); 63 | }); 64 | }); 65 | 66 | it("__filename", function () { 67 | var vm = new VM(); 68 | vm.contextify(path.join(process.cwd(), "some", "file.js")); 69 | assert.doesNotThrow(function () { 70 | vm.run(` 71 | var path = require('path'); 72 | var expected = path.join(process.cwd(), 'some', 'file.js'); 73 | if (__filename !== expected) { 74 | throw new Error('__filename "' + __filename + '" did not match "' + expected + '"'); 75 | } 76 | `); 77 | }); 78 | }); 79 | 80 | it("require", function () { 81 | var vm = new VM(); 82 | vm.contextify(); 83 | assert.doesNotThrow(function () { vm.run("var fs = require('fs')"); }); 84 | }); 85 | 86 | it("console", function () { 87 | var vm = new VM(); 88 | vm.contextify(); 89 | assert.doesNotThrow(function () { vm.run("var log = console.log"); }); 90 | }); 91 | 92 | it("process.env", function () { 93 | var vm = new VM(); 94 | vm.contextify(); 95 | assert.doesNotThrow(function () { vm.run("var GAUGE_PROJECT_ROOT = process.env.GAUGE_PROJECT_ROOT"); }); 96 | }); 97 | 98 | it("step", function () { 99 | var vm = new VM(); 100 | vm.contextify(); 101 | assert.doesNotThrow(function () { vm.run("step('step', function(){})"); }); 102 | }); 103 | 104 | it("hooks", function () { 105 | var vm = new VM(); 106 | vm.contextify(); 107 | hookRegistry.types.forEach(function (type) { 108 | assert.doesNotThrow(function () { vm.run(type + "(function(){})"); }); 109 | }); 110 | }); 111 | 112 | it("setImmediate", function () { 113 | var vm = new VM(); 114 | vm.contextify(); 115 | assert.doesNotThrow(function () { vm.run("setImmediate(function () {})"); }); 116 | }); 117 | 118 | it("clearImmediate", function () { 119 | var vm = new VM(); 120 | vm.contextify(); 121 | assert.doesNotThrow(function () { vm.run("clearImmediate()"); }); 122 | }); 123 | }); 124 | 125 | it("should not read from file and run code", function () { 126 | var vm = new VM(); 127 | vm.contextify(); 128 | assert.doesNotThrow(function () { vm.runFile("mytest_implementation.js"); }); 129 | assert.equal(vm.options.filename, "mytest_implementation.js"); 130 | }); 131 | 132 | }); 133 | -------------------------------------------------------------------------------- /update_version.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | import re 4 | import sys 5 | from subprocess import check_output 6 | 7 | file_name = "js.json" 8 | 9 | 10 | def update_version(): 11 | with open(file_name, 'r') as f: 12 | data = json.load(f) 13 | new_version = re.sub('\d$', lambda x: str( 14 | int(x.group(0)) + 1), data["version"]) 15 | data["version"] = new_version 16 | 17 | os.remove(file_name) 18 | 19 | with open(file_name, 'w') as f: 20 | json.dump(data, f, indent=4) 21 | 22 | return new_version 23 | 24 | 25 | def update_pom(version): 26 | c = "npm version {0} --no-git-tag-version" 27 | check_output(c.format(version), shell=True) 28 | 29 | 30 | if __name__ == "__main__": 31 | version = update_version() 32 | update_pom(version) 33 | print(version) 34 | --------------------------------------------------------------------------------