diff --git a/.gitignore b/.gitignore index f3ef8df8b783..bddf57563d0e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,8 @@ .DS_Store .huskyrc.json out -node_modules +log.log +**/node_modules *.pyc *.vsix **/.vscode/.ropeproject/** @@ -14,9 +15,10 @@ npm-debug.log **/.mypy_cache/** !yarn.lock coverage/ -.vscode-test/** -.vscode test/** -.vscode-smoke/** +cucumber-report.json +**/.vscode-test/** +**/.vscode test/** +**/.vscode-smoke/** **/.venv*/ port.txt precommit.hook @@ -25,6 +27,8 @@ pythonFiles/lib/** debug_coverage*/** languageServer/** languageServer.*/** +!uitests/features/languageServer/** +!uitests/src/languageServer/** bin/** obj/** .pytest_cache @@ -32,4 +36,5 @@ tmp/** .python-version .vs/ test-results.xml +uitests/out/** !build/ diff --git a/.prettierrc.js b/.prettierrc.js new file mode 100644 index 000000000000..786cdd4ed869 --- /dev/null +++ b/.prettierrc.js @@ -0,0 +1,6 @@ +module.exports = { + semi: true, + singleQuote: true, + printWidth: 180, + tabWidth: 4 +}; diff --git a/.vscode/extensions.json b/.vscode/extensions.json index b0d7a2ff1e6d..508eae4c5ddc 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -3,6 +3,7 @@ // for the documentation about the extensions.json format "recommendations": [ "ms-vscode.vscode-typescript-tslint-plugin", - "editorconfig.editorconfig" + "editorconfig.editorconfig", + "esbenp.prettier-vscode" ] } diff --git a/.vscode/settings.json b/.vscode/settings.json index 5cf6ea8acbef..e8483c2c2efa 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,16 +2,22 @@ { "files.exclude": { "out": true, // set this to true to hide the "out" folder with the compiled JS files + "uitests/out": true, "**/*.pyc": true, ".nyc_output": true, "obj": true, "bin": true, "**/__pycache__": true, + "**/node_modules": true, + ".vscode-test": false, + ".vscode test": false, "**/.mypy_cache/**": true, "**/.ropeproject/**": true }, "search.exclude": { "out": true, // set this to false to include "out" folder in search results + "uitests/out": true, + "**/node_modules": true, "coverage": true, "languageServer*/**": true, ".vscode-test": true, diff --git a/.vscodeignore b/.vscodeignore index b68abbf92982..18d189ff281a 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -18,6 +18,7 @@ CODING_STANDARDS.md CONTRIBUTING.md CONTRIBUTING - LANGUAGE SERVER.md coverconfig.json +cucumber-report.json gulpfile.js package.datascience-ui.dependencies.json package-lock.json diff --git a/build/ci/templates/jobs/merge_upload_uitest_report.yml b/build/ci/templates/jobs/merge_upload_uitest_report.yml new file mode 100644 index 000000000000..e3188ee732cc --- /dev/null +++ b/build/ci/templates/jobs/merge_upload_uitest_report.yml @@ -0,0 +1,46 @@ +# Overview: +# Generic jobs template to compile and build extension + +jobs: +- job: UI_Test_Report + pool: + vmImage: "macos-latest" + steps: + - template: ../steps/initialization.yml + parameters: + workingDirectory: $(Build.SourcesDirectory)/uitests + compile: 'true' + + - bash: mkdir -p reports + workingDirectory: $(Build.SourcesDirectory)/uitests + displayName: "Create Reports Directory" + + - task: DownloadBuildArtifacts@0 + inputs: + buildType: "current" + allowPartiallySucceededBuilds: true + downloadType: "Specific" + itemPattern: "**/.vscode test/reports/cucumber_report_*.json" + downloadPath: "$(Build.SourcesDirectory)/uitests/reports" + displayName: "Restore Cucumber Reports" + condition: always() + + - bash: node ./out/index.js report --jsonDir=./reports --htmlOutput=./reports + workingDirectory: $(Build.SourcesDirectory)/uitests + displayName: "Merge and generate report" + condition: always() + + - task: CopyFiles@2 + inputs: + sourceFolder: $(Build.SourcesDirectory)/uitests/reports + contents: "**" + targetFolder: $(Build.ArtifactStagingDirectory) + displayName: "Copy Report" + condition: always() + + - task: PublishBuildArtifacts@1 + inputs: + pathtoPublish: $(Build.ArtifactStagingDirectory) + artifactName: UIReport + displayName: "Publish Report" + condition: always() diff --git a/build/ci/templates/jobs/smoke_test.yml b/build/ci/templates/jobs/smoke.yml similarity index 100% rename from build/ci/templates/jobs/smoke_test.yml rename to build/ci/templates/jobs/smoke.yml diff --git a/build/ci/templates/jobs/uitest.yml b/build/ci/templates/jobs/uitest.yml index c6b98e2e9c22..852406bcdd64 100644 --- a/build/ci/templates/jobs/uitest.yml +++ b/build/ci/templates/jobs/uitest.yml @@ -25,18 +25,18 @@ # parameters: # jobs: # - test: "Smoke" -# tags: "--tags=@smoke" +# tags: "@smoke" # - test: "Test" -# tags: "--tags=@test" +# tags: "@test" # - test: "Terminal" -# tags: "--tags=@terminal" +# tags: "@terminal" # ``` # Based on this sample, we're running 3 tests with the names `Smoke`, `Test`, and `Terminal`. # The tags inside each test contains the arguments that needs to be passd into behave. # I.e. we're only testing BDD tests that contain the tag `@smoke`, `@test` & `@terminal` (as separate jobs). # Please pass in just the `tags` arguments. # Multiple tag values can be passed in as follows: -# tags: "--tags=@debug --tags=@remote" +# tags: "@debug and @remote" # More information on --tags argument for behave can be found here: # * https://behave.readthedocs.io/en/latest/tutorial.html#controlling-things-with-tags # * https://behave.readthedocs.io/en/latest/tag_expressions.html @@ -52,7 +52,7 @@ # vscodeChannels: ['stable'] # jobs: # - test: "Smoke" -# tags: "--tags=@smoke" +# tags: "@smoke" # ignorePythonVersions: "3.6,3.5" # ``` # Based on this sample, we're running 1 test with the name `Smoke`. @@ -73,7 +73,7 @@ # vscodeChannels: ['stable'] # jobs: # - test: "Smoke" -# tags: "--tags=@smoke" +# tags: "@smoke" # ignorePythonVersions: "3.6,3.5" # ``` # Based on this sample, we're running 1 test with the name `Smoke`. @@ -93,36 +93,33 @@ parameters: vscodeChannels: ['stable', 'insider'] pythonVersions: [ { - "version": "3.7", + "version": "3.7.4", "displayName": "37", - "excludeTags": "--tags=~@python3.6 --tags=~@python3.5 --tags=~@python2" + "excludeTags": "not @python3.6 and not @python3.5 and not @python2" }, { "version": "3.6", "displayName": "36", - "excludeTags": "--tags=~@python3.7 --tags=~@python3.5 --tags=~@python2" + "excludeTags": "not @python3.7 and not @python3.5 and not @python2 and not @noNeedToTestInAllPython" }, { "version": "3.5", "displayName": "35", - "excludeTags": "--tags=~@python3.7 --tags=~@python3.6 --tags=~@python2" + "excludeTags": "not @python3.7 and not @python3.6 and not @python2 and not @noNeedToTestInAllPython" }, { "version": "2.7", "displayName": "27", - "excludeTags": "--tags=~@python3.7 --tags=~@python3.5 --tags=~@python3" + "excludeTags": "not @python3.7 and not @python3.5 and not @python3 and not @noNeedToTestInAllPython" } ] jobs: - job: UITest - dependsOn: - - Compile - - Build # Remember, some tests can take easily an hour (the `tests` features take just around 1 hour). timeoutInMinutes: 90 - # Build our matrix (permutations of all environments & tests). + # Build our matrix (permutations of VS Code + Tests + Pyhton + OS). strategy: matrix: ${{ each channel in parameters.vscodeChannels }}: @@ -134,24 +131,24 @@ jobs: PythonVersion: ${{ py.version }} VMImageName: "macos-latest" VSCodeChannel: ${{ channel }} - Tags: ${{ format('{0} {1} --tags=~@win --tags=~@linux', job.tags, py.excludeTags) }} + Tags: ${{ format('{0} and {1} and not @win and not @linux', job.tags, py.excludeTags) }} ${{ if not(contains(coalesce(job.ignoreOperatingSystems, ''), 'win')) }}: ${{ format('Win{2}{0}{1}', py.displayName, job.test, channel) }}: PythonVersion: ${{ py.version }} VSCodeChannel: ${{ channel }} VMImageName: "vs2017-win2016" - Tags: ${{ format('{0} {1} --tags=~@mac --tags=~@linux', job.tags, py.excludeTags) }} + Tags: ${{ format('{0} and {1} and not @mac and not @linux', job.tags, py.excludeTags) }} ${{ if not(contains(coalesce(job.ignoreOperatingSystems, ''), 'linux')) }}: ${{ format('Linux{2}{0}{1}', py.displayName, job.test, channel) }}: PythonVersion: ${{ py.version }} VSCodeChannel: ${{ channel }} VMImageName: "ubuntu-latest" - Tags: ${{ format('{0} {1} --tags=~@mac --tags=~@win', job.tags, py.excludeTags) }} + Tags: ${{ format('{0} and {1} and not @mac and not @win', job.tags, py.excludeTags) }} pool: vmImage: $(VMImageName) steps: - - template: uitest_phases.yml + - template: ../steps/uitest.yml diff --git a/build/ci/templates/steps/initialization.yml b/build/ci/templates/steps/initialization.yml index 54a370ef151b..c0c089a9af58 100644 --- a/build/ci/templates/steps/initialization.yml +++ b/build/ci/templates/steps/initialization.yml @@ -17,7 +17,6 @@ parameters: PythonVersion: '' compile: 'true' sqlite: 'false' - installVSCEorNPX: 'true' steps: - bash: | @@ -85,7 +84,6 @@ steps: npm install -g vsce npm install -g npx displayName: "Install vsce & npx" - condition: and(succeeded(), eq('${{ parameters.installVSCEorNPX }}', 'true')) - bash: npx tsc -p ./ displayName: "compile (npx tsc -p ./)" diff --git a/build/ci/templates/steps/initialization.yml~HEAD b/build/ci/templates/steps/initialization.yml~HEAD new file mode 100644 index 000000000000..54a370ef151b --- /dev/null +++ b/build/ci/templates/steps/initialization.yml~HEAD @@ -0,0 +1,113 @@ +# ----------------------------------------------------------------------------------------------------------------------------- +# Overview: +# ----------------------------------------------------------------------------------------------------------------------------- +# Set of common steps required when initializing a job. +# +# ----------------------------------------------------------------------------------------------------------------------------- +# Variables +# ----------------------------------------------------------------------------------------------------------------------------- +# 1. workingDirectory +# Mandatory +# Working directory. +# 2. PythonVersion +# Python version to be used. +# If not provided, python will not be setup on CI. +parameters: + workingDirectory: '' + PythonVersion: '' + compile: 'true' + sqlite: 'false' + installVSCEorNPX: 'true' + +steps: + - bash: | + printenv + displayName: "Show all env vars" + condition: eq(variables['system.debug'], 'true') + + - task: NodeTool@0 + displayName: "Use Node $(NodeVersion)" + inputs: + versionSpec: $(NodeVersion) + + - task: UsePythonVersion@0 + displayName: "Use Python ${{ parameters.PythonVersion }}" + condition: and(succeeded(), not(eq('${{ parameters.PythonVersion }}', ''))) + inputs: + versionSpec: ${{ parameters.PythonVersion }} + + # Install the a version of python that works with sqlite3 until this bug is addressed + # https://mseng.visualstudio.com/AzureDevOps/_workitems/edit/1535830 + # + # This task will only run if variable `NeedsPythonFunctionalReqs` is true. + - bash: | + sudo apt-get install libsqlite3-dev + version=$(python -V 2>&1 | grep -Po '(?<=Python )(.+)') + wget https://www.python.org/ftp/python/$version/Python-$version.tar.xz + tar xvf Python-$version.tar.xz + cd Python-$version + ./configure --enable-loadable-sqlite-extensions --with-ensurepip=install --prefix=$HOME/py-$version + make + sudo make install + sudo chmod -R 777 $HOME/py-$version + export PATH=$HOME/py-$version/bin:$PATH + sudo ln -s $HOME/py-$version/bin/python3 $HOME/py-$version/bin/python + echo '##vso[task.prependpath]'$HOME/py-$version/bin + displayName: 'Setup python to run with sqlite on 3.*' + condition: and(succeeded(), eq('${{ parameters.sqlite }}', 'true'), not(eq('${{ parameters.PythonVersion }}', '')), not(eq('${{ parameters.PythonVersion }}', '2.7')), eq(variables['Agent.Os'], 'Linux')) + + - task: Npm@1 + displayName: "Use NPM $(NpmVersion)" + inputs: + command: custom + verbose: true + customCommand: "install -g npm@$(NpmVersion)" + + - task: Npm@1 + displayName: "npm ci" + inputs: + workingDir: ${{ parameters.workingDirectory }} + command: custom + verbose: true + customCommand: ci + + # On Mac, the command `node` doesn't always point to the current node version. + # Debugger tests use the variable process.env.NODE_PATH + - script: | + export NODE_PATH=`which node` + echo $NODE_PATH + echo '##vso[task.setvariable variable=NODE_PATH]'$NODE_PATH + displayName: "Setup NODE_PATH for extension (Debugger Tests)" + condition: and(succeeded(), eq(variables['agent.os'], 'Darwin')) + + # We need these almost always. + - bash: | + npm install -g vsce + npm install -g npx + displayName: "Install vsce & npx" + condition: and(succeeded(), eq('${{ parameters.installVSCEorNPX }}', 'true')) + + - bash: npx tsc -p ./ + displayName: "compile (npx tsc -p ./)" + workingDirectory: ${{ parameters.workingDirectory }} + condition: and(succeeded(), eq('${{ parameters.compile }}', 'true')) + + # https://code.visualstudio.com/api/working-with-extensions/continuous-integration#azure-pipelines + - bash: | + set -e + /usr/bin/Xvfb :10 -ac >> /tmp/Xvfb.out 2>&1 & + disown -ar + displayName: 'Start Display Server (xvfb) to launch VS Code)' + condition: and(succeeded(), eq(variables['Agent.Os'], 'Linux')) + + # # Show all versions installed/available on PATH if in verbose mode. + # # Example command line (windows pwsh): + # # > Write-Host Node ver: $(& node -v) NPM Ver: $(& npm -v) Python ver: $(& python --version)" + # - bash: | + # echo AVAILABLE DEPENDENCY VERSIONS + # echo Node Version = `node -v` + # echo NPM Version = `npm -v` + # echo Python Version = `python --version` + # echo Gulp Version = `gulp --version` + # condition: and(succeeded(), eq(variables['system.debug'], 'true')) + # displayName: Show Dependency Versions diff --git a/build/ci/templates/steps/uitest.yml b/build/ci/templates/steps/uitest.yml index 651484175c69..0dd6fcec8ed7 100644 --- a/build/ci/templates/steps/uitest.yml +++ b/build/ci/templates/steps/uitest.yml @@ -14,15 +14,14 @@ # 9. Start xvfb - Start in-memory display server (for launching VSC). # 10. Restore VSIX - VSIX has been built in another Job, download that from artifacts. # 11. Copy VSIX - Copy the VSIX into root directory (test suite expects it to be in root - default setup). -# 12. Setup pyperclicp dependency - We use pyperclip to copy text into clipboard buffer (see where this is used in code for info). -# 13. Download & install UI Test dependencies - Download & Install everything required for the UI tests. -# 14. Run Tests - Launch the UI tests in Python -# 15. Copy Reports -# 16. Copy Screenshots -# 17. Copy Extension Logs -# 18. Copy VSC Logs -# 19. Upload Reports - Upload as artifacts to Azure Devops -# 20. Test Results - Upload test results to Azure Devops +# 12. Compile - Npm compile +# 13. Run Tests - Launch the UI tests in Nodejs +# 14. Copy Reports +# 15. Copy Screenshots +# 16. Copy Extension Logs +# 17. Copy VSC Logs +# 18. Upload Reports - Upload as artifacts to Azure Devops +# 19. Test Results - Upload test results to Azure Devops # ----------------------------------------------------------------------------------------------------------------------------- # Variables: # ----------------------------------------------------------------------------------------------------------------------------- @@ -32,7 +31,7 @@ # 2. Tags # Mandatory. # Contain the `--tags=....` arguments to be passed into behave to exclude certain tags. -# Multiple tags can be passed as `--tags=@smoke --tags=~@ignore1 --tags=~@another --tags=~@andMore` +# Multiple tags can be passed as `@smoke and not @ignore1 and @another and not @andMore` # More information on --tags argument for behave can be found here: # * https://behave.readthedocs.io/en/latest/tutorial.html#controlling-things-with-tags # * https://behave.readthedocs.io/en/latest/tag_expressions.html @@ -41,95 +40,24 @@ # 4. VMImageName # VM Image to be used (standard Azure Devops variable). - steps: - - bash: | - printenv - displayName: "Show all env vars" - condition: eq(variables['system.debug'], 'true') - - - task: NodeTool@0 - displayName: "Use Node $(NodeVersion)" - inputs: - versionSpec: $(NodeVersion) - - - task: UsePythonVersion@0 - displayName: "Setup Python $(PythonVersion) for extension" - inputs: - versionSpec: $(PythonVersion) - - # Conda - - bash: echo "##vso[task.prependpath]$CONDA/bin" - displayName: Add conda to PATH - condition: and(succeeded(), not(eq(variables['agent.os'], 'Windows_NT'))) - - - powershell: Write-Host "##vso[task.prependpath]$env:CONDA\Scripts" - displayName: Add conda to PATH - condition: and(succeeded(), eq(variables['agent.os'], 'Windows_NT')) - - # On Hosted macOS, the agent user doesn't have ownership of Miniconda's installation directory/ - # We need to take ownership if we want to update conda or install packages globally - - bash: sudo chown -R $USER $CONDA - displayName: Take ownership of conda installation - condition: and(succeeded(), eq(variables['agent.os'], 'Darwin')) - - - script: | - export CI_PYTHON_PATH=`which python` - echo '##vso[task.setvariable variable=CI_PYTHON_PATH]'$CI_PYTHON_PATH - displayName: "Setup CI_PYTHON_PATH for extension" - condition: and(succeeded(), not(eq(variables['agent.os'], 'Windows_NT'))) - - - powershell: | - $CI_PYTHON_PATH = (get-command python).path - Write-Host "##vso[task.setvariable variable=CI_PYTHON_PATH]$CI_PYTHON_PATH" - Write-Host $CI_PYTHON_PATH - displayName: "Setup CI_PYTHON_PATH for extension" - condition: and(succeeded(), eq(variables['agent.os'], 'Windows_NT')) - # Some tests need to have both 2.7 & 3.7 available. - # Also, use Python 3.7 to run the scripts that drive the ui tests. - # Order matters, currently active python version will be used to drive tests. - # Hence ensure 3.7 is setup last. - task: UsePythonVersion@0 - displayName: "Use Python 2.7" + displayName: 'Use Python 2.7' inputs: versionSpec: 2.7 - task: UsePythonVersion@0 - displayName: "Use Python 3.7 (to drive tests)" + displayName: 'Use Python 3.7' inputs: versionSpec: 3.7 - - task: Npm@1 - displayName: "Use NPM $(NpmVersion)" - inputs: - command: custom - verbose: true - customCommand: "install -g npm@$(NpmVersion)" - - - task: Npm@1 - displayName: "npm ci" - inputs: - command: custom - verbose: true - customCommand: ci - - - bash: | - echo AVAILABLE DEPENDENCY VERSIONS - echo Node Version = `node -v` - echo NPM Version = `npm -v` - echo Python Version = `python --version` - echo Gulp Version = `gulp --version` - condition: and(succeeded(), eq(variables['system.debug'], 'true')) - displayName: Show Dependency Versions - - # https://code.visualstudio.com/api/working-with-extensions/continuous-integration#azure-pipelines - - bash: | - set -e - /usr/bin/Xvfb :10 -ac >> /tmp/Xvfb.out 2>&1 & - disown -ar - displayName: "Start xvfb" - condition: and(succeeded(), eq(variables['Agent.Os'], 'Linux'), not(variables['SkipXvfb'])) + # Setup python environment in current path for extension to use. + - template: initialization.yml + parameters: + PythonVersion: $(PythonVersion) + workingDirectory: $(Build.SourcesDirectory)/uitests + compile: 'true' - task: DownloadBuildArtifacts@0 inputs: @@ -142,104 +70,131 @@ steps: - task: CopyFiles@2 inputs: sourceFolder: "$(Build.SourcesDirectory)/VSIX" - targetFolder: $(Build.SourcesDirectory) + targetFolder: $(Build.SourcesDirectory)/uitests displayName: "Copy VSIX" condition: succeeded() - # pyperclip needs more dependencies installed on Linux - # See https://github.com/asweigart/pyperclip/blob/master/docs/introduction.rst - - bash: sudo apt-get install xsel - displayName: "Setup pyperclip dependency" - condition: and(succeeded(), eq(variables['Agent.Os'], 'Linux')) - - # Run the UI Tests. - bash: | - python -m pip install -U pip - python -m pip install --upgrade -r ./uitests/requirements.txt - python uitests download --channel=$(VSCodeChannel) - npm install -g vsce - python uitests install --channel=$(VSCodeChannel) + cd ./bootstrap/extension + npm run build + workingDirectory: $(Build.SourcesDirectory)/uitests + displayName: "Build Bootstrap Extension" + condition: succeeded() + + - bash: node ./out/index.js download --channel=$(VSCodeChannel) + workingDirectory: $(Build.SourcesDirectory)/uitests env: - DISPLAY: :10 - AgentJobName: $(Agent.JobName) - displayName: "Download & Install UI Test Dependencies" + VSCODE_CHANNEL: $(VSCodeChannel) + displayName: 'Download VS Code $(VSCodeChannel)' + condition: succeeded() + + - bash: node ./out/index.js install --channel=$(VSCodeChannel) + workingDirectory: $(Build.SourcesDirectory)/uitests + env: + VSCODE_CHANNEL: $(VSCodeChannel) + displayName: 'Install Extension(s)' + condition: succeeded() + + # Setup python environment in current path for extension. + - task: UsePythonVersion@0 + displayName: 'Setup Python $(PythonVersion) for extension' + inputs: + versionSpec: $(PythonVersion) + + # On Hosted macOS, the agent user doesn't have ownership of Miniconda's installation directory/ + # We need to take ownership if we want to update conda or install packages globally + - bash: sudo chown -R $USER $CONDA + displayName: Take ownership of conda installation + condition: and(succeeded(), eq(variables['agent.os'], 'Darwin')) + + - script: | + export CI_PYTHON_PATH=`which python` + echo '##vso[task.setvariable variable=CI_PYTHON_PATH]'$CI_PYTHON_PATH + displayName: 'Setup CI_PYTHON_PATH for extension' + condition: and(succeeded(), not(eq(variables['agent.os'], 'Windows_NT'))) + + - powershell: | + $CI_PYTHON_PATH = (get-command python).path + Write-Host "##vso[task.setvariable variable=CI_PYTHON_PATH]$CI_PYTHON_PATH" + Write-Host $CI_PYTHON_PATH + displayName: 'Setup CI_PYTHON_PATH for extension' + condition: and(succeeded(), eq(variables['agent.os'], 'Windows_NT')) + + # Conda + - bash: echo "##vso[task.prependpath]$CONDA/bin" + displayName: Add conda to PATH + condition: and(succeeded(), not(eq(variables['agent.os'], 'Windows_NT'))) + + - powershell: Write-Host "##vso[task.prependpath]$env:CONDA\Scripts" + displayName: Add conda to PATH + condition: and(succeeded(), eq(variables['agent.os'], 'Windows_NT')) + + # Ensure reports folder exists. + - bash: mkdir -p './.vscode test/reports' + workingDirectory: $(Build.SourcesDirectory)/uitests + displayName: 'Create Reports folder' condition: succeeded() # Skip @skip tagged tests # Always dump to a text file (easier than scrolling and downloading the logs seprately). # This way all logs are part of the artificat for each test. - - script: python uitests test --channel=$(VSCodeChannel) -- --format=pretty $(Tags) --tags=~@skip --logging-level=INFO --no-logcapture --no-capture -D python_path=$(CI_PYTHON_PATH) | tee '.vscode test/reports/behave_log.log' + # Passing CI_PYTHON_PATH on windows sucks, as `\` needs to be escaped. Just use env variable. + # npm run test -- --verbose --channel=$(VSCodeChannel) --pythonPath=$(CI_PYTHON_PATH) -- --tags='$(Tags) and not @skip' --exit | tee './.vscode test/reports/log.log' + # Don't use `npm run test` (we need the exit code to propagage all the way up). + - bash: node ./out/index.js test --verbose --channel=$(VSCodeChannel) -- --tags='$(Tags) and not @skip' + workingDirectory: $(Build.SourcesDirectory)/uitests env: DISPLAY: :10 AgentJobName: $(Agent.JobName) - AZURE_COGNITIVE_ENDPOINT: $(AZURE_COGNITIVE_ENDPOINT) - AZURE_COGNITIVE_KEY: $(AZURE_COGNITIVE_KEY) VSCODE_CHANNEL: $(VSCodeChannel) CI_PYTHON_PATH: $(CI_PYTHON_PATH) PYTHON_VERSION: $(PythonVersion) - failOnStderr: false - displayName: "Run Tests" + displayName: 'Run Tests' condition: succeeded() - # Write exit code to a text file, so we can read it and fail CI in a separate task (fail if file exists). - # CI doesn't seem to fail based on exit codes. - # We can't fail on writing to stderr either as python logs stuff there & other errors that can be ignored are written there. - - bash: | - FILE=uitests/uitests/uitest_failed.txt - if [[ -f "$FILE" ]]; - then - echo "UI Tests failed" - exit 1 - fi - displayName: "Check if UI Tests Passed" - condition: succeeded() - - # Generate and publis results even if there are failures in previous steps. - - script: python uitests report - env: - AgentJobName: $(Agent.JobName) - displayName: "Generate Reports" - condition: always() - - task: CopyFiles@2 inputs: - contents: ".vscode test/reports/**" + sourceFolder: $(Build.SourcesDirectory)/uitests + contents: '.vscode test/reports/**' targetFolder: $(Build.ArtifactStagingDirectory) - displayName: "Copy Reports" + displayName: 'Copy Reports' condition: always() - task: CopyFiles@2 inputs: - contents: ".vscode test/screenshots/**" + sourceFolder: $(Build.SourcesDirectory)/uitests + contents: '.vscode test/screenshots/**' targetFolder: $(Build.ArtifactStagingDirectory) - displayName: "Copy Screenshots" + displayName: 'Copy Screenshots' condition: always() - task: CopyFiles@2 inputs: - contents: ".vscode test/logs/**" + sourceFolder: $(Build.SourcesDirectory)/uitests + contents: '.vscode test/logs/**' targetFolder: $(Build.ArtifactStagingDirectory) - displayName: "Copy Extension Logs" + displayName: 'Copy Extension Logs' condition: always() - task: CopyFiles@2 inputs: - contents: ".vscode test/user/logs/**" + sourceFolder: $(Build.SourcesDirectory)/uitests + contents: '.vscode test/user/logs/**' targetFolder: $(Build.ArtifactStagingDirectory) - displayName: "Copy VSC Logs" + displayName: 'Copy VSC Logs' condition: always() - task: PublishBuildArtifacts@1 inputs: pathtoPublish: $(Build.ArtifactStagingDirectory) artifactName: $(Agent.JobName) - displayName: "Upload Reports" + displayName: 'Upload Reports' condition: always() - task: PublishTestResults@2 - displayName: "TestResults" + displayName: 'TestResults' inputs: testRunTitle: $(Agent.JobName) testRunner: JUnit - testResultsFiles: "$(Build.SourcesDirectory)/.vscode test/reports/*.xml" + testResultsFiles: '$(Build.SourcesDirectory)/uitests/.vscode test/reports/*.xml' condition: always() diff --git a/build/ci/vscode-python-nightly-uitest.yaml b/build/ci/vscode-python-nightly-uitest.yaml index 08fff482531e..f94a3a7c1b68 100644 --- a/build/ci/vscode-python-nightly-uitest.yaml +++ b/build/ci/vscode-python-nightly-uitest.yaml @@ -13,42 +13,36 @@ schedules: # Variables that are available for the entire pipeline. variables: - PythonVersion: '3.7' - NodeVersion: '10.5.0' - NpmVersion: '6.10.3' - MOCHA_FILE: '$(Build.ArtifactStagingDirectory)/test-junit.xml' # All test files will write their JUnit xml output to this file, clobbering the last time it was written. - MOCHA_REPORTER_JUNIT: true # Use the mocha-multi-reporters and send output to both console (spec) and JUnit (mocha-junit-reporter). - VSC_PYTHON_FORCE_LOGGING: true # Enable this to turn on console output for the logger - VSC_PYTHON_LOG_FILE: '$(Build.ArtifactStagingDirectory)/pvsc.log' +- template: templates/globals.yml jobs: -- template: templates/build_compile_jobs.yml +- template: templates/jobs/build_compile.yml -- template: templates/uitest_jobs.yml +- template: templates/jobs/uitest.yml parameters: jobs: - test: "Smoke" - tags: "--tags=@smoke" + tags: "@smoke" # Smoke tests are cheap, so run them against all Python Versions. - test: "Test" - tags: "--tags=@testing" + tags: "@testing" # We have python code that is involved in running/discovering tests. # Hence test against all versions, until we have CI running for the Python code. # I.e. when all test discovery/running is done purely in Python. - test: "Terminal" - tags: "--tags=@terminal --tags=~@terminal.pipenv" + tags: "@terminal and not @terminal.pipenv" # No need to run tests against all versions. # This is faster/cheaper, besides activation of terminals is generic enough # not to warrant testing against all versions. ignorePythonVersions: "3.6,3.5" - test: "Debugging" - tags: "--tags=@debugging" + tags: "@debugging" # No need to run tests against all versions. # This is faster/cheaper, and these are external packages. # We expect them to work (or 3rd party packages to test against all PY versions). ignorePythonVersions: "3.6,3.5" - test: "Jedi_Language_Server" - tags: "--tags=@ls" + tags: "@ls" # No need to run tests against all versions. # This is faster/cheaper, and these are external packages. # We expect them to work (or 3rd party packages to test against all PY versions). diff --git a/build/ci/vscode-python-pr-validation.yaml b/build/ci/vscode-python-pr-validation.yaml index f3cd983bf235..ecbb2f21f9a6 100644 --- a/build/ci/vscode-python-pr-validation.yaml +++ b/build/ci/vscode-python-pr-validation.yaml @@ -22,7 +22,11 @@ stages: jobs: - template: templates/jobs/build_compile.yml -# - template: templates/jobs/smoke.yml +- stage: Smoke + dependsOn: + - Build + jobs: + - template: templates/jobs/smoke.yml - stage: Tests dependsOn: @@ -94,38 +98,12 @@ stages: steps: - template: templates/test_phases.yml - - job: 'Smoke' - dependsOn: [] - strategy: - matrix: - 'Mac-Py3.7': - PythonVersion: '3.7' - VMImageName: 'macos-10.13' - TestsToRun: 'testSmoke' - NeedsPythonTestReqs: true - 'Linux-Py3.7': - PythonVersion: '3.7' - VMImageName: 'ubuntu-16.04' - TestsToRun: 'testSmoke' - NeedsPythonTestReqs: true - 'Win-Py3.7': - PythonVersion: '3.7' - VMImageName: 'vs2017-win2016' - TestsToRun: 'testSmoke' - NeedsPythonTestReqs: true - - pool: - vmImage: $(VMImageName) - - steps: - - template: templates/test_phases.yml - - stage: Reports dependsOn: - # - Smoke_Tests + - Smoke - Tests condition: always() jobs: - # - template: templates/jobs/merge_upload_uitest_report.yml + - template: templates/jobs/merge_upload_uitest_report.yml - template: templates/jobs/coverage.yml diff --git a/package.json b/package.json index ce8231dbe653..a33f17a95c03 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,7 @@ "onCommand:python.setInterpreter", "onCommand:python.setShebangInterpreter", "onCommand:python.viewTestUI", + "onCommand:python.viewLanguageServerOutput", "onCommand:python.viewTestOutput", "onCommand:python.viewOutput", "onCommand:python.selectAndRunTestMethod", @@ -248,6 +249,12 @@ "dark": "resources/dark/repl.svg" } }, + { + "command": "python.viewLanguageServerOutput", + "title": "%python.command.python.viewLanguageServerOutput.title%", + "category": "Python", + "enablement": "python.hasLanguageServerOutputChannel" + }, { "command": "python.viewOutput", "title": "%python.command.python.viewOutput.title%", diff --git a/package.nls.json b/package.nls.json index 110782d458ca..aaf78441107e 100644 --- a/package.nls.json +++ b/package.nls.json @@ -15,6 +15,7 @@ "python.command.python.refactorExtractMethod.title": "Extract Method", "python.command.python.viewOutput.title": "Show Output", "python.command.python.viewTestOutput.title": "Show Test Output", + "python.command.python.viewLanguageServerOutput.title": "Show Language Server Output", "python.command.python.selectAndRunTestMethod.title": "Run Test Method ...", "python.command.python.selectAndDebugTestMethod.title": "Debug Test Method ...", "python.command.python.selectAndRunTestFile.title": "Run Test File ...", diff --git a/pvsc.code-workspace b/pvsc.code-workspace new file mode 100644 index 000000000000..f42f591908b2 --- /dev/null +++ b/pvsc.code-workspace @@ -0,0 +1,21 @@ +{ + "folders": [ + { + "path": "." + }, + { + "path": "uitests" + } + ], + "settings": { + "typescript.tsdk": "./node_modules/typescript/lib", + "search.exclude": { + "**/node_modules/**": true, + "**/.vscode test/insider/**": true, + "**/.vscode test/stable/**": true, + "**/.vscode-test/insider/**": true, + "**/.vscode-test/stable/**": true, + "**/out/**": true + } + } +} diff --git a/src/client/activation/languageServer/outputChannel.ts b/src/client/activation/languageServer/outputChannel.ts index 3a625ed93cc0..e0c68817147e 100644 --- a/src/client/activation/languageServer/outputChannel.ts +++ b/src/client/activation/languageServer/outputChannel.ts @@ -4,7 +4,8 @@ 'use strict'; import { inject, injectable } from 'inversify'; -import { IApplicationShell } from '../../common/application/types'; +import { IApplicationShell, ICommandManager } from '../../common/application/types'; +import '../../common/extensions'; import { IOutputChannel } from '../../common/types'; import { OutputChannelNames } from '../../common/utils/localize'; import { ILanguageServerOutputChannel } from '../types'; @@ -12,14 +13,25 @@ import { ILanguageServerOutputChannel } from '../types'; @injectable() export class LanguageServerOutputChannel implements ILanguageServerOutputChannel { public output: IOutputChannel | undefined; + private registered = false; constructor( - @inject(IApplicationShell) private readonly appShell: IApplicationShell + @inject(IApplicationShell) private readonly appShell: IApplicationShell, + @inject(ICommandManager) private readonly commandManager: ICommandManager ) { } public get channel() { if (!this.output) { this.output = this.appShell.createOutputChannel(OutputChannelNames.languageServer()); + this.registerCommand().ignoreErrors(); } return this.output; } + private async registerCommand(){ + if (this.registered){ + return; + } + this.registered = true; + await this.commandManager.executeCommand('setContext', 'python.hasLanguageServerOutputChannel', true); + this.commandManager.registerCommand('python.viewLanguageServerOutput', () => this.output!.show()); + } } diff --git a/src/client/common/application/commands.ts b/src/client/common/application/commands.ts index d379256bb94a..8c5aad011dfe 100644 --- a/src/client/common/application/commands.ts +++ b/src/client/common/application/commands.ts @@ -78,6 +78,7 @@ export interface ICommandNameArgumentTypeMapping extends ICommandNameWithoutArgu ['revealLine']: [{ lineNumber: number; at: 'top' | 'center' | 'bottom' }]; ['python._loadLanguageServerExtension']: {}[]; ['python.SelectAndInsertDebugConfiguration']: [TextDocument, Position, CancellationToken]; + ['python.viewLanguageServerOutput']: []; [Commands.Build_Workspace_Symbols]: [boolean, CancellationToken]; [Commands.Sort_Imports]: [undefined, Uri]; [Commands.Exec_In_Terminal]: [undefined, Uri]; diff --git a/src/client/common/constants.ts b/src/client/common/constants.ts index a7efe37de0e3..fa84355323a1 100644 --- a/src/client/common/constants.ts +++ b/src/client/common/constants.ts @@ -80,6 +80,8 @@ export namespace Delays { export const STANDARD_OUTPUT_CHANNEL = 'STANDARD_OUTPUT_CHANNEL'; +export const isCI = process.env.TRAVIS === 'true' || process.env.TF_BUILD !== undefined; + export function isTestExecution(): boolean { return process.env.VSC_PYTHON_CI_TEST === '1' || isUnitTestExecution(); } diff --git a/src/client/common/process/logger.ts b/src/client/common/process/logger.ts index 05ab913d2afd..7fe0479a23fc 100644 --- a/src/client/common/process/logger.ts +++ b/src/client/common/process/logger.ts @@ -1,8 +1,11 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { inject, injectable, named } from 'inversify'; -import { STANDARD_OUTPUT_CHANNEL } from '../constants'; +'use strict'; + +import { inject, injectable, named } from 'inversify'; +import { workspace } from 'vscode'; +import { isCI, isTestExecution, STANDARD_OUTPUT_CHANNEL } from '../constants'; import { traceInfo } from '../logger'; import { IOutputChannel, IPathUtils } from '../types'; import { Logging } from '../utils/localize'; @@ -13,9 +16,18 @@ export class ProcessLogger implements IProcessLogger { constructor( @inject(IOutputChannel) @named(STANDARD_OUTPUT_CHANNEL) private readonly outputChannel: IOutputChannel, @inject(IPathUtils) private readonly pathUtils: IPathUtils - ) {} + ) { } public logProcess(file: string, args: string[], options?: SpawnOptions) { + if ( + !isTestExecution() && + isCI && + !workspace.getConfiguration('python', null).get('enableProcessLogging', true) + ) { + // Added to disable logging of process execution commands during UI Tests. + // Used only during UI Tests (hence this setting need not be exposed as a valid setting). + return; + } const argsList = args.reduce((accumulator, current, index) => { let formattedArg = this.pathUtils.getDisplayName(current).toCommandArgument(); if (current[0] === '\'' || current[0] === '"') { diff --git a/src/client/testing/display/main.ts b/src/client/testing/display/main.ts index 17c736a0a198..e56b4bacaccd 100644 --- a/src/client/testing/display/main.ts +++ b/src/client/testing/display/main.ts @@ -134,7 +134,7 @@ export class TestResultDisplay implements ITestResultDisplay { this.statusBar.tooltip = tooltip; this.statusBar.show(); this.clearProgressTicker(); - this.progressTimeout = setInterval(() => this.updateProgressTicker(), 150); + this.progressTimeout = setInterval(() => this.updateProgressTicker(), 1000); } private updateProgressTicker() { const text = `${this.progressPrefix} ${this.ticker[this.discoverCounter % 7]}`; diff --git a/src/test/activation/languageServer/outputChannel.unit.test.ts b/src/test/activation/languageServer/outputChannel.unit.test.ts index 53fe700b2b1a..a37f7ceaa331 100644 --- a/src/test/activation/languageServer/outputChannel.unit.test.ts +++ b/src/test/activation/languageServer/outputChannel.unit.test.ts @@ -6,18 +6,20 @@ import { expect } from 'chai'; import * as TypeMoq from 'typemoq'; import { LanguageServerOutputChannel } from '../../../client/activation/languageServer/outputChannel'; -import { IApplicationShell } from '../../../client/common/application/types'; +import { IApplicationShell, ICommandManager } from '../../../client/common/application/types'; import { IOutputChannel } from '../../../client/common/types'; import { OutputChannelNames } from '../../../client/common/utils/localize'; suite('Language Server Output Channel', () => { let appShell: TypeMoq.IMock; let languageServerOutputChannel: LanguageServerOutputChannel; + let commandManager: TypeMoq.IMock; let output: IOutputChannel; setup(() => { appShell = TypeMoq.Mock.ofType(); output = TypeMoq.Mock.ofType().object; - languageServerOutputChannel = new LanguageServerOutputChannel(appShell.object); + commandManager = TypeMoq.Mock.ofType(); + languageServerOutputChannel = new LanguageServerOutputChannel(appShell.object, commandManager.object); }); test('Create output channel if one does not exist before and return it', async () => { diff --git a/tsconfig.json b/tsconfig.json index d84df6db4064..946ac3e298e6 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -20,8 +20,6 @@ "noImplicitThis": true, "noUnusedLocals": true, "noUnusedParameters": true, - // We don't worry about this one: - //"noImplicitReturns": true, "noFallthroughCasesInSwitch": true }, "exclude": [ @@ -32,6 +30,8 @@ "src/client/node_modules", "src/server/src/typings", "src/client/src/typings", - "build" + "src/smoke", + "build", + "uitests" ] } diff --git a/uitests/.gitignore b/uitests/.gitignore new file mode 100644 index 000000000000..e05b1948e39e --- /dev/null +++ b/uitests/.gitignore @@ -0,0 +1,24 @@ +@rerun*.txt +.DS_Store +out +**/node_modules +*.vsix +**/.vscode/.ropeproject/** +**/testFiles/**/.cache/** +*.noseids +.nyc_output +npm-debug.log +**/.mypy_cache/** +cucumber-report.json +.vscode-test/** +.vscode test/** +**/.venv*/ +bin/** +obj/** +.pytest_cache +tmp/** +.python-version +.vs/ +test-results.xml +uitests/out/** +!build/ diff --git a/uitests/.prettierrc.js b/uitests/.prettierrc.js new file mode 100644 index 000000000000..786cdd4ed869 --- /dev/null +++ b/uitests/.prettierrc.js @@ -0,0 +1,6 @@ +module.exports = { + semi: true, + singleQuote: true, + printWidth: 180, + tabWidth: 4 +}; diff --git a/uitests/.vscode/launch.json b/uitests/.vscode/launch.json new file mode 100644 index 000000000000..3462ba52e747 --- /dev/null +++ b/uitests/.vscode/launch.json @@ -0,0 +1,24 @@ +// A launch configuration that compiles the extension and then opens it inside a new window +{ + "version": "0.1.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "UI Tests", + "program": "${workspaceFolder}/out/index.js", + "sourceMaps": true, + "outFiles": ["${workspaceFolder}/out/**/*.js"], + "args": [ + "test", + "--pythonPath", + "/Users/donjayamanne/.pyenv/versions/3.7.3/bin/python", + "--", + // Change the tag `@wip` to what ever you want to run. + // Default is assumed to be somethign that's a work in progress (wip). + "--tags=@wip" + ], + "skipFiles": ["/**"] + } + ] +} diff --git a/uitests/.vscode/settings.json b/uitests/.vscode/settings.json new file mode 100644 index 000000000000..2052022f3c52 --- /dev/null +++ b/uitests/.vscode/settings.json @@ -0,0 +1,42 @@ +// Place your settings in this file to overwrite default and user settings. +{ + "files.exclude": { + "out": true, + "**/*.pyc": true, + ".nyc_output": true, + "obj": true, + "bin": true, + "**/__pycache__": true, + "**/node_modules": true, + ".vscode test": false, + ".vscode-test": false, + "**/.mypy_cache/**": true, + "**/.ropeproject/**": true + }, + "search.exclude": { + "out": true, + "**/node_modules": true, + "coverage": true, + "languageServer*/**": true, + ".vscode-test": true, + ".vscode test": true + }, + "[python]": { + "editor.formatOnSave": true + }, + "[typescript]": { + "editor.formatOnSave": true + }, + "typescript.preferences.quoteStyle": "single", + "javascript.preferences.quoteStyle": "single", + "typescriptHero.imports.stringQuoteStyle": "'", + "prettier.tslintIntegration": true, + "cucumberautocomplete.skipDocStringsFormat": true, + "[javascript]": { + "editor.formatOnSave": true + }, + "editor.codeActionsOnSave": { + "source.fixAll": true, + "source.fixAll.tslint": true + } +} diff --git a/uitests/.vscode/tasks.json b/uitests/.vscode/tasks.json new file mode 100644 index 000000000000..8f9f1f627275 --- /dev/null +++ b/uitests/.vscode/tasks.json @@ -0,0 +1,28 @@ +{ + "version": "2.0.0", + "presentation": { + "echo": true, + "reveal": "always", + "focus": false, + "panel": "shared" + }, + "tasks": [ + { + "label": "Compile UI Tests", + "type": "npm", + "script": "compile", + "isBackground": true, + "problemMatcher": [ + "$tsc-watch", + { + "base": "$tslint5", + "fileLocation": "relative" + } + ], + "group": { + "kind": "build", + "isDefault": true + } + } + ] +} diff --git a/uitests/README.md b/uitests/README.md new file mode 100644 index 000000000000..f2513486ab9a --- /dev/null +++ b/uitests/README.md @@ -0,0 +1,152 @@ +# VS Code Smoke Test + +## Usage + +```shell +$ # The step `npm run package` is required to ensure the 'ms-python-insiders.vsix' is available locally. +$ # You could instead just download this and dump into the working directory (much faster). +$ # npm run package # see notes above. + + +$ npm run compile-smoke +$ npm run smokeTest # Use the `-- --tags=@wip` argument to run specific tests. +$ npm run smokeTest -- --help # for more information (see src/smoke/src/cli.ts) +$ npm run smokeTest:report # To generate report (output is './vscode test/reports/report.html') +``` + +## Overview + +* These are a set of UI tests for the Python Extension in VSC. +* The UI is driven using the same infrastructure as used by `VS Code` for their smoke tests. +* [BDD](https://en.wikipedia.org/wiki/Behavior-driven_development) is used to create the tests, and executed using [cucumberjs](https://github.com/cucumber/cucumber-js). + +## How to run smoke tests? + +Here are the steps involved in running the tests: + +* Setup environment: + * Pull down `ms-python-extension.vsix` from Azure Pipline. + * Download a completely fresh version of VS Code (`stable/insiders`. Defaults to `stable`). + (configurable using the `--channel=stable | --channel=insider`) + * Create a folder named `.vscode test` where test specific files will be created (reports, logs, VS Code, etc). + +## How does it work? +* When launching VSC, we will launch it as a completely stand alone version of VSC. + * I.E. even if it is installed on the current machine, we'll download and launch a new instance. + * This new instance will not interfere with currently installed version of VSC. + * All user settings, etc will be in a separate directory (see `user` folder). + * VSC will not have any extensions. We are in control of what extensions are installed (see `.vscode test/extensions` folder). +* Automate VSC UI + * Use the VS Code smoke test API to automate the UI. + * The [BDD](https://en.wikipedia.org/wiki/Behavior-driven_development) tests are written and executed using [cucumberjs](https://github.com/cucumber/cucumber-js). +* Workspace folder/files + * Each [feature](https://docs.cucumber.io/gherkin/reference/#feature) can have its own set of files in the form of a github repo. + * Just add a tag with the path of the github repo url to the `feature`. + * When starting the tests for a feature, the repo is downloaded into a new random directory `.vscode test/temp/workspace folder xyz` + * At the begining of every scenario, we repeat the previous step. + * This ensures each scenario starts with a clean workspace folder. +* Reports + * Test results are stored in the `.vscode test/reports` directory + * These `json` (`cucumber format`) report files are converted into HTML using an `npm` script [cucumber-html-reporter](https://www.npmjs.com/package/cucumber-html-reporter). + * For each `scenario` that's executed, we create a corresponding directory in `.vscode test/reports` directory. + * This will contain all screenshots realted to that scenario. + * If the scenario fails, all logs, workspace folder are copied into this directory. + * Thus, when ever a test fails, we have everything related to that test. + * If the scenario passes, this directory is deleted (we don't need them on CI server). + +## Technology + +* 100% of the code is written in `nodejs`. +* The tests are written using [cucumberjs](https://github.com/cucumber/cucumber-js). +* VS Code [smoke tests API](https://github.com/microsoft/vscode/tree/master/test/smoke) is used to automate VS Code. +* `GitHub` repos are used to provide the files to be used for testing in a workspace folder. +* reports (`cucumber format`) are converted into HTML using an `npm` script [cucumber-html-reporter](https://www.npmjs.com/package/cucumber-html-reporter). +* Test result reports are generated using `junit` format, for Azure Devops. + + +## Files & Folders + +* `~/vscode test` Directory used for storing everything related to a test run (VS Code, reports, logs, etc). + * `./stable` This is VS Code stable is downloaded. + * `./insider` This is VS Code insider is downloaded. + * `./user` Directory VS Code uses to store user information (settings, etc) + * `./extensions` This is where the extensions get installed for the instance of VSC used for testing. + * `./workspace folder` Folder opened in VS Code for testing + * `./temp path` Temporary directory for testing. (sometimes tests will create folders named `workspace folder xyz` to be used as workspace folders used for testing) + * `./reports` Location where generated reports are stored. + * `./logs` Logs for tests + * `./screenshots` Screen shots captured during tests +* `~/src/uitest/bootstrap` Contains just the bootstrap extension code. +* `~/src/uitests/features` [Feature files](https://cucumber.io/docs/gherkin/reference/#feature) used to drive the [BDD](https://en.wikipedia.org/wiki/Behavior-driven_development) tests are stored here. +* `~/src/uitests/src` Source code for smoke Tests (features, nodejs code, etc). + +## CI Integration + +* For more details please check `build/ci`. +* We generally try to run all tests against all permutations of OS + Python Version + VSC + * I.e. we run tests across permutations of the follows: + - OS: Windows, Mac, Linux + - Python: 2.7, 3.5, 3.6, 3.7 + - VSC: Stable, Insiders +* Each scenario is treated as a test + - These results are published on Azure Devops + - Artifacts are published containing a folder named `.vscode test/reports/` + - This folder contains all information related to that test run: + - Screenshots (including the point in time the test failed) for every step in the scenario (sequentially named files) + - VS Code logs (including output from the output panels) + - The workspace folder that was opened in VSC code (we have the exact files used by VSC) + - Our logs (Extension logs, debugger logs) + - Basically we have everything we'd need to diagnoze the failure. +* The report for the entire run is uploaded as part of the artifact for the test job. + - The HTML report contains test results (screenshots & all the steps). +* The same ui tests are run as smoke tests as part of a PR. + + +## Caveats +* The tests rely on the structure of the HTML elements (& their corresponding CSS/style attribute values). + - Basically we have hardcoded the CSS queries. If VS Code were to change these, then the tests would fail. + - One solution is to pin the UI tests against a stable version of VS Code. + - When ever a new version of VS Code is released, then move CSS queries from `insider` into `stable` found in the `src/uitests/src/selectors.ts` file. + - This way tests/CI will not fail and we'll have time to address the CSS/HTML changes. + +## Miscellaneous + +* For debugging follow these steps: + * Run the npm command `smokeTest:debug` + * Then attach the debugger using the debug configuration `Attach to Smoke Tests`. + * What about regular debugging? + * It has been observed that the instance of VSC launched for smoke tests just falls over when debugging from within VSC. + * Solution: Launch code in debug mode and attach (yes this works). + * Not entirely sure why it works, or why it doesn't work. + * Got a solution, hence not investing much more time time trying to identify why debugging is failing. +* In order to pass custom arguments to `cucumberjs`, refer to the `CLI` (pass `cucumber` specific args after `--` in `npm run smokeTest`). + * E.g. `npm run smokeTest -- --tags=@wip --more-cucumberjs-args` +* Remember, the automated UI interactions can be faster than normal user interactions. + * E.g. just because we started debugging (using command `Debug: Start Debugging`), that doesn't mean the debug panel will open immediately. User interactions are slower compared to code execution. + * Solution, always wait for the UI elements to be available/active. E.g. when you open a file, check whether the corresponding elements are visible. + +## Code Overview +* Tests are written in nodejs. Why? + * Short answer - We're using the VS Code Smoke test infrastructure. + * Previously we wrote tests using `selenium`. However a week after the tests were running, VSC released a new version. This new version of VSC had a version of Electron + Chromium that didn't have a compatible version of `chrome driver`. + * The chrome `chrome driver` is used by `selenium` to drive the tests. Also using `selenium` we had tonnes of issues. + * Solution - Use the same technique used by VS Code to drive their UI Tests. +* Code borrowed from VS Code ([src/smoke/vscode](https://github.com/microsoft/vscode-python/tree/master/src/smoke/vscode)). + * Short answer - We're using the VS Code Smoke test infrastructure (this is where that code resides). + * The code in [src/smoke/vscode](https://github.com/microsoft/vscode-python/tree/master/src/smoke/vscode) code has been borrowed from [VS Code Smoke tests](https://github.com/microsoft/vscode/tree/master/test/smoke). + * This contains the code required to launch VS Code and drive some tests. + * Rather than picking and choosing some files, we've copied the entire source folder. + * This makes it easy to update this code with later versions of changes from upstream VS Code. + * We could optionally package this into a seperate `npm package` and pull it in for testing purposes, however that adds the overhead of maintaining an `npm package`. + * There's also the option of creating a seprate repo and publishign this code into a internal package repository (`GitHub` or `Azure Pipelines`). + * To be discussed +* Bootstrap extension ([src/smoke/bootstrap](https://github.com/microsoft/vscode-python/tree/master/src/smoke/bootstrap)) + * Short answer - Used to update the `settings.json` and detect loading of `Python Extension`. + * When updating settings in VSC, do not alter the settings files directly. VSC could take a while to detect file changes and load the settings. + - An even better way, is to use the VSC api to update the settings (via the bootstrap API) or edit the settings file directly through the UI. + - Updating settings through the editor (by editing the `settings.json` file directly is not easy, as its not easy to update/remove settings). + - Using the API we can easily determine when VSC is aware of the changes (basically when API completes, VSC is aware of the new settings). + - (This is made possible by writing the settings to be updated into `settingsToUpdate.txt`, and letting the bootstrap extension read that file and update the VSC settings using the VSC API). + * Similarly checking whether the `Python Extension` has activated is done by the `bootstrap` extension by creating a new status bar item + * The prescence of this new status bar indicates the fact that the extension has activated successfully. + * The code for this extension resides in [src/smoke/bootstrap](https://github.com/microsoft/vscode-python/tree/master/src/smoke/bootstrap) diff --git a/uitests/TODO.md b/uitests/TODO.md new file mode 100644 index 000000000000..606bd7a65422 --- /dev/null +++ b/uitests/TODO.md @@ -0,0 +1,10 @@ +- [ ] Dynamic detection of where `pyenv` environments are created and stored +- [ ] Conda on Azure Pipelines don't work as the `environments.txt` file is not available/not updated. + - Is this the case in realworld? + - We need a fix/work around. +- [ ] Ensure we use spaces in path to the extension + - We have had bugs where extension fails due to spaces in paths (user name) + - Debugger fails +- [ ] Fail CI if a file is not created or vice versa. + Or run another script that'll check the existence and fail on stderr. + We don't want behave to monitor stderr, as we can ignore many errors. diff --git a/uitests/bootstrap/extension/.vscodeignore b/uitests/bootstrap/extension/.vscodeignore new file mode 100644 index 000000000000..ff73d4744519 --- /dev/null +++ b/uitests/bootstrap/extension/.vscodeignore @@ -0,0 +1,10 @@ +.vscode/** +.vscode-test/** +out/test/** +out/**/*.map +src/** +.gitignore +tsconfig.json +vsc-extension-quickstart.md +tslint.json +*.vsix diff --git a/uitests/bootstrap/extension/extension.js b/uitests/bootstrap/extension/extension.js new file mode 100644 index 000000000000..6da640ce0a3e --- /dev/null +++ b/uitests/bootstrap/extension/extension.js @@ -0,0 +1,194 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +Object.defineProperty(exports, '__esModule', { value: true }); +const vscode = require('vscode'); +const fs = require('fs'); +const path = require('path'); +const util = require('util'); + +let activated = false; +async function sleep(timeout) { + return new Promise(resolve => setTimeout(resolve, timeout)); +} +function activate(context) { + const statusBarItemActivated = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 10000000); + const lineColumnStatusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 10000000); + const statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 10000000); + statusBarItem.command = 'workbench.action.quickOpen'; + statusBarItem.text = '1'; + statusBarItem.tooltip = 'Py'; + statusBarItem.show(); + lineColumnStatusBarItem.command = 'workbench.action.quickOpen'; + lineColumnStatusBarItem.text = ''; + lineColumnStatusBarItem.tooltip = 'PyLine'; + lineColumnStatusBarItem.show(); + + context.subscriptions.push(statusBarItem); + context.subscriptions.push(lineColumnStatusBarItem); + // Always display editor line, column in this statusbar. + // Sometimes we cannot detect the line,column of editor (because that item in statubar is not visbible due to lack of realestate). + // This will get around that problem. + vscode.window.onDidChangeTextEditorSelection(e => { + try { + lineColumnStatusBarItem.text = `${e.textEditor.selection.start.line + 1},${e.textEditor.selection.start.character + 1}`; + } catch {} + }); + // vscode.window.onDidChangeActiveTextEditor() + let lastSetText = ''; + let interval = undefined; + function monitorEditor() { + clearInterval(interval); + // if (!vscode.window.activeTextEditor) { + // return; + // } + interval = setInterval(() => { + if (vscode.window.activeTextEditor) { + try { + const newText = `${vscode.window.activeTextEditor.selection.start.line + 1},${vscode.window.activeTextEditor.selection.start.character + 1}`; + if (lastSetText === newText) { + return; + } + lastSetText = lineColumnStatusBarItem.text = newText; + } catch {} + } + }, 500); + } + vscode.window.onDidChangeActiveTextEditor(monitorEditor); + vscode.window.onDidChangeVisibleTextEditors(monitorEditor); + vscode.commands.registerCommand('smoketest.activatePython', async () => { + if (activated) { + return; + } + const ext = vscode.extensions.getExtension('ms-python.python'); + if (!ext.isActive) { + await ext.activate(); + console.log('Bootstrap extension'); + console.log('ext.exports'); + console.log(ext.exports); + // Wait for extension to complete. + await ext.exports.ready; + } + statusBarItemActivated.text = '2'; + statusBarItemActivated.tooltip = 'Py2'; + // Don't remove this command, else the CSS selector for this will be different. + // VSC will render a span if there's no span. + statusBarItemActivated.command = 'workbench.action.quickOpen'; + statusBarItemActivated.show(); + + activated = true; + context.subscriptions.push(statusBarItemActivated); + }); + vscode.commands.registerCommand('smoketest.runInTerminal', async () => { + const filePath = path.join(__dirname, '..', 'commands.txt'); + const command = fs + .readFileSync(filePath) + .toString() + .trim(); + for (let counter = 0; counter < 5; counter++) { + if (!vscode.window.activeTerminal) { + await sleep(5000); + } + } + if (!vscode.window.activeTerminal) { + vscode.window.createTerminal('Manual'); + await sleep(5000); + } + if (!vscode.window.activeTerminal) { + vscode.window.showErrorMessage('No Terminal in Bootstrap Extension'); + } + await vscode.window.activeTerminal.sendText(command, true); + fs.unlinkSync(filePath); + }); + vscode.commands.registerCommand('smoketest.updateSettings', async () => { + const filePath = path.join(__dirname, '..', 'settingsToUpdate.txt'); + try { + const setting = getSettingsToUpdateRemove(filePath); + const configTarget = + setting.type === 'user' + ? vscode.ConfigurationTarget.Global + : setting.type === 'workspace' + ? vscode.ConfigurationTarget.Workspace + : vscode.ConfigurationTarget.WorkspaceFolder; + + if (configTarget === vscode.ConfigurationTarget.WorkspaceFolder && !setting.workspaceFolder) { + vscode.window.showErrorMessage('Workspace Folder not defined for udpate/remove of settings'); + throw new Error('Workspace Folder not defined'); + } + + const resource = setting.workspaceFolder ? vscode.Uri.file(setting.workspaceFolder) : undefined; + + for (let settingToRemove in setting.remove || []) { + const parentSection = settingToRemove.split('.')[0]; + const childSection = settingToRemove + .split('.') + .filter((_, i) => i > 0) + .join('.'); + const settings = vscode.workspace.getConfiguration(parentSection, resource); + await settings.update(childSection, undefined, configTarget); + } + for (let settingToAddUpdate in setting.update || []) { + const parentSection = settingToAddUpdate.split('.')[0]; + const childSection = settingToAddUpdate + .split('.') + .filter((_, i) => i > 0) + .join('.'); + const settings = vscode.workspace.getConfiguration(parentSection, resource); + await settings.update(childSection, setting.update[settingToAddUpdate], configTarget); + } + fs.unlinkSync(filePath); + } catch (ex) { + fs.appendFileSync(path.join(__dirname, '..', 'settingsToUpdate_error.txt'), util.format(ex)); + } + }); + vscode.commands.registerCommand('smoketest.openFile', async () => { + const file = fs + .readFileSync(path.join(__dirname, '..', 'commands.txt')) + .toString() + .trim(); + const doc = await vscode.workspace.openTextDocument(file); + await vscode.window.showTextDocument(doc); + }); + // Custom command to stop debug sessions. + // Basically we need a way to stop any existing debug sessions. + // Using the vsc command, as we can invoke it even if a debugger isn't running. + // We can use the command `Debug: Stop` from the command palette only if a debug session is active. + // Using this approach we can send a command regardless, easy. + vscode.commands.registerCommand('smoketest.stopDebuggingPython', async () => { + try { + await vscode.commands.executeCommand('workbench.action.debug.stop'); + } catch { + // Do nothing. + } + }); +} + +/** + * @typedef {Object} SettingsToUpdate - creates a new type named 'SpecialType' + * @property {'user' | 'workspace' | 'workspaceFolder'} [type] - Type. + * @property {?string} workspaceFolder - Workspace Folder + * @property {Object.} update - Settings to update. + * @property {Array} remove - Skip format checks. + */ + +/** + * + * + * @param {*} filePath + * @return {SettingsToUpdate} Settings to update/remove. + */ +function getSettingsToUpdateRemove(filePath) { + return JSON.parse( + fs + .readFileSync(filePath) + .toString() + .trim() + ); +} +exports.activate = activate; +function deactivate() { + // Do nothing. +} +exports.deactivate = deactivate; diff --git a/uitests/bootstrap/extension/package.json b/uitests/bootstrap/extension/package.json new file mode 100644 index 000000000000..e4a8cc8b31fa --- /dev/null +++ b/uitests/bootstrap/extension/package.json @@ -0,0 +1,50 @@ +{ + "name": "smoketest", + "publisher": "ms-python", + "displayName": "smokeTestPython", + "description": "Bootstrap for Python Smoke Tests", + "version": "0.0.1", + "license": "MIT", + "homepage": "https://github.com/Microsoft/vscode-python", + "repository": { + "type": "git", + "url": "https://github.com/Microsoft/vscode-python" + }, + "bugs": { + "url": "https://github.com/Microsoft/vscode-python/issues" + }, + "qna": "https://stackoverflow.com/questions/tagged/visual-studio-code+python", + "engines": { + "vscode": "^1.32.0" + }, + "categories": [ + "Other" + ], + "activationEvents": [ + "*" + ], + "main": "./extension", + "contributes": { + "commands": [ + { + "command": "smoketest.activatePython", + "title": "Activate Python Extension" + }, + { + "command": "smoketest.stopDebuggingPython", + "title": "Stop Debugging Python" + }, + { + "command": "smoketest.runInTerminal", + "title": "Smoke: Run Command In Terminal" + }, + { + "command": "smoketest.updateSettings", + "title": "Smoke: Update Settings" + } + ] + }, + "scripts": { + "build": "vsce package --out ../bootstrap.vsix" + } +} diff --git a/uitests/features/README.md b/uitests/features/README.md new file mode 100644 index 000000000000..82a6d09c5d43 --- /dev/null +++ b/uitests/features/README.md @@ -0,0 +1,38 @@ +# Tags +* @wip + * Used only for debugging purposes. + * When debugging in VSC, only features/scenarios with @wip tag will be executed. +* @skip + * Used to skip a feature/scenario. +* @https://github.com/xxx/yyy.git + * Can only be used at a feature level. + * The conents of the above repo will be used as the contents of the workspace folder. + * Note: assume the tag is `@https://github.com/DonJayamanne/pyvscSmokeTesting.git` + * The above repo is cloned directly into the workspace. + * If however the tag is `@https://github.com/DonJayamanne/pyvscSmokeTesting/tests` + * Now, the contents of the workspace is the `tests` directory in the above repo. + * This allows us to have a single repo with files/tests for more than just one feature/scenario. + * Else we'd need to have multiple repos for each feature/scenario. +* @preserve.workspace + * Ensures the previous workspace state is used for testing. + * I.e. state setup in previous state is not reset (makes it easier to re-use state from preivous scenarios). +* @mac, @win, @linux + * Used to ensure a particular feature/scenario runs only in mac, win or linux respectively. +* @python2, @python3, @python3.5, @python3.6, @python3.7 + * Used to ensure a particular feature/scenario runs only in specific version of Python, respectively. +* @insider + * Used to ensure a particular feature/scenario runs only in VS Code Insiders. +* @stable + * Used to ensure a particular feature/scenario runs only in VS Code Stableccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc. +* @smoke + * All smoke test related functionality. +* @test + * All testing related functionality. +* @debug + * All debugger related functionality. +* @terminal + * All terminal related functionality. +* @terminal.venv + * Related to virtual environments (`python -m venv`) +* @terminal.pipenv + * Related to pipenv environments (`pipenv shell`) diff --git a/uitests/features/datascience/basic.feature b/uitests/features/datascience/basic.feature new file mode 100644 index 000000000000..12d46ba01c08 --- /dev/null +++ b/uitests/features/datascience/basic.feature @@ -0,0 +1,39 @@ +# @ds @smoke +# @https://github.com/DonJayamanne/vscode-python-uitests/datascience +# Feature: Data Science +# Scenario: Can display an image and print text into the interactive window +# Given the package "jupyter" is installed +# And a file named "log.log" does not exist +# # Increase font size for text detection. +# And the workspace setting "editor.fontSize" has the value 15 +# And the file "smoke.py" is open +# When I wait for the Python extension to activate +# # Code will display an image and print stuff into interactive window. +# When I select the command "Python: Run All Cells" +# # Wait for Interactive Window to open +# And I wait for 10 seconds +# # Close the file, to close it, first set focus to it by opening it again. +# And I open the file "smoke.py" +# And I select the command "View: Revert and Close Editor" +# And I select the command "View: Close Panel" +# # Wait for 2 minutes for Jupyter to start +# Then a file named "log.log" will be created within 120 seconds +# # This is the content of the image rendered in the interactive window. +# # And the text "VSCODEROCKS" is displayed in the Interactive Window +# # # This is the content printed by a python script. +# # And the text "DATASCIENCEROCKS" is displayed in the Interactive Window + +# Scenario: Workspace directory is used as cwd for untitled python files +# Given the package "jupyter" is installed +# And a file named "log.log" does not exist +# When I wait for the Python extension to activate +# When I create an untitled Python file with the following content +# """ +# open("log.log", "w").write("Hello") +# """ +# # Code will display an image and print stuff into interactive window. +# When I select the command "Python: Run All Cells" +# # Wait for Interactive Window to open +# And I wait for 10 seconds +# # Wait for 2 minutes for Jupyter to start +# Then a file named "log.log" will be created within 120 seconds diff --git a/uitests/features/debugging/basic.feature b/uitests/features/debugging/basic.feature new file mode 100644 index 000000000000..b6c279f02ecd --- /dev/null +++ b/uitests/features/debugging/basic.feature @@ -0,0 +1,86 @@ +@debugging +Feature: Debugging + Scenario: Debugging a python file without creating a launch configuration (with delays in user code) + """ + Ensure we can debug a python file (the code in the python file is slow). + I.e. it will not run to completion immediately. + """ + Given the file ".vscode/launch.json" does not exist + And a file named "simple sample.py" is created with the following content + """ + # Add a minor delay for tests to confirm debugger has started + import time + + + time.sleep(2) + print("Hello World") + open("log.log", "w").write("Hello") + """ + When I wait for the Python extension to activate + When I open the file "simple sample.py" + When I select the command "Debug: Start Debugging" + Then the Python Debug Configuration picker is displayed + When I select the debug configuration "Python File" + # This is when VSC displays the toolbar, (but actual debugger may not have started just yet). + Then the debugger starts + # Starting the debugger takes a while, (open terminal, activate it, etc) + And the debugger will stop within 20 seconds + And a file named "log.log" will be created + + Scenario: Confirm Run without debugging without creating a launch configuration works + """ + Ensure we can run a python file without debugging. + I.e. it will not run to completion immediately. + + In the past when the debugger would run to completion quicly, the debugger wouldn't work correctly. + Here, we need to ensure that no notifications/messages are displayed at the end of the debug session. + (in the past VSC would display error messages). + """ + Given the file ".vscode/launch.json" does not exist + And a file named "simple sample.py" is created with the following content + """ + print("Hello World") + open("log.log", "w").write("Hello") + """ + When I wait for the Python extension to activate + # For for some time for all messages to be displayed, then hide all of them. + Then wait for 10 seconds + And select the command "Notifications: Clear All Notifications" + When I open the file "simple sample.py" + And I select the command "Debug: Start Without Debugging" + # This is when VSC displays the toolbar, (but actual debugger may not have started just yet). + Then the debugger starts + # Starting the debugger takes a while, (open terminal, activate it, etc) + And the debugger will stop within 5 seconds + And a file named "log.log" will be created + And take a screenshot + And no error notifications are displayed + + @smoke + Scenario: Debugging a python file without creating a launch configuration (hello world) + """ + In the past when the debugger would run to completion quicly, the debugger wouldn't work correctly. + Here, we need to ensure that no notifications/messages are displayed at the end of the debug session. + (in the past VSC would display error messages). + """ + Given the file ".vscode/launch.json" does not exist + And a file named "simple sample.py" is created with the following content + """ + print("Hello World") + open("log.log", "w").write("Hello") + """ + When I wait for the Python extension to activate + # For for some time for all messages to be displayed, then hide all of them. + Then wait for 10 seconds + And select the command "Notifications: Clear All Notifications" + When I open the file "simple sample.py" + And I select the command "Debug: Start Debugging" + Then the Python Debug Configuration picker is displayed + When I select the debug configuration "Python File" + # This is when VSC displays the toolbar, (but actual debugger may not have started just yet). + Then the debugger starts + # Starting the debugger takes a while, (open terminal, activate it, etc) + And the debugger will stop within 20 seconds + And a file named "log.log" will be created + Then take a screenshot + And no error notifications are displayed diff --git a/uitests/features/debugging/breakpoints.feature b/uitests/features/debugging/breakpoints.feature new file mode 100644 index 000000000000..94109f180c3e --- /dev/null +++ b/uitests/features/debugging/breakpoints.feature @@ -0,0 +1,65 @@ +@debugging +Feature: Debugging + Scenario: Debugging a python file with breakpoints + Given a file named ".vscode/launch.json" is created with the following content + """ + { + "version": "0.2.0", + "configurations": [ + { + "name": "Python: Current File", + "type": "python", + "request": "launch", + "program": "${workspaceFolder}/simple sample.py", + "console": "integratedTerminal" + } + ] + } + """ + And a file named "simple sample.py" is created with the following content + """ + open("log.log", "w").write("Hello") + """ + When I wait for the Python extension to activate + And I open the file "simple sample.py" + And I add a breakpoint to line 1 + And I select the command "View: Close All Editors" + And I select the command "Debug: Start Debugging" + Then the debugger starts + And the debugger pauses + And the file "simple sample.py" is opened + And the cursor is on line 1 + And the current stack frame is at line 1 in "simple sample.py" + When I select the command "Debug: Continue" + Then the debugger stops + + Scenario: Debugging a python file without breakpoints + Given a file named ".vscode/launch.json" is created with the following content + """ + { + "version": "0.2.0", + "configurations": [ + { + "name": "Python: Current File", + "type": "python", + "request": "launch", + "program": "${workspaceFolder}/simple sample.py", + "console": "integratedTerminal", + "stopOnEntry": true + } + ] + } + """ + And a file named "simple sample.py" is created with the following content + """ + open("log.log", "w").write("Hello") + """ + When I wait for the Python extension to activate + And I select the command "Debug: Start Debugging" + Then the debugger starts + And the debugger pauses + And the file "simple sample.py" is opened + And the cursor is on line 1 + And the current stack frame is at line 1 in "simple sample.py" + When I select the command "Debug: Continue" + Then the debugger stops diff --git a/uitests/features/debugging/debugger.feature b/uitests/features/debugging/debugger.feature new file mode 100644 index 000000000000..ab4e38757d4b --- /dev/null +++ b/uitests/features/debugging/debugger.feature @@ -0,0 +1,91 @@ +# Feature: Debugger +# @debug @WorkspaceFolder:/Users/donjayamanne/Desktop/Development/PythonStuff/smoke_tests/env_0-virtualenv +# Scenario: Debug Python File with launch.json +# Given the file "main.py" is open +# When stopOnEntry is false in launch.json +# When I add a breakpoint to line 6 +# When I select the command "View: Toggle Integrated Terminal" +# When I press "F5" +# Then debugger starts +# Then take a screenshot +# When I open the debug console +# Then the text "Application launched successfully" is displayed in the debug console +# Then take a screenshot +# Then number of variables in variable window is 1 +# When I select the command "Debug: Step Over" +# Then stack frame for file "main.py" is displayed +# When I select the command "Debug: Step Over" +# Then stack frame for file "main.py" and line 6 is displayed +# When I select the command "Debug: Step Over" +# Then stack frame for file "main.py" and line 5 is displayed +# When I select the command "Debug: Step Over" +# When I select the command "Debug: Step Into" +# Then stack frame for file "wow.py" and line 7 is displayed +# When I select the command "Debug: Continue" +# Then debugger stops + +# @debug @WorkspaceFolder:/Users/donjayamanne/Desktop/Development/PythonStuff/smoke_tests/env_0-virtualenv +# Scenario: Debug Python File without launch.json +# Given the file "main.py" is open +# Given the file ".vscode/launch.json" does not exist +# When I add a breakpoint to line 6 +# When I select the command "View: Toggle Integrated Terminal" +# When I press "F5" +# Then debugger starts +# Then take a screenshot +# When I open the debug console +# Then the text "Application launched successfully" is displayed in the debug console +# Then take a screenshot +# Then number of variables in variable window is 1 +# When I select the command "Debug: Step Over" +# Then stack frame for file "main.py" is displayed +# When I select the command "Debug: Step Over" +# Then stack frame for file "main.py" and line 6 is displayed +# When I select the command "Debug: Step Over" +# Then stack frame for file "main.py" and line 5 is displayed +# When I select the command "Debug: Step Over" +# When I select the command "Debug: Step Into" +# Then stack frame for file "wow.py" and line 7 is displayed +# When I select the command "Debug: Continue" +# Then debugger stops + +# @debug @WorkspaceFolder:/Users/donjayamanne/Desktop/Development/PythonStuff/smoke_tests/env_0-virtualenv +# Scenario: Debug Python File and stop on entry +# Given the file "debugAndStopOnEntry.py" is open +# When stopOnEntry is true in launch.json +# When I open the file "debugAndStopOnEntry.py" +# When I press "F5" +# Then debugger starts +# Then take a screenshot +# Then stack frame for file "debugAndStopOnEntry.py" and line 3 is displayed +# When I select the command "Debug: Continue" +# Then debugger stops + +# @debug @WorkspaceFolder:/Users/donjayamanne/Desktop/Development/PythonStuff/smoke_tests/env_0-virtualenv +# Scenario: Debug Python File without breakpoints +# Given the file "debugWithoutBreakpoints.py" is open +# When I press "F5" +# Then debugger starts +# Then take a screenshot +# Then debugger stops +# When I select the command "View: Debug Console" +# Then the text "Debugging completed" is displayed in the debug console +# Then take a screenshot + +# @debug @WorkspaceFolder:/Users/donjayamanne/Desktop/Development/PythonStuff/smoke_tests/env_0-virtualenv +# Scenario: Run Python File without debugging +# Given the file "runWithoutDebugging.py" is open +# When I select the command "Debug: Start Without Debugging" +# Then debugger stops +# When I select the command "View: Debug Console" +# Then the text "Ran without debugging" is displayed in the debug console +# Then take a screenshot + +# @debug @WorkspaceFolder:/Users/donjayamanne/Desktop/Development/vscode/smokeTests/debugSimple +# Scenario: Run Python File without debugging +# Given the file "runWithoutDebugging.py" is open +# When I select the command "Debug: Start Without Debugging" +# Then debugger stops +# When I select the command "View: Debug Console" +# Then the text "Ran without debugging and no launch.json" is displayed in the debug console +# Then take a screenshot diff --git a/uitests/features/environmentFiles/terminal.feature b/uitests/features/environmentFiles/terminal.feature new file mode 100644 index 000000000000..7081c12c2198 --- /dev/null +++ b/uitests/features/environmentFiles/terminal.feature @@ -0,0 +1,89 @@ +# @terminal +# Feature: Environment Files +# Background: Activted Extension +# Given the Python extension has been activated +# Given a file named ".env" is created with the following content +# """ +# MY_FILE_NAME=log1.log +# """ +# Given a file named ".env2" is created with the following content +# """ +# MY_FILE_NAME=log2.log +# """ +# Given a file named "simple sample.py" is created with the following content +# """ +# import os +# file_name = os.environ.get("MY_FILE_NAME", "other.log") +# with open(file_name, "w") as fp: +# fp.write("Hello") +# """ +# And a file named "log1.log" does not exist +# And a file named "log2.log" does not exist + +# Scenario: Environment variable defined in default environment file is used by debugger +# Given a file named ".vscode/launch.json" is created with the following content +# """ +# { +# "version": "0.2.0", +# "configurations": [ +# { +# "name": "Python: Current File", +# "type": "python", +# "request": "launch", +# "program": "${workspaceFolder}/simple sample.py", +# "console": "integratedTerminal" +# } +# ] +# } +# """ +# When I open the file "simple sample.py" +# And I select the command "Debug: Start Debugging" +# Then the debugger starts +# And the debugger stops +# And a file named "log1.log" will be created + +# Scenario: Environment variable defined in envFile of launch.json is used by debugger +# Given a file named ".vscode/launch.json" is created with the following content +# """ +# { +# "version": "0.2.0", +# "configurations": [ +# { +# "name": "Python: Current File", +# "type": "python", +# "request": "launch", +# "program": "${workspaceFolder}/simple sample.py", +# "console": "integratedTerminal", +# "envFile": "${workspaceFolder}/.env2" +# } +# ] +# } +# """ +# When I open the file "simple sample.py" +# And I select the command "Debug: Start Debugging" +# Then the debugger starts +# And the debugger stops +# And a file named "log2.log" will be created + +# Scenario: Environment variable defined in envFile of settings.json is used by debugger +# Given the workspace setting "python.envFile" has the value "${workspaceFolder}/.env2" +# Given a file named ".vscode/launch.json" is created with the following content +# """ +# { +# "version": "0.2.0", +# "configurations": [ +# { +# "name": "Python: Current File", +# "type": "python", +# "request": "launch", +# "program": "${workspaceFolder}/simple sample.py", +# "console": "integratedTerminal" +# } +# ] +# } +# """ +# When I open the file "simple sample.py" +# And I select the command "Debug: Start Debugging" +# Then the debugger starts +# And the debugger stops +# And a file named "log2.log" will be created diff --git a/uitests/features/interpreter/basic.feature b/uitests/features/interpreter/basic.feature new file mode 100644 index 000000000000..8a8b5bd3aaec --- /dev/null +++ b/uitests/features/interpreter/basic.feature @@ -0,0 +1,32 @@ +@terminal +Feature: Interpreter + @mac @python2 + Scenario: Display message when selecting default Mac 2.7 Interpreter + Given the Python extension has been activated + When I select the Python Interpreter containing the text "/usr/bin/python" + Then a message containing the text "You have selected the macOS system install of Python" is displayed + + Scenario: Opening VS Code for the first time will display tip about selecting interpreter + Given VS Code is opened for the first time + When the Python extension has activated + Then a message containing the text "Tip: you can change the Python interpreter used by the Python extension by clicking" is displayed + + Scenario: Re-opening VS Code will display tip about selecting interpreter + Given VS Code is opened for the first time + When the Python extension has activated + Then a message containing the text "Tip: you can change the Python interpreter used by the Python extension by clicking" is displayed + When I reload VS Code + And the Python extension has activated + Then a message containing the text "Tip: you can change the Python interpreter used by the Python extension by clicking" is displayed + + Scenario: Re-opening VS Code will not display tip about selecting interpreter after clicking the 'Got it' button + Given VS Code is opened for the first time + Then the Python extension has activated + Then a message containing the text "Tip: you can change the Python interpreter used by the Python extension by clicking" is displayed + When I click the "Got it!" button for the message with the text "Tip: you can change the Python interpreter used by the Python extension by clicking" + # Wait for state information to get persisted (of the fact that we closed this message). + # I.e. wait a while before we close VS Code. + And wait for 5 seconds + And I reload VS Code + And the Python extension has activated + Then a message containing the text "Tip: you can change the Python interpreter used by the Python extension by clicking" is not displayed diff --git a/uitests/features/interpreter/conda.feature b/uitests/features/interpreter/conda.feature new file mode 100644 index 000000000000..6caf5569ce5a --- /dev/null +++ b/uitests/features/interpreter/conda.feature @@ -0,0 +1,88 @@ +# @terminal @terminal.conda +# @skip +# @https://github.com/DonJayamanne/vscode-python-uitests/terminal/execution +# Feature: Terminal (conda) +# Scenario: Interpreter display name contains the name of the environment and conda +# Given the user setting "python.pythonPath" does not exist +# And a conda environment is created with the name "helloworld" +# Then take a screenshot +# When I wait for 20 seconds +# Then take a screenshot +# # Wait for some time for the new conda environment to get discovered. +# When I reload VSC +# Then take a screenshot +# When I send the command "conda env list" to the terminal +# When I wait for 5 seconds +# Then take a screenshot +# When I wait for 20 seconds +# Then take a screenshot +# When I select the command "Python: Select Interpreter" +# When I wait for 3 seconds +# Then take a screenshot +# When I reload VSC +# And I wait for 30 seconds +# And I select the Python Interpreter containing the name "helloworld" +# Then take a screenshot +# Then the python interpreter displayed in the the status bar contains the value "conda" in the display name +# And the python interpreter displayed in the the status bar contains the value "helloworld" in the display name +# And the workspace setting "python.pythonPath" exists + +# # @preserve.workspace +# # Scenario: Pipenv is auto selected +# # Given the workspace setting "python.pythonPath" does not exist +# # And the user setting "python.pythonPath" does not exist +# # When I reload VSC +# # Then the python interpreter displayed in the the status bar contains the value "pipenv" in the display name +# # And the python interpreter displayed in the the status bar contains the value "workspace folder" in the display name +# # And the workspace setting "python.pythonPath" exists + +# # @preserve.workspace +# # Scenario: Pipenv is not auto selected (if we already have a local interpreter selected) +# # Given a generic Python Interpreter is selected +# # When I reload VSC +# # Then the python interpreter displayed in the the status bar does not contain the value "pipenv" in the display name +# # And the python interpreter displayed in the the status bar does not contain the value "workspace folder" in the display name +# # And the workspace setting "python.pythonPath" exists + +# # @preserve.workspace +# # Scenario: Pipenv is not auto selected (if we have a global interpreter selected) +# # Given the workspace setting "python.pythonPath" does not exist +# # And the user setting "python.pythonPath" exists +# # When I reload VSC +# # Then open the file "settings.json" +# # Then the python interpreter displayed in the the status bar does not contain the value "pipenv" in the display name +# # And the python interpreter displayed in the the status bar does not contain the value "workspace folder" in the display name + +# # @preserve.workspace +# # Scenario: Environment is not activated in the Terminal +# # Given the workspace setting "python.pythonPath" does not exist +# # And the user setting "python.pythonPath" does not exist +# # When I reload VSC +# # Then the python interpreter displayed in the the status bar contains the value "pipenv" in the display name +# # And the python interpreter displayed in the the status bar contains the value "workspace folder" in the display name +# # Given the file "write_pyPath_in_log.py" is open +# # And a file named "log.log" does not exist +# # And the workspace setting "python.terminal.activateEnvironment" is disabled +# # And a terminal is opened +# # When I send the command "python run_in_terminal.py" to the terminal +# # Then a file named "log.log" is created +# # And open the file "log.log" +# # And the file "log.log" does not contain the value "workspace_folder" +# # And take a screenshot + +# # @preserve.workspace +# # Scenario: Environment is activated in the Terminal +# # Given the workspace setting "python.pythonPath" does not exist +# # And the user setting "python.pythonPath" does not exist +# # When I reload VSC +# # Then the python interpreter displayed in the the status bar contains the value "pipenv" in the display name +# # And the python interpreter displayed in the the status bar contains the value "workspace folder" in the display name +# # Given the file "run_in_terminal.py" is open +# # And a file named "log.log" does not exist +# # And the workspace setting "python.terminal.activateEnvironment" is enabled +# # And a terminal is opened +# # When I send the command "python run_in_terminal.py" to the terminal +# # Then a file named "log.log" is created +# # And open the file "log.log" +# # And the file "log.log" contains the value "workspace_folder" +# # And take a screenshot diff --git a/uitests/features/interpreter/interpreters.feature b/uitests/features/interpreter/interpreters.feature new file mode 100644 index 000000000000..4dfceb90b778 --- /dev/null +++ b/uitests/features/interpreter/interpreters.feature @@ -0,0 +1,19 @@ +# Feature: Interpreters +# @WorkspaceFolder:/Users/donjayamanne/Desktop/Development/PythonStuff/smoke_tests/env_0-virtualenv +# Scenario: Validate selection of interpreter +# Given some random interpreter is selected +# When I select a python interpreter +# Then interpreter informantion in status bar has refreshed + +# @WorkspaceFolder:/Users/donjayamanne/Desktop/Development/PythonStuff/smoke_tests/env_0-virtualenv +# Scenario: Validate selection of interpreter when nothing was selected +# Given there is no python path in settings.json +# When I select a python interpreter +# Then interpreter informantion in status bar has refreshed + +# # @pipenv +# # Scenario: Auto select existing pipenv +# # Given the setting 'python.pythonPath' does not exist +# # When I reload vscode +# # Then settings.json will automatically be updated with pythonPath +# # Then the selected interpreter contains the name 'pipenv' diff --git a/uitests/features/interpreter/pipenv.feature b/uitests/features/interpreter/pipenv.feature new file mode 100644 index 000000000000..c725399abc62 --- /dev/null +++ b/uitests/features/interpreter/pipenv.feature @@ -0,0 +1,71 @@ +# @terminal @terminal.pipenv +# @https://github.com/DonJayamanne/vscode-python-uitests/terminal/execution +# Feature: Terminal (pipenv) +# Scenario: Interpreter display name contains the name of the current workspace folder and pipenv +# Given the user setting "python.pythonPath" does not exist +# And a pipenv environment is created +# When I reload VSC +# And I select the Python Interpreter containing the name "workspace folder pipenv" +# Then the python interpreter displayed in the the status bar contains the value "pipenv" in the display name +# And the python interpreter displayed in the the status bar contains the value "workspace folder" in the display name +# And the workspace setting "python.pythonPath" exists + +# @preserve.workspace +# Scenario: Pipenv is auto selected +# Given the workspace setting "python.pythonPath" does not exist +# And the user setting "python.pythonPath" does not exist +# When I reload VSC +# Then the python interpreter displayed in the the status bar contains the value "pipenv" in the display name +# And the python interpreter displayed in the the status bar contains the value "workspace folder" in the display name +# And the workspace setting "python.pythonPath" exists + +# @preserve.workspace +# Scenario: Pipenv is not auto selected (if we already have a local interpreter selected) +# Given a generic Python Interpreter is selected +# When I reload VSC +# Then the python interpreter displayed in the the status bar does not contain the value "pipenv" in the display name +# And the python interpreter displayed in the the status bar does not contain the value "workspace folder" in the display name +# And the workspace setting "python.pythonPath" exists + +# @preserve.workspace +# Scenario: Pipenv is not auto selected (if we have a global interpreter selected) +# Given the workspace setting "python.pythonPath" does not exist +# And the user setting "python.pythonPath" exists +# When I reload VSC +# Then open the file "settings.json" +# Then the python interpreter displayed in the the status bar does not contain the value "pipenv" in the display name +# And the python interpreter displayed in the the status bar does not contain the value "workspace folder" in the display name + +# @preserve.workspace +# Scenario: Environment is not activated in the Terminal +# Given the workspace setting "python.pythonPath" does not exist +# And the user setting "python.pythonPath" does not exist +# When I reload VSC +# Then the python interpreter displayed in the the status bar contains the value "pipenv" in the display name +# And the python interpreter displayed in the the status bar contains the value "workspace folder" in the display name +# Given the file "write_pyPath_in_log.py" is open +# And a file named "log.log" does not exist +# And the workspace setting "python.terminal.activateEnvironment" is disabled +# And a terminal is opened +# When I send the command "python run_in_terminal.py" to the terminal +# Then a file named "log.log" is created +# And open the file "log.log" +# And the file "log.log" does not contain the value "workspace_folder" +# And take a screenshot + +# @preserve.workspace +# Scenario: Environment is activated in the Terminal +# Given the workspace setting "python.pythonPath" does not exist +# And the user setting "python.pythonPath" does not exist +# When I reload VSC +# Then the python interpreter displayed in the the status bar contains the value "pipenv" in the display name +# And the python interpreter displayed in the the status bar contains the value "workspace folder" in the display name +# Given the file "run_in_terminal.py" is open +# And a file named "log.log" does not exist +# And the workspace setting "python.terminal.activateEnvironment" is enabled +# And a terminal is opened +# When I send the command "python run_in_terminal.py" to the terminal +# Then a file named "log.log" is created +# And open the file "log.log" +# And the file "log.log" contains the value "workspace_folder" +# And take a screenshot diff --git a/uitests/features/interpreter/statusbar.feature b/uitests/features/interpreter/statusbar.feature new file mode 100644 index 000000000000..4c3ce7054a4f --- /dev/null +++ b/uitests/features/interpreter/statusbar.feature @@ -0,0 +1,41 @@ +@terminal +Feature: Statusbar + @smoke + Scenario: Interpreter is displayed in the statusbar when a python file is opened + When I create a new file + And I change the language of the file to "Python" + And the Python extension has activated + Then the python the status bar contains the text "Python" + + @status + Scenario: Interpreter is displayed in the statusbar when the extension is activated + When the Python extension has activated + Then the python the status bar contains the text "Python" + And take a screenshot + + @python2 + Scenario: Can select a Python 2.7 interpreter and the statusbar will be updated accordingly + Given the Python extension has been activated + When I select the Python Interpreter containing the text "2.7" + Then the python the status bar contains the text "2.7" + And the python the status bar does not contain the text "3." + And take a screenshot + + @python3 + Scenario: Can select a Python 3. interpreter and the statusbar will be updated accordingly + Given the Python extension has been activated + When I select the Python Interpreter containing the text "3." + Then the python the status bar contains the text "3." + And the python the status bar does not contain the text "2.7" + And take a screenshot + + @python2 @python3 + Scenario: Can switch between 2.7 and 3.* interpreters and the statusbar will be updated accordingly + Given the Python extension has been activated + When I select the Python Interpreter containing the text "2.7" + Then the python the status bar contains the text "2.7" + And the python the status bar does not contain the text "3." + When I select the Python Interpreter containing the text "3." + Then the python the status bar contains the text "3." + And the python the status bar does not contain the text "2.7" + And take a screenshot diff --git a/uitests/features/interpreter/terminal.feature b/uitests/features/interpreter/terminal.feature new file mode 100644 index 000000000000..5c225259809c --- /dev/null +++ b/uitests/features/interpreter/terminal.feature @@ -0,0 +1,57 @@ +@terminal +Feature: Terminal + Background: Activted Extension + Then take a screenshot + Given the Python extension has been activated + Then take a screenshot + + @smoke + Scenario: Execute File in Terminal + # Use folders and paths with spaces. + Given a file named "run in terminal.py" is created with the following content + """ + open('log.log', 'w').write('Hello World') + """ + And a file named "log.log" does not exist + Then take a screenshot + When I open the file "run in terminal.py" + Then take a screenshot + Then wait for 1 second + When I select the command "Python: Run Python File in Terminal" + Then take a screenshot + # Wait for some time, as it could take a while for terminal to get activated. + # Slow on windows. + Then a file named "log.log" is created within 30 seconds + + Scenario: Execute File within a sub directory in Terminal + # Use folders and paths with spaces. + Given a file named "hello word/run in terminal.py" is created with the following content + """ + open('log.log', 'w').write('Hello World') + """ + And a file named "hello word/log.log" does not exist + When I open the file "run in terminal.py" + And I select the command "Python: Run Python File in Terminal" + # Wait for some time, as it could take a while for terminal to get activated. + # Slow on windows. + Then a file named "log.log" is created within 20 seconds + + Scenario: Execute Selection in Terminal + # Use folders and paths with spaces. + Given a file named "run in terminal.py" is created with the following content + """ + open('log1.log', 'w').write('Hello World') + open('log2.log', 'w').write('Hello World') + """ + And a file named "log1.log" does not exist + And a file named "log2.log" does not exist + When I open the file "run in terminal.py" + And I go to line 1 + And I select the command "Python: Run Selection/Line in Python Terminal" + Then a file named "log1.log" is created within 20 seconds + And take a screenshot + When I go to line 2 + And I select the command "Python: Run Selection/Line in Python Terminal" + # Wait for some time, as it could take a while for terminal to get activated. + # Slow on windows. + Then a file named "log2.log" is created within 20 seconds diff --git a/uitests/features/interpreter/venv.feature b/uitests/features/interpreter/venv.feature new file mode 100644 index 000000000000..85426262d8dc --- /dev/null +++ b/uitests/features/interpreter/venv.feature @@ -0,0 +1,63 @@ +# @terminal @terminal.venv @python3 +# @https://github.com/DonJayamanne/vscode-python-uitests/terminal/execution +# Feature: Terminal (venv) +# Scenario: Interpreter display name contains the name of the venv folder +# Given a venv with the name "venv 1" is created +# When In Mac, I update the workspace setting "python.pythonPath" with the value "venv 1/bin/python" +# When In Linux, I update the workspace setting "python.pythonPath" with the value "venv 1/bin/python" +# When In Windows, I update the workspace setting "python.pythonPath" with the value "venv 1/Scripts/python.exe" +# Then the python interpreter displayed in the the status bar contains the value "venv 1" in the display name + +# @preserve.workspace +# Scenario: Venv is auto selected +# Given the workspace setting "python.pythonPath" does not exist +# And the user setting "python.pythonPath" does not exist +# Then the python interpreter displayed in the the status bar does not contain the value "venv 1" in the display name +# When I reload VSC +# Then the python interpreter displayed in the the status bar contains the value "venv 1" in the display name + +# @preserve.workspace +# Scenario: Venv is not auto selected (if we already have a local interpreter selected) +# Given a generic Python Interpreter is selected +# And the user setting "python.pythonPath" does not exist +# Then the python interpreter displayed in the the status bar does not contain the value "venv 1" in the display name +# When I reload VSC +# Then the python interpreter displayed in the the status bar does not contain the value "venv 1" in the display name + +# @preserve.workspace +# Scenario: Venv is not auto selected (if we have a global interpreter selected) +# Given the workspace setting "python.pythonPath" does not exist +# And the user setting "python.pythonPath" exists +# Then the python interpreter displayed in the the status bar does not contain the value "venv 1" in the display name +# When I reload VSC +# Then the python interpreter displayed in the the status bar does not contain the value "venv 1" in the display name + +# @preserve.workspace +# Scenario: Environment is not activated in the Terminal +# When In Mac, I update the workspace setting "python.pythonPath" with the value "venv 1/bin/python" +# When In Linux, I update the workspace setting "python.pythonPath" with the value "venv 1/bin/python" +# When In Windows, I update the workspace setting "python.pythonPath" with the value "venv 1/Scripts/python.exe" +# Given the file "write_pyPath_in_log.py" is open +# And a file named "log.log" does not exist +# And the workspace setting "python.terminal.activateEnvironment" is disabled +# And a terminal is opened +# When I send the command "python write_pyPath_in_log.py" to the terminal +# Then a file named "log.log" is created +# And open the file "log.log" +# And the file "log.log" does not contain the value "env 1" +# And take a screenshot + +# @preserve.workspace +# Scenario: Environment is activated in the Terminal +# When In Mac, I update the workspace setting "python.pythonPath" with the value "venv 1/bin/python" +# When In Linux, I update the workspace setting "python.pythonPath" with the value "venv 1/bin/python" +# When In Windows, I update the workspace setting "python.pythonPath" with the value "venv 1/Scripts/python.exe" +# Given the file "write_pyPath_in_log.py" is open +# And a file named "log.log" does not exist +# And the workspace setting "python.terminal.activateEnvironment" is enabled +# And a terminal is opened +# When I send the command "python write_pyPath_in_log.py" to the terminal +# Then a file named "log.log" is created +# And open the file "log.log" +# And the file "log.log" contains the value "env 1" +# And take a screenshot diff --git a/uitests/features/languageServer/basic.feature b/uitests/features/languageServer/basic.feature new file mode 100644 index 000000000000..4cb78f02ae22 --- /dev/null +++ b/uitests/features/languageServer/basic.feature @@ -0,0 +1,69 @@ +@ls +@https://github.com/DonJayamanne/pvscSmokeLS.git +Feature: Language Server + Scenario Outline: Check output of 'Python' output panel when starting VS Code with Jedi d + When I the workspace setting "python.jediEnabled" + And I wait for the Python extension to activate + And I select the command "Python: Show Output" + Then the text "" will be displayed in the output panel within seconds + + Examples: + | jedi_enable | time_to_activate | first_text_in_ooutput_panel | + | enable | 10 | Jedi Python language engine | + | disable | 120 | Microsoft Python language server | + + Scenario Outline: Language Server is downloaded with http.proxyStrictSSL setting + When I open VS Code for the first time + And I disable the workspace setting "python.jediEnabled" + And the user setting "http.proxyStrictSSL" is + And I wait for the Python extension to activate + And I select the command "Python: Show Output" + Then the text "Microsoft Python language server" will be displayed in the output panel within 120 seconds + When I select the command "Python: Show Language Server Output" + Then the text "" will be displayed in the output panel within 120 seconds + Then the text "Initializing for" will be displayed in the output panel within 120 seconds + + Examples: + | enabled_disabled | protocol_to_look_for | + | enabled | https:// | + | disabled | http:// | + + @autoretry @smoke + Scenario Outline: Navigate to definition of a variable when extension has already been activated with Jedi d + When I reload VS Code + And I the workspace setting "python.jediEnabled" + And I wait for the Python extension to activate + And I select the command "Python: Show Output" + Then the text "" will be displayed in the output panel within seconds + # Because LS is slow. + And wait for seconds + When I open the file "my_sample.py" + And I go to line 3, column 10 + # Wait for intellisense to kick in (sometimes slow in jedi & ls) + And I wait for 10 seconds + When I select the command "Go to Definition" + Then the cursor is on line 1 + + Examples: + | jedi_enable | time_to_activate | first_text_in_ooutput_panel | + | enable | 10 | Jedi Python language engine | + | disable | 120 | Microsoft Python language server | + +# @autoretry @wip +# Scenario Outline: Navigate to definition of a variable after opening a file with Jedi +# Given the workspace setting "python.jediEnabled" is +# When I open the file "my_sample.py" +# And I wait for the Python extension to activate +# And I select the command "Python: Show Output" +# Then the text "" will be displayed in the output panel within seconds +# And the text "" will be displayed in the output panel within seconds +# When I go to line 3, column 10 +# # Wait for intellisense to kick in (sometimes slow in jedi & ls) +# And I wait for 10 seconds +# And I select the command "Go to Definition" +# Then the cursor is on line 1 + +# Examples: +# | jedi_enabled | time_to_activate | first_text_in_ooutput_panel | second_text_in_output_panel | +# | enabled | 10 | Jedi Python language engine | Jedi Python language engine | +# | disabled | 120 | Microsoft Python language server | Initializing for | diff --git a/uitests/features/languageServer/goToDefinition.feature b/uitests/features/languageServer/goToDefinition.feature new file mode 100644 index 000000000000..50f17f7c71f7 --- /dev/null +++ b/uitests/features/languageServer/goToDefinition.feature @@ -0,0 +1,63 @@ +@ls +@https://github.com/DonJayamanne/pvscSmokeLS.git +Feature: Language Server + Scenario Outline: When with Jedi d then output contains + When + And I the workspace setting "python.jediEnabled" + And I wait for the Python extension to activate + And I select the command "" + Then the text "" will be displayed in the output panel within seconds + + Examples: + | jedi_enable | reload_or_start_vs_for_first_time | time_to_activate | text_in_output_panel | output_panel_command | + | enable | I open VS Code for the first time | 5 | Jedi Python language engine | Python: Show Output | + | enable | I reload VS Code | 5 | Jedi Python language engine | Python: Show Output | + | disable | I open VS Code for the first time | 120 | Microsoft Python language server | Python: Show Output | + | disable | I open VS Code for the first time | 120 | Downloading | Python: Show Language Server Output | + | disable | I reload VS Code | 120 | Microsoft Python language server | Python: Show Output | + + @autoretry + Scenario Outline: When with Jedi d then navigate to definition of a variable + When + And I the workspace setting "python.jediEnabled" + And I wait for the Python extension to activate + And I select the command "" + Then the text "" will be displayed in the output panel within seconds + # Because LS is slow. + And wait for seconds + When I open the file "my_sample.py" + And I go to line 3, column 10 + # Wait for intellisense to kick in (sometimes slow in jedi & ls) + And I wait for 5 seconds + And I select the command "Go to Definition" + Then the cursor is on line 1 + + Examples: + | jedi_enable | reload_or_start_vs_for_first_time | time_to_activate | text_in_output_panel | output_panel_command | + | enable | I open VS Code for the first time | 5 | Jedi Python language engine | Python: Show Output | + | enable | I reload VS Code | 5 | Jedi Python language engine | Python: Show Output | + | disable | I open VS Code for the first time | 120 | Microsoft Python language server | Python: Show Output | + | disable | I open VS Code for the first time | 120 | Downloading | Python: Show Language Server Output | + | disable | I reload VS Code | 120 | Microsoft Python language server | Python: Show Output | + + @autoretry + Scenario Outline: When I open VS Code for the first time with Jedi d, open a file then navigate to definition of a variable + When I open VS Code for the first time + And I the workspace setting "python.jediEnabled" + And I select the command "Python: Show Output" + And I wait for the Python extension to activate + And I open the file "my_sample.py" + Then the text "" will be displayed in the output panel within seconds + # Because LS is slow. + And wait for seconds + When I go to line 3, column 10 + # Wait for intellisense to kick in (sometimes slow in jedi & ls) + And I wait for 5 seconds + And I select the command "Go to Definition" + Then the cursor is on line 1 + + Examples: + | jedi_enable | time_to_activate | text_in_output_panel | output_panel_command | + | enable | 5 | Jedi Python language engine | Python: Show Output | + | disable | 120 | Microsoft Python language server | Python: Show Output | + | disable | 120 | Downloading | Python: Show Language Server Output | diff --git a/uitests/features/languageServer/intellisense.feature b/uitests/features/languageServer/intellisense.feature new file mode 100644 index 000000000000..cf709cc9ce2f --- /dev/null +++ b/uitests/features/languageServer/intellisense.feature @@ -0,0 +1,79 @@ +@ls +@https://github.com/DonJayamanne/pvscSmokeLS.git +Feature: Language Server + @autoretry + Scenario Outline: When with Jedi d then intellisense works + When + And I the workspace setting "python.jediEnabled" + And I wait for the Python extension to activate + And I select the command "Python: Show Output" + Then the text "" will be displayed in the output panel within seconds + And wait for seconds + # Get more realestate on UI (hide what we don't need). + And select the command "View: Close Panel" + When I open the file "intelli_sample.py" + # Wait for intellisense to kick in (sometimes slow in jedi & ls) + And I wait for seconds + And I go to line 3, column 13 + And I press ctrl+space + Then auto completion list contains the item "excepthook" + And auto completion list contains the item "exec_prefix" + And auto completion list contains the item "executable" + When I go to line 11, column 21 + And I press ctrl+space + Then auto completion list contains the item "age" + When I go to line 12, column 21 + And I press ctrl+space + Then auto completion list contains the item "name" + When I go to line 17, column 10 + And I press ctrl+space + Then auto completion list contains the item "say_something" + When I go to line 18, column 10 + And I press ctrl+space + Then auto completion list contains the item "age" + When I go to line 19, column 10 + And I press ctrl+space + Then auto completion list contains the item "name" + When I go to line 17, column 24 + And I press . + Then auto completion list contains the item "capitalize" + And auto completion list contains the item "count" + + Examples: + | jedi_enable | reload_or_start_vs_for_first_time | time_to_activate | first_text_in_ooutput_panel | wait_time | + | enable | I open VS Code for the first time | 5 | Jedi Python language engine | 5 | + | enable | I reload VS Code | 5 | Jedi Python language engine | 5 | + | disable | I open VS Code for the first time | 120 | Microsoft Python language server | 5 | + | disable | I reload VS Code | 120 | Microsoft Python language server | 5 | + + @autoretry + Scenario Outline: When with Jedi d then intellisense works for untitled files + When + And I the workspace setting "python.jediEnabled" + And I wait for the Python extension to activate + And I select the command "Python: Show Output" + Then the text "" will be displayed in the output panel within seconds + And wait for seconds + # Get more realestate on UI (hide what we don't need). + And select the command "View: Close Panel" + When I create a new file with the following content + """ + import sys + + print(sys.executable) + """ + And I change the language of the file to "Python" + # Wait for intellisense to kick in (sometimes slow in jedi & ls) + And I wait for seconds + And I go to line 3, column 13 + And I press ctrl+space + Then auto completion list contains the item "excepthook" + And auto completion list contains the item "exec_prefix" + And auto completion list contains the item "executable" + + Examples: + | jedi_enable | reload_or_start_vs_for_first_time | time_to_activate | first_text_in_ooutput_panel | wait_time | + | enable | I open VS Code for the first time | 5 | Jedi Python language engine | 5 | + | enable | I reload VS Code | 5 | Jedi Python language engine | 5 | + | disable | I open VS Code for the first time | 120 | Microsoft Python language server | 5 | + | disable | I reload VS Code | 120 | Microsoft Python language server | 5 | diff --git a/uitests/features/languageServer/unresolvedImport.feature b/uitests/features/languageServer/unresolvedImport.feature new file mode 100644 index 000000000000..eb331d5d201f --- /dev/null +++ b/uitests/features/languageServer/unresolvedImport.feature @@ -0,0 +1,47 @@ +@ls +Feature: Language Server + Background: Unresolved imports + Given a file named "sample.py" is created with the following content + """ + import requests + """ + Given the workspace setting "python.jediEnabled" is disabled + Given the package "requests" is not installed + When I reload VS Code + And I open the file "sample.py" + And I wait for the Python extension to activate + And I select the command "Python: Show Output" + Then the text "Microsoft Python language server" will be displayed in the output panel within 120 seconds + And wait for 120 seconds + When I select the command "View: Focus Problems (Errors, Warnings, Infos)" + Then there is at least one problem in the problems panel + And there is a problem with the file named "sample.py" + And there is a problem with the message "unresolved import 'requests'" + + Scenario: Display problem about unresolved imports + """ + Just execute the background and ensure problems are displayed. + """ + Then do nothing + + Scenario: There should be no problem related to unresolved imports when reloading VSC + When I install the package "requests" + When I reload VS Code + # Wait for some time for LS to detect this. + # And I wait for 5 seconds + And I open the file "sample.py" + And I wait for the Python extension to activate + And I select the command "Python: Show Output" + Then the text "Microsoft Python language server" will be displayed in the output panel within 120 seconds + And wait for 120 seconds + When I select the command "View: Focus Problems (Errors, Warnings, Infos)" + # Ensure we are not too eager, possible LS hasn't analyzed yet. + And I wait for 10 seconds + Then there are no problems in the problems panel + + @skip + Scenario: Unresolved import message should go away when package is installed + When I install the package "requests" + # Wait for some time for LS to detect this new package. + And I wait for 10 seconds + Then there are no problems in the problems panel diff --git a/uitests/features/linting/problems.feature b/uitests/features/linting/problems.feature new file mode 100644 index 000000000000..70bb7f4828ff --- /dev/null +++ b/uitests/features/linting/problems.feature @@ -0,0 +1,48 @@ +# Feature: Linters +# We will need to reload LS, its slow at picking missing modules when they are installed/uninstalled. +# @ls @pylint @linter @WorkspaceFolder:/Users/donjayamanne/Desktop/Development/PythonStuff/smoke_tests/env_0-virtualenv +# Scenario: Language server displays warnings +# Given the module "pylint" is not installed +# Given the module "requests" is not installed +# Given the setting "python.linting.enabled" is not enabled +# Given the setting "python.linting.pylintEnabled" is not enabled +# Given the file "pylint errors.py" is open +# Given the problems panel is open +# Then wait for 1 second +# Then there is 1 warning in the problems panel +# Then there is a warning with the message "unresolved import 'requests'" in the problems panel + +# @ls @pylint @linter @WorkspaceFolder:/Users/donjayamanne/Desktop/Development/PythonStuff/smoke_tests/env_0-virtualenv +# Scenario: Pylint displays problems +# Given the module "pylint" is installed +# Given the module "requests" is not installed +# Given the setting "python.linting.enabled" is enabled +# Given the setting "python.linting.pylintEnabled" is enabled +# Given the file "pylint errors.py" is open +# Given the problems panel is open +# # Then wait for 1 second +# Then there are 2 errors in the problems panel +# Then log message "taking screenshot" +# Then take a screenshot +# # Then log message "done taking screenshot" +# # Then there is 1 warning in the problems panel +# Then there is an error with the message "Unable to import 'requests'" in the problems panel +# # Then there is an error with the message "Unable to import 'numpy'" in the problems panel +# # Then there is a warning with the message "unresolved import 'requests'" in the problems panel +# Then take a screenshot + +# # @ls @pylint @linter @WorkspaceFolder:/Users/donjayamanne/Desktop/Development/PythonStuff/smoke_tests/env_0-virtualenv +# # Scenario: Pylint + LS problems vanish upon installing module +# # Given the module "pylint" is installed +# # Given the module "requests" is not installed +# # Given the file "pylint errors.py" is open +# # Given the setting "python.linting.enabled" is enabled +# # Given the setting "python.linting.pylintEnabled" is enabled +# # Given the problems panel is open +# # Then there are 2 errors in the problems panel +# # Then take a screenshot +# # When I close all editors +# # When I install the module "requests" +# # When I open the file "pylint errors.py" +# # Then there are 1 errors in the problems panel +# # Then there is an error with the message "Unable to import 'numpy'" in the problems panel diff --git a/uitests/features/terminal/terminal.feature b/uitests/features/terminal/terminal.feature new file mode 100644 index 000000000000..e6a9bb7bfcb4 --- /dev/null +++ b/uitests/features/terminal/terminal.feature @@ -0,0 +1,51 @@ +# Feature: Terminal +# @terminal @debug @WorkspaceFolder:/Users/donjayamanne/Desktop/Development/PythonStuff/smoke_tests/env_0-virtualenv +# Scenario: Activation of environment in terminal +# Given "python.terminal.activateEnvironment:true" in settings.json +# Then environment will auto-activate in the terminal + +# @terminal @debug @WorkspaceFolder:/Users/donjayamanne/Desktop/Development/PythonStuff/smoke_tests/env_0-virtualenv +# Scenario: Non-activation of environment in terminal +# Given "python.terminal.activateEnvironment:false" in settings.json +# Then environment will not auto-activate in the terminal + +# @terminal @debug @WorkspaceFolder:/Users/donjayamanne/Desktop/Development/PythonStuff/smoke_tests/env_0-virtualenv +# Scenario: Python file will run in activated terminal +# Given "python.terminal.activateEnvironment:true" in settings.json +# Then a python file run in the terminal will run in the activated environment + +# @terminal @debug @WorkspaceFolder:/Users/donjayamanne/Desktop/Development/PythonStuff/smoke_tests/env_0-virtualenv +# Scenario: Sending lines from editor to an auto activted terminal +# Given "python.terminal.activateEnvironment:true" in settings.json +# Given the file "runSelection.py" is open +# Then log message "23241324" + +# When I set cursor to line 1 of file "runSelection.py" +# When I select the command "Python: Run Selection/Line in Python Terminal" +# Then the text "Hello World!" will be displayed in the terminal +# Then the text "And hello again!" will not be displayed in the terminal +# When I press "down" +# When I select the command "Python: Run Selection/Line in Python Terminal" +# Then the text "And hello again!" will be displayed in the terminal + +# @terminal @debug @WorkspaceFolder:/Users/donjayamanne/Desktop/Development/PythonStuff/smoke_tests/env_0-virtualenv +# Scenario: Sending lines from editor to terminal +# Given "python.terminal.activateEnvironment:false" in settings.json +# Given the file "runSelection.py" is open +# When I set cursor to line 1 of file "runSelection.py" +# When I select the command "Python: Run Selection/Line in Python Terminal" +# Then the text "Hello World!" will be displayed in the terminal +# Then the text "And hello again!" will not be displayed in the terminal +# When I press "down" +# When I select the command "Python: Run Selection/Line in Python Terminal" +# Then the text "And hello again!" will be displayed in the terminal + +# @terminal @debug @WorkspaceFolder:/Users/donjayamanne/Desktop/Development/PythonStuff/smoke_tests/env_0-virtualenv +# Scenario: Sending multiple lines from editor to terminal +# Given "python.terminal.activateEnvironment:false" in settings.json +# Given the file "runSelection.py" is open +# When I set cursor to line 1 of file "runSelection.py" +# When I press "shift+down" +# When I press "shift+down" +# When I select the command "Python: Run Selection/Line in Python Terminal" +# Then the text "Hello World!" and "And hello again!" will be displayed in the terminal diff --git a/uitests/features/testing/discover.feature b/uitests/features/testing/discover.feature new file mode 100644 index 000000000000..b007e9f71f7e --- /dev/null +++ b/uitests/features/testing/discover.feature @@ -0,0 +1,38 @@ +@test +Feature: Testing + Scenario: Discover will display prompt to configure when not configured + Given the file ".vscode/settings.json" does not exist + When the Python extension has activated + And I select the command "Python: Discover Tests" + Then a message containing the text "No test framework configured" is displayed + + Scenario Outline: Discover will prompt to install + Given the package "" is not installed + And the workspace setting "python.testing." is enabled + When the Python extension has activated + And I select the command "Python: Discover Tests" + Then a message containing the text "" is displayed + + Examples: + | package | setting_to_enable | message | + | pytest | pytestEnabled | pytest is not installed | + | nose | nosetestsEnabled | nosetest is not installed | + + Scenario Outline: Discover will display prompt indicating there are no tests () + Given a file named ".vscode/settings.json" is created with the following content + """ + { + "python.testing.": , + "python.testing.": true + } + """ + And the package "" is installed + When the Python extension has activated + And I select the command "Python: Discover Tests" + Then a message containing the text "No tests discovered" is displayed + + Examples: + | package | setting_to_enable | args_setting | args | + | unittest | unittestEnabled | unittestArgs | ["-v","-s",".","-p","*test*.py"] | + | pytest | pytestEnabled | pytestArgs | ["."] | + | nose | nosetestsEnabled | nosetestArgs | ["."] | diff --git a/uitests/features/testing/explorer/basic.feature b/uitests/features/testing/explorer/basic.feature new file mode 100644 index 000000000000..8a6540a5393e --- /dev/null +++ b/uitests/features/testing/explorer/basic.feature @@ -0,0 +1,82 @@ +@testing +@https://github.com/DonJayamanne/pyvscSmokeTesting/testing +Feature: Test Explorer + Background: Activted Extension + Given a file named ".vscode/settings.json" is created with the following content + """ + { + "python.testing.unittestArgs": [ + "-v", + "-s", + "./tests", + "-p", + "test_*.py" + ], + "python.testing.unittestEnabled": false, + "python.testing.pytestArgs": [ + "." + ], + "python.testing.pytestEnabled": false, + "python.testing.nosetestArgs": [ + "." + ], + "python.testing.nosetestsEnabled": false + } + """ + Given the Python extension has been activated + + Scenario Outline: Explorer icon will be displayed when tests are discovered () + Given the package "" is installed + And the workspace setting "python.testing." is enabled + When I select the command "Python: Discover Tests" + And I wait for test discovery to complete + Then the test explorer icon will be visible + + Examples: + | package | setting_to_enable | + | unittest | unittestEnabled | + | pytest | pytestEnabled | + | nose | nosetestsEnabled | + + Scenario Outline: All expected items (nodes) are displayed in the test explorer () + Given the package "" is installed + And the workspace setting "python.testing." is enabled + When I select the command "Python: Discover Tests" + And I wait for test discovery to complete + Then the test explorer icon will be visible + When I select the command "View: Show Test" + And I expand all of the nodes in the test explorer + Then there are nodes in the test explorer + + Examples: + | package | setting_to_enable | node_count | + | unittest | unittestEnabled | 14 | + | pytest | pytestEnabled | 15 | + | nose | nosetestsEnabled | 14 | + + Scenario Outline: When discovering tests, the nodes will have the progress icon and clicking stop will stop discovery () + Given the package "" is installed + And the workspace setting "python.testing." is enabled + When I select the command "Python: Discover Tests" + And I wait for test discovery to complete + Then the test explorer icon will be visible + When I select the command "View: Show Test" + And I expand all of the nodes in the test explorer + Then there are nodes in the test explorer + # Now, add a delay for the discovery of the tests + # This way, we have enough time to test visibility of UI elements & the like. + Given a file named "tests/test_discovery_delay" is created with the following content + """ + 10 + """ + When I select the command "Python: Discover Tests" + Then all of the test tree nodes have a progress icon + And the stop icon is visible in the toolbar + When I stop discovering tests + Then the stop icon is not visible in the toolbar + + Examples: + | package | setting_to_enable | node_count | + | unittest | unittestEnabled | 14 | + | pytest | pytestEnabled | 15 | + | nose | nosetestsEnabled | 14 | diff --git a/uitests/features/testing/explorer/code.navigation.feature b/uitests/features/testing/explorer/code.navigation.feature new file mode 100644 index 000000000000..d4f9face2f00 --- /dev/null +++ b/uitests/features/testing/explorer/code.navigation.feature @@ -0,0 +1,79 @@ +@testing +@https://github.com/DonJayamanne/pyvscSmokeTesting/testing +Feature: Test Explorer (code nav) + Background: Activted Extension + Given a file named ".vscode/settings.json" is created with the following content + """ + { + "python.testing.unittestArgs": [ + "-v", + "-s", + "./tests", + "-p", + "test_*.py" + ], + "python.testing.unittestEnabled": false, + "python.testing.pytestArgs": [ + "." + ], + "python.testing.pytestEnabled": false, + "python.testing.nosetestArgs": [ + "." + ], + "python.testing.nosetestsEnabled": false + } + """ + Given the Python extension has been activated + + Scenario Outline: When navigating to a test file, suite & test, then open the file and set the cursor at the right line () + Given the package "" is installed + And the workspace setting "python.testing." is enabled + When I select the command "Python: Discover Tests" + And I wait for test discovery to complete + Then the test explorer icon will be visible + When I select the command "View: Show Test" + And I expand all of the nodes in the test explorer + And I navigate to the code associated with the test node "" + Then the file "" is opened + And + + Examples: + | package | setting_to_enable | node_label | file | optionally_check_line | + | unittest | unittestEnabled | test_one.py | test_one.py | do nothing | + | unittest | unittestEnabled | test_one_first_suite | test_one.py | the cursor is on line 20 | + | unittest | unittestEnabled | test_three_first_suite | test_one.py | the cursor is on line 30 | + | unittest | unittestEnabled | test_two_first_suite | test_one.py | the cursor is on line 25 | + | pytest | pytestEnabled | test_one.py | test_one.py | do nothing | + | pytest | pytestEnabled | test_one_first_suite | test_one.py | the cursor is on line 20 | + | pytest | pytestEnabled | test_three_first_suite | test_one.py | the cursor is on line 30 | + | pytest | pytestEnabled | test_two_first_suite | test_one.py | the cursor is on line 25 | + | nose | nosetestsEnabled | tests/test_one.py | test_one.py | do nothing | + | nose | nosetestsEnabled | test_one_first_suite | test_one.py | the cursor is on line 20 | + | nose | nosetestsEnabled | test_three_first_suite | test_one.py | the cursor is on line 30 | + | nose | nosetestsEnabled | test_two_first_suite | test_one.py | the cursor is on line 25 | + + Scenario Outline: When selecting a node, then open the file () + Given the package "" is installed + And the workspace setting "python.testing." is enabled + When I select the command "Python: Discover Tests" + And I wait for test discovery to complete + Then the test explorer icon will be visible + When I select the command "View: Show Test" + And I expand all of the nodes in the test explorer + When I click the test node with the label "" + Then the file "" is opened + + Examples: + | package | setting_to_enable | node_label | file | + | unittest | unittestEnabled | TestFirstSuite | test_one.py | + | unittest | unittestEnabled | test_one_first_suite | test_one.py | + | unittest | unittestEnabled | test_three_first_suite | test_one.py | + | unittest | unittestEnabled | test_two_third_suite | test_two.py | + | pytest | pytestEnabled | TestFirstSuite | test_one.py | + | pytest | pytestEnabled | test_one_first_suite | test_one.py | + | pytest | pytestEnabled | test_three_first_suite | test_one.py | + | pytest | pytestEnabled | test_two_third_suite | test_two.py | + | nose | nosetestsEnabled | TestFirstSuite | test_one.py | + | nose | nosetestsEnabled | test_one_first_suite | test_one.py | + | nose | nosetestsEnabled | test_three_first_suite | test_one.py | + | nose | nosetestsEnabled | test_two_third_suite | test_two.py | diff --git a/uitests/features/testing/explorer/debug.feature b/uitests/features/testing/explorer/debug.feature new file mode 100644 index 000000000000..7a88d218560a --- /dev/null +++ b/uitests/features/testing/explorer/debug.feature @@ -0,0 +1,133 @@ +@testing +@https://github.com/DonJayamanne/pyvscSmokeTesting/testing +Feature: Test Explorer (debugging) + Background: Activted Extension + Given a file named ".vscode/settings.json" is created with the following content + """ + { + "python.testing.unittestArgs": [ + "-v", + "-s", + "./tests", + "-p", + "test_*.py" + ], + "python.testing.unittestEnabled": false, + "python.testing.pytestArgs": ["."], + "python.testing.pytestEnabled": false, + "python.testing.nosetestArgs": ["."], + "python.testing.nosetestsEnabled": false + } + """ + Given the Python extension has been activated + + Scenario Outline: When debugging tests, the nodes will have the progress icon and clicking stop will stop the debugger () + Given the package "" is installed + And the workspace setting "python.testing." is enabled + When I select the command "Python: Discover Tests" + And I wait for test discovery to complete + Then the test explorer icon will be visible + When I select the command "View: Show Test" + And I expand all of the nodes in the test explorer + # The number entered in this file will be used in a `time.sleep(?)` statement. + # Resulting in delays in running the tests (delay is in the python code in the above repo). + Given a file named "tests/test_running_delay" is created with the following content + """ + 30 + """ + Then there are nodes in the test explorer + And nodes in the test explorer have a status of "Unknown" + When I debug the node "test_three_first_suite" from the test explorer + Then the debugger starts + When I select the command "Debug: Stop" + Then the debugger stops + + Examples: + | package | setting_to_enable | node_count | + | unittest | unittestEnabled | 14 | + | pytest | pytestEnabled | 15 | + | nose | nosetestsEnabled | 14 | + + Scenario Outline: When debugging tests, only the specific function will be debugged () + Given the package "" is installed + And the workspace setting "python.testing." is enabled + When I select the command "Python: Discover Tests" + And I wait for test discovery to complete + Then the test explorer icon will be visible + When I select the command "View: Show Test" + And I expand all of the nodes in the test explorer + When I add a breakpoint to line 33 in "test_one.py" + And I add a breakpoint to line 23 in "test_one.py" + And I debug the node "test_three_first_suite" from the test explorer + Then the debugger starts + And the debugger pauses + And the current stack frame is at line 33 in "test_one.py" + When I select the command "Debug: Continue" + Then the debugger stops + + Examples: + | package | setting_to_enable | + | unittest | unittestEnabled | + | pytest | pytestEnabled | + | nose | nosetestsEnabled | + + Scenario Outline: When debugging tests, only the specific suite will be debugged () + Given the package "" is installed + And the workspace setting "python.testing." is enabled + When I select the command "Python: Discover Tests" + And I wait for test discovery to complete + Then the test explorer icon will be visible + When I select the command "View: Show Test" + And I expand all of the nodes in the test explorer + When I add a breakpoint to line 33 in "test_one.py" + And I add a breakpoint to line 28 in "test_one.py" + And I add a breakpoint to line 23 in "test_one.py" + And I debug the node "TestFirstSuite" from the test explorer + Then the debugger starts + And the debugger pauses + And the current stack frame is at line 23 in "test_one.py" + When I select the command "Debug: Continue" + Then the debugger pauses + And the current stack frame is at line 33 in "test_one.py" + When I select the command "Debug: Continue" + Then the debugger pauses + And the current stack frame is at line 28 in "test_one.py" + When I select the command "Debug: Continue" + Then the debugger stops + + Examples: + | package | setting_to_enable | + | unittest | unittestEnabled | + | pytest | pytestEnabled | + | nose | nosetestsEnabled | + + + Scenario Outline: When debugging tests, everything will be debugged () + Given the package "" is installed + And the workspace setting "python.testing." is enabled + When I select the command "Python: Discover Tests" + And I wait for test discovery to complete + Then the test explorer icon will be visible + When I select the command "View: Show Test" + And I expand all of the nodes in the test explorer + When I add a breakpoint to line 23 in "test_one.py" + And I add a breakpoint to line 38 in "test_one.py" + And I add a breakpoint to line 23 in "test_two.py" + And I select the command "Python: Debug All Tests" + Then the debugger starts + And the debugger pauses + And the current stack frame is at line 23 in "test_one.py" + When I select the command "Debug: Continue" + Then the debugger pauses + And the current stack frame is at line 38 in "test_one.py" + When I select the command "Debug: Continue" + Then the debugger pauses + And the current stack frame is at line 23 in "test_two.py" + When I select the command "Debug: Continue" + Then the debugger stops + + Examples: + | package | setting_to_enable | + | unittest | unittestEnabled | + | pytest | pytestEnabled | + | nose | nosetestsEnabled | diff --git a/uitests/features/testing/explorer/run.failed.feature b/uitests/features/testing/explorer/run.failed.feature new file mode 100644 index 000000000000..f7c97a58a06d --- /dev/null +++ b/uitests/features/testing/explorer/run.failed.feature @@ -0,0 +1,133 @@ +@testing +@https://github.com/DonJayamanne/pyvscSmokeTesting/testing +Feature: Test Explorer - Re-run Failed Tests + Background: Activted Extension + Given a file named ".vscode/settings.json" is created with the following content + """ + { + "python.testing.unittestArgs": [ + "-v", + "-s", + "./tests", + "-p", + "test_*.py" + ], + "python.testing.unittestEnabled": false, + "python.testing.pytestArgs": ["."], + "python.testing.pytestEnabled": false, + "python.testing.nosetestArgs": ["."], + "python.testing.nosetestsEnabled": false + } + """ + Given the Python extension has been activated + + Scenario Outline: We are able to re-run a failed tests () + Given the package "" is installed + And the workspace setting "python.testing." is enabled + When I select the command "Python: Discover Tests" + And I wait for test discovery to complete + Then the test explorer icon will be visible + When I select the command "View: Show Test" + And I expand all of the nodes in the test explorer + Then there are nodes in the test explorer + And nodes in the test explorer have a status of "Unknown" + Given a file named "tests/test_running_delay" is created with the following content + """ + 0 + """ + And a file named "tests/data.json" is created with the following content + """ + [1,-1,-1,4,5,6] + """ + When I select the command "Python: Run All Tests" + And I wait for tests to complete running + Then the node "" in the test explorer has a status of "Fail" + And the node "TestFirstSuite" in the test explorer has a status of "Fail" + And the node "test_three_first_suite" in the test explorer has a status of "Fail" + And the node "test_two_first_suite" in the test explorer has a status of "Fail" + And the node "" in the test explorer has a status of "Fail" + And the node "TestThirdSuite" in the test explorer has a status of "Fail" + And the node "test_three_third_suite" in the test explorer has a status of "Fail" + And the node "test_two_third_suite" in the test explorer has a status of "Fail" + And 6 nodes in the test explorer have a status of "Success" + And the run failed tests icon is visible in the toolbar + Given a file named "tests/test_running_delay" is created with the following content + """ + 1 + """ + And a file named "tests/data.json" is created with the following content + """ + [1,2,3,4,5,6] + """ + When I run failed tests + And I wait for tests to complete running + And I expand all of the nodes in the test explorer + Then nodes in the test explorer have a status of "Success" + + Examples: + | package | setting_to_enable | node_count | test_one_file_label | test_two_file_label | + | unittest | unittestEnabled | 14 | test_one.py | test_two.py | + | pytest | pytestEnabled | 15 | test_one.py | test_two.py | + | nose | nosetestsEnabled | 14 | tests/test_one.py | tests/test_two.py | + + Scenario Outline: We are able to stop tests after re-running failed tests () + Given the package "" is installed + And the workspace setting "python.testing." is enabled + When I select the command "Python: Discover Tests" + And I wait for test discovery to complete + Then the test explorer icon will be visible + When I select the command "View: Show Test" + And I expand all of the nodes in the test explorer + Then there are nodes in the test explorer + And nodes in the test explorer have a status of "Unknown" + Given a file named "tests/test_running_delay" is created with the following content + """ + 0 + """ + And a file named "tests/data.json" is created with the following content + """ + [1,-1,-1,4,5,6] + """ + When I select the command "Python: Run All Tests" + And I wait for tests to complete running + Then the node "" in the test explorer has a status of "Fail" + And the node "TestFirstSuite" in the test explorer has a status of "Fail" + And the node "test_three_first_suite" in the test explorer has a status of "Fail" + And the node "test_two_first_suite" in the test explorer has a status of "Fail" + And the node "" in the test explorer has a status of "Fail" + And the node "TestThirdSuite" in the test explorer has a status of "Fail" + And the node "test_three_third_suite" in the test explorer has a status of "Fail" + And the node "test_two_third_suite" in the test explorer has a status of "Fail" + And 6 nodes in the test explorer have a status of "Success" + And the run failed tests icon is visible in the toolbar + Given a file named "tests/test_running_delay" is created with the following content + """ + 100 + """ + And a file named "tests/data.json" is created with the following content + """ + [1,2,3,4,5,6] + """ + When I run failed tests + Then the stop icon is visible in the toolbar + And I expand all of the nodes in the test explorer + Then the node "TestFirstSuite" in the test explorer has a status of "Progress" + And the node "test_three_first_suite" in the test explorer has a status of "Progress" + And the node "test_two_first_suite" in the test explorer has a status of "Progress" + And the node "TestThirdSuite" in the test explorer has a status of "Progress" + And the node "test_three_third_suite" in the test explorer has a status of "Progress" + And the node "test_two_third_suite" in the test explorer has a status of "Progress" + And nodes in the test explorer have a status of "Progress" + When I stop running tests + And I wait for tests to complete running + Then the stop icon is not visible in the toolbar + And the node "test_three_first_suite" in the test explorer has a status of "Unknown" + And the node "test_two_first_suite" in the test explorer has a status of "Unknown" + And the node "test_three_third_suite" in the test explorer has a status of "Unknown" + And the node "test_two_third_suite" in the test explorer has a status of "Unknown" + + Examples: + | package | setting_to_enable | node_count | failed_node_count | test_one_file_label | test_two_file_label | + | unittest | unittestEnabled | 14 | 6 | test_one.py | test_two.py | + | pytest | pytestEnabled | 15 | 6 | test_one.py | test_two.py | + | nose | nosetestsEnabled | 14 | 6 | tests/test_one.py | tests/test_two.py | diff --git a/uitests/features/testing/explorer/run.progress.feature b/uitests/features/testing/explorer/run.progress.feature new file mode 100644 index 000000000000..4033b9038ab1 --- /dev/null +++ b/uitests/features/testing/explorer/run.progress.feature @@ -0,0 +1,46 @@ +@testing +@https://github.com/DonJayamanne/pyvscSmokeTesting/testing +Feature: Test Explorer + Background: Activted Extension + Given a file named ".vscode/settings.json" is created with the following content + """ + { + "python.testing.unittestArgs": [ + "-v", + "-s", + "./tests", + "-p", + "test_*.py" + ], + "python.testing.unittestEnabled": false, + "python.testing.pytestArgs": ["."], + "python.testing.pytestEnabled": false, + "python.testing.nosetestArgs": ["."], + "python.testing.nosetestsEnabled": false + } + """ + Given the Python extension has been activated + + Scenario Outline: When running tests, the nodes will have the progress icon and clicking stop will stop running () + Given the package "" is installed + And the workspace setting "python.testing." is enabled + When I select the command "Python: Discover Tests" + And I wait for test discovery to complete + Then the test explorer icon will be visible + When I select the command "View: Show Test" + And I expand all of the nodes in the test explorer + And the file "tests/test_running_delay" has the following content + """ + 10 + """ + When I select the command "Python: Run All Tests" + Then all of the test tree nodes have a progress icon + And the stop icon is visible in the toolbar + When I stop running tests + Then the stop icon is not visible in the toolbar + + Examples: + | package | setting_to_enable | + | unittest | unittestEnabled | + | pytest | pytestEnabled | + | nose | nosetestsEnabled | diff --git a/uitests/features/testing/explorer/run.success.feature b/uitests/features/testing/explorer/run.success.feature new file mode 100644 index 000000000000..61d4b63758da --- /dev/null +++ b/uitests/features/testing/explorer/run.success.feature @@ -0,0 +1,87 @@ +@testing +@https://github.com/DonJayamanne/pyvscSmokeTesting/testing +Feature: Test Explorer + Background: Activted Extension + Given a file named ".vscode/settings.json" is created with the following content + """ + { + "python.testing.unittestArgs": [ + "-v", + "-s", + "./tests", + "-p", + "test_*.py" + ], + "python.testing.unittestEnabled": false, + "python.testing.pytestArgs": ["."], + "python.testing.pytestEnabled": false, + "python.testing.nosetestArgs": ["."], + "python.testing.nosetestsEnabled": false + } + """ + Given the Python extension has been activated + + Scenario Outline: When running tests, the nodes will have the progress icon and when completed will have a success status () + Given the package "" is installed + And a file named "tests/test_running_delay" is created with the following content + """ + 5 + """ + And the workspace setting "python.testing." is enabled + When I select the command "Python: Discover Tests" + And I wait for test discovery to complete + Then the test explorer icon will be visible + When I select the command "View: Show Test" + And I expand all of the nodes in the test explorer + Then there are nodes in the test explorer + And nodes in the test explorer have a status of "Unknown" + When I run the node "test_two_first_suite" from the test explorer + Then the stop icon is visible in the toolbar + And 1 node in the test explorer has a status of "Progress" + And the node "test_two_first_suite" in the test explorer has a status of "Progress" + When I wait for tests to complete running + Then the node "" in the test explorer has a status of "Success" + And the node "TestFirstSuite" in the test explorer has a status of "Success" + And the node "test_two_first_suite" in the test explorer has a status of "Success" + And 11 nodes in the test explorer have a status of "Unknown" + + Examples: + | package | setting_to_enable | node_count | test_one_file_label | + | unittest | unittestEnabled | 14 | test_one.py | + | pytest | pytestEnabled | 15 | test_one.py | + | nose | nosetestsEnabled | 14 | tests/test_one.py | + + Scenario Outline: When running tests, the nodes will have the progress icon and when completed will have a error status () + Given the package "" is installed + And a file named "tests/test_running_delay" is created with the following content + """ + 5 + """ + And a file named "tests/data.json" is created with the following content + """ + [1,2,-1,4,5,6] + """ + And the workspace setting "python.testing." is enabled + # Then I wait for 1000 seconds + When I select the command "Python: Discover Tests" + And I wait for test discovery to complete + Then the test explorer icon will be visible + When I select the command "View: Show Test" + And I expand all of the nodes in the test explorer + Then there are nodes in the test explorer + And nodes in the test explorer have a status of "Unknown" + When I run the node "test_three_first_suite" from the test explorer + Then the stop icon is visible in the toolbar + And 1 node in the test explorer has a status of "Progress" + And the node "test_three_first_suite" in the test explorer has a status of "Progress" + When I wait for tests to complete running + Then the node "" in the test explorer has a status of "Fail" + And the node "TestFirstSuite" in the test explorer has a status of "Fail" + And the node "test_three_first_suite" in the test explorer has a status of "Fail" + And 11 nodes in the test explorer have a status of "Unknown" + + Examples: + | package | setting_to_enable | node_count | test_one_file_label | + | unittest | unittestEnabled | 14 | test_one.py | + | pytest | pytestEnabled | 15 | test_one.py | + | nose | nosetestsEnabled | 14 | tests/test_one.py | diff --git a/uitests/features/testing/explorerx/code.navigation.feature b/uitests/features/testing/explorerx/code.navigation.feature new file mode 100644 index 000000000000..54a9584f3420 --- /dev/null +++ b/uitests/features/testing/explorerx/code.navigation.feature @@ -0,0 +1,146 @@ +# @test +# @https://github.com/DonJayamanne/pyvscSmokeTesting.git/testing +# Feature: Test Explorer Discovering icons and stop discovery +# Scenario Outline: Can navigate to the source of a test file, and line of the suite & test function () +# Given the workspace setting "python.testing." is enabled +# And the package "" is installed +# When I reload VS Code +# And the Python extension has been activated +# And I select the command "View: Close All Editors" +# And I select the command "Python: Discover Tests" +# And I wait for test discovery to complete +# Then the test explorer icon will be visible +# When I select the command "View: Show Test" +# And I expand all of the nodes in the test explorer +# Then log the message "1" +# When I navigate to the code associated with the test node "test_one.py" +# Then log the message "2" +# Then the file "test_one.py" is opened +# When I navigate to the code associated with the test node "test_one_first_suite" +# Then the file "test_one.py" is opened +# And the cursor is on line 20 +# When I navigate to the code associated with the test node "test_three_first_suite" +# Then the file "test_one.py" is opened +# And the cursor is on line 30 +# When I navigate to the code associated with the test node "test_two_first_suite" +# Then the file "test_one.py" is opened +# And the cursor is on line 25 + +# Examples: +# | package | setting_to_enable | +# | unittest | unittestEnabled | +# # | pytest | pytestEnabled | +# # | nose | nosetestsEnabled | + + +# # # Scenario: When navigating to a test file, suite & test, then open the file and set the cursor at the right line (pytest) +# # # Given the package "pytest" is installed +# # # And the workspace setting "python.testing.pytestEnabled" is enabled +# # # And the workspace setting "python.testing.unittestEnabled" is disabled +# # # And the workspace setting "python.testing.nosetestsEnabled" is disabled +# # # When I reload VSC +# # # When I select the command "Python: Discover Tests" +# # # Then the test explorer icon will be visible +# # # When I select the command "View: Show Test" +# # # And I expand all of the nodes in the test explorer +# # # When I navigate to the code associated with the test node "test_one.py" +# # # Then the file "test_one.py" is opened +# # # When I navigate to the code associated with the test node "test_one_first_suite" +# # # Then the file "test_one.py" is opened +# # # And the cursor is on line 20 +# # # When I navigate to the code associated with the test node "test_three_first_suite" +# # # Then the file "test_one.py" is opened +# # # And the cursor is on line 30 +# # # When I navigate to the code associated with the test node "test_two_first_suite" +# # # Then the file "test_one.py" is opened +# # # And the cursor is on line 25 + +# # # Scenario: When navigating to a test file, suite & test, then open the file and set the cursor at the right line (nose) +# # # Given the package "nose" is installed +# # # And the workspace setting "python.testing.pytestEnabled" is disabled +# # # And the workspace setting "python.testing.unittestEnabled" is disabled +# # # And the workspace setting "python.testing.nosetestsEnabled" is enabled +# # # When I reload VSC +# # # When I select the command "Python: Discover Tests" +# # # Then the test explorer icon will be visible +# # # When I select the command "View: Show Test" +# # # And I expand all of the nodes in the test explorer +# # # When I navigate to the code associated with the test node "tests/test_one.py" +# # # Then the file "test_one.py" is opened +# # # When I navigate to the code associated with the test node "test_one_first_suite" +# # # Then the file "test_one.py" is opened +# # # And the cursor is on line 20 +# # # When I navigate to the code associated with the test node "test_three_first_suite" +# # # Then the file "test_one.py" is opened +# # # And the cursor is on line 30 +# # # When I navigate to the code associated with the test node "test_two_first_suite" +# # # Then the file "test_one.py" is opened +# # # And the cursor is on line 25 + + +# # # Scenario: When selecting a node, then open the file (unitest) +# # # Given the workspace setting "python.testing.pytestEnabled" is disabled +# # # And the workspace setting "python.testing.unittestEnabled" is enabled +# # # And the workspace setting "python.testing.nosetestsEnabled" is disabled +# # # And the command "View: Close All Editors" is selected +# # # When I reload VSC +# # # When I select the command "Python: Discover Tests" +# # # Then the test explorer icon will be visible +# # # When I select the command "View: Show Test" +# # # And I expand all of the nodes in the test explorer +# # # When I click the test node with the label "TestFirstSuite" +# # # Then the file "test_one.py" is opened +# # # Given the command "View: Close All Editors" is selected +# # # When I click the test node with the label "test_one_first_suite" +# # # Then the file "test_one.py" is opened +# # # Given the command "View: Close All Editors" is selected +# # # When I click the test node with the label "test_three_first_suite" +# # # Then the file "test_one.py" is opened +# # # Given the command "View: Close All Editors" is selected +# # # When I click the test node with the label "test_two_third_suite" +# # # Then the file "test_two.py" is opened + + +# # # Scenario: When selecting a node, then open the file (pytest) +# # # Given the package "pytest" is installed +# # # And the workspace setting "python.testing.pytestEnabled" is enabled +# # # And the workspace setting "python.testing.unittestEnabled" is disabled +# # # And the workspace setting "python.testing.nosetestsEnabled" is disabled +# # # When I reload VSC +# # # When I select the command "Python: Discover Tests" +# # # Then the test explorer icon will be visible +# # # When I select the command "View: Show Test" +# # # And I expand all of the nodes in the test explorer +# # # When I click the test node with the label "TestFirstSuite" +# # # Then the file "test_one.py" is opened +# # # Given the command "View: Close All Editors" is selected +# # # When I click the test node with the label "test_one_first_suite" +# # # Then the file "test_one.py" is opened +# # # Given the command "View: Close All Editors" is selected +# # # When I click the test node with the label "test_three_first_suite" +# # # Then the file "test_one.py" is opened +# # # Given the command "View: Close All Editors" is selected +# # # When I click the test node with the label "test_two_third_suite" +# # # Then the file "test_two.py" is opened + +# # # Scenario: When selecting a node, then open the file (nose) +# # # Given the package "nose" is installed +# # # And the workspace setting "python.testing.pytestEnabled" is disabled +# # # And the workspace setting "python.testing.unittestEnabled" is disabled +# # # And the workspace setting "python.testing.nosetestsEnabled" is enabled +# # # When I reload VSC +# # # When I select the command "Python: Discover Tests" +# # # Then the test explorer icon will be visible +# # # When I select the command "View: Show Test" +# # # And I expand all of the nodes in the test explorer +# # # When I click the test node with the label "TestFirstSuite" +# # # Then the file "test_one.py" is opened +# # # Given the command "View: Close All Editors" is selected +# # # When I click the test node with the label "test_one_first_suite" +# # # Then the file "test_one.py" is opened +# # # Given the command "View: Close All Editors" is selected +# # # When I click the test node with the label "test_three_first_suite" +# # # Then the file "test_one.py" is opened +# # # Given the command "View: Close All Editors" is selected +# # # When I click the test node with the label "test_two_third_suite" +# # # Then the file "test_two.py" is opened diff --git a/uitests/features/testing/explorerx/debug.feature b/uitests/features/testing/explorerx/debug.feature new file mode 100644 index 000000000000..c50cc97f3f77 --- /dev/null +++ b/uitests/features/testing/explorerx/debug.feature @@ -0,0 +1,278 @@ +# @test +# @https://github.com/DonJayamanne/pyvscSmokeTesting.git +# Feature: Test Explorer Discovering icons and stop discovery +# Scenario: When debugging tests, the nodes will have the progress icon and clicking stop will stop the debugger (unitest) +# Given the workspace setting "python.testing.pytestEnabled" is disabled +# And the workspace setting "python.testing.unittestEnabled" is enabled +# And the workspace setting "python.testing.nosetestsEnabled" is disabled +# And a file named "tests/test_running_delay" is created with the following content +# """ +# 5 +# """ +# When I reload VSC +# When I select the command "Python: Discover Tests" +# Then the test explorer icon will be visible +# When I select the command "View: Show Test" +# And I expand all of the nodes in the test explorer +# Then there are 14 nodes in the test explorer +# And 14 nodes in the test explorer have a status of "Unknown" +# When I debug the node "test_three_first_suite" from the test explorer +# Then the debugger starts +# When I select the command "Debug: Stop" +# Then the debugger stops + +# Scenario: When debugging tests, the nodes will have the progress icon and clicking stop will stop the debugger (pytest) +# Given the package "pytest" is installed +# And the workspace setting "python.testing.pytestEnabled" is enabled +# And the workspace setting "python.testing.unittestEnabled" is disabled +# And the workspace setting "python.testing.nosetestsEnabled" is disabled +# And a file named "tests/test_running_delay" is created with the following content +# """ +# 5 +# """ +# When I reload VSC +# When I select the command "Python: Discover Tests" +# Then the test explorer icon will be visible +# When I select the command "View: Show Test" +# And I expand all of the nodes in the test explorer +# Then there are 15 nodes in the test explorer +# And 15 nodes in the test explorer have a status of "Unknown" +# When I debug the node "test_three_first_suite" from the test explorer +# Then the debugger starts +# When I select the command "Debug: Stop" +# Then the debugger stops + +# Scenario: When debugging tests, the nodes will have the progress icon and clicking stop will stop the debugger (nose) +# Given the package "nose" is installed +# And the workspace setting "python.testing.pytestEnabled" is disabled +# And the workspace setting "python.testing.unittestEnabled" is disabled +# And the workspace setting "python.testing.nosetestsEnabled" is enabled +# And a file named "tests/test_running_delay" is created with the following content +# """ +# 5 +# """ +# When I reload VSC +# When I select the command "Python: Discover Tests" +# Then the test explorer icon will be visible +# When I select the command "View: Show Test" +# And I expand all of the nodes in the test explorer +# Then there are 14 nodes in the test explorer +# And 14 nodes in the test explorer have a status of "Unknown" +# When I debug the node "test_three_first_suite" from the test explorer +# Then the debugger starts +# When I select the command "Debug: Stop" +# Then the debugger stops + +# Scenario: When debugging tests, only the specific function will be debugged (unitest) +# Given the workspace setting "python.testing.pytestEnabled" is disabled +# And the workspace setting "python.testing.unittestEnabled" is enabled +# And the workspace setting "python.testing.nosetestsEnabled" is disabled +# When I reload VSC +# When I select the command "Python: Discover Tests" +# Then the test explorer icon will be visible +# When I select the command "View: Show Test" +# And I expand all of the nodes in the test explorer +# When I add a breakpoint to line 33 in "test_one.py" +# And I add a breakpoint to line 23 in "test_one.py" +# And I debug the node "test_three_first_suite" from the test explorer +# Then the debugger starts +# And the debugger pauses +# And the current stack frame is at line 33 in "test_one.py" +# When I select the command "Debug: Continue" +# Then the debugger stops + + +# Scenario: When debugging tests, only the specific function will be debugged (pytest) +# Given the package "pytest" is installed +# And the workspace setting "python.testing.pytestEnabled" is enabled +# And the workspace setting "python.testing.unittestEnabled" is disabled +# And the workspace setting "python.testing.nosetestsEnabled" is disabled +# When I reload VSC +# When I select the command "Python: Discover Tests" +# Then the test explorer icon will be visible +# When I select the command "View: Show Test" +# And I expand all of the nodes in the test explorer +# When I add a breakpoint to line 33 in "test_one.py" +# And I add a breakpoint to line 23 in "test_one.py" +# And I debug the node "test_three_first_suite" from the test explorer +# Then the debugger starts +# And the debugger pauses +# And the current stack frame is at line 33 in "test_one.py" +# When I select the command "Debug: Continue" +# Then the debugger stops + +# Scenario: When debugging tests, only the specific function will be debugged (nose) +# Given the package "nose" is installed +# And the workspace setting "python.testing.pytestEnabled" is disabled +# And the workspace setting "python.testing.unittestEnabled" is disabled +# And the workspace setting "python.testing.nosetestsEnabled" is enabled +# When I reload VSC +# When I select the command "Python: Discover Tests" +# Then the test explorer icon will be visible +# When I select the command "View: Show Test" +# And I expand all of the nodes in the test explorer +# When I add a breakpoint to line 33 in "test_one.py" +# And I add a breakpoint to line 23 in "test_one.py" +# And I debug the node "test_three_first_suite" from the test explorer +# Then the debugger starts +# And the debugger pauses +# And the current stack frame is at line 33 in "test_one.py" +# When I select the command "Debug: Continue" +# Then the debugger stops + + +# Scenario: When debugging tests, only the specific suite will be debugged (unitest) +# Given the workspace setting "python.testing.pytestEnabled" is disabled +# And the workspace setting "python.testing.unittestEnabled" is enabled +# And the workspace setting "python.testing.nosetestsEnabled" is disabled +# When I reload VSC +# When I select the command "Python: Discover Tests" +# Then the test explorer icon will be visible +# When I select the command "View: Show Test" +# And I expand all of the nodes in the test explorer +# When I add a breakpoint to line 33 in "test_one.py" +# And I add a breakpoint to line 28 in "test_one.py" +# And I add a breakpoint to line 23 in "test_one.py" +# And I debug the node "TestFirstSuite" from the test explorer +# Then the debugger starts +# And the debugger pauses +# And the current stack frame is at line 23 in "test_one.py" +# When I select the command "Debug: Continue" +# Then the debugger pauses +# And the current stack frame is at line 33 in "test_one.py" +# When I select the command "Debug: Continue" +# Then the debugger pauses +# And the current stack frame is at line 28 in "test_one.py" +# When I select the command "Debug: Continue" +# Then the debugger stops + + +# Scenario: When debugging tests, only the specific suite will be debugged (pytest) +# Given the package "pytest" is installed +# And the workspace setting "python.testing.pytestEnabled" is enabled +# And the workspace setting "python.testing.unittestEnabled" is disabled +# And the workspace setting "python.testing.nosetestsEnabled" is disabled +# When I reload VSC +# When I select the command "Python: Discover Tests" +# Then the test explorer icon will be visible +# When I select the command "View: Show Test" +# And I expand all of the nodes in the test explorer +# When I add a breakpoint to line 33 in "test_one.py" +# And I add a breakpoint to line 28 in "test_one.py" +# And I add a breakpoint to line 23 in "test_one.py" +# And I debug the node "TestFirstSuite" from the test explorer +# Then the debugger starts +# And the debugger pauses +# And the current stack frame is at line 23 in "test_one.py" +# When I select the command "Debug: Continue" +# Then the debugger pauses +# And the current stack frame is at line 33 in "test_one.py" +# When I select the command "Debug: Continue" +# Then the debugger pauses +# And the current stack frame is at line 28 in "test_one.py" +# When I select the command "Debug: Continue" +# Then the debugger stops + +# Scenario: When debugging tests, only the specific suite will be debugged (nose) +# Given the package "nose" is installed +# And the workspace setting "python.testing.pytestEnabled" is disabled +# And the workspace setting "python.testing.unittestEnabled" is disabled +# And the workspace setting "python.testing.nosetestsEnabled" is enabled +# When I reload VSC +# When I select the command "Python: Discover Tests" +# Then the test explorer icon will be visible +# When I select the command "View: Show Test" +# And I expand all of the nodes in the test explorer +# When I add a breakpoint to line 33 in "test_one.py" +# And I add a breakpoint to line 28 in "test_one.py" +# And I add a breakpoint to line 23 in "test_one.py" +# And I debug the node "TestFirstSuite" from the test explorer +# Then the debugger starts +# And the debugger pauses +# And the current stack frame is at line 23 in "test_one.py" +# When I select the command "Debug: Continue" +# Then the debugger pauses +# And the current stack frame is at line 33 in "test_one.py" +# When I select the command "Debug: Continue" +# Then the debugger pauses +# And the current stack frame is at line 28 in "test_one.py" +# When I select the command "Debug: Continue" +# Then the debugger stops + +# Scenario: When debugging tests, everything will be debugged (unitest) +# Given the workspace setting "python.testing.pytestEnabled" is disabled +# And the workspace setting "python.testing.unittestEnabled" is enabled +# And the workspace setting "python.testing.nosetestsEnabled" is disabled +# When I reload VSC +# When I select the command "Python: Discover Tests" +# Then the test explorer icon will be visible +# When I select the command "View: Show Test" +# And I expand all of the nodes in the test explorer +# When I add a breakpoint to line 23 in "test_one.py" +# And I add a breakpoint to line 38 in "test_one.py" +# And I add a breakpoint to line 23 in "test_two.py" +# And I select the command "Python: Debug All Tests" +# Then the debugger starts +# And the debugger pauses +# And the current stack frame is at line 23 in "test_one.py" +# When I select the command "Debug: Continue" +# Then the debugger pauses +# And the current stack frame is at line 38 in "test_one.py" +# When I select the command "Debug: Continue" +# Then the debugger pauses +# And the current stack frame is at line 23 in "test_two.py" +# When I select the command "Debug: Continue" +# Then the debugger stops + + +# Scenario: When debugging tests, everything will be debugged (pytest) +# Given the package "pytest" is installed +# And the workspace setting "python.testing.pytestEnabled" is enabled +# And the workspace setting "python.testing.unittestEnabled" is disabled +# And the workspace setting "python.testing.nosetestsEnabled" is disabled +# When I reload VSC +# When I select the command "Python: Discover Tests" +# Then the test explorer icon will be visible +# When I select the command "View: Show Test" +# And I expand all of the nodes in the test explorer +# When I add a breakpoint to line 23 in "test_one.py" +# And I add a breakpoint to line 38 in "test_one.py" +# And I add a breakpoint to line 23 in "test_two.py" +# And I select the command "Python: Debug All Tests" +# Then the debugger starts +# And the debugger pauses +# And the current stack frame is at line 23 in "test_one.py" +# When I select the command "Debug: Continue" +# Then the debugger pauses +# And the current stack frame is at line 38 in "test_one.py" +# When I select the command "Debug: Continue" +# Then the debugger pauses +# And the current stack frame is at line 23 in "test_two.py" +# When I select the command "Debug: Continue" +# Then the debugger stops + +# Scenario: When debugging tests, everything will be debugged (nose) +# Given the package "nose" is installed +# And the workspace setting "python.testing.pytestEnabled" is disabled +# And the workspace setting "python.testing.unittestEnabled" is disabled +# And the workspace setting "python.testing.nosetestsEnabled" is enabled +# When I reload VSC +# When I select the command "Python: Discover Tests" +# Then the test explorer icon will be visible +# When I select the command "View: Show Test" +# And I expand all of the nodes in the test explorer +# When I add a breakpoint to line 23 in "test_one.py" +# And I add a breakpoint to line 38 in "test_one.py" +# And I add a breakpoint to line 23 in "test_two.py" +# And I select the command "Python: Debug All Tests" +# Then the debugger starts +# And the debugger pauses +# And the current stack frame is at line 23 in "test_one.py" +# When I select the command "Debug: Continue" +# Then the debugger pauses +# And the current stack frame is at line 38 in "test_one.py" +# When I select the command "Debug: Continue" +# Then the debugger pauses +# And the current stack frame is at line 23 in "test_two.py" +# When I select the command "Debug: Continue" +# Then the debugger stops diff --git a/uitests/features/testing/explorerx/discover.count.feature b/uitests/features/testing/explorerx/discover.count.feature new file mode 100644 index 000000000000..6edbef28feed --- /dev/null +++ b/uitests/features/testing/explorerx/discover.count.feature @@ -0,0 +1,38 @@ +# @test +# @https://github.com/DonJayamanne/pyvscSmokeTesting.git +# Feature: Test Explorer + +# Scenario: Explorer will be displayed when tests are discovered (unitest) +# Given the workspace setting "python.testing.pytestEnabled" is disabled +# And the workspace setting "python.testing.unittestEnabled" is enabled +# And the workspace setting "python.testing.nosetestsEnabled" is disabled +# When I reload VSC +# When I select the command "Python: Discover Tests" +# Then the test explorer icon will be visible +# When I select the command "View: Show Test" +# And I expand all of the nodes in the test explorer +# Then there are 14 nodes in the test explorer + +# Scenario: Explorer will be displayed when tests are discovered (pytest) +# Given the package "pytest" is installed +# And the workspace setting "python.testing.pytestEnabled" is enabled +# And the workspace setting "python.testing.unittestEnabled" is disabled +# And the workspace setting "python.testing.nosetestsEnabled" is disabled +# When I reload VSC +# When I select the command "Python: Discover Tests" +# Then the test explorer icon will be visible +# When I select the command "View: Show Test" +# And I expand all of the nodes in the test explorer +# Then there are 15 nodes in the test explorer + +# Scenario: Explorer will be displayed when tests are discovered (nose) +# Given the package "nose" is installed +# And the workspace setting "python.testing.pytestEnabled" is disabled +# And the workspace setting "python.testing.unittestEnabled" is disabled +# And the workspace setting "python.testing.nosetestsEnabled" is enabled +# When I reload VSC +# When I select the command "Python: Discover Tests" +# Then the test explorer icon will be visible +# When I select the command "View: Show Test" +# And I expand all of the nodes in the test explorer +# Then there are 14 nodes in the test explorer diff --git a/uitests/features/testing/explorerx/discover.icons.feature b/uitests/features/testing/explorerx/discover.icons.feature new file mode 100644 index 000000000000..b229ca80bcdd --- /dev/null +++ b/uitests/features/testing/explorerx/discover.icons.feature @@ -0,0 +1,71 @@ +# @test +# @https://github.com/DonJayamanne/pyvscSmokeTesting.git +# Feature: Test Explorer Discovering icons and stop discovery + +# Scenario: When discovering tests, the nodes will have the progress icon and clicking stop will stop discovery (unitest) +# Given the workspace setting "python.testing.pytestEnabled" is disabled +# And the workspace setting "python.testing.unittestEnabled" is enabled +# And the workspace setting "python.testing.nosetestsEnabled" is disabled +# When I reload VSC +# When I select the command "Python: Discover Tests" +# Then the test explorer icon will be visible +# When I select the command "View: Show Test" +# And I expand all of the nodes in the test explorer +# Then there are 14 nodes in the test explorer +# # Now, add a delay for the discovery of the tests +# Given a file named "tests/test_discovery_delay" is created with the following content +# """ +# 10 +# """ +# When I select the command "Python: Discover Tests" +# And I wait for 1 second +# Then all of the test tree nodes have a progress icon +# And the stop icon is visible in the toolbar +# When I stop discovering tests +# Then the stop icon is not visible in the toolbar + +# Scenario: When discovering tests, the nodes will have the progress icon and clicking stop will stop discovery (pytest) +# Given the package "pytest" is installed +# And the workspace setting "python.testing.pytestEnabled" is enabled +# And the workspace setting "python.testing.unittestEnabled" is disabled +# And the workspace setting "python.testing.nosetestsEnabled" is disabled +# When I reload VSC +# When I select the command "Python: Discover Tests" +# Then the test explorer icon will be visible +# When I select the command "View: Show Test" +# And I expand all of the nodes in the test explorer +# Then there are 15 nodes in the test explorer +# # Now, add a delay for the discovery of the tests +# Given a file named "tests/test_discovery_delay" is created with the following content +# """ +# 10 +# """ +# When I select the command "Python: Discover Tests" +# And I wait for 1 second +# Then all of the test tree nodes have a progress icon +# And the stop icon is visible in the toolbar +# When I stop discovering tests +# Then the stop icon is not visible in the toolbar + +# Scenario: When discovering tests, the nodes will have the progress icon and clicking stop will stop discovery (nose) +# Given the package "nose" is installed +# And the workspace setting "python.testing.pytestEnabled" is enabled +# And the workspace setting "python.testing.unittestEnabled" is disabled +# And the workspace setting "python.testing.nosetestsEnabled" is disabled +# When I reload VSC +# When I select the command "Python: Discover Tests" +# Then the test explorer icon will be visible +# When I select the command "View: Show Test" +# And I expand all of the nodes in the test explorer +# Then there are 15 nodes in the test explorer +# # Now, add a delay for the discovery of the tests +# Given a file named "tests/test_discovery_delay" is created with the following content +# """ +# 10 +# """ +# When I select the command "Python: Discover Tests" +# And I wait for 1 second +# Then all of the test tree nodes have a progress icon +# And the stop icon is visible in the toolbar +# When I stop discovering tests +# Then the stop icon is not visible in the toolbar diff --git a/uitests/features/testing/explorerx/run.failed.feature b/uitests/features/testing/explorerx/run.failed.feature new file mode 100644 index 000000000000..bd9780d1b015 --- /dev/null +++ b/uitests/features/testing/explorerx/run.failed.feature @@ -0,0 +1,308 @@ +# @test +# @https://github.com/DonJayamanne/pyvscSmokeTesting.git +# Feature: Test Explorer - Re-run Failed Tests + +# Scenario: We are able to re-run a failed tests (unitest) +# Given the workspace setting "python.testing.pytestEnabled" is disabled +# And the workspace setting "python.testing.unittestEnabled" is enabled +# And the workspace setting "python.testing.nosetestsEnabled" is disabled +# And a file named "tests/test_running_delay" is created with the following content +# """ +# 0 +# """ +# And a file named "tests/data.json" is created with the following content +# """ +# [1,-1,-1,4,5,6] +# """ +# When I reload VSC +# When I select the command "Python: Discover Tests" +# Then the test explorer icon will be visible +# When I select the command "View: Show Test" +# And I expand all of the nodes in the test explorer +# Then there are 14 nodes in the test explorer +# And 14 nodes in the test explorer have a status of "Unknown" +# When I select the command "Python: Run All Tests" +# And I wait for tests to complete running +# Then the node "test_one.py" in the test explorer has a status of "Fail" +# And the node "TestFirstSuite" in the test explorer has a status of "Fail" +# And the node "test_three_first_suite" in the test explorer has a status of "Fail" +# And the node "test_two_first_suite" in the test explorer has a status of "Fail" +# And the node "test_two.py" in the test explorer has a status of "Fail" +# And the node "TestThirdSuite" in the test explorer has a status of "Fail" +# And the node "test_three_third_suite" in the test explorer has a status of "Fail" +# And the node "test_two_third_suite" in the test explorer has a status of "Fail" +# And 6 nodes in the test explorer have a status of "Success" +# And the run failed tests icon is visible in the toolbar +# Given a file named "tests/test_running_delay" is created with the following content +# """ +# 1 +# """ +# And a file named "tests/data.json" is created with the following content +# """ +# [1,2,3,4,5,6] +# """ +# When I run failed tests +# And I wait for tests to complete running +# Then 14 nodes in the test explorer have a status of "Success" + +# Scenario: We are able to re-run a failed tests (pytest) +# Given the package "pytest" is installed +# And the workspace setting "python.testing.pytestEnabled" is enabled +# And the workspace setting "python.testing.unittestEnabled" is disabled +# And the workspace setting "python.testing.nosetestsEnabled" is disabled +# And a file named "tests/test_running_delay" is created with the following content +# """ +# 0 +# """ +# And a file named "tests/data.json" is created with the following content +# """ +# [1,-1,-1,4,5,6] +# """ +# When I reload VSC +# When I select the command "Python: Discover Tests" +# Then the test explorer icon will be visible +# When I select the command "View: Show Test" +# And I expand all of the nodes in the test explorer +# Then there are 15 nodes in the test explorer +# And 15 nodes in the test explorer have a status of "Unknown" +# When I select the command "Python: Run All Tests" +# And I wait for tests to complete running +# Then the node "test_one.py" in the test explorer has a status of "Fail" +# And the node "TestFirstSuite" in the test explorer has a status of "Fail" +# And the node "test_three_first_suite" in the test explorer has a status of "Fail" +# And the node "test_two_first_suite" in the test explorer has a status of "Fail" +# And the node "test_two.py" in the test explorer has a status of "Fail" +# And the node "TestThirdSuite" in the test explorer has a status of "Fail" +# And the node "test_three_third_suite" in the test explorer has a status of "Fail" +# And the node "test_two_third_suite" in the test explorer has a status of "Fail" +# And 6 nodes in the test explorer have a status of "Success" +# And the run failed tests icon is visible in the toolbar +# Given a file named "tests/test_running_delay" is created with the following content +# """ +# 1 +# """ +# And a file named "tests/data.json" is created with the following content +# """ +# [1,2,3,4,5,6] +# """ +# When I run failed tests +# And I wait for tests to complete running +# Then 15 nodes in the test explorer have a status of "Success" + +# Scenario: We are able to re-run a failed tests (nose) +# Given the package "nose" is installed +# And the workspace setting "python.testing.pytestEnabled" is disabled +# And the workspace setting "python.testing.unittestEnabled" is disabled +# And the workspace setting "python.testing.nosetestsEnabled" is enabled +# And a file named "tests/test_running_delay" is created with the following content +# """ +# 0 +# """ +# And a file named "tests/data.json" is created with the following content +# """ +# [1,-1,-1,4,5,6] +# """ +# When I reload VSC +# When I select the command "Python: Discover Tests" +# Then the test explorer icon will be visible +# When I select the command "View: Show Test" +# And I expand all of the nodes in the test explorer +# Then there are 14 nodes in the test explorer +# And 14 nodes in the test explorer have a status of "Unknown" +# When I select the command "Python: Run All Tests" +# And I wait for tests to complete running +# Then the node "tests/test_one.py" in the test explorer has a status of "Fail" +# And the node "TestFirstSuite" in the test explorer has a status of "Fail" +# And the node "test_three_first_suite" in the test explorer has a status of "Fail" +# And the node "test_two_first_suite" in the test explorer has a status of "Fail" +# And the node "tests/test_two.py" in the test explorer has a status of "Fail" +# And the node "TestThirdSuite" in the test explorer has a status of "Fail" +# And the node "test_three_third_suite" in the test explorer has a status of "Fail" +# And the node "test_two_third_suite" in the test explorer has a status of "Fail" +# And 6 nodes in the test explorer have a status of "Success" +# And the run failed tests icon is visible in the toolbar +# Given a file named "tests/test_running_delay" is created with the following content +# """ +# 1 +# """ +# And a file named "tests/data.json" is created with the following content +# """ +# [1,2,3,4,5,6] +# """ +# When I run failed tests +# And I wait for tests to complete running +# Then 14 nodes in the test explorer have a status of "Success" + +# Scenario: We are able to stop tests after re-running failed tests (unitest) +# Given the workspace setting "python.testing.pytestEnabled" is disabled +# And the workspace setting "python.testing.unittestEnabled" is enabled +# And the workspace setting "python.testing.nosetestsEnabled" is disabled +# And a file named "tests/test_running_delay" is created with the following content +# """ +# 0 +# """ +# And a file named "tests/data.json" is created with the following content +# """ +# [1,-1,-1,4,5,6] +# """ +# When I reload VSC +# When I select the command "Python: Discover Tests" +# Then the test explorer icon will be visible +# When I select the command "View: Show Test" +# And I expand all of the nodes in the test explorer +# Then there are 14 nodes in the test explorer +# And 14 nodes in the test explorer have a status of "Unknown" +# When I select the command "Python: Run All Tests" +# And I wait for tests to complete running +# Then the node "test_one.py" in the test explorer has a status of "Fail" +# And the node "TestFirstSuite" in the test explorer has a status of "Fail" +# And the node "test_three_first_suite" in the test explorer has a status of "Fail" +# And the node "test_two_first_suite" in the test explorer has a status of "Fail" +# And the node "test_two.py" in the test explorer has a status of "Fail" +# And the node "TestThirdSuite" in the test explorer has a status of "Fail" +# And the node "test_three_third_suite" in the test explorer has a status of "Fail" +# And the node "test_two_third_suite" in the test explorer has a status of "Fail" +# And 6 nodes in the test explorer have a status of "Success" +# And the run failed tests icon is visible in the toolbar +# Given a file named "tests/test_running_delay" is created with the following content +# """ +# 100 +# """ +# And a file named "tests/data.json" is created with the following content +# """ +# [1,2,3,4,5,6] +# """ +# When I run failed tests +# And I wait for 1 seconds +# Then the stop icon is visible in the toolbar +# Then the node "TestFirstSuite" in the test explorer has a status of "Progress" +# And the node "test_three_first_suite" in the test explorer has a status of "Progress" +# And the node "test_two_first_suite" in the test explorer has a status of "Progress" +# And the node "TestThirdSuite" in the test explorer has a status of "Progress" +# And the node "test_three_third_suite" in the test explorer has a status of "Progress" +# And the node "test_two_third_suite" in the test explorer has a status of "Progress" +# And 6 nodes in the test explorer have a status of "Progress" +# When I stop running tests +# And I wait for tests to complete running +# Then the stop icon is not visible in the toolbar +# And the node "test_three_first_suite" in the test explorer has a status of "Unknown" +# And the node "test_two_first_suite" in the test explorer has a status of "Unknown" +# And the node "test_three_third_suite" in the test explorer has a status of "Unknown" +# And the node "test_two_third_suite" in the test explorer has a status of "Unknown" + + +# Scenario: We are able to stop tests after re-running failed tests (pytest) +# Given the package "pytest" is installed +# And the workspace setting "python.testing.pytestEnabled" is enabled +# And the workspace setting "python.testing.unittestEnabled" is disabled +# And the workspace setting "python.testing.nosetestsEnabled" is disabled +# And a file named "tests/test_running_delay" is created with the following content +# """ +# 0 +# """ +# And a file named "tests/data.json" is created with the following content +# """ +# [1,-1,-1,4,5,6] +# """ +# When I reload VSC +# When I select the command "Python: Discover Tests" +# Then the test explorer icon will be visible +# When I select the command "View: Show Test" +# And I expand all of the nodes in the test explorer +# Then there are 15 nodes in the test explorer +# And 15 nodes in the test explorer have a status of "Unknown" +# When I select the command "Python: Run All Tests" +# And I wait for tests to complete running +# Then the node "test_one.py" in the test explorer has a status of "Fail" +# And the node "TestFirstSuite" in the test explorer has a status of "Fail" +# And the node "test_three_first_suite" in the test explorer has a status of "Fail" +# And the node "test_two_first_suite" in the test explorer has a status of "Fail" +# And the node "test_two.py" in the test explorer has a status of "Fail" +# And the node "TestThirdSuite" in the test explorer has a status of "Fail" +# And the node "test_three_third_suite" in the test explorer has a status of "Fail" +# And the node "test_two_third_suite" in the test explorer has a status of "Fail" +# And 6 nodes in the test explorer have a status of "Success" +# And the run failed tests icon is visible in the toolbar +# Given a file named "tests/test_running_delay" is created with the following content +# """ +# 100 +# """ +# And a file named "tests/data.json" is created with the following content +# """ +# [1,2,3,4,5,6] +# """ +# When I run failed tests +# And I wait for 1 seconds +# Then the stop icon is visible in the toolbar +# Then the node "TestFirstSuite" in the test explorer has a status of "Progress" +# And the node "test_three_first_suite" in the test explorer has a status of "Progress" +# And the node "test_two_first_suite" in the test explorer has a status of "Progress" +# And the node "TestThirdSuite" in the test explorer has a status of "Progress" +# And the node "test_three_third_suite" in the test explorer has a status of "Progress" +# And the node "test_two_third_suite" in the test explorer has a status of "Progress" +# And 6 nodes in the test explorer have a status of "Progress" +# When I stop running tests +# And I wait for tests to complete running +# Then the stop icon is not visible in the toolbar +# And the node "test_three_first_suite" in the test explorer has a status of "Unknown" +# And the node "test_two_first_suite" in the test explorer has a status of "Unknown" +# And the node "test_three_third_suite" in the test explorer has a status of "Unknown" +# And the node "test_two_third_suite" in the test explorer has a status of "Unknown" + +# Scenario: We are able to stop tests after re-running failed tests (nose) +# Given the package "nose" is installed +# And the workspace setting "python.testing.pytestEnabled" is disabled +# And the workspace setting "python.testing.unittestEnabled" is disabled +# And the workspace setting "python.testing.nosetestsEnabled" is enabled +# And a file named "tests/test_running_delay" is created with the following content +# """ +# 0 +# """ +# And a file named "tests/data.json" is created with the following content +# """ +# [1,-1,-1,4,5,6] +# """ +# When I reload VSC +# When I select the command "Python: Discover Tests" +# Then the test explorer icon will be visible +# When I select the command "View: Show Test" +# And I expand all of the nodes in the test explorer +# Then there are 14 nodes in the test explorer +# And 14 nodes in the test explorer have a status of "Unknown" +# When I select the command "Python: Run All Tests" +# And I wait for tests to complete running +# Then the node "tests/test_one.py" in the test explorer has a status of "Fail" +# And the node "TestFirstSuite" in the test explorer has a status of "Fail" +# And the node "test_three_first_suite" in the test explorer has a status of "Fail" +# And the node "test_two_first_suite" in the test explorer has a status of "Fail" +# And the node "tests/test_two.py" in the test explorer has a status of "Fail" +# And the node "TestThirdSuite" in the test explorer has a status of "Fail" +# And the node "test_three_third_suite" in the test explorer has a status of "Fail" +# And the node "test_two_third_suite" in the test explorer has a status of "Fail" +# And 6 nodes in the test explorer have a status of "Success" +# And the run failed tests icon is visible in the toolbar +# Given a file named "tests/test_running_delay" is created with the following content +# """ +# 100 +# """ +# And a file named "tests/data.json" is created with the following content +# """ +# [1,2,3,4,5,6] +# """ +# When I run failed tests +# And I wait for 1 seconds +# Then the stop icon is visible in the toolbar +# Then the node "TestFirstSuite" in the test explorer has a status of "Progress" +# And the node "test_three_first_suite" in the test explorer has a status of "Progress" +# And the node "test_two_first_suite" in the test explorer has a status of "Progress" +# And the node "TestThirdSuite" in the test explorer has a status of "Progress" +# And the node "test_three_third_suite" in the test explorer has a status of "Progress" +# And the node "test_two_third_suite" in the test explorer has a status of "Progress" +# And 6 nodes in the test explorer have a status of "Progress" +# When I stop running tests +# And I wait for tests to complete running +# Then the stop icon is not visible in the toolbar +# And the node "test_three_first_suite" in the test explorer has a status of "Unknown" +# And the node "test_two_first_suite" in the test explorer has a status of "Unknown" +# And the node "test_three_third_suite" in the test explorer has a status of "Unknown" +# And the node "test_two_third_suite" in the test explorer has a status of "Unknown" diff --git a/uitests/features/testing/explorerx/run.progress.feature b/uitests/features/testing/explorerx/run.progress.feature new file mode 100644 index 000000000000..51197ab12267 --- /dev/null +++ b/uitests/features/testing/explorerx/run.progress.feature @@ -0,0 +1,64 @@ +# @test +# @https://github.com/DonJayamanne/pyvscSmokeTesting.git +# Feature: Test Explorer Running icons and stop running +# Scenario: When running tests, the nodes will have the progress icon and clicking stop will stop running (unitest) +# Given the workspace setting "python.testing.pytestEnabled" is disabled +# And the workspace setting "python.testing.unittestEnabled" is enabled +# And the workspace setting "python.testing.nosetestsEnabled" is disabled +# When I reload VSC +# When I select the command "Python: Discover Tests" +# Then the test explorer icon will be visible +# When I select the command "View: Show Test" +# And I expand all of the nodes in the test explorer +# And the file "tests/test_running_delay" has the following content +# """ +# 10 +# """ +# When I select the command "Python: Run All Tests" +# And I wait for 1 second +# Then all of the test tree nodes have a progress icon +# And the stop icon is visible in the toolbar +# When I stop running tests +# Then the stop icon is not visible in the toolbar + +# Scenario: When running tests, the nodes will have the progress icon and clicking stop will stop running (pytest) +# Given the package "pytest" is installed +# And the workspace setting "python.testing.pytestEnabled" is enabled +# And the workspace setting "python.testing.unittestEnabled" is disabled +# And the workspace setting "python.testing.nosetestsEnabled" is disabled +# When I reload VSC +# When I select the command "Python: Discover Tests" +# Then the test explorer icon will be visible +# When I select the command "View: Show Test" +# And I expand all of the nodes in the test explorer +# And the file "tests/test_running_delay" has the following content +# """ +# 10 +# """ +# When I select the command "Python: Run All Tests" +# And I wait for 1 second +# Then all of the test tree nodes have a progress icon +# And the stop icon is visible in the toolbar +# When I stop running tests +# Then the stop icon is not visible in the toolbar + +# Scenario: When running tests, the nodes will have the progress icon and clicking stop will stop running (nose) +# Given the package "nose" is installed +# And the workspace setting "python.testing.pytestEnabled" is enabled +# And the workspace setting "python.testing.unittestEnabled" is disabled +# And the workspace setting "python.testing.nosetestsEnabled" is disabled +# When I reload VSC +# When I select the command "Python: Discover Tests" +# Then the test explorer icon will be visible +# When I select the command "View: Show Test" +# And I expand all of the nodes in the test explorer +# And the file "tests/test_running_delay" has the following content +# """ +# 10 +# """ +# When I select the command "Python: Run All Tests" +# And I wait for 1 second +# Then all of the test tree nodes have a progress icon +# And the stop icon is visible in the toolbar +# When I stop running tests +# Then the stop icon is not visible in the toolbar diff --git a/uitests/features/testing/explorerx/run.success.feature b/uitests/features/testing/explorerx/run.success.feature new file mode 100644 index 000000000000..2e494b00c9b4 --- /dev/null +++ b/uitests/features/testing/explorerx/run.success.feature @@ -0,0 +1,176 @@ +# @test +# @https://github.com/DonJayamanne/pyvscSmokeTesting.git +# Feature: Test Explorer Discovering icons and stop discovery +# Scenario: When running tests, the nodes will have the progress icon and when completed will have a success status (unitest) +# Given the workspace setting "python.testing.pytestEnabled" is disabled +# And the workspace setting "python.testing.unittestEnabled" is enabled +# And the workspace setting "python.testing.nosetestsEnabled" is disabled +# And a file named "tests/test_running_delay" is created with the following content +# """ +# 5 +# """ +# When I reload VSC +# When I select the command "Python: Discover Tests" +# Then the test explorer icon will be visible +# When I select the command "View: Show Test" +# And I expand all of the nodes in the test explorer +# Then there are 14 nodes in the test explorer +# And 14 nodes in the test explorer have a status of "Unknown" +# When I run the node "test_two_first_suite" from the test explorer +# And I wait for 1 seconds +# Then the stop icon is visible in the toolbar +# And 1 node in the test explorer has a status of "Progress" +# And the node "test_two_first_suite" in the test explorer has a status of "Progress" +# When I wait for tests to complete running +# Then the node "test_one.py" in the test explorer has a status of "Success" +# And the node "TestFirstSuite" in the test explorer has a status of "Success" +# And the node "test_two_first_suite" in the test explorer has a status of "Success" +# And 11 nodes in the test explorer have a status of "Unknown" + + +# Scenario: When running tests, the nodes will have the progress icon and when completed will have a success status (pytest) +# Given the package "pytest" is installed +# And the workspace setting "python.testing.pytestEnabled" is enabled +# And the workspace setting "python.testing.unittestEnabled" is disabled +# And the workspace setting "python.testing.nosetestsEnabled" is disabled +# And a file named "tests/test_running_delay" is created with the following content +# """ +# 5 +# """ +# When I reload VSC +# When I select the command "Python: Discover Tests" +# Then the test explorer icon will be visible +# When I select the command "View: Show Test" +# And I expand all of the nodes in the test explorer +# Then there are 15 nodes in the test explorer +# And 15 nodes in the test explorer have a status of "Unknown" +# When I run the node "test_two_first_suite" from the test explorer +# And I wait for 1 seconds +# Then the stop icon is visible in the toolbar +# And 1 node in the test explorer has a status of "Progress" +# And the node "test_two_first_suite" in the test explorer has a status of "Progress" +# When I wait for tests to complete running +# Then the node "test_one.py" in the test explorer has a status of "Success" +# And the node "TestFirstSuite" in the test explorer has a status of "Success" +# And the node "test_two_first_suite" in the test explorer has a status of "Success" +# And 11 nodes in the test explorer have a status of "Unknown" + +# Scenario: When running tests, the nodes will have the progress icon and when completed will have a success status (nose) +# Given the package "nose" is installed +# And the workspace setting "python.testing.pytestEnabled" is disabled +# And the workspace setting "python.testing.unittestEnabled" is disabled +# And the workspace setting "python.testing.nosetestsEnabled" is enabled +# And a file named "tests/test_running_delay" is created with the following content +# """ +# 5 +# """ +# When I reload VSC +# When I select the command "Python: Discover Tests" +# Then the test explorer icon will be visible +# When I select the command "View: Show Test" +# And I expand all of the nodes in the test explorer +# Then there are 14 nodes in the test explorer +# And 14 nodes in the test explorer have a status of "Unknown" +# When I run the node "test_two_first_suite" from the test explorer +# And I wait for 1 seconds +# Then the stop icon is visible in the toolbar +# And 1 node in the test explorer has a status of "Progress" +# And the node "test_two_first_suite" in the test explorer has a status of "Progress" +# When I wait for tests to complete running +# Then the node "tests/test_one.py" in the test explorer has a status of "Success" +# And the node "TestFirstSuite" in the test explorer has a status of "Success" +# And the node "test_two_first_suite" in the test explorer has a status of "Success" +# And 11 nodes in the test explorer have a status of "Unknown" + + +# Scenario: When running tests, the nodes will have the progress icon and when completed will have a error status (unitest) +# Given the workspace setting "python.testing.pytestEnabled" is disabled +# And the workspace setting "python.testing.unittestEnabled" is enabled +# And the workspace setting "python.testing.nosetestsEnabled" is disabled +# And a file named "tests/test_running_delay" is created with the following content +# """ +# 5 +# """ +# And a file named "tests/data.json" is created with the following content +# """ +# [1,2,-1,4,5,6] +# """ +# When I reload VSC +# When I select the command "Python: Discover Tests" +# Then the test explorer icon will be visible +# When I select the command "View: Show Test" +# And I expand all of the nodes in the test explorer +# Then there are 14 nodes in the test explorer +# And 14 nodes in the test explorer have a status of "Unknown" +# When I run the node "test_three_first_suite" from the test explorer +# And I wait for 1 seconds +# Then the stop icon is visible in the toolbar +# And 1 node in the test explorer has a status of "Progress" +# And the node "test_three_first_suite" in the test explorer has a status of "Progress" +# When I wait for tests to complete running +# Then the node "test_one.py" in the test explorer has a status of "Fail" +# And the node "TestFirstSuite" in the test explorer has a status of "Fail" +# And the node "test_three_first_suite" in the test explorer has a status of "Fail" +# And 11 nodes in the test explorer have a status of "Unknown" + +# Scenario: When running tests, the nodes will have the progress icon and when completed will have a error status (pytest) +# Given the package "pytest" is installed +# And the workspace setting "python.testing.pytestEnabled" is enabled +# And the workspace setting "python.testing.unittestEnabled" is disabled +# And the workspace setting "python.testing.nosetestsEnabled" is disabled +# And a file named "tests/test_running_delay" is created with the following content +# """ +# 5 +# """ +# And a file named "tests/data.json" is created with the following content +# """ +# [1,2,-1,4,5,6] +# """ +# When I reload VSC +# When I select the command "Python: Discover Tests" +# Then the test explorer icon will be visible +# When I select the command "View: Show Test" +# And I expand all of the nodes in the test explorer +# Then there are 15 nodes in the test explorer +# And 15 nodes in the test explorer have a status of "Unknown" +# When I run the node "test_three_first_suite" from the test explorer +# And I wait for 1 seconds +# Then the stop icon is visible in the toolbar +# And 1 node in the test explorer has a status of "Progress" +# And the node "test_three_first_suite" in the test explorer has a status of "Progress" +# When I wait for tests to complete running +# Then the node "test_one.py" in the test explorer has a status of "Fail" +# And the node "TestFirstSuite" in the test explorer has a status of "Fail" +# And the node "test_three_first_suite" in the test explorer has a status of "Fail" +# And 11 nodes in the test explorer have a status of "Unknown" + +# Scenario: When running tests, the nodes will have the progress icon and when completed will have a error status (nose) +# Given the package "nose" is installed +# And the workspace setting "python.testing.pytestEnabled" is disabled +# And the workspace setting "python.testing.unittestEnabled" is disabled +# And the workspace setting "python.testing.nosetestsEnabled" is enabled +# And a file named "tests/test_running_delay" is created with the following content +# """ +# 5 +# """ +# And a file named "tests/data.json" is created with the following content +# """ +# [1,2,-1,4,5,6] +# """ +# When I reload VSC +# When I select the command "Python: Discover Tests" +# Then the test explorer icon will be visible +# When I select the command "View: Show Test" +# And I expand all of the nodes in the test explorer +# Then there are 14 nodes in the test explorer +# And 14 nodes in the test explorer have a status of "Unknown" +# When I run the node "test_three_first_suite" from the test explorer +# And I wait for 1 seconds +# Then the stop icon is visible in the toolbar +# And 1 node in the test explorer has a status of "Progress" +# And the node "test_three_first_suite" in the test explorer has a status of "Progress" +# When I wait for tests to complete running +# Then the node "tests/test_one.py" in the test explorer has a status of "Fail" +# And the node "TestFirstSuite" in the test explorer has a status of "Fail" +# And the node "test_three_first_suite" in the test explorer has a status of "Fail" +# And 11 nodes in the test explorer have a status of "Unknown" diff --git a/uitests/features/testing/explorerx/visible.feature b/uitests/features/testing/explorerx/visible.feature new file mode 100644 index 000000000000..cebdb5b3cc5d --- /dev/null +++ b/uitests/features/testing/explorerx/visible.feature @@ -0,0 +1,16 @@ +# @test +# @https://github.com/DonJayamanne/pyvscSmokeTesting.git +# Feature: Testing +# Scenario Outline: Explorer will be displayed when tests are discovered () +# Given the setting "python.testing." is enabled +# And the package "" is installed +# When I reload VS Code +# And the Python extension has been activated +# And I select the command "Python: Discover Tests" +# Then the test explorer icon will be visible + +# Examples: +# | package | setting_to_enable | +# | unittest | unittestEnabled | +# | pytest | pytestEnabled | +# | nose | nosetestsEnabled | diff --git a/uitests/features/testing/testing.feature b/uitests/features/testing/testing.feature new file mode 100644 index 000000000000..5a3ed7ea9024 --- /dev/null +++ b/uitests/features/testing/testing.feature @@ -0,0 +1,1073 @@ +# @testing +# Feature: Testing +# Scenario: Start +# Given the workspace is based on "/Users/donjayamanne/Desktop/Development/vscode/smokeTests/testing" +# Given the file "tests/test_discovery_delay" is updated with the value "0" +# Given the file "tests/test_running_delay" is updated with the value "0" +# Given the file "tests/data.json" is updated with the value "[1,2,3,4,5,6]" + +# @continue +# Scenario: Discovery completed +# When I select the command "Python: Discover Unit Tests" +# Then wait for 5 seconds +# When I select the command "View: Show Test" +# Then the toolbar button with the text "Run All Unit Tests" is visible +# Then the toolbar button with the text "Debug All Unit Tests" is visible +# Then the toolbar button with the text "Discover Unit Tests" is visible +# Then the toolbar button with the text "Show Unit Test Output" is visible +# Then the toolbar button with the text "Run Failed Unit Tests" is not visible +# Then the toolbar button with the text "Stop" is not visible +# Then take a screenshot + +# @continue +# Scenario: Discovering icons +# When I update file "tests/test_discovery_delay" with value "5" +# When I select the command "Python: Discover Unit Tests" +# Then wait for 1 second +# Then the toolbar button with the text "Run All Unit Tests" is not visible +# Then the toolbar button with the text "Debug All Unit Tests" is not visible +# # The `Discover Unit Tests` is still visible with a progress icon. +# # Probably, we should change the tooltip at this point to `Discovering Tests` +# Then the toolbar button with the text "Discover Unit Tests" is visible +# Then the toolbar button with the text "Show Unit Test Output" is visible +# Then the toolbar button with the text "Run Failed Unit Tests" is not visible +# Then the toolbar button with the text "Stop" is visible +# Then take a screenshot + +# @continue +# Scenario: Stop discovering +# When I stop the tests +# Then wait for 1 second +# Then the toolbar button with the text "Run All Unit Tests" is visible +# Then the toolbar button with the text "Debug All Unit Tests" is visible +# Then the toolbar button with the text "Discover Unit Tests" is visible +# Then the toolbar button with the text "Show Unit Test Output" is visible +# Then the toolbar button with the text "Run Failed Unit Tests" is not visible +# Then the toolbar button with the text "Stop" is not visible +# Then select the command "Notifications: Clear All Notifications" +# Then take a screenshot + +# @continue +# Scenario: Running icons +# When I update file "tests/test_discovery_delay" with value "0" +# When I update file "tests/test_running_delay" with value "5" +# When I select the command "Python: Run All Unit Tests" +# Then wait for 1 second +# Then the toolbar button with the text "Run All Unit Tests" is not visible +# Then the toolbar button with the text "Debug All Unit Tests" is not visible +# Then the toolbar button with the text "Discover Unit Tests" is not visible +# Then the toolbar button with the text "Show Unit Test Output" is visible +# Then the toolbar button with the text "Run Failed Unit Tests" is not visible +# Then the toolbar button with the text "Stop" is visible +# Then take a screenshot + +# @continue +# Scenario: Stop running +# When I stop the tests +# Then wait for 1 second +# Then the toolbar button with the text "Run All Unit Tests" is visible +# Then the toolbar button with the text "Debug All Unit Tests" is visible +# Then the toolbar button with the text "Discover Unit Tests" is visible +# Then the toolbar button with the text "Show Unit Test Output" is visible +# Then the toolbar button with the text "Run Failed Unit Tests" is not visible +# Then the toolbar button with the text "Stop" is not visible +# Then select the command "Notifications: Clear All Notifications" +# Then take a screenshot + +# @continue +# Scenario: Run without errors +# When I update file "tests/test_discovery_delay" with value "0" +# When I update file "tests/test_running_delay" with value "0" +# When I select the command "Python: Run All Unit Tests" +# Then wait for 1 second +# Then wait for the toolbar button with the text "Run All Unit Tests" to appear within 5 seconds +# Then take a screenshot + +# @continue +# Scenario: Expand test explorer +# Then select first node +# When I press "down" +# When I press "right" +# When I press "down" +# When I press "right" +# When I press "down" +# When I press "down" +# When I press "down" +# When I press "down" +# When I press "right" +# When I press "down" +# When I press "down" +# When I press "down" +# When I press "down" +# When I press "right" +# When I press "down" +# When I press "right" + +# @continue +# Scenario: Expand check icons in test explorer +# Then the toolbar button with the text "Run All Unit Tests" is visible +# Then the toolbar button with the text "Debug All Unit Tests" is visible +# Then the toolbar button with the text "Discover Unit Tests" is visible +# Then the toolbar button with the text "Show Unit Test Output" is visible +# Then the toolbar button with the text "Run Failed Unit Tests" is not visible +# Then the toolbar button with the text "Stop" is not visible +# Then there are 9 success test items +# Then take a screenshot + +# @continue +# Scenario: Run with errors +# Given the file "tests/data.json" is updated with the value "[1,2,1,1,1,6]" +# When I select the command "Python: Run All Unit Tests" +# Then wait for 1 second +# Then wait for the toolbar button with the text "Run All Unit Tests" to appear within 5 seconds +# Then the toolbar button with the text "Run All Unit Tests" is visible +# Then the toolbar button with the text "Debug All Unit Tests" is visible +# Then the toolbar button with the text "Discover Unit Tests" is visible +# Then the toolbar button with the text "Show Unit Test Output" is visible +# Then the toolbar button with the text "Run Failed Unit Tests" is visible +# Then the toolbar button with the text "Stop" is not visible +# Then take a screenshot + +# @continue +# Scenario: Failed and succcess icon +# Then there are at least 4 error test items +# Then there are 5 success test items +# Then take a screenshot + +# @continue +# Scenario: Running single item +# Given the file "tests/data.json" is updated with the value "[1,2,3,4,5,6]" +# When I select the command "Python: Run All Unit Tests" +# Then wait for 1 second +# Then wait for the toolbar button with the text "Run All Unit Tests" to appear within 5 seconds +# Then select first node +# When I press "right" +# When I press "down" +# When I press "down" +# When I press "down" +# When I press "down" +# When I press "down" +# Given the file "tests/test_running_delay" is updated with the value "2" +# When I select test tree node number 6 and press run +# Then take a screenshot +# Then there are at least 1 running test items +# Then there are 8 success test items +# Then wait for the toolbar button with the text "Run All Unit Tests" to appear within 10 seconds +# Then there are 9 success test items +# Then take a screenshot + +# @continue +# Scenario: Running failed tests and still fail +# Given the file "tests/test_running_delay" is updated with the value "0" +# Given the file "tests/data.json" is updated with the value "[0,0,0,4,5,6]" +# When I select the command "Python: Run All Unit Tests" +# Then wait for 1 second +# Then wait for the toolbar button with the text "Run All Unit Tests" to appear within 5 seconds +# Then there are at least 6 error test items +# Then there are 3 success test items +# Then take a screenshot +# Given the file "tests/test_running_delay" is updated with the value "2" +# When I select the command "Python: Run Failed Unit Tests" +# Then wait for 1 second +# Then there are at least 6 running test items +# Then there are 3 success test items +# Then take a screenshot +# Then wait for the toolbar button with the text "Run All Unit Tests" to appear within 10 seconds +# Then there are at least 6 error test items +# Then there are 3 success test items +# Then take a screenshot + +# @continue +# Scenario: Running failed tests and all are fixed +# Given the file "tests/test_running_delay" is updated with the value "0" +# Given the file "tests/data.json" is updated with the value "[1,2,3,4,5,6]" +# When I select the command "Python: Run Failed Unit Tests" +# Then wait for 1 second +# Then wait for the toolbar button with the text "Run All Unit Tests" to appear within 5 seconds +# Then there are 9 success test items +# Then take a screenshot + +# @continue +# Scenario: Opens test file +# Then I select the command "View: Close All Editors" +# When I select test tree node number 2 +# Then the file "test_one.py" will be opened +# Then take a screenshot +# Then I select the command "View: Close All Editors" +# When I select test tree node number 3 +# Then the file "test_one.py" will be opened +# Then take a screenshot +# Then I select the command "View: Close All Editors" +# When I select test tree node number 11 +# Then the file "test_two.py" will be opened +# Then take a screenshot +# Then I select the command "View: Close All Editors" +# When I select test tree node number 12 +# Then the file "test_two.py" will be opened +# Then take a screenshot + +# @continue +# Scenario: Opens test file and sets focus +# When I select the command "View: Show Test" +# When I select test tree node number 3 +# When I select test tree node number 3 and press open +# Then line 20 of file "test_one.py" will be highlighted +# Then take a screenshot +# Then I select the command "View: Close All Editors" +# When I select test tree node number 12 +# When I select test tree node number 12 and press open +# Then the file "test_two.py" will be opened +# Then line 10 of file "test_two.py" will be highlighted +# Then take a screenshot + +# @continue +# Scenario: Debug all tests and add breakpoints to two files +# When I select the command "View: Show Test" +# Given the file ".vscode/launch.json" does not exist +# Given the file "test_one.py" is open +# Given the file is scrolled to the top +# Then I select the command "View: Close All Editors" +# Given the file "test_one.py" is open +# When I add a breakpoint to line 22 +# Given the file "test_two.py" is open +# Given the file is scrolled to the top +# Then take a screenshot +# Then I select the command "View: Close All Editors" +# Given the file "test_two.py" is open +# When I add a breakpoint to line 12 +# Then take a screenshot +# Then I select the command "View: Close All Editors" +# When I select the command "Python: Debug All Unit Tests" +# Then debugger starts +# Then stack frame for file "test_one.py" and line 22 is displayed +# Then take a screenshot +# When I select the command "Debug: Continue" +# Then stack frame for file "test_two.py" and line 12 is displayed +# Then take a screenshot +# When I select the command "Debug: Stop" +# Then I select the command "Debug: Remove All Breakpoints" +# Then I select the command "View: Close Panel" +# Then I select the command "View: Close All Editors" + +# @continue +# Scenario: Debug file with breakpoint +# Given the file ".vscode/launch.json" does not exist +# Given the file "test_one.py" is open +# Given the file is scrolled to the top +# Then I select the command "View: Close All Editors" +# Given the file "test_one.py" is open +# When I add a breakpoint to line 22 +# Then I select the command "View: Close All Editors" +# When I select the command "View: Show Test" +# When I select test tree node number 2 +# When I select test tree node number 2 and press debug +# Then debugger starts +# Then stack frame for file "test_one.py" and line 22 is displayed +# Then take a screenshot +# When I select the command "Debug: Stop" +# Then I select the command "Debug: Remove All Breakpoints" +# Then I select the command "View: Close Panel" +# Then I select the command "View: Close All Editors" + +# @continue +# Scenario: Debug suite with breakpoint +# Given the file ".vscode/launch.json" does not exist +# Given the file "test_one.py" is open +# Given the file is scrolled to the top +# Then I select the command "View: Close All Editors" +# Given the file "test_one.py" is open +# When I add a breakpoint to line 22 +# Then I select the command "View: Close All Editors" +# When I select the command "View: Show Test" +# When I select test tree node number 3 +# When I select test tree node number 3 and press debug +# Then debugger starts +# Then stack frame for file "test_one.py" and line 22 is displayed +# Then take a screenshot +# When I select the command "Debug: Stop" +# Then I select the command "Debug: Remove All Breakpoints" +# Then I select the command "View: Close Panel" +# Then I select the command "View: Close All Editors" + +# @continue +# Scenario: Debug function with breakpoint +# Given the file "test_one.py" is open +# Given the file is scrolled to the top +# When I close all editors +# Given the file "test_one.py" is open +# When I add a breakpoint to line 22 +# Then I select the command "View: Close All Editors" +# When I select the command "View: Show Test" +# When I select test tree node number 4 +# When I select test tree node number 4 and press debug +# Then debugger starts +# Then stack frame for file "test_one.py" and line 22 is displayed +# Then take a screenshot +# When I select the command "Debug: Stop" +# Then I select the command "Debug: Remove All Breakpoints" +# Then I select the command "View: Close Panel" +# Then I select the command "View: Close All Editors" + +# @continue +# Scenario: Code Lenses appear +# Given the file "test_one.py" is open +# Given the file is scrolled to the top +# Then code lens "Run Test" is visible in 5 seconds +# Then code lens "Debug Test" is visible +# Then take a screenshot + +# @continue +# Scenario: Running test suite via Code Lenses will display progress indicator on tree +# Given the file "tests/test_running_delay" is updated with the value "5" +# When I click first code lens "Run Test" +# When I select the command "View: Show Test" +# Then wait for 1 second +# Then there are at least 4 running test items +# Then the toolbar button with the text "Stop" is visible +# Then stop the tests + +# @continue +# Scenario: Running test function via Code Lenses will display progress indicator on tree +# When I click second code lens "Run Test" +# Then wait for 1 second +# Then there are 1 running test items +# Then the toolbar button with the text "Stop" is visible +# Then take a screenshot +# Then stop the tests + +# @continue +# Scenario: Debugging test suite via Code Lenses +# Given the file "tests/test_running_delay" is updated with the value "0" +# Given the file "test_one.py" is open +# Given the file is scrolled to the top +# When I add a breakpoint to line 22 +# When I select the command "View: Show Test" +# When I click first code lens "Debug Test" +# Then wait for 1 second +# Then debugger starts +# Then stack frame for file "test_one.py" and line 22 is displayed +# Then take a screenshot +# When I select the command "Debug: Stop" +# Then I select the command "View: Close Panel" +# Then I select the command "View: Close All Editors" + +# @continue +# Scenario: Debugging test function via Code Lenses +# Given the file "test_one.py" is open +# Given the file is scrolled to the top +# Then code lens "Run Test" is visible in 5 seconds +# When I click second code lens "Debug Test" +# Then wait for 1 second +# Then debugger starts +# Then stack frame for file "test_one.py" and line 22 is displayed +# Then take a screenshot +# When I select the command "Debug: Stop" +# Then I select the command "Debug: Remove All Breakpoints" +# Then I select the command "View: Close Panel" +# Then I select the command "View: Close All Editors" + +# # Scenario: Icons with no failures +# # Given the workspace is based on "/Users/donjayamanne/Desktop/Development/vscode/smokeTests/testing" +# # Given the file "tests/test_discovery_delay" is updated with the value "0" +# # Given the file "tests/test_running_delay" is updated with the value "0" +# # Given the file "tests/data.json" is updated with the value "[1,2,3,4,5,6]" +# # Then wait for 1 second +# # When I select the command "Python: Run All Unit Tests" +# # Then wait for 5 second +# # When I select the command "View: Show Test" +# # Then take a screenshot +# # Then the toolbar button with the text "Run All Unit Tests" is visible +# # Then the toolbar button with the text "Debug All Unit Tests" is visible +# # Then the toolbar button with the text "Discover Unit Tests" is visible +# # Then the toolbar button with the text "Show Unit Test Output" is visible +# # Then the toolbar button with the text "Run Failed Unit Tests" is not visible +# # Then the toolbar button with the text "Stop" is not visible + +# # Scenario: Icons with failures +# # Given the workspace is based on "/Users/donjayamanne/Desktop/Development/vscode/smokeTests/testing" +# # Given the file "tests/test_discovery_delay" is updated with the value "0" +# # Given the file "tests/test_running_delay" is updated with the value "0" +# # Given the file "tests/data.json" is updated with the value "[0,2,3,4,5,6]" +# # Then wait for 1 second +# # When I select the command "Python: Run All Unit Tests" +# # Then wait for 5 second +# # When I select the command "View: Show Test" +# # Then take a screenshot +# # Then the toolbar button with the text "Run All Unit Tests" is visible +# # Then the toolbar button with the text "Debug All Unit Tests" is visible +# # Then the toolbar button with the text "Discover Unit Tests" is visible +# # Then the toolbar button with the text "Show Unit Test Output" is visible +# # Then the toolbar button with the text "Run Failed Unit Tests" is visible +# # Then the toolbar button with the text "Stop" is not visible + +# # Scenario: Icons while discovering +# # Given the workspace is based on "/Users/donjayamanne/Desktop/Development/vscode/smokeTests/testing" +# # Given the file "tests/test_discovery_delay" is updated with the value "0" +# # Given the file "tests/test_running_delay" is updated with the value "0" +# # Then wait for 1 second +# # When I select the command "Python: Discover Unit Tests" +# # Then wait for 5 second +# # When I select the command "View: Show Test" +# # When I update file "tests/test_discovery_delay" with value "3" +# # Then wait for 1 second +# # When I select the command "Python: Discover Unit Tests" +# # Then wait for 1 second +# # Then take a screenshot +# # Then the toolbar button with the text "Run All Unit Tests" is not visible +# # Then the toolbar button with the text "Debug All Unit Tests" is not visible +# # # The `Discover Unit Tests` is still visible with a progress icon. +# # # Probably, we should change the tooltip at this point to `Discovering Tests` +# # Then the toolbar button with the text "Discover Unit Tests" is visible +# # Then the toolbar button with the text "Show Unit Test Output" is visible +# # Then the toolbar button with the text "Run Failed Unit Tests" is not visible +# # Then the toolbar button with the text "Stop" is visible +# # Then wait for 5 second +# # Then the toolbar button with the text "Stop" is not visible + +# # Scenario: Icons while running +# # Given the workspace is based on "/Users/donjayamanne/Desktop/Development/vscode/smokeTests/testing" +# # Given the file "tests/test_discovery_delay" is updated with the value "0" +# # Given the file "tests/test_running_delay" is updated with the value "0" +# # Then wait for 1 second +# # When I select the command "Python: Discover Unit Tests" +# # Then wait for 5 second +# # When I select the command "View: Show Test" +# # When I update file "tests/test_running_delay" with value "3" +# # Then wait for 1 second +# # When I select the command "Python: Run All Unit Tests" +# # Then wait for 1 second +# # Then take a screenshot +# # Then the toolbar button with the text "Run All Unit Tests" is not visible +# # Then the toolbar button with the text "Debug All Unit Tests" is not visible +# # Then the toolbar button with the text "Discover Unit Tests" is not visible +# # Then the toolbar button with the text "Show Unit Test Output" is visible +# # Then the toolbar button with the text "Run Failed Unit Tests" is not visible +# # Then the toolbar button with the text "Stop" is visible +# # Then wait for 10 second +# # Then the toolbar button with the text "Stop" is not visible + +# # Scenario: Stop discovering slow tests +# # Given the workspace is based on "/Users/donjayamanne/Desktop/Development/vscode/smokeTests/testing" +# # Given the file "tests/test_discovery_delay" is updated with the value "0" +# # Given the file "tests/test_running_delay" is updated with the value "0" +# # Given the file "tests/data.json" is updated with the value "[1,2,1,4,5,6]" +# # Then wait for 1 second +# # When I select the command "Python: Discover Unit Tests" +# # Then wait for 5 second +# # When I select the command "View: Show Test" +# # When I update file "tests/test_discovery_delay" with value "10" +# # Then wait for 1 second +# # When I select the command "Python: Discover Unit Tests" +# # Then wait for 1 second +# # Then take a screenshot +# # Then the toolbar button with the text "Run All Unit Tests" is not visible +# # Then the toolbar button with the text "Debug All Unit Tests" is not visible +# # # The `Discover Unit Tests` is still visible with a progress icon. +# # # Probably, we should change the tooltip at this point to `Discovering Tests` +# # Then the toolbar button with the text "Discover Unit Tests" is visible +# # Then the toolbar button with the text "Show Unit Test Output" is visible +# # Then the toolbar button with the text "Run Failed Unit Tests" is not visible +# # Then the toolbar button with the text "Stop" is visible +# # Then take a screenshot +# # Then wait for 5 second +# # When I stop the tests +# # Then wait for 5 second +# # Then the toolbar button with the text "Run All Unit Tests" is visible +# # Then the toolbar button with the text "Debug All Unit Tests" is visible +# # Then the toolbar button with the text "Discover Unit Tests" is visible +# # Then the toolbar button with the text "Show Unit Test Output" is visible +# # Then the toolbar button with the text "Run Failed Unit Tests" is not visible +# # Then the toolbar button with the text "Stop" is not visible +# # Then take a screenshot + +# # Scenario: Stop slow running tests +# # Given the workspace is based on "/Users/donjayamanne/Desktop/Development/vscode/smokeTests/testing" +# # Given the file "tests/test_discovery_delay" is updated with the value "0" +# # Given the file "tests/test_running_delay" is updated with the value "10" +# # Given the file "tests/data.json" is updated with the value "[1,2,1,4,5,6]" +# # Then wait for 1 second +# # When I select the command "Python: Discover Unit Tests" +# # Then wait for 5 second +# # When I select the command "View: Show Test" +# # Then wait for 1 second +# # When I select the command "Python: Run All Unit Tests" +# # Then wait for 1 second +# # Then take a screenshot +# # Then the toolbar button with the text "Run All Unit Tests" is not visible +# # Then the toolbar button with the text "Debug All Unit Tests" is not visible +# # Then the toolbar button with the text "Discover Unit Tests" is not visible +# # Then the toolbar button with the text "Show Unit Test Output" is visible +# # Then the toolbar button with the text "Run Failed Unit Tests" is not visible +# # Then the toolbar button with the text "Stop" is visible +# # Then take a screenshot +# # Then wait for 5 second +# # When I stop the tests +# # Then wait for 5 second +# # Then the toolbar button with the text "Run All Unit Tests" is visible +# # Then the toolbar button with the text "Debug All Unit Tests" is visible +# # Then the toolbar button with the text "Discover Unit Tests" is visible +# # Then the toolbar button with the text "Show Unit Test Output" is visible +# # Then the toolbar button with the text "Run Failed Unit Tests" is not visible +# # Then the toolbar button with the text "Stop" is not visible +# # Then take a screenshot + +# # Scenario: Failed and success icons +# # Given the workspace is based on "/Users/donjayamanne/Desktop/Development/vscode/smokeTests/testing" +# # Given the file "tests/test_discovery_delay" is updated with the value "0" +# # Given the file "tests/test_running_delay" is updated with the value "0" +# # Given the file "tests/data.json" is updated with the value "[1,2,1,1,1,6]" +# # Then wait for 1 second +# # When I select the command "Python: Discover Unit Tests" +# # Then wait for 5 second +# # When I select the command "View: Show Test" +# # Then select first node +# # When I press "down" +# # When I press "right" +# # When I press "down" +# # When I press "right" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # When I press "right" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # When I press "right" +# # When I press "down" +# # When I press "right" +# # When I select the command "Python: Run All Unit Tests" +# # Then wait for 5 second +# # Then there are at least 4 error test items +# # Then there are 5 success test items +# # Then take a screenshot +# # Given the file "tests/data.json" is updated with the value "[1,2,3,4,5,6]" +# # When I select the command "Python: Run All Unit Tests" +# # Then wait for 5 second +# # Then there are 9 success test items +# # Then take a screenshot + +# # Scenario: Running single item +# # Given the workspace is based on "/Users/donjayamanne/Desktop/Development/vscode/smokeTests/testing" +# # Given the file "tests/test_discovery_delay" is updated with the value "0" +# # Given the file "tests/test_running_delay" is updated with the value "0" +# # Given the file "tests/data.json" is updated with the value "[1,2,3,4,5,6]" +# # Then wait for 1 second +# # When I select the command "Python: Run All Unit Tests" +# # Then wait for 5 second +# # When I select the command "View: Show Test" +# # Then select first node +# # When I press "down" +# # When I press "right" +# # When I press "down" +# # When I press "right" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # When I press "right" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # When I press "right" +# # When I press "down" +# # When I press "right" +# # Then there are 9 success test items +# # Then select first node +# # When I press "right" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # Given the file "tests/test_running_delay" is updated with the value "2" +# # Then wait for 1 second +# # When I select test tree node number 6 and press run +# # Then there are at least 1 running test items +# # Then there are 8 success test items +# # Then take a screenshot +# # Then wait for 5 second +# # Then there are 9 success test items +# # Then take a screenshot + +# # Scenario: Running failed tests +# # Given the workspace is based on "/Users/donjayamanne/Desktop/Development/vscode/smokeTests/testing" +# # Given the file "tests/test_discovery_delay" is updated with the value "0" +# # Given the file "tests/test_running_delay" is updated with the value "0" +# # Given the file "tests/data.json" is updated with the value "[0,0,0,4,5,6]" +# # Then wait for 1 second +# # When I select the command "Python: Run All Unit Tests" +# # Then wait for 5 second +# # When I select the command "View: Show Test" +# # Then select first node +# # When I press "down" +# # When I press "right" +# # When I press "down" +# # When I press "right" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # When I press "right" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # When I press "right" +# # When I press "down" +# # When I press "right" +# # Then there are at least 6 error test items +# # Then there are 3 success test items +# # Then the toolbar button with the text "Run All Unit Tests" is visible +# # Then the toolbar button with the text "Run Failed Unit Tests" is visible +# # Then the toolbar button with the text "Debug All Unit Tests" is visible +# # Then the toolbar button with the text "Discover Unit Tests" is visible +# # Then the toolbar button with the text "Show Unit Test Output" is visible +# # Given the file "tests/test_running_delay" is updated with the value "2" +# # Then wait for 1 second +# # When I select the command "Python: Run Failed Unit Tests" +# # Then wait for 1 second +# # Then there are at least 6 running test items +# # Then there are 3 success test items +# # Then take a screenshot +# # Then wait for 10 second +# # Then there are at least 6 error test items +# # Then there are 3 success test items +# # Then take a screenshot +# # Given the file "tests/test_running_delay" is updated with the value "0" +# # Given the file "tests/data.json" is updated with the value "[1,2,3,4,5,6]" +# # Then wait for 1 second +# # When I select the command "Python: Run Failed Unit Tests" +# # Then wait for 5 second +# # Then there are 9 success test items +# # Then take a screenshot + +# # Scenario: Opens test file +# # Given the workspace is based on "/Users/donjayamanne/Desktop/Development/vscode/smokeTests/testing" +# # Given the file "tests/test_discovery_delay" is updated with the value "0" +# # Given the file "tests/test_running_delay" is updated with the value "0" +# # Given the file "tests/data.json" is updated with the value "[1,2,1,1,1,6]" +# # Then wait for 1 second +# # When I select the command "Python: Run All Unit Tests" +# # Then wait for 5 second +# # When I select the command "View: Show Test" +# # Then select first node +# # When I press "down" +# # When I press "right" +# # When I press "down" +# # When I press "right" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # When I press "right" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # When I press "right" +# # When I press "down" +# # When I press "right" +# # When I close all editors +# # When I select test tree node number 2 +# # Then the file "test_one.py" will be opened +# # Then take a screenshot +# # When I close all editors +# # When I select test tree node number 3 +# # Then the file "test_one.py" will be opened +# # Then take a screenshot +# # When I close all editors +# # When I select test tree node number 11 +# # Then the file "test_two.py" will be opened +# # Then take a screenshot +# # When I close all editors +# # When I select test tree node number 12 +# # Then the file "test_two.py" will be opened +# # Then take a screenshot + +# # Scenario: Opens test file and sets focus +# # Given the workspace is based on "/Users/donjayamanne/Desktop/Development/vscode/smokeTests/testing" +# # Given the file "tests/test_discovery_delay" is updated with the value "0" +# # Given the file "tests/test_running_delay" is updated with the value "0" +# # Given the file "tests/data.json" is updated with the value "[1,2,1,1,1,6]" +# # Then wait for 1 second +# # When I select the command "Python: Run All Unit Tests" +# # Then wait for 5 second +# # When I select the command "View: Show Test" +# # Then select first node +# # When I press "down" +# # When I press "right" +# # When I press "down" +# # When I press "right" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # When I press "right" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # When I press "right" +# # When I press "down" +# # When I press "right" +# # When I close all editors +# # When I select test tree node number 3 +# # When I select test tree node number 3 and press open +# # Then line 20 of file "test_one.py" will be highlighted +# # Then take a screenshot +# # When I close all editors +# # When I select test tree node number 12 +# # When I select test tree node number 12 and press open +# # Then the file "test_two.py" will be opened +# # Then line 10 of file "test_two.py" will be highlighted +# # Then take a screenshot + +# # @debug +# # Scenario: Debug all tests and add breakpoints to two files +# # Given the workspace is based on "/Users/donjayamanne/Desktop/Development/vscode/smokeTests/testing" +# # Given the file "tests/test_discovery_delay" is updated with the value "0" +# # Given the file "tests/test_running_delay" is updated with the value "0" +# # Given the file "tests/data.json" is updated with the value "[1,2,3,4,5,6]" +# # Given the file ".vscode/launch.json" does not exist +# # Given the file "test_one.py" is open +# # Given the file is scrolled to the top +# # When I close all editors +# # Given the file "test_one.py" is open +# # Then wait for 1 second +# # When I add a breakpoint to line 22 +# # Given the file "test_two.py" is open +# # Given the file is scrolled to the top +# # When I close all editors +# # Given the file "test_two.py" is open +# # Then wait for 1 second +# # When I add a breakpoint to line 12 +# # When I close all editors +# # Then wait for 1 second +# # When I select the command "Python: Debug All Unit Tests" +# # Then debugger starts +# # Then stack frame for file "test_one.py" and line 22 is displayed +# # Then take a screenshot +# # When I select the command "Debug: Continue" +# # Then stack frame for file "test_two.py" and line 12 is displayed +# # Then take a screenshot +# # When I select the command "Debug: Continue" +# # Then wait for 1 second +# # # Continue again, as the debugger breaks into sys.exit. +# # When I select the command "Debug: Continue" + +# # @debug +# # Scenario: Debug file with breakpoint +# # Given the workspace is based on "/Users/donjayamanne/Desktop/Development/vscode/smokeTests/testing" +# # Given the file "tests/test_discovery_delay" is updated with the value "0" +# # Given the file "tests/test_running_delay" is updated with the value "0" +# # Given the file "tests/data.json" is updated with the value "[1,2,3,4,5,6]" +# # Given the file ".vscode/launch.json" does not exist +# # Given the file "test_one.py" is open +# # Given the file is scrolled to the top +# # When I close all editors +# # Given the file "test_one.py" is open +# # Then wait for 1 second +# # When I add a breakpoint to line 22 +# # When I close all editors +# # When I select the command "Python: Discover Unit Tests" +# # Then wait for 5 second +# # When I select the command "View: Show Test" +# # Then select first node +# # When I press "down" +# # When I press "right" +# # When I press "down" +# # When I press "right" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # When I press "right" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # When I press "right" +# # When I press "down" +# # When I press "right" +# # When I select test tree node number 2 +# # When I select test tree node number 2 and press debug +# # Then debugger starts +# # Then stack frame for file "test_one.py" and line 22 is displayed +# # Then take a screenshot +# # When I select the command "Debug: Continue" +# # Then wait for 1 second +# # # Continue again, as the debugger breaks into sys.exit. +# # When I select the command "Debug: Continue" + +# # @debug +# # Scenario: Debug suite with breakpoint +# # Given the workspace is based on "/Users/donjayamanne/Desktop/Development/vscode/smokeTests/testing" +# # Given the file "tests/test_discovery_delay" is updated with the value "0" +# # Given the file "tests/test_running_delay" is updated with the value "0" +# # Given the file "tests/data.json" is updated with the value "[1,2,3,4,5,6]" +# # Given the file ".vscode/launch.json" does not exist +# # Given the file "test_one.py" is open +# # Given the file is scrolled to the top +# # When I close all editors +# # Given the file "test_one.py" is open +# # Then wait for 1 second +# # When I add a breakpoint to line 22 +# # When I close all editors +# # When I select the command "Python: Discover Unit Tests" +# # Then wait for 5 second +# # When I select the command "View: Show Test" +# # Then select first node +# # When I press "down" +# # When I press "right" +# # When I press "down" +# # When I press "right" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # When I press "right" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # When I press "right" +# # When I press "down" +# # When I press "right" +# # When I select test tree node number 3 +# # When I select test tree node number 3 and press debug +# # Then debugger starts +# # Then stack frame for file "test_one.py" and line 22 is displayed +# # Then take a screenshot +# # When I select the command "Debug: Continue" +# # Then wait for 1 second +# # # Continue again, as the debugger breaks into sys.exit. +# # When I select the command "Debug: Continue" + +# # @debug +# # Scenario: Debug function with breakpoint +# # Given the workspace is based on "/Users/donjayamanne/Desktop/Development/vscode/smokeTests/testing" +# # Given the file "tests/test_discovery_delay" is updated with the value "0" +# # Given the file "tests/test_running_delay" is updated with the value "0" +# # Given the file "tests/data.json" is updated with the value "[1,2,3,4,5,6]" +# # Given the file ".vscode/launch.json" does not exist +# # Given the file "test_one.py" is open +# # Given the file is scrolled to the top +# # When I close all editors +# # Given the file "test_one.py" is open +# # Then wait for 1 second +# # When I add a breakpoint to line 22 +# # When I close all editors +# # When I select the command "Python: Discover Unit Tests" +# # Then wait for 5 second +# # When I select the command "View: Show Test" +# # Then select first node +# # When I press "down" +# # When I press "right" +# # When I press "down" +# # When I press "right" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # When I press "right" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # When I press "right" +# # When I press "down" +# # When I press "right" +# # When I select test tree node number 4 +# # When I select test tree node number 4 and press debug +# # Then debugger starts +# # Then stack frame for file "test_one.py" and line 22 is displayed +# # Then take a screenshot +# # When I select the command "Debug: Continue" +# # Then wait for 1 second +# # # Continue again, as the debugger breaks into sys.exit. +# # When I select the command "Debug: Continue" + +# # @codelens +# # Scenario: Code Lenses appear +# # Given the workspace is based on "/Users/donjayamanne/Desktop/Development/vscode/smokeTests/testing" +# # Given the file "tests/test_discovery_delay" is updated with the value "0" +# # Given the file "tests/test_running_delay" is updated with the value "0" +# # When I select the command "Python: Discover Unit Tests" +# # Then wait for 5 second +# # Given the file "test_one.py" is open +# # Given the file is scrolled to the top +# # Then wait for 5 second +# # Then code lens "Run Test" is visible +# # Then code lens "Debug Test" is visible + +# # @codelens +# # Scenario: Running test suite via Code Lenses will display progress indicator on tree +# # Given the workspace is based on "/Users/donjayamanne/Desktop/Development/vscode/smokeTests/testing" +# # Given the file "tests/test_discovery_delay" is updated with the value "0" +# # Given the file "tests/test_running_delay" is updated with the value "2" +# # When I select the command "Python: Discover Unit Tests" +# # Then wait for 5 second +# # When I select the command "View: Show Test" +# # Then select first node +# # When I press "down" +# # When I press "right" +# # When I press "down" +# # When I press "right" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # When I press "right" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # When I press "right" +# # When I press "down" +# # When I press "right" +# # Given the file "test_one.py" is open +# # Given the file is scrolled to the top +# # Then wait for 5 second +# # When I click first code lens "Run Test" +# # Then wait for 1 second +# # Then there are at least 4 running test items +# # Then the toolbar button with the text "Stop" is visible +# # Then wait for 10 second +# # Then the toolbar button with the text "Stop" is not visible + +# # @codelens +# # Scenario: Running test function via Code Lenses will display progress indicator on tree +# # Given the workspace is based on "/Users/donjayamanne/Desktop/Development/vscode/smokeTests/testing" +# # Given the file "tests/test_discovery_delay" is updated with the value "0" +# # Given the file "tests/test_running_delay" is updated with the value "2" +# # When I select the command "Python: Discover Unit Tests" +# # Then wait for 5 second +# # When I select the command "View: Show Test" +# # Then select first node +# # When I press "down" +# # When I press "right" +# # When I press "down" +# # When I press "right" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # When I press "right" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # When I press "right" +# # When I press "down" +# # When I press "right" +# # Given the file "test_one.py" is open +# # Given the file is scrolled to the top +# # Then wait for 5 second +# # When I click second code lens "Run Test" +# # Then wait for 1 second +# # Then there are 1 running test items +# # Then the toolbar button with the text "Stop" is visible +# # Then wait for 10 second +# # Then the toolbar button with the text "Stop" is not visible + +# # @codelens @debug +# # Scenario: Debugging test suite via Code Lenses +# # Given the workspace is based on "/Users/donjayamanne/Desktop/Development/vscode/smokeTests/testing" +# # Given the file "tests/test_discovery_delay" is updated with the value "0" +# # Given the file "tests/test_running_delay" is updated with the value "0" +# # Given the file "test_one.py" is open +# # Given the file is scrolled to the top +# # When I close all editors +# # Given the file "test_one.py" is open +# # Then wait for 1 second +# # When I add a breakpoint to line 22 +# # When I add a breakpoint to line 27 +# # When I add a breakpoint to line 32 +# # When I close all editors +# # When I select the command "Python: Discover Unit Tests" +# # Then wait for 5 second +# # When I select the command "View: Show Test" +# # Then select first node +# # When I press "down" +# # When I press "right" +# # When I press "down" +# # When I press "right" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # When I press "right" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # When I press "right" +# # When I press "down" +# # When I press "right" +# # Given the file "test_one.py" is open +# # Given the file is scrolled to the top +# # Then wait for 5 second +# # When I click first code lens "Debug Test" +# # Then wait for 1 second +# # Then debugger starts +# # Then stack frame for file "test_one.py" and line 22 is displayed +# # Then take a screenshot +# # When I select the command "Debug: Continue" +# # Then stack frame for file "test_one.py" and line 32 is displayed +# # Then take a screenshot +# # When I select the command "Debug: Continue" +# # Then stack frame for file "test_one.py" and line 27 is displayed +# # Then take a screenshot +# # When I select the command "Debug: Continue" +# # Then wait for 1 second +# # # Continue again, as the debugger breaks into sys.exit. +# # When I select the command "Debug: Continue" + +# # Scenario: Debugging test function via Code Lenses +# # Given the workspace is based on "/Users/donjayamanne/Desktop/Development/vscode/smokeTests/testing" +# # Given the file "tests/test_discovery_delay" is updated with the value "0" +# # Given the file "tests/test_running_delay" is updated with the value "0" +# # Given the file "test_one.py" is open +# # Given the file is scrolled to the top +# # When I close all editors +# # Given the file "test_one.py" is open +# # Then wait for 1 second +# # When I add a breakpoint to line 22 +# # When I add a breakpoint to line 27 +# # When I add a breakpoint to line 32 +# # When I close all editors +# # When I select the command "Python: Discover Unit Tests" +# # Then wait for 5 second +# # When I select the command "View: Show Test" +# # Then select first node +# # When I press "down" +# # When I press "right" +# # When I press "down" +# # When I press "right" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # When I press "right" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # When I press "right" +# # When I press "down" +# # When I press "right" +# # Given the file "test_one.py" is open +# # Given the file is scrolled to the top +# # Then wait for 5 second +# # When I click second code lens "Debug Test" +# # Then wait for 1 second +# # Then debugger starts +# # Then stack frame for file "test_one.py" and line 22 is displayed +# # Then take a screenshot +# # # Continue again, as the debugger breaks into sys.exit. +# # When I select the command "Debug: Continue" +# # When I select the command "Debug: Continue" +# # Then debugger stops diff --git a/uitests/features/testing/testing.run.debug.codelens.feature b/uitests/features/testing/testing.run.debug.codelens.feature new file mode 100644 index 000000000000..11fbe5210801 --- /dev/null +++ b/uitests/features/testing/testing.run.debug.codelens.feature @@ -0,0 +1,143 @@ +# @testing @ci @debug @run +# @/Users/donjayamanne/Desktop/Development/vscode/smokeTests/testing +# Feature: Testing (run, debug, code lenses) +# Background: Set up tests +# Given the problems panel is open +# Given the workspace is based on "/Users/donjayamanne/Desktop/Development/vscode/smokeTests/testing" +# Given the file "tests/test_discovery_delay" is updated with the value "0" +# Given the file "tests/test_running_delay" is updated with the value "0" +# Given the file "tests/data.json" is updated with the value "[1,2,3,4,5,6]" +# Given the file ".vscode/launch.json" does not exist +# When I select the command "Python: Discover Unit Tests" +# Then wait for 1 second +# Then wait for the test icon to appear within 5 seconds +# When I select the command "View: Show Test" +# Then take a screenshot +# Then the toolbar button with the text "Run All Unit Tests" is visible +# Then the toolbar button with the text "Debug All Unit Tests" is visible +# Then the toolbar button with the text "Discover Unit Tests" is visible +# Then the toolbar button with the text "Show Unit Test Output" is visible +# Then the toolbar button with the text "Run Failed Unit Tests" is not visible +# Then the toolbar button with the text "Stop" is not visible +# Then expand test explorer tree + +# @scenario1 +# Scenario: Debug all tests and add breakpoints to two files +# Given the file "test_one.py" is open +# Given the file is scrolled to the top +# When I close all editors +# Given the file "test_one.py" is open +# When I add a breakpoint to line 22 +# Given the file "test_two.py" is open +# Given the file is scrolled to the top +# When I close all editors +# Given the file "test_two.py" is open +# When I add a breakpoint to line 12 +# When I close all editors +# When I select the command "Python: Debug All Unit Tests" +# Then debugger starts +# Then stack frame for file "test_one.py" and line 22 is displayed +# Then take a screenshot +# When I select the command "Debug: Continue" +# Then stack frame for file "test_two.py" and line 12 is displayed + +# @scenario2 +# Scenario: Debug file by clicking a node with breakpoint +# Given the file "test_one.py" is open +# Given the file is scrolled to the top +# When I close all editors +# Given the file "test_one.py" is open +# When I add a breakpoint to line 22 +# When I close all editors +# When I select test tree node number 2 and press debug +# Then debugger starts +# Then stack frame for file "test_one.py" and line 22 is displayed + +# @scenario3 +# Scenario: Debug suite with breakpoint +# Given the file "test_one.py" is open +# Given the file is scrolled to the top +# When I close all editors +# Given the file "test_one.py" is open +# When I add a breakpoint to line 22 +# When I close all editors +# When I select test tree node number 3 and press debug +# Then debugger starts +# Then stack frame for file "test_one.py" and line 22 is displayed + +# @scenario3 +# Scenario: Debug function with breakpoint +# Given the file "test_one.py" is open +# Given the file is scrolled to the top +# When I close all editors +# Given the file "test_one.py" is open +# When I add a breakpoint to line 22 +# When I close all editors +# When I select test tree node number 4 and press debug +# Then debugger starts +# Then stack frame for file "test_one.py" and line 22 is displayed + +# @scenario4 +# Scenario: Code Lenses appear +# Given the file "test_one.py" is open +# Given the file is scrolled to the top +# When I close all editors +# Given the file "test_one.py" is open +# Then code lens "Run Test" is visible in 5 seconds +# Then code lens "Debug Test" is visible + +# @scenario5 +# Scenario: Running test suite via Code Lenses will display progress indicator on tree +# Given the file "tests/test_running_delay" is updated with the value "5" +# Given the file "test_one.py" is open +# Given the file is scrolled to the top +# When I close all editors +# Given the file "test_one.py" is open +# Then code lens "Run Test" is visible in 5 seconds +# When I click first code lens "Run Test" +# When I select the command "View: Show Test" +# Then wait for 1 second +# Then there are at least 4 running test items +# Then the toolbar button with the text "Stop" is visible +# Then stop the tests + +# @scenario6 +# Scenario: Running test function via Code Lenses will display progress indicator on tree +# Given the file "tests/test_running_delay" is updated with the value "5" +# Given the file "test_one.py" is open +# Given the file is scrolled to the top +# When I close all editors +# Given the file "test_one.py" is open +# Then code lens "Run Test" is visible in 5 seconds +# When I click second code lens "Run Test" +# Then wait for 1 second +# Then there are 1 running test items +# Then the toolbar button with the text "Stop" is visible +# Then take a screenshot +# Then stop the tests + +# @scenario7 +# Scenario: Debugging test suite via Code Lenses +# Given the file "test_one.py" is open +# Given the file is scrolled to the top +# When I close all editors +# Given the file "test_one.py" is open +# When I add a breakpoint to line 22 +# When I select the command "View: Show Test" +# When I click first code lens "Debug Test" +# Then wait for 1 second +# Then debugger starts +# Then stack frame for file "test_one.py" and line 22 is displayed + +# @scenario8 +# Scenario: Debugging test function via Code Lenses +# Given the file "test_one.py" is open +# Given the file is scrolled to the top +# When I close all editors +# Given the file "test_one.py" is open +# When I add a breakpoint to line 22 +# Then code lens "Run Test" is visible in 5 seconds +# When I click second code lens "Debug Test" +# Then wait for 1 second +# Then debugger starts +# Then stack frame for file "test_one.py" and line 22 is displayed diff --git a/uitests/features/testing/testing.toolbar.feature b/uitests/features/testing/testing.toolbar.feature new file mode 100644 index 000000000000..5e48cf33cf84 --- /dev/null +++ b/uitests/features/testing/testing.toolbar.feature @@ -0,0 +1,149 @@ +# @testing @ci @toolbar +# @/Users/donjayamanne/Desktop/Development/vscode/smokeTests/testing +# Feature: Testing (toolbar) +# Background: Set up tests +# Given the workspace is based on "/Users/donjayamanne/Desktop/Development/vscode/smokeTests/testing" +# Given the file "tests/test_discovery_delay" is updated with the value "0" +# Given the file "tests/test_running_delay" is updated with the value "0" +# Given the file "tests/data.json" is updated with the value "[1,2,3,4,5,6]" +# When I select the command "Python: Discover Unit Tests" +# Then wait for 1 second +# Then wait for the test icon to appear within 5 seconds +# When I select the command "View: Show Test" +# Then take a screenshot +# Then the toolbar button with the text "Run All Unit Tests" is visible +# Then the toolbar button with the text "Debug All Unit Tests" is visible +# Then the toolbar button with the text "Discover Unit Tests" is visible +# Then the toolbar button with the text "Show Unit Test Output" is visible +# Then the toolbar button with the text "Run Failed Unit Tests" is not visible +# Then the toolbar button with the text "Stop" is not visible +# Then expand test explorer tree + +# @scenario1 +# Scenario: Icons with no failures +# When I select the command "Python: Run All Unit Tests" +# Then wait for 1 second +# Then wait for the toolbar button with the text "Run All Unit Tests" to appear within 5 seconds +# Then the toolbar button with the text "Run All Unit Tests" is visible +# Then the toolbar button with the text "Debug All Unit Tests" is visible +# Then the toolbar button with the text "Discover Unit Tests" is visible +# Then the toolbar button with the text "Show Unit Test Output" is visible +# Then the toolbar button with the text "Run Failed Unit Tests" is not visible +# Then the toolbar button with the text "Stop" is not visible + +# @scenario2 +# Scenario: Icons with failures and then no failures +# Given the file "tests/data.json" is updated with the value "[0,2,3,4,5,6]" +# When I select the command "Python: Run All Unit Tests" +# Then wait for the toolbar button with the text "Run All Unit Tests" to appear within 5 seconds +# Then the toolbar button with the text "Run All Unit Tests" is visible +# Then the toolbar button with the text "Debug All Unit Tests" is visible +# Then the toolbar button with the text "Discover Unit Tests" is visible +# Then the toolbar button with the text "Show Unit Test Output" is visible +# Then the toolbar button with the text "Run Failed Unit Tests" is visible +# Then the toolbar button with the text "Stop" is not visible +# Given the file "tests/data.json" is updated with the value "[1,2,3,4,5,6]" +# When I select the command "Python: Run All Unit Tests" +# Then wait for 1 second +# Then wait for the toolbar button with the text "Run All Unit Tests" to appear within 5 seconds +# Then the toolbar button with the text "Run All Unit Tests" is visible +# Then the toolbar button with the text "Debug All Unit Tests" is visible +# Then the toolbar button with the text "Discover Unit Tests" is visible +# Then the toolbar button with the text "Show Unit Test Output" is visible +# Then the toolbar button with the text "Run Failed Unit Tests" is not visible +# Then the toolbar button with the text "Stop" is not visible + +# @scenario3 +# Scenario: Icons while discovering +# When I update file "tests/test_discovery_delay" with value "3" +# When I select the command "Python: Discover Unit Tests" +# Then wait for 1 second +# Then the toolbar button with the text "Run All Unit Tests" is not visible +# Then the toolbar button with the text "Debug All Unit Tests" is not visible +# # The `Discover Unit Tests` is still visible with a progress icon. +# # Probably, we should change the tooltip at this point to `Discovering Tests` +# Then the toolbar button with the text "Discover Unit Tests" is visible +# Then the toolbar button with the text "Show Unit Test Output" is visible +# Then the toolbar button with the text "Run Failed Unit Tests" is not visible +# Then the toolbar button with the text "Stop" is visible +# Then take a screenshot +# Then wait for the toolbar button with the text "Run All Unit Tests" to appear within 10 seconds +# Then the toolbar button with the text "Stop" is not visible + +# @scenario4 +# Scenario: Icons while running +# When I update file "tests/test_running_delay" with value "3" +# When I select the command "Python: Run All Unit Tests" +# Then wait for 1 second +# Then the toolbar button with the text "Run All Unit Tests" is not visible +# Then the toolbar button with the text "Debug All Unit Tests" is not visible +# Then the toolbar button with the text "Discover Unit Tests" is not visible +# Then the toolbar button with the text "Show Unit Test Output" is visible +# Then the toolbar button with the text "Run Failed Unit Tests" is not visible +# Then the toolbar button with the text "Stop" is visible +# Then take a screenshot +# Then wait for the toolbar button with the text "Run All Unit Tests" to appear within 10 seconds +# Then the toolbar button with the text "Stop" is not visible + +# @scenario5 +# Scenario: Stop discovering slow tests +# When I update file "tests/test_discovery_delay" with value "10" +# When I select the command "Python: Discover Unit Tests" +# Then wait for 1 second +# Then the toolbar button with the text "Run All Unit Tests" is not visible +# Then the toolbar button with the text "Debug All Unit Tests" is not visible +# # The `Discover Unit Tests` is still visible with a progress icon. +# # Probably, we should change the tooltip at this point to `Discovering Tests` +# Then the toolbar button with the text "Discover Unit Tests" is visible +# Then the toolbar button with the text "Show Unit Test Output" is visible +# Then the toolbar button with the text "Run Failed Unit Tests" is not visible +# Then the toolbar button with the text "Stop" is visible +# Then take a screenshot +# When I stop the tests +# Then wait for 2 second +# Then the toolbar button with the text "Run All Unit Tests" is visible +# Then the toolbar button with the text "Debug All Unit Tests" is visible +# Then the toolbar button with the text "Discover Unit Tests" is visible +# Then the toolbar button with the text "Show Unit Test Output" is visible +# Then the toolbar button with the text "Run Failed Unit Tests" is not visible +# Then the toolbar button with the text "Stop" is not visible +# Then take a screenshot + +# @scenario6 +# Scenario: Stop slow running tests +# Given the file "tests/test_running_delay" is updated with the value "10" +# Given the file "tests/data.json" is updated with the value "[1,2,1,4,5,6]" +# When I select the command "Python: Run All Unit Tests" +# Then wait for 1 second +# Then the toolbar button with the text "Run All Unit Tests" is not visible +# Then the toolbar button with the text "Debug All Unit Tests" is not visible +# Then the toolbar button with the text "Discover Unit Tests" is not visible +# Then the toolbar button with the text "Show Unit Test Output" is visible +# Then the toolbar button with the text "Run Failed Unit Tests" is not visible +# Then the toolbar button with the text "Stop" is visible +# Then take a screenshot +# When I stop the tests +# Then wait for 2 second +# Then the toolbar button with the text "Run All Unit Tests" is visible +# Then the toolbar button with the text "Debug All Unit Tests" is visible +# Then the toolbar button with the text "Discover Unit Tests" is visible +# Then the toolbar button with the text "Show Unit Test Output" is visible +# Then the toolbar button with the text "Run Failed Unit Tests" is not visible +# Then the toolbar button with the text "Stop" is not visible +# Then take a screenshot + +# @scenario7 +# Scenario: Failed and success icons +# Given the file "tests/data.json" is updated with the value "[1,2,1,1,1,6]" +# When I select the command "Python: Run All Unit Tests" +# Then wait for 1 second +# Then wait for the toolbar button with the text "Run All Unit Tests" to appear within 5 seconds +# Then there are at least 4 error test items +# Then there are 5 success test items +# Then take a screenshot +# Given the file "tests/data.json" is updated with the value "[1,2,3,4,5,6]" +# When I select the command "Python: Run All Unit Tests" +# Then wait for 1 second +# Then wait for the toolbar button with the text "Run All Unit Tests" to appear within 5 seconds +# Then there are 9 success test items +# Then take a screenshot diff --git a/uitests/features/testing/testing.tree.feature b/uitests/features/testing/testing.tree.feature new file mode 100644 index 000000000000..c258c89bf756 --- /dev/null +++ b/uitests/features/testing/testing.tree.feature @@ -0,0 +1,451 @@ +# @testing @ci @tree +# @/Users/donjayamanne/Desktop/Development/vscode/smokeTests/testing +# Feature: Testing (tree view) +# Background: Set up tests +# Given the workspace is based on "/Users/donjayamanne/Desktop/Development/vscode/smokeTests/testing" +# Given the file "tests/test_discovery_delay" is updated with the value "0" +# Given the file "tests/test_running_delay" is updated with the value "0" +# Given the file "tests/data.json" is updated with the value "[1,2,3,4,5,6]" +# When I select the command "Python: Discover Unit Tests" +# Then wait for 1 second +# Then wait for the test icon to appear within 5 seconds +# When I select the command "View: Show Test" +# Then take a screenshot +# Then the toolbar button with the text "Run All Unit Tests" is visible +# Then the toolbar button with the text "Debug All Unit Tests" is visible +# Then the toolbar button with the text "Discover Unit Tests" is visible +# Then the toolbar button with the text "Show Unit Test Output" is visible +# Then the toolbar button with the text "Run Failed Unit Tests" is not visible +# Then the toolbar button with the text "Stop" is not visible +# Then expand test explorer tree + +# @scenario1 +# Scenario: Successful tree nodes +# When I select the command "Python: Run All Unit Tests" +# Then wait for 1 second +# Then wait for the toolbar button with the text "Run All Unit Tests" to appear within 5 seconds +# Then there are 9 success test items + +# @scenario2 +# Scenario: Running single item +# When I select the command "Python: Run All Unit Tests" +# Then wait for 1 second +# Then wait for the toolbar button with the text "Run All Unit Tests" to appear within 5 seconds +# Given the file "tests/test_running_delay" is updated with the value "2" +# When I select test tree node number 6 and press run +# Then there are at least 1 running test items +# Then there are 8 success test items +# Then take a screenshot +# Then wait for the toolbar button with the text "Run All Unit Tests" to appear within 5 seconds +# Then there are 9 success test items +# Then take a screenshot + +# @scenario3 +# Scenario: Running failed tests, run agian and then fix and run again +# Given the file "tests/data.json" is updated with the value "[0,0,0,4,5,6]" +# When I select the command "Python: Run All Unit Tests" +# Then wait for the toolbar button with the text "Run All Unit Tests" to appear within 5 seconds +# Then there are at least 6 error test items +# Then there are 3 success test items +# Then the toolbar button with the text "Run All Unit Tests" is visible +# Then the toolbar button with the text "Run Failed Unit Tests" is visible +# Then the toolbar button with the text "Debug All Unit Tests" is visible +# Then the toolbar button with the text "Discover Unit Tests" is visible +# Then the toolbar button with the text "Show Unit Test Output" is visible +# Given the file "tests/test_running_delay" is updated with the value "2" +# When I select the command "Python: Run Failed Unit Tests" +# Then wait for 1 second +# Then there are at least 6 running test items +# Then there are 3 success test items +# Then take a screenshot +# Then wait for the toolbar button with the text "Run All Unit Tests" to appear within 5 seconds +# Then there are at least 6 error test items +# Then there are 3 success test items +# Then take a screenshot +# Given the file "tests/test_running_delay" is updated with the value "0" +# Given the file "tests/data.json" is updated with the value "[1,2,3,4,5,6]" +# When I select the command "Python: Run Failed Unit Tests" +# Then wait for 1 second +# Then wait for the toolbar button with the text "Run All Unit Tests" to appear within 5 seconds +# Then there are 9 success test items +# Then take a screenshot + +# @scenario4 +# Scenario: Opens test file +# When I close all editors +# When I select test tree node number 2 +# Then the file "test_one.py" will be opened +# Then take a screenshot +# When I close all editors +# When I select test tree node number 3 +# Then the file "test_one.py" will be opened +# Then take a screenshot +# When I close all editors +# When I select test tree node number 11 +# Then the file "test_two.py" will be opened +# Then take a screenshot +# When I close all editors +# When I select test tree node number 12 +# Then the file "test_two.py" will be opened +# Then take a screenshot + +# @scenario5 +# Scenario: Opens test file and sets focus +# When I close all editors +# When I select test tree node number 3 +# When I select test tree node number 3 and press open +# Then line 20 of file "test_one.py" will be highlighted +# Then take a screenshot +# When I close all editors +# When I select test tree node number 12 +# When I select test tree node number 12 and press open +# Then the file "test_two.py" will be opened +# Then line 10 of file "test_two.py" will be highlighted +# Then take a screenshot + +# # @debug +# # Scenario: Debug all tests and add breakpoints to two files +# # Given the workspace is based on "/Users/donjayamanne/Desktop/Development/vscode/smokeTests/testing" +# # Given the file "tests/test_discovery_delay" is updated with the value "0" +# # Given the file "tests/test_running_delay" is updated with the value "0" +# # Given the file "tests/data.json" is updated with the value "[1,2,3,4,5,6]" +# # Given the file ".vscode/launch.json" does not exist +# # Given the file "test_one.py" is open +# # Given the file is scrolled to the top +# # When I close all editors +# # Given the file "test_one.py" is open +# # Then wait for 1 second +# # When I add a breakpoint to line 22 +# # Given the file "test_two.py" is open +# # Given the file is scrolled to the top +# # When I close all editors +# # Given the file "test_two.py" is open +# # Then wait for 1 second +# # When I add a breakpoint to line 12 +# # When I close all editors +# # Then wait for 1 second +# # When I select the command "Python: Debug All Unit Tests" +# # Then debugger starts +# # Then stack frame for file "test_one.py" and line 22 is displayed +# # Then take a screenshot +# # When I select the command "Debug: Continue" +# # Then stack frame for file "test_two.py" and line 12 is displayed +# # Then take a screenshot +# # When I select the command "Debug: Continue" +# # Then wait for 1 second +# # # Continue again, as the debugger breaks into sys.exit. +# # When I select the command "Debug: Continue" + +# # @debug +# # Scenario: Debug file with breakpoint +# # Given the workspace is based on "/Users/donjayamanne/Desktop/Development/vscode/smokeTests/testing" +# # Given the file "tests/test_discovery_delay" is updated with the value "0" +# # Given the file "tests/test_running_delay" is updated with the value "0" +# # Given the file "tests/data.json" is updated with the value "[1,2,3,4,5,6]" +# # Given the file ".vscode/launch.json" does not exist +# # Given the file "test_one.py" is open +# # Given the file is scrolled to the top +# # When I close all editors +# # Given the file "test_one.py" is open +# # Then wait for 1 second +# # When I add a breakpoint to line 22 +# # When I close all editors +# # When I select the command "Python: Discover Unit Tests" +# # Then wait for 5 second +# # When I select the command "View: Show Test" +# # Then select first node +# # When I press "down" +# # When I press "right" +# # When I press "down" +# # When I press "right" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # When I press "right" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # When I press "right" +# # When I press "down" +# # When I press "right" +# # When I select test tree node number 2 +# # When I select test tree node number 2 and press debug +# # Then debugger starts +# # Then stack frame for file "test_one.py" and line 22 is displayed +# # Then take a screenshot +# # When I select the command "Debug: Continue" +# # Then wait for 1 second +# # # Continue again, as the debugger breaks into sys.exit. +# # When I select the command "Debug: Continue" + +# # @debug +# # Scenario: Debug suite with breakpoint +# # Given the workspace is based on "/Users/donjayamanne/Desktop/Development/vscode/smokeTests/testing" +# # Given the file "tests/test_discovery_delay" is updated with the value "0" +# # Given the file "tests/test_running_delay" is updated with the value "0" +# # Given the file "tests/data.json" is updated with the value "[1,2,3,4,5,6]" +# # Given the file ".vscode/launch.json" does not exist +# # Given the file "test_one.py" is open +# # Given the file is scrolled to the top +# # When I close all editors +# # Given the file "test_one.py" is open +# # Then wait for 1 second +# # When I add a breakpoint to line 22 +# # When I close all editors +# # When I select the command "Python: Discover Unit Tests" +# # Then wait for 5 second +# # When I select the command "View: Show Test" +# # Then select first node +# # When I press "down" +# # When I press "right" +# # When I press "down" +# # When I press "right" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # When I press "right" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # When I press "right" +# # When I press "down" +# # When I press "right" +# # When I select test tree node number 3 +# # When I select test tree node number 3 and press debug +# # Then debugger starts +# # Then stack frame for file "test_one.py" and line 22 is displayed +# # Then take a screenshot +# # When I select the command "Debug: Continue" +# # Then wait for 1 second +# # # Continue again, as the debugger breaks into sys.exit. +# # When I select the command "Debug: Continue" + +# # @debug +# # Scenario: Debug function with breakpoint +# # Given the workspace is based on "/Users/donjayamanne/Desktop/Development/vscode/smokeTests/testing" +# # Given the file "tests/test_discovery_delay" is updated with the value "0" +# # Given the file "tests/test_running_delay" is updated with the value "0" +# # Given the file "tests/data.json" is updated with the value "[1,2,3,4,5,6]" +# # Given the file ".vscode/launch.json" does not exist +# # Given the file "test_one.py" is open +# # Given the file is scrolled to the top +# # When I close all editors +# # Given the file "test_one.py" is open +# # Then wait for 1 second +# # When I add a breakpoint to line 22 +# # When I close all editors +# # When I select the command "Python: Discover Unit Tests" +# # Then wait for 5 second +# # When I select the command "View: Show Test" +# # Then select first node +# # When I press "down" +# # When I press "right" +# # When I press "down" +# # When I press "right" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # When I press "right" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # When I press "right" +# # When I press "down" +# # When I press "right" +# # When I select test tree node number 4 +# # When I select test tree node number 4 and press debug +# # Then debugger starts +# # Then stack frame for file "test_one.py" and line 22 is displayed +# # Then take a screenshot +# # When I select the command "Debug: Continue" +# # Then wait for 1 second +# # # Continue again, as the debugger breaks into sys.exit. +# # When I select the command "Debug: Continue" + +# # @codelens +# # Scenario: Code Lenses appear +# # Given the workspace is based on "/Users/donjayamanne/Desktop/Development/vscode/smokeTests/testing" +# # Given the file "tests/test_discovery_delay" is updated with the value "0" +# # Given the file "tests/test_running_delay" is updated with the value "0" +# # When I select the command "Python: Discover Unit Tests" +# # Then wait for 5 second +# # Given the file "test_one.py" is open +# # Given the file is scrolled to the top +# # Then wait for 5 second +# # Then code lens "Run Test" is visible +# # Then code lens "Debug Test" is visible + +# # @codelens +# # Scenario: Running test suite via Code Lenses will display progress indicator on tree +# # Given the workspace is based on "/Users/donjayamanne/Desktop/Development/vscode/smokeTests/testing" +# # Given the file "tests/test_discovery_delay" is updated with the value "0" +# # Given the file "tests/test_running_delay" is updated with the value "2" +# # When I select the command "Python: Discover Unit Tests" +# # Then wait for 5 second +# # When I select the command "View: Show Test" +# # Then select first node +# # When I press "down" +# # When I press "right" +# # When I press "down" +# # When I press "right" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # When I press "right" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # When I press "right" +# # When I press "down" +# # When I press "right" +# # Given the file "test_one.py" is open +# # Given the file is scrolled to the top +# # Then wait for 5 second +# # When I click first code lens "Run Test" +# # Then wait for 1 second +# # Then there are at least 4 running test items +# # Then the toolbar button with the text "Stop" is visible +# # Then wait for 10 second +# # Then the toolbar button with the text "Stop" is not visible + +# # @codelens +# # Scenario: Running test function via Code Lenses will display progress indicator on tree +# # Given the workspace is based on "/Users/donjayamanne/Desktop/Development/vscode/smokeTests/testing" +# # Given the file "tests/test_discovery_delay" is updated with the value "0" +# # Given the file "tests/test_running_delay" is updated with the value "2" +# # When I select the command "Python: Discover Unit Tests" +# # Then wait for 5 second +# # When I select the command "View: Show Test" +# # Then select first node +# # When I press "down" +# # When I press "right" +# # When I press "down" +# # When I press "right" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # When I press "right" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # When I press "right" +# # When I press "down" +# # When I press "right" +# # Given the file "test_one.py" is open +# # Given the file is scrolled to the top +# # Then wait for 5 second +# # When I click second code lens "Run Test" +# # Then wait for 1 second +# # Then there are 1 running test items +# # Then the toolbar button with the text "Stop" is visible +# # Then wait for 10 second +# # Then the toolbar button with the text "Stop" is not visible + +# # @codelens @debug +# # Scenario: Debugging test suite via Code Lenses +# # Given the workspace is based on "/Users/donjayamanne/Desktop/Development/vscode/smokeTests/testing" +# # Given the file "tests/test_discovery_delay" is updated with the value "0" +# # Given the file "tests/test_running_delay" is updated with the value "0" +# # Given the file "test_one.py" is open +# # Given the file is scrolled to the top +# # When I close all editors +# # Given the file "test_one.py" is open +# # Then wait for 1 second +# # When I add a breakpoint to line 22 +# # When I add a breakpoint to line 27 +# # When I add a breakpoint to line 32 +# # When I close all editors +# # When I select the command "Python: Discover Unit Tests" +# # Then wait for 5 second +# # When I select the command "View: Show Test" +# # Then select first node +# # When I press "down" +# # When I press "right" +# # When I press "down" +# # When I press "right" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # When I press "right" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # When I press "right" +# # When I press "down" +# # When I press "right" +# # Given the file "test_one.py" is open +# # Given the file is scrolled to the top +# # Then wait for 5 second +# # When I click first code lens "Debug Test" +# # Then wait for 1 second +# # Then debugger starts +# # Then stack frame for file "test_one.py" and line 22 is displayed +# # Then take a screenshot +# # When I select the command "Debug: Continue" +# # Then stack frame for file "test_one.py" and line 32 is displayed +# # Then take a screenshot +# # When I select the command "Debug: Continue" +# # Then stack frame for file "test_one.py" and line 27 is displayed +# # Then take a screenshot +# # When I select the command "Debug: Continue" +# # Then wait for 1 second +# # # Continue again, as the debugger breaks into sys.exit. +# # When I select the command "Debug: Continue" + +# # Scenario: Debugging test function via Code Lenses +# # Given the workspace is based on "/Users/donjayamanne/Desktop/Development/vscode/smokeTests/testing" +# # Given the file "tests/test_discovery_delay" is updated with the value "0" +# # Given the file "tests/test_running_delay" is updated with the value "0" +# # Given the file "test_one.py" is open +# # Given the file is scrolled to the top +# # When I close all editors +# # Given the file "test_one.py" is open +# # Then wait for 1 second +# # When I add a breakpoint to line 22 +# # When I add a breakpoint to line 27 +# # When I add a breakpoint to line 32 +# # When I close all editors +# # When I select the command "Python: Discover Unit Tests" +# # Then wait for 5 second +# # When I select the command "View: Show Test" +# # Then select first node +# # When I press "down" +# # When I press "right" +# # When I press "down" +# # When I press "right" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # When I press "right" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # When I press "down" +# # When I press "right" +# # When I press "down" +# # When I press "right" +# # Given the file "test_one.py" is open +# # Given the file is scrolled to the top +# # Then wait for 5 second +# # When I click second code lens "Debug Test" +# # Then wait for 1 second +# # Then debugger starts +# # Then stack frame for file "test_one.py" and line 22 is displayed +# # Then take a screenshot +# # # Continue again, as the debugger breaks into sys.exit. +# # When I select the command "Debug: Continue" +# # When I select the command "Debug: Continue" +# # Then debugger stops diff --git a/uitests/features/wip/wip.feature b/uitests/features/wip/wip.feature new file mode 100644 index 000000000000..7778afbc2441 --- /dev/null +++ b/uitests/features/wip/wip.feature @@ -0,0 +1,17 @@ +# @terminal +# Feature: Terminal +# Background: Activted Extension +# Given the Python extension has been activated + +# @smoke +# Scenario: Open a terminal +# Then take a screenshot +# Then wait for 1 second +# When I select the command "Python: Create Terminal" +# Then take a screenshot +# Then wait for 1 second +# Then take a screenshot +# Then wait for 5 seconds +# Then take a screenshot +# Then wait for 5 seconds +# Then take a screenshot diff --git a/uitests/package-lock.json b/uitests/package-lock.json new file mode 100644 index 000000000000..578ab0a8487f --- /dev/null +++ b/uitests/package-lock.json @@ -0,0 +1,8809 @@ +{ + "name": "uitests", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@babel/code-frame": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz", + "integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==", + "requires": { + "@babel/highlight": "^7.0.0" + } + }, + "@babel/highlight": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.5.0.tgz", + "integrity": "sha512-7dV4eu9gBxoM0dAnj/BCFDW9LFU0zvTrkq0ugM7pnHEgguOEeOz1so2ZghEdzviYzQEED0r4EAgpsBChKy1TRQ==", + "requires": { + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" + } + }, + "@babel/polyfill": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/polyfill/-/polyfill-7.4.4.tgz", + "integrity": "sha512-WlthFLfhQQhh+A2Gn5NSFl0Huxz36x86Jn+E9OW7ibK8edKPq+KLy4apM1yDpQ8kJOVi1OVjpP4vSDLdrI04dg==", + "requires": { + "core-js": "^2.6.5", + "regenerator-runtime": "^0.13.2" + } + }, + "@types/caseless": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.2.tgz", + "integrity": "sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w==" + }, + "@types/chai": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.2.0.tgz", + "integrity": "sha512-zw8UvoBEImn392tLjxoavuonblX/4Yb9ha4KBU10FirCfwgzhKO0dvyJSF9ByxV1xK1r2AgnAi/tvQaLgxQqxA==" + }, + "@types/chai-arrays": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@types/chai-arrays/-/chai-arrays-1.0.3.tgz", + "integrity": "sha512-phRR7fP3qQSGyElel6MOObDE4BQvPZXPjZypgSZ7PvNZlKVK/LgChhpnsH3z/m/yGavXh7Qwa2Ih4/BYP3ynmg==", + "requires": { + "@types/chai": "*" + } + }, + "@types/chai-as-promised": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@types/chai-as-promised/-/chai-as-promised-7.1.2.tgz", + "integrity": "sha512-PO2gcfR3Oxa+u0QvECLe1xKXOqYTzCmWf0FhLhjREoW3fPAVamjihL7v1MOVLJLsnAMdLcjkfrs01yvDMwVK4Q==", + "requires": { + "@types/chai": "*" + } + }, + "@types/cucumber": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@types/cucumber/-/cucumber-4.0.7.tgz", + "integrity": "sha512-uYvDSl+JV1fT1U9EIFXlnb7DjZdRNYZHQFppQf9aYAe71joppErWenznupwZUj9zuo+SAFYNoongarQpNOb+pQ==" + }, + "@types/events": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", + "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==" + }, + "@types/fs-extra": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-8.0.0.tgz", + "integrity": "sha512-bCtL5v9zdbQW86yexOlXWTEGvLNqWxMFyi7gQA7Gcthbezr2cPSOb8SkESVKA937QD5cIwOFLDFt0MQoXOEr9Q==", + "requires": { + "@types/node": "*" + } + }, + "@types/glob": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz", + "integrity": "sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==", + "requires": { + "@types/events": "*", + "@types/minimatch": "*", + "@types/node": "*" + } + }, + "@types/glob-stream": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@types/glob-stream/-/glob-stream-6.1.0.tgz", + "integrity": "sha512-RHv6ZQjcTncXo3thYZrsbAVwoy4vSKosSWhuhuQxLOTv74OJuFQxXkmUuZCr3q9uNBEVCvIzmZL/FeRNbHZGUg==", + "requires": { + "@types/glob": "*", + "@types/node": "*" + } + }, + "@types/gulp": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@types/gulp/-/gulp-4.0.6.tgz", + "integrity": "sha512-0E8/iV/7FKWyQWSmi7jnUvgXXgaw+pfAzEB06Xu+l0iXVJppLbpOye5z7E2klw5akXd+8kPtYuk65YBcZPM4ow==", + "requires": { + "@types/undertaker": "*", + "@types/vinyl-fs": "*", + "chokidar": "^2.1.2" + } + }, + "@types/minimatch": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", + "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==" + }, + "@types/node": { + "version": "12.7.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.7.2.tgz", + "integrity": "sha512-dyYO+f6ihZEtNPDcWNR1fkoTDf3zAK3lAABDze3mz6POyIercH0lEUawUFXlG8xaQZmm1yEBON/4TsYv/laDYg==" + }, + "@types/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-bPOsfCZ4tsTlKiBjBhKnM8jpY5nmIll166IPD58D92hR7G7kZDfx5iB9wGF4NfZrdKolebjeAr3GouYkSGoJ/A==", + "requires": { + "@types/node": "*" + } + }, + "@types/puppeteer": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/@types/puppeteer/-/puppeteer-1.19.1.tgz", + "integrity": "sha512-ReWZvoEfMiJIA3AG+eM+nCx5GKrU2ANVYY5TC0nbpeiTCtnJbcqnmBbR8TkXMBTvLBYcuTOAELbTcuX73siDNQ==", + "requires": { + "@types/node": "*" + } + }, + "@types/puppeteer-core": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@types/puppeteer-core/-/puppeteer-core-1.9.0.tgz", + "integrity": "sha512-YJwGTq0a8xZxN7/QDeW59XMdKTRNzDTc8ZVBPDB6J13GgXn1+QzgMA8pAq1/bj2FD0R7xj3nYoZra10b0HLzFw==", + "requires": { + "@types/puppeteer": "*" + } + }, + "@types/request": { + "version": "2.48.2", + "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.2.tgz", + "integrity": "sha512-gP+PSFXAXMrd5PcD7SqHeUjdGshAI8vKQ3+AvpQr3ht9iQea+59LOKvKITcQI+Lg+1EIkDP6AFSBUJPWG8GDyA==", + "requires": { + "@types/caseless": "*", + "@types/node": "*", + "@types/tough-cookie": "*", + "form-data": "^2.5.0" + } + }, + "@types/rimraf": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@types/rimraf/-/rimraf-2.0.2.tgz", + "integrity": "sha512-Hm/bnWq0TCy7jmjeN5bKYij9vw5GrDFWME4IuxV08278NtU/VdGbzsBohcCUJ7+QMqmUq5hpRKB39HeQWJjztQ==", + "requires": { + "@types/glob": "*", + "@types/node": "*" + } + }, + "@types/tmp": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@types/tmp/-/tmp-0.1.0.tgz", + "integrity": "sha512-6IwZ9HzWbCq6XoQWhxLpDjuADodH/MKXRUIDFudvgjcVdjFknvmR+DNsoUeer4XPrEnrZs04Jj+kfV9pFsrhmA==" + }, + "@types/tough-cookie": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-2.3.5.tgz", + "integrity": "sha512-SCcK7mvGi3+ZNz833RRjFIxrn4gI1PPR3NtuIS+6vMkvmsGjosqTJwRt5bAEFLRz+wtJMWv8+uOnZf2hi2QXTg==" + }, + "@types/undertaker": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@types/undertaker/-/undertaker-1.2.2.tgz", + "integrity": "sha512-j4iepCSuY2JGW/hShVtUBagic0klYNFIXP7VweavnYnNC2EjiKxJFeaS9uaJmAT0ty9sQSqTS1aagWMZMV0HyA==", + "requires": { + "@types/undertaker-registry": "*" + } + }, + "@types/undertaker-registry": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/undertaker-registry/-/undertaker-registry-1.0.1.tgz", + "integrity": "sha512-Z4TYuEKn9+RbNVk1Ll2SS4x1JeLHecolIbM/a8gveaHsW0Hr+RQMraZACwTO2VD7JvepgA6UO1A1VrbktQrIbQ==" + }, + "@types/vinyl": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/vinyl/-/vinyl-2.0.3.tgz", + "integrity": "sha512-hrT6xg16CWSmndZqOTJ6BGIn2abKyTw0B58bI+7ioUoj3Sma6u8ftZ1DTI2yCaJamOVGLOnQWiPH3a74+EaqTA==", + "requires": { + "@types/node": "*" + } + }, + "@types/vinyl-fs": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/@types/vinyl-fs/-/vinyl-fs-2.4.11.tgz", + "integrity": "sha512-2OzQSfIr9CqqWMGqmcERE6Hnd2KY3eBVtFaulVo3sJghplUcaeMdL9ZjEiljcQQeHjheWY9RlNmumjIAvsBNaA==", + "requires": { + "@types/glob-stream": "*", + "@types/node": "*", + "@types/vinyl": "*" + } + }, + "@types/yargs": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.2.tgz", + "integrity": "sha512-lwwgizwk/bIIU+3ELORkyuOgDjCh7zuWDFqRtPPhhVgq9N1F7CvLNKg1TX4f2duwtKQ0p044Au9r1PLIXHrIzQ==", + "requires": { + "@types/yargs-parser": "*" + } + }, + "@types/yargs-parser": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-13.0.0.tgz", + "integrity": "sha512-wBlsw+8n21e6eTd4yVv8YD/E3xq0O6nNnJIquutAsFGE7EyMKz7W6RNT6BRu1SmdgmlCZ9tb0X+j+D6HGr8pZw==" + }, + "agent-base": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", + "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", + "requires": { + "es6-promisify": "^5.0.0" + } + }, + "ajv": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", + "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-colors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", + "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", + "requires": { + "ansi-wrap": "^0.1.0" + } + }, + "ansi-gray": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz", + "integrity": "sha1-KWLPVOyXksSFEKPetSRDaGHvclE=", + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "ansi-wrap": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", + "integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=" + }, + "any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=" + }, + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + }, + "dependencies": { + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "requires": { + "remove-trailing-separator": "^1.0.1" + } + } + } + }, + "append-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/append-buffer/-/append-buffer-1.0.2.tgz", + "integrity": "sha1-2CIM9GYIFSXv6lBhTz3mUU36WPE=", + "requires": { + "buffer-equal": "^1.0.0" + } + }, + "arch": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/arch/-/arch-2.1.1.tgz", + "integrity": "sha512-BLM56aPo9vLLFVa8+/+pJLnrZ7QGGTVHWsCwieAWT9o9K8UeGaQbzZbGoabWLOo2ksBCztoXdqBZBplqLDDCSg==" + }, + "archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=" + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=" + }, + "arr-filter": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/arr-filter/-/arr-filter-1.1.2.tgz", + "integrity": "sha1-Q/3d0JHo7xGqTEXZzcGOLf8XEe4=", + "requires": { + "make-iterator": "^1.0.0" + } + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==" + }, + "arr-map": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/arr-map/-/arr-map-2.0.2.tgz", + "integrity": "sha1-Onc0X/wc814qkYJWAfnljy4kysQ=", + "requires": { + "make-iterator": "^1.0.0" + } + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=" + }, + "array-differ": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-3.0.0.tgz", + "integrity": "sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg==" + }, + "array-each": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", + "integrity": "sha1-p5SvDAWrF1KEbudTofIRoFugxE8=" + }, + "array-initial": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/array-initial/-/array-initial-1.1.0.tgz", + "integrity": "sha1-L6dLJnOTccOUe9enrcc74zSz15U=", + "requires": { + "array-slice": "^1.0.0", + "is-number": "^4.0.0" + }, + "dependencies": { + "is-number": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==" + } + } + }, + "array-last": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/array-last/-/array-last-1.3.0.tgz", + "integrity": "sha512-eOCut5rXlI6aCOS7Z7kCplKRKyiFQ6dHFBem4PwlwKeNFk2/XxTrhRh5T9PyaEWGy/NHTZWbY+nsZlNFJu9rYg==", + "requires": { + "is-number": "^4.0.0" + }, + "dependencies": { + "is-number": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==" + } + } + }, + "array-slice": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", + "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==" + }, + "array-sort": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-sort/-/array-sort-1.0.0.tgz", + "integrity": "sha512-ihLeJkonmdiAsD7vpgN3CRcx2J2S0TiYW+IS/5zHBI7mKUq3ySvBdzzBfD236ubDBQFiiyG3SWCPc+msQ9KoYg==", + "requires": { + "default-compare": "^1.0.0", + "get-value": "^2.0.6", + "kind-of": "^5.0.2" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" + } + } + }, + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==" + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=" + }, + "arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==" + }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + }, + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==" + }, + "assertion-error-formatter": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error-formatter/-/assertion-error-formatter-2.0.1.tgz", + "integrity": "sha512-cjC3jUCh9spkroKue5PDSKH5RFQ/KNuZJhk3GwHYmB/8qqETxLOmMdLH+ohi/VukNzxDlMvIe7zScvLoOdhb6Q==", + "requires": { + "diff": "^3.0.0", + "pad-right": "^0.2.2", + "repeat-string": "^1.6.1" + } + }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=" + }, + "async": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", + "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "requires": { + "lodash": "^4.17.14" + } + }, + "async-done": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/async-done/-/async-done-1.3.2.tgz", + "integrity": "sha512-uYkTP8dw2og1tu1nmza1n1CMW0qb8gWWlwqMmLb7MhBVs4BXrFziT6HXUd+/RlRA/i4H9AkofYloUbs1fwMqlw==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.2", + "process-nextick-args": "^2.0.0", + "stream-exhaust": "^1.0.1" + } + }, + "async-each": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", + "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==" + }, + "async-limiter": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" + }, + "async-settle": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-settle/-/async-settle-1.0.0.tgz", + "integrity": "sha1-HQqRS7Aldb7IqPOnTlCA9yssDGs=", + "requires": { + "async-done": "^1.2.2" + } + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==" + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + }, + "aws4": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" + }, + "azure-devops-node-api": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/azure-devops-node-api/-/azure-devops-node-api-7.2.0.tgz", + "integrity": "sha512-pMfGJ6gAQ7LRKTHgiRF+8iaUUeGAI0c8puLaqHLc7B8AR7W6GJLozK9RFeUHFjEGybC9/EB3r67WPd7e46zQ8w==", + "requires": { + "os": "0.1.1", + "tunnel": "0.0.4", + "typed-rest-client": "1.2.0", + "underscore": "1.8.3" + } + }, + "bach": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/bach/-/bach-1.2.0.tgz", + "integrity": "sha1-Szzpa/JxNPeaG0FKUcFONMO9mIA=", + "requires": { + "arr-filter": "^1.1.1", + "arr-flatten": "^1.0.1", + "arr-map": "^2.0.0", + "array-each": "^1.0.0", + "array-initial": "^1.0.0", + "array-last": "^1.1.1", + "async-done": "^1.2.2", + "async-settle": "^1.0.0", + "now-and-later": "^2.0.0" + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "requires": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "becke-ch--regex--s0-0-v1--base--pl--lib": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/becke-ch--regex--s0-0-v1--base--pl--lib/-/becke-ch--regex--s0-0-v1--base--pl--lib-1.4.0.tgz", + "integrity": "sha1-Qpzuu/pffpNueNc/vcfacWKyDiA=" + }, + "binary-extensions": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", + "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==" + }, + "block-stream": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", + "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=", + "requires": { + "inherits": "~2.0.0" + } + }, + "bluebird": { + "version": "3.5.5", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.5.tgz", + "integrity": "sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w==" + }, + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=" + }, + "buffer-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.0.tgz", + "integrity": "sha1-WWFrSYME1Var1GaWayLu2j7KX74=" + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" + }, + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=" + }, + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "requires": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + } + }, + "camelcase": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", + "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=" + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, + "chai": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz", + "integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==", + "requires": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", + "get-func-name": "^2.0.0", + "pathval": "^1.1.0", + "type-detect": "^4.0.5" + } + }, + "chai-array": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/chai-array/-/chai-array-0.0.2.tgz", + "integrity": "sha1-gatM6CTc4si+/rGdFdEWFXE1PEI=" + }, + "chai-as-promised": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-7.1.1.tgz", + "integrity": "sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA==", + "requires": { + "check-error": "^1.0.2" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=" + }, + "cheerio": { + "version": "1.0.0-rc.3", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.3.tgz", + "integrity": "sha512-0td5ijfUPuubwLUu0OBoe98gZj8C/AA+RW3v67GPlGOrvxWjZmBXiBCRU+I8VEiNyJzjth40POfHiz2RB3gImA==", + "requires": { + "css-select": "~1.2.0", + "dom-serializer": "~0.1.1", + "entities": "~1.1.1", + "htmlparser2": "^3.9.1", + "lodash": "^4.15.0", + "parse5": "^3.0.1" + } + }, + "chokidar": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.6.tgz", + "integrity": "sha512-V2jUo67OKkc6ySiRpJrjlpJKl9kDuG+Xb8VgsGzb+aEouhgS1D0weyPU4lEzdAcsCAvrih2J2BqyXqHWvVLw5g==", + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "fsevents": "^1.2.7", + "glob-parent": "^3.1.0", + "inherits": "^2.0.3", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^3.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.2.1", + "upath": "^1.1.1" + } + }, + "class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "requires": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "cli-table3": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.5.1.tgz", + "integrity": "sha512-7Qg2Jrep1S/+Q3EceiZtQcDPWxhAvBw+ERf1162v4sikJrvojMHFqXt8QIVha8UlH9rgU0BeWPytZ9/TzYqlUw==", + "requires": { + "colors": "^1.1.2", + "object-assign": "^4.1.0", + "string-width": "^2.1.1" + } + }, + "clipboardy": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-2.1.0.tgz", + "integrity": "sha512-2pzOUxWcLlXWtn+Jd6js3o12TysNOOVes/aQfg+MT/35vrxWzedHlLwyoJpXjsFKWm95BTNEcMGD9+a7mKzZkQ==", + "requires": { + "arch": "^2.1.1", + "execa": "^1.0.0" + } + }, + "cliui": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", + "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wrap-ansi": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + } + } + }, + "clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=" + }, + "clone-buffer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz", + "integrity": "sha1-4+JbIHrE5wGvch4staFnksrD3Fg=" + }, + "clone-stats": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", + "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=" + }, + "cloneable-readable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.1.3.tgz", + "integrity": "sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ==", + "requires": { + "inherits": "^2.0.1", + "process-nextick-args": "^2.0.0", + "readable-stream": "^2.3.5" + } + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" + }, + "collection-map": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-map/-/collection-map-1.0.0.tgz", + "integrity": "sha1-rqDwb40mx4DCt1SUOFVEsiVa8Yw=", + "requires": { + "arr-map": "^2.0.2", + "for-own": "^1.0.0", + "make-iterator": "^1.0.0" + } + }, + "collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "requires": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + } + }, + "color": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/color/-/color-3.0.0.tgz", + "integrity": "sha512-jCpd5+s0s0t7p3pHQKpnJ0TpQKKdleP71LWcA0aqiljpiuAkOSUFN/dyH8ZwF0hRmFlrIuRhufds1QyEP9EB+w==", + "requires": { + "color-convert": "^1.9.1", + "color-string": "^1.5.2" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "color-string": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.3.tgz", + "integrity": "sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw==", + "requires": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==" + }, + "colornames": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/colornames/-/colornames-1.1.1.tgz", + "integrity": "sha1-+IiQMGhcfE/54qVZ9Qd+t2qBb5Y=" + }, + "colors": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.3.3.tgz", + "integrity": "sha512-mmGt/1pZqYRjMxB1axhTo16/snVZ5krrKkcmMeVKxzECMMXoCgnvTPp10QgHfcbQZw8Dq2jMNG6je4JlWU0gWg==" + }, + "colorspace": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.2.tgz", + "integrity": "sha512-vt+OoIP2d76xLhjwbBaucYlNSpPsrJWPlBTtwCpQKIu6/CSMutyzX93O/Do0qzpH3YoHEes8YEFXyZ797rEhzQ==", + "requires": { + "color": "3.0.x", + "text-hex": "1.0.x" + } + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", + "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==" + }, + "component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "convert-source-map": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", + "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==", + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=" + }, + "copy-props": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/copy-props/-/copy-props-2.0.4.tgz", + "integrity": "sha512-7cjuUME+p+S3HZlbllgsn2CDwS+5eCCX16qBgNC4jgSTf49qR1VKy/Zhl400m0IQXl/bPGEVqncgUUMjrr4s8A==", + "requires": { + "each-props": "^1.3.0", + "is-plain-object": "^2.0.1" + } + }, + "core-js": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.9.tgz", + "integrity": "sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A==" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "css-select": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", + "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", + "requires": { + "boolbase": "~1.0.0", + "css-what": "2.1", + "domutils": "1.5.1", + "nth-check": "~1.0.1" + } + }, + "css-what": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz", + "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==" + }, + "cucumber": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cucumber/-/cucumber-5.1.0.tgz", + "integrity": "sha512-zrl2VYTBRgvxucwV2GKAvLqcfA1Naeax8plPvWgPEzl3SCJiuPPv3WxBHIRHtPYcEdbHDR6oqLpZP4bJ8UIdmA==", + "requires": { + "@babel/polyfill": "^7.2.3", + "assertion-error-formatter": "^2.0.1", + "bluebird": "^3.4.1", + "cli-table3": "^0.5.1", + "colors": "^1.1.2", + "commander": "^2.9.0", + "cross-spawn": "^6.0.5", + "cucumber-expressions": "^6.0.0", + "cucumber-tag-expressions": "^1.1.1", + "duration": "^0.2.1", + "escape-string-regexp": "^1.0.5", + "figures": "2.0.0", + "gherkin": "^5.0.0", + "glob": "^7.1.3", + "indent-string": "^3.1.0", + "is-generator": "^1.0.2", + "is-stream": "^1.1.0", + "knuth-shuffle-seeded": "^1.0.6", + "lodash": "^4.17.10", + "mz": "^2.4.0", + "progress": "^2.0.0", + "resolve": "^1.3.3", + "serialize-error": "^3.0.0", + "stack-chain": "^2.0.0", + "stacktrace-js": "^2.0.0", + "string-argv": "0.1.1", + "title-case": "^2.1.1", + "util-arity": "^1.0.2", + "verror": "^1.9.0" + } + }, + "cucumber-expressions": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/cucumber-expressions/-/cucumber-expressions-6.6.2.tgz", + "integrity": "sha512-WcFSVBiWNLJbIcAAC3t/ACU46vaOKfe1UIF5H3qveoq+Y4XQm9j3YwHurQNufRKBBg8nCnpU7Ttsx7egjS3hwA==", + "requires": { + "becke-ch--regex--s0-0-v1--base--pl--lib": "^1.2.0" + } + }, + "cucumber-html-reporter": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cucumber-html-reporter/-/cucumber-html-reporter-5.0.0.tgz", + "integrity": "sha512-rZokbzSjeVONJVmode4eZbZF8NWJksvtebG15ZSdw1JnVx/pn4WqMRiwDz+XRmS2x1O/POSqVv5f/e+UJ1z8kQ==", + "requires": { + "find": "^0.2.7", + "fs-extra": "^3.0.1", + "js-base64": "^2.3.2", + "jsonfile": "^3.0.0", + "lodash": "^4.17.11", + "opn": "5.3.0" + }, + "dependencies": { + "fs-extra": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-3.0.1.tgz", + "integrity": "sha1-N5TzeMWLNC6n27sjCVEJxLO2IpE=", + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^3.0.0", + "universalify": "^0.1.0" + } + } + } + }, + "cucumber-junit": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/cucumber-junit/-/cucumber-junit-1.7.1.tgz", + "integrity": "sha1-odqXyRuM7crxX5n4zudJUeXEYbA=", + "requires": { + "xml": "0.0.12", + "yargs": "^5.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "yargs": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-5.0.0.tgz", + "integrity": "sha1-M1UUSXfQV1fbuG1uOOwFYSOzpm4=", + "requires": { + "cliui": "^3.2.0", + "decamelize": "^1.1.1", + "get-caller-file": "^1.0.1", + "lodash.assign": "^4.2.0", + "os-locale": "^1.4.0", + "read-pkg-up": "^1.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^1.0.2", + "which-module": "^1.0.0", + "window-size": "^0.2.0", + "y18n": "^3.2.1", + "yargs-parser": "^3.2.0" + } + } + } + }, + "cucumber-pretty": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/cucumber-pretty/-/cucumber-pretty-1.5.2.tgz", + "integrity": "sha512-mZLqD+L2Q5DrfbUvAVBsYRFjU+W8dWXWCb6MniJq9XCeozvIsSR3oC1hGX5YLe3seXpJf/blVSVwAREL5HWW0w==", + "requires": { + "cli-table3": "^0.5.1", + "colors": "^1.3.3", + "figures": "^3.0.0" + }, + "dependencies": { + "figures": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.0.0.tgz", + "integrity": "sha512-HKri+WoWoUgr83pehn/SIgLOMZ9nAWC6dcGj26RY2R4F50u4+RTUz0RCrUlOV3nKRAICW1UGzyb+kcX2qK1S/g==", + "requires": { + "escape-string-regexp": "^1.0.5" + } + } + } + }, + "cucumber-tag-expressions": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cucumber-tag-expressions/-/cucumber-tag-expressions-1.1.1.tgz", + "integrity": "sha1-f1x7cACbwrZmWRv+ZIVFeL7e6Fo=" + }, + "d": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", + "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", + "requires": { + "es5-ext": "^0.10.50", + "type": "^1.0.1" + } + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" + }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=" + }, + "deep-assign": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/deep-assign/-/deep-assign-1.0.0.tgz", + "integrity": "sha1-sJJ0O+hCfcYh6gBnzex+cN0Z83s=", + "requires": { + "is-obj": "^1.0.0" + } + }, + "deep-eql": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "requires": { + "type-detect": "^4.0.0" + } + }, + "default-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/default-compare/-/default-compare-1.0.0.tgz", + "integrity": "sha512-QWfXlM0EkAbqOCbD/6HjdwT19j7WCkMyiRhWilc4H9/5h/RzTF9gv5LYh1+CmDV5d1rki6KAWLtQale0xt20eQ==", + "requires": { + "kind-of": "^5.0.2" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" + } + } + }, + "default-resolution": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/default-resolution/-/default-resolution-2.0.0.tgz", + "integrity": "sha1-vLgrqnKtebQmp2cy8aga1t8m1oQ=" + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "requires": { + "object-keys": "^1.0.12" + } + }, + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "requires": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "dependencies": { + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "denodeify": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/denodeify/-/denodeify-1.2.1.tgz", + "integrity": "sha1-OjYof1A05pnnV3kBBSwubJQlFjE=" + }, + "detect-file": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", + "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=" + }, + "diagnostics": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/diagnostics/-/diagnostics-1.1.1.tgz", + "integrity": "sha512-8wn1PmdunLJ9Tqbx+Fx/ZEuHfJf4NKSN2ZBj7SJC/OWRWha843+WsTjqMe1B5E3p28jqBlp+mJ2fPVxPyNgYKQ==", + "requires": { + "colorspace": "1.1.x", + "enabled": "1.0.x", + "kuler": "1.0.x" + } + }, + "didyoumean": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.1.tgz", + "integrity": "sha1-6S7f2tplN9SE1zwBcv0eugxJdv8=" + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==" + }, + "doctrine": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-0.7.2.tgz", + "integrity": "sha1-fLhgNZujvpDgQLJrcpzkv6ZUxSM=", + "requires": { + "esutils": "^1.1.6", + "isarray": "0.0.1" + }, + "dependencies": { + "esutils": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-1.1.6.tgz", + "integrity": "sha1-wBzKqa5LiXxtDD4hCuUvPHqEQ3U=" + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + } + } + }, + "dom-serializer": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz", + "integrity": "sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==", + "requires": { + "domelementtype": "^1.3.0", + "entities": "^1.1.1" + } + }, + "domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==" + }, + "domhandler": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", + "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", + "requires": { + "domelementtype": "1" + } + }, + "domutils": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", + "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", + "requires": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "duplexer": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", + "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=" + }, + "duplexify": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", + "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", + "requires": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + } + }, + "duration": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/duration/-/duration-0.2.2.tgz", + "integrity": "sha512-06kgtea+bGreF5eKYgI/36A6pLXggY7oR4p1pq4SmdFBn1ReOL5D8RhG64VrqfTTKNucqqtBAwEj8aB88mcqrg==", + "requires": { + "d": "1", + "es5-ext": "~0.10.46" + } + }, + "each-props": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/each-props/-/each-props-1.3.2.tgz", + "integrity": "sha512-vV0Hem3zAGkJAyU7JSjixeU66rwdynTAa1vofCrSA5fEln+m67Az9CcnkVD776/fsN/UjIWmBDoNRS6t6G9RfA==", + "requires": { + "is-plain-object": "^2.0.1", + "object.defaults": "^1.1.0" + } + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==" + }, + "enabled": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-1.0.2.tgz", + "integrity": "sha1-ll9lE9LC0cX0ZStkouM5ZGf8L5M=", + "requires": { + "env-variable": "0.0.x" + } + }, + "end-of-stream": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", + "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", + "requires": { + "once": "^1.4.0" + } + }, + "entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==" + }, + "env-variable": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/env-variable/-/env-variable-0.0.5.tgz", + "integrity": "sha512-zoB603vQReOFvTg5xMl9I1P2PnHsHQQKTEowsKKD7nseUfJq6UWzK+4YtlWUO1nhiQUxe6XMkk+JleSZD1NZFA==" + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "error-stack-parser": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.0.3.tgz", + "integrity": "sha512-vRC4rKv87twMZy92X4+TmUdv3iYMsmePbpG/YguHsfzmZ8bYJZYYep7yrXH09yFUaCEPKgNK5X79+Yq7hwLVOA==", + "requires": { + "stackframe": "^1.0.4" + } + }, + "es5-ext": { + "version": "0.10.50", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.50.tgz", + "integrity": "sha512-KMzZTPBkeQV/JcSQhI5/z6d9VWJ3EnQ194USTUwIYZ2ZbpN8+SGXQKt1h68EX44+qt+Fzr8DO17vnxrw7c3agw==", + "requires": { + "es6-iterator": "~2.0.3", + "es6-symbol": "~3.1.1", + "next-tick": "^1.0.0" + } + }, + "es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", + "requires": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" + }, + "es6-promisify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", + "requires": { + "es6-promise": "^4.0.3" + } + }, + "es6-symbol": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", + "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", + "requires": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, + "es6-weak-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", + "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", + "requires": { + "d": "1", + "es5-ext": "^0.10.46", + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.1" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + }, + "eslint-plugin-prettier": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-2.7.0.tgz", + "integrity": "sha512-CStQYJgALoQBw3FsBzH0VOVDRnJ/ZimUlpLm226U8qgqYJfPOY/CPK6wyRInMxh73HSKg5wyRwdS4BVYYHwokA==", + "requires": { + "fast-diff": "^1.1.1", + "jest-docblock": "^21.0.0" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" + }, + "event-stream": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", + "integrity": "sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=", + "requires": { + "duplexer": "~0.1.1", + "from": "~0", + "map-stream": "~0.1.0", + "pause-stream": "0.0.11", + "split": "0.3", + "stream-combiner": "~0.0.4", + "through": "~2.3.1" + } + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "expand-tilde": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", + "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=", + "requires": { + "homedir-polyfill": "^1.0.1" + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "extract-zip": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.6.7.tgz", + "integrity": "sha1-qEC0uK9kAyZMjbV/Txp0Mz74H+k=", + "requires": { + "concat-stream": "1.6.2", + "debug": "2.6.9", + "mkdirp": "0.5.1", + "yauzl": "2.4.1" + }, + "dependencies": { + "fd-slicer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz", + "integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=", + "requires": { + "pend": "~1.2.0" + } + }, + "yauzl": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.4.1.tgz", + "integrity": "sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU=", + "requires": { + "fd-slicer": "~1.0.1" + } + } + } + }, + "extsprintf": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.4.0.tgz", + "integrity": "sha1-4mifjzVvrWLMplo6kcXfX5VRaS8=" + }, + "fancy-log": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.3.tgz", + "integrity": "sha512-k9oEhlyc0FrVh25qYuSELjr8oxsCoc4/LEZfg2iJJrfEk/tZL9bCoJE47gqAvI2m/AUjluCS4+3I0eTx8n3AEw==", + "requires": { + "ansi-gray": "^0.1.1", + "color-support": "^1.1.3", + "parse-node-version": "^1.0.0", + "time-stamp": "^1.0.0" + } + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" + }, + "fast-diff": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", + "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==" + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" + }, + "fast-safe-stringify": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.6.tgz", + "integrity": "sha512-q8BZ89jjc+mz08rSxROs8VsrBBcn1SIw1kq9NjolL509tkABRk9io01RAjSaEv1Xb2uFLt8VtRiZbGp5H8iDtg==" + }, + "fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", + "requires": { + "pend": "~1.2.0" + } + }, + "fecha": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-2.3.3.tgz", + "integrity": "sha512-lUGBnIamTAwk4znq5BcqsDaxSmZ9nDVJaij6NvRt/Tg4R69gERA+otPKbS86ROw9nxVMw2/mp1fnaiWqbs6Sdg==" + }, + "figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "find": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/find/-/find-0.2.9.tgz", + "integrity": "sha1-S3Px/55WrZG3bnFkB/5f/mVUu4w=", + "requires": { + "traverse-chain": "~0.1.0" + } + }, + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "requires": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "findup-sync": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-3.0.0.tgz", + "integrity": "sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg==", + "requires": { + "detect-file": "^1.0.0", + "is-glob": "^4.0.0", + "micromatch": "^3.0.4", + "resolve-dir": "^1.0.1" + } + }, + "fined": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fined/-/fined-1.2.0.tgz", + "integrity": "sha512-ZYDqPLGxDkDhDZBjZBb+oD1+j0rA4E0pXY50eplAAOPg2N/gUBSSk5IM1/QhPfyVo19lJ+CvXpqfvk+b2p/8Ng==", + "requires": { + "expand-tilde": "^2.0.2", + "is-plain-object": "^2.0.3", + "object.defaults": "^1.1.0", + "object.pick": "^1.2.0", + "parse-filepath": "^1.0.1" + } + }, + "flagged-respawn": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.1.tgz", + "integrity": "sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q==" + }, + "flush-write-stream": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", + "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", + "requires": { + "inherits": "^2.0.3", + "readable-stream": "^2.3.6" + } + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=" + }, + "for-own": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", + "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", + "requires": { + "for-in": "^1.0.1" + } + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + }, + "form-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.0.tgz", + "integrity": "sha512-WXieX3G/8side6VIqx44ablyULoGruSde5PNTxoUyo5CeyAMX6nVWUd0rgist/EuX655cjhUhTo1Fo3tRYqbcA==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "requires": { + "map-cache": "^0.2.2" + } + }, + "from": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", + "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=" + }, + "fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "dependencies": { + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "requires": { + "graceful-fs": "^4.1.6" + } + } + } + }, + "fs-mkdirp-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-mkdirp-stream/-/fs-mkdirp-stream-1.0.0.tgz", + "integrity": "sha1-C3gV/DIBxqaeFNuYzgmMFpNSWes=", + "requires": { + "graceful-fs": "^4.1.11", + "through2": "^2.0.3" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "fsevents": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.9.tgz", + "integrity": "sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw==", + "optional": true, + "requires": { + "nan": "^2.12.1", + "node-pre-gyp": "^0.12.0" + }, + "dependencies": { + "abbrev": { + "version": "1.1.1", + "bundled": true, + "optional": true + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true, + "optional": true + }, + "aproba": { + "version": "1.2.0", + "bundled": true, + "optional": true + }, + "are-we-there-yet": { + "version": "1.1.5", + "bundled": true, + "optional": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true, + "optional": true + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "optional": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "chownr": { + "version": "1.1.1", + "bundled": true, + "optional": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true, + "optional": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true, + "optional": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true, + "optional": true + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true, + "optional": true + }, + "debug": { + "version": "4.1.1", + "bundled": true, + "optional": true, + "requires": { + "ms": "^2.1.1" + } + }, + "deep-extend": { + "version": "0.6.0", + "bundled": true, + "optional": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true, + "optional": true + }, + "detect-libc": { + "version": "1.0.3", + "bundled": true, + "optional": true + }, + "fs-minipass": { + "version": "1.2.5", + "bundled": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "optional": true + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "optional": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "glob": { + "version": "7.1.3", + "bundled": true, + "optional": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true, + "optional": true + }, + "iconv-lite": { + "version": "0.4.24", + "bundled": true, + "optional": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ignore-walk": { + "version": "3.0.1", + "bundled": true, + "optional": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "optional": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true, + "optional": true + }, + "ini": { + "version": "1.3.5", + "bundled": true, + "optional": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "optional": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "bundled": true, + "optional": true + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "optional": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "bundled": true, + "optional": true + }, + "minipass": { + "version": "2.3.5", + "bundled": true, + "optional": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.2.1", + "bundled": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "optional": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.1.1", + "bundled": true, + "optional": true + }, + "needle": { + "version": "2.3.0", + "bundled": true, + "optional": true, + "requires": { + "debug": "^4.1.0", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + } + }, + "node-pre-gyp": { + "version": "0.12.0", + "bundled": true, + "optional": true, + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.1", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.2.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4" + } + }, + "nopt": { + "version": "4.0.1", + "bundled": true, + "optional": true, + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "npm-bundled": { + "version": "1.0.6", + "bundled": true, + "optional": true + }, + "npm-packlist": { + "version": "1.4.1", + "bundled": true, + "optional": true, + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" + } + }, + "npmlog": { + "version": "4.1.2", + "bundled": true, + "optional": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true, + "optional": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "optional": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "optional": true, + "requires": { + "wrappy": "1" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "optional": true + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true, + "optional": true + }, + "osenv": { + "version": "0.1.5", + "bundled": true, + "optional": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "optional": true + }, + "process-nextick-args": { + "version": "2.0.0", + "bundled": true, + "optional": true + }, + "rc": { + "version": "1.2.8", + "bundled": true, + "optional": true, + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "bundled": true, + "optional": true + } + } + }, + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "optional": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "rimraf": { + "version": "2.6.3", + "bundled": true, + "optional": true, + "requires": { + "glob": "^7.1.3" + } + }, + "safe-buffer": { + "version": "5.1.2", + "bundled": true, + "optional": true + }, + "safer-buffer": { + "version": "2.1.2", + "bundled": true, + "optional": true + }, + "sax": { + "version": "1.2.4", + "bundled": true, + "optional": true + }, + "semver": { + "version": "5.7.0", + "bundled": true, + "optional": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "optional": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "optional": true + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "optional": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "optional": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "optional": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true, + "optional": true + }, + "tar": { + "version": "4.4.8", + "bundled": true, + "optional": true, + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.3.4", + "minizlib": "^1.1.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.2" + } + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true, + "optional": true + }, + "wide-align": { + "version": "1.1.3", + "bundled": true, + "optional": true, + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true, + "optional": true + }, + "yallist": { + "version": "3.0.3", + "bundled": true, + "optional": true + } + } + }, + "fstream": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz", + "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==", + "requires": { + "graceful-fs": "^4.1.2", + "inherits": "~2.0.0", + "mkdirp": ">=0.5 0", + "rimraf": "2" + } + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "get-caller-file": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==" + }, + "get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=" + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "requires": { + "pump": "^3.0.0" + } + }, + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=" + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "gherkin": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/gherkin/-/gherkin-5.1.0.tgz", + "integrity": "sha1-aEu7A63STq9731RPWAM+so+zxtU=" + }, + "glob": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "glob-stream": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-6.1.0.tgz", + "integrity": "sha1-cEXJlBOz65SIjYOrRtC0BMx73eQ=", + "requires": { + "extend": "^3.0.0", + "glob": "^7.1.1", + "glob-parent": "^3.1.0", + "is-negated-glob": "^1.0.0", + "ordered-read-streams": "^1.0.0", + "pumpify": "^1.3.5", + "readable-stream": "^2.1.5", + "remove-trailing-separator": "^1.0.1", + "to-absolute-glob": "^2.0.0", + "unique-stream": "^2.0.2" + } + }, + "glob-watcher": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-5.0.3.tgz", + "integrity": "sha512-8tWsULNEPHKQ2MR4zXuzSmqbdyV5PtwwCaWSGQ1WwHsJ07ilNeN1JB8ntxhckbnpSHaf9dXFUHzIWvm1I13dsg==", + "requires": { + "anymatch": "^2.0.0", + "async-done": "^1.2.0", + "chokidar": "^2.0.0", + "is-negated-glob": "^1.0.0", + "just-debounce": "^1.0.0", + "object.defaults": "^1.1.0" + } + }, + "global-modules": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", + "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", + "requires": { + "global-prefix": "^1.0.1", + "is-windows": "^1.0.1", + "resolve-dir": "^1.0.0" + } + }, + "global-prefix": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", + "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=", + "requires": { + "expand-tilde": "^2.0.2", + "homedir-polyfill": "^1.0.1", + "ini": "^1.3.4", + "is-windows": "^1.0.1", + "which": "^1.2.14" + } + }, + "glogg": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.2.tgz", + "integrity": "sha512-5mwUoSuBk44Y4EshyiqcH95ZntbDdTQqA3QYSrxmzj28Ai0vXBGMH1ApSANH14j2sIRtqCEyg6PfsuP7ElOEDA==", + "requires": { + "sparkles": "^1.0.0" + } + }, + "graceful-fs": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.2.tgz", + "integrity": "sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q==" + }, + "gulp": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/gulp/-/gulp-4.0.2.tgz", + "integrity": "sha512-dvEs27SCZt2ibF29xYgmnwwCYZxdxhQ/+LFWlbAW8y7jt68L/65402Lz3+CKy0Ov4rOs+NERmDq7YlZaDqUIfA==", + "requires": { + "glob-watcher": "^5.0.3", + "gulp-cli": "^2.2.0", + "undertaker": "^1.2.1", + "vinyl-fs": "^3.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "gulp-cli": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/gulp-cli/-/gulp-cli-2.2.0.tgz", + "integrity": "sha512-rGs3bVYHdyJpLqR0TUBnlcZ1O5O++Zs4bA0ajm+zr3WFCfiSLjGwoCBqFs18wzN+ZxahT9DkOK5nDf26iDsWjA==", + "requires": { + "ansi-colors": "^1.0.1", + "archy": "^1.0.0", + "array-sort": "^1.0.0", + "color-support": "^1.1.3", + "concat-stream": "^1.6.0", + "copy-props": "^2.0.1", + "fancy-log": "^1.3.2", + "gulplog": "^1.0.0", + "interpret": "^1.1.0", + "isobject": "^3.0.1", + "liftoff": "^3.1.0", + "matchdep": "^2.0.0", + "mute-stdout": "^1.0.0", + "pretty-hrtime": "^1.0.0", + "replace-homedir": "^1.0.0", + "semver-greatest-satisfied-range": "^1.1.0", + "v8flags": "^3.0.1", + "yargs": "^7.1.0" + } + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "yargs": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.0.tgz", + "integrity": "sha1-a6MY6xaWFyf10oT46gA+jWFU0Mg=", + "requires": { + "camelcase": "^3.0.0", + "cliui": "^3.2.0", + "decamelize": "^1.1.1", + "get-caller-file": "^1.0.1", + "os-locale": "^1.4.0", + "read-pkg-up": "^1.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^1.0.2", + "which-module": "^1.0.0", + "y18n": "^3.2.1", + "yargs-parser": "^5.0.0" + } + }, + "yargs-parser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.0.tgz", + "integrity": "sha1-J17PDX/+Bcd+ZOfIbkzZS/DhIoo=", + "requires": { + "camelcase": "^3.0.0" + } + } + } + }, + "gulp-chmod": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/gulp-chmod/-/gulp-chmod-3.0.0.tgz", + "integrity": "sha512-fDKj+yuLsUD3ayaHuvD0OzeUNOPeHmIC4wlwVQf3d3WYaB233zgq4sDBncIH5GRBmhtFc32LWFF04IdnrQFxig==", + "requires": { + "deep-assign": "^1.0.0", + "stat-mode": "^0.3.0", + "through2": "^3.0.1" + }, + "dependencies": { + "through2": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.1.tgz", + "integrity": "sha512-M96dvTalPT3YbYLaKaCuwu+j06D/8Jfib0o/PxbVt6Amhv3dUAtW6rTV1jPgJSBG83I/e04Y6xkVdVhSRhi0ww==", + "requires": { + "readable-stream": "2 || 3" + } + } + } + }, + "gulp-filter": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/gulp-filter/-/gulp-filter-6.0.0.tgz", + "integrity": "sha512-veQFW93kf6jBdWdF/RxMEIlDK2mkjHyPftM381DID2C9ImTVngwYpyyThxm4/EpgcNOT37BLefzMOjEKbyYg0Q==", + "requires": { + "multimatch": "^4.0.0", + "plugin-error": "^1.0.1", + "streamfilter": "^3.0.0" + } + }, + "gulp-gunzip": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/gulp-gunzip/-/gulp-gunzip-1.1.0.tgz", + "integrity": "sha512-3INeprGyz5fUtAs75k6wVslGuRZIjKAoQp39xA7Bz350ReqkrfYaLYqjZ67XyIfLytRXdzeX04f+DnBduYhQWw==", + "requires": { + "through2": "~2.0.3", + "vinyl": "~2.0.1" + }, + "dependencies": { + "clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=" + }, + "vinyl": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.0.2.tgz", + "integrity": "sha1-CjcT2NTpIhxY8QyhbAEWyeJe2nw=", + "requires": { + "clone": "^1.0.0", + "clone-buffer": "^1.0.0", + "clone-stats": "^1.0.0", + "cloneable-readable": "^1.0.0", + "is-stream": "^1.1.0", + "remove-trailing-separator": "^1.0.1", + "replace-ext": "^1.0.0" + } + } + } + }, + "gulp-untar": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/gulp-untar/-/gulp-untar-0.0.8.tgz", + "integrity": "sha512-mqW7v2uvrxd8IoCCwJ04sPYgWjR3Gsi6yfhVWBK3sFMDP7FuoT7GNmxrCMwkk4RWqQohx8DRv+cDq4SRDXATGA==", + "requires": { + "event-stream": "3.3.4", + "streamifier": "~0.1.1", + "tar": "^2.2.1", + "through2": "~2.0.3", + "vinyl": "^1.2.0" + }, + "dependencies": { + "clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=" + }, + "clone-stats": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz", + "integrity": "sha1-uI+UqCzzi4eR1YBG6kAprYjKmdE=" + }, + "replace-ext": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-0.0.1.tgz", + "integrity": "sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ=" + }, + "vinyl": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-1.2.0.tgz", + "integrity": "sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ=", + "requires": { + "clone": "^1.0.0", + "clone-stats": "^0.0.1", + "replace-ext": "0.0.1" + } + } + } + }, + "gulp-vinyl-zip": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/gulp-vinyl-zip/-/gulp-vinyl-zip-2.1.2.tgz", + "integrity": "sha512-wJn09jsb8PyvUeyFF7y7ImEJqJwYy40BqL9GKfJs6UGpaGW9A+N68Q+ajsIpb9AeR6lAdjMbIdDPclIGo1/b7Q==", + "requires": { + "event-stream": "3.3.4", + "queue": "^4.2.1", + "through2": "^2.0.3", + "vinyl": "^2.0.2", + "vinyl-fs": "^3.0.3", + "yauzl": "^2.2.1", + "yazl": "^2.2.1" + } + }, + "gulplog": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-1.0.0.tgz", + "integrity": "sha1-4oxNRdBey77YGDY86PnFkmIp/+U=", + "requires": { + "glogg": "^1.0.0" + } + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + }, + "har-validator": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "requires": { + "ajv": "^6.5.5", + "har-schema": "^2.0.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + }, + "has-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", + "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=" + }, + "has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "requires": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + } + }, + "has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "requires": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "dependencies": { + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "homedir-polyfill": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", + "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", + "requires": { + "parse-passwd": "^1.0.0" + } + }, + "hosted-git-info": { + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.4.tgz", + "integrity": "sha512-pzXIvANXEFrc5oFFXRMkbLPQ2rXRoDERwDLyrcUxGhaZhgP54BBSl9Oheh7Vv0T090cszWBxPjkQQ5Sq1PbBRQ==" + }, + "htmlparser2": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", + "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", + "requires": { + "domelementtype": "^1.3.1", + "domhandler": "^2.3.0", + "domutils": "^1.5.1", + "entities": "^1.1.1", + "inherits": "^2.0.1", + "readable-stream": "^3.1.1" + }, + "dependencies": { + "readable-stream": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz", + "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "https-proxy-agent": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.2.tgz", + "integrity": "sha512-c8Ndjc9Bkpfx/vCJueCPy0jlP4ccCCSNDp8xwCZzPjKJUm+B+u9WX2x98Qx4n1PiMNTWo3D7KK5ifNV/yJyRzg==", + "requires": { + "agent-base": "^4.3.0", + "debug": "^3.1.0" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "indent-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", + "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=" + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "ini": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" + }, + "interpret": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.2.0.tgz", + "integrity": "sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw==" + }, + "invert-kv": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", + "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=" + }, + "is-absolute": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", + "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", + "requires": { + "is-relative": "^1.0.0", + "is-windows": "^1.0.1" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" + }, + "is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "requires": { + "binary-extensions": "^1.0.0" + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" + } + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + }, + "is-generator": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-generator/-/is-generator-1.0.3.tgz", + "integrity": "sha1-wUwhBX7TbjKNuANHlmxpP4hjifM=" + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-negated-glob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz", + "integrity": "sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI=" + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=" + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "requires": { + "isobject": "^3.0.1" + } + }, + "is-relative": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", + "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", + "requires": { + "is-unc-path": "^1.0.0" + } + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "is-unc-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", + "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", + "requires": { + "unc-path-regex": "^0.1.2" + } + }, + "is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=" + }, + "is-valid-glob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-valid-glob/-/is-valid-glob-1.0.0.tgz", + "integrity": "sha1-Kb8+/3Ab4tTTFdusw5vDn+j2Aao=" + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==" + }, + "is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "jest-docblock": { + "version": "21.2.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-21.2.0.tgz", + "integrity": "sha512-5IZ7sY9dBAYSV+YjQ0Ovb540Ku7AO9Z5o2Cg789xj167iQuZ2cG+z0f3Uct6WeYLbU6aQiM2pCs7sZ+4dotydw==" + }, + "js-base64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.5.1.tgz", + "integrity": "sha512-M7kLczedRMYX4L8Mdh4MzyAMM9O5osx+4FcOQuTvr3A9F2D9S5JXheN0ewNbrvK2UatkTRhL5ejGmGSjNMiZuw==" + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=" + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "jsonc-parser": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-2.1.0.tgz", + "integrity": "sha512-n9GrT8rrr2fhvBbANa1g+xFmgGK5X91KFeDwlKQ3+SJfmH5+tKv/M/kahx/TXOMflfWHKGKqKyfHQaLKTNzJ6w==" + }, + "jsonfile": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-3.0.1.tgz", + "integrity": "sha1-pezG9l9T9mLEQVx2daAzHQmS7GY=", + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + }, + "dependencies": { + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + } + } + }, + "just-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/just-debounce/-/just-debounce-1.0.0.tgz", + "integrity": "sha1-h/zPrv/AtozRnVX2cilD+SnqNeo=" + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" + }, + "knuth-shuffle-seeded": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/knuth-shuffle-seeded/-/knuth-shuffle-seeded-1.0.6.tgz", + "integrity": "sha1-AfG2VzOqdUDuCNiwF0Fk0iCB5OE=", + "requires": { + "seed-random": "~2.2.0" + } + }, + "kuler": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-1.0.1.tgz", + "integrity": "sha512-J9nVUucG1p/skKul6DU3PUZrhs0LPulNaeUOox0IyXDi8S4CztTHs1gQphhuZmzXG7VOQSf6NJfKuzteQLv9gQ==", + "requires": { + "colornames": "^1.1.1" + } + }, + "last-run": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/last-run/-/last-run-1.1.1.tgz", + "integrity": "sha1-RblpQsF7HHnHchmCWbqUO+v4yls=", + "requires": { + "default-resolution": "^2.0.0", + "es6-weak-map": "^2.0.1" + } + }, + "lazystream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.0.tgz", + "integrity": "sha1-9plf4PggOS9hOWvolGJAe7dxaOQ=", + "requires": { + "readable-stream": "^2.0.5" + } + }, + "lcid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", + "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", + "requires": { + "invert-kv": "^1.0.0" + } + }, + "lead": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lead/-/lead-1.0.0.tgz", + "integrity": "sha1-bxT5mje+Op3XhPVJVpDlkDRm7kI=", + "requires": { + "flush-write-stream": "^1.0.2" + } + }, + "liftoff": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-3.1.0.tgz", + "integrity": "sha512-DlIPlJUkCV0Ips2zf2pJP0unEoT1kwYhiiPUGF3s/jtxTCjziNLoiVVh+jqWOWeFi6mmwQ5fNxvAUyPad4Dfog==", + "requires": { + "extend": "^3.0.0", + "findup-sync": "^3.0.0", + "fined": "^1.0.1", + "flagged-respawn": "^1.0.0", + "is-plain-object": "^2.0.4", + "object.map": "^1.0.0", + "rechoir": "^0.6.2", + "resolve": "^1.1.7" + } + }, + "lines-and-columns": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", + "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=" + }, + "linkify-it": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.2.0.tgz", + "integrity": "sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw==", + "requires": { + "uc.micro": "^1.0.1" + } + }, + "load-json-file": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "dependencies": { + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" + } + } + }, + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" + }, + "lodash.assign": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz", + "integrity": "sha1-DZnzzNem0mHRm9rrkkUAXShYCOc=" + }, + "logform": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.1.2.tgz", + "integrity": "sha512-+lZh4OpERDBLqjiwDLpAWNQu6KMjnlXH2ByZwCuSqVPJletw0kTWJf5CgSNAUKn1KUkv3m2cUz/LK8zyEy7wzQ==", + "requires": { + "colors": "^1.2.1", + "fast-safe-stringify": "^2.0.4", + "fecha": "^2.3.3", + "ms": "^2.1.1", + "triple-beam": "^1.3.0" + }, + "dependencies": { + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "lower-case": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", + "integrity": "sha1-miyr0bno4K6ZOkv31YdcOcQujqw=" + }, + "make-iterator": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", + "integrity": "sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw==", + "requires": { + "kind-of": "^6.0.2" + } + }, + "map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=" + }, + "map-stream": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", + "integrity": "sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ=" + }, + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "requires": { + "object-visit": "^1.0.0" + } + }, + "markdown-it": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-8.4.2.tgz", + "integrity": "sha512-GcRz3AWTqSUphY3vsUqQSFMbgR38a4Lh3GWlHRh/7MRwz8mcu9n2IO7HOh+bXHrR9kOPDl5RNCaEsrneb+xhHQ==", + "requires": { + "argparse": "^1.0.7", + "entities": "~1.1.1", + "linkify-it": "^2.0.0", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + } + }, + "matchdep": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/matchdep/-/matchdep-2.0.0.tgz", + "integrity": "sha1-xvNINKDY28OzfCfui7yyfHd1WC4=", + "requires": { + "findup-sync": "^2.0.0", + "micromatch": "^3.0.4", + "resolve": "^1.4.0", + "stack-trace": "0.0.10" + }, + "dependencies": { + "findup-sync": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-2.0.0.tgz", + "integrity": "sha1-kyaxSIwi0aYIhlCoaQGy2akKLLw=", + "requires": { + "detect-file": "^1.0.0", + "is-glob": "^3.1.0", + "micromatch": "^3.0.4", + "resolve-dir": "^1.0.1" + } + }, + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=" + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "mime": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz", + "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==" + }, + "mime-db": { + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", + "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==" + }, + "mime-types": { + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", + "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", + "requires": { + "mime-db": "1.40.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + }, + "mixin-deep": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", + "requires": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "requires": { + "minimist": "0.0.8" + } + }, + "moment": { + "version": "2.24.0", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", + "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==" + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "multimatch": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-4.0.0.tgz", + "integrity": "sha512-lDmx79y1z6i7RNx0ZGCPq1bzJ6ZoDDKbvh7jxr9SJcWLkShMzXrHbYVpTdnhNM5MXpDUxCQ4DgqVttVXlBgiBQ==", + "requires": { + "@types/minimatch": "^3.0.3", + "array-differ": "^3.0.0", + "array-union": "^2.1.0", + "arrify": "^2.0.1", + "minimatch": "^3.0.4" + } + }, + "multiple-cucumber-html-reporter": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/multiple-cucumber-html-reporter/-/multiple-cucumber-html-reporter-1.12.0.tgz", + "integrity": "sha512-aDQnFKJhUaEiJyww5Bd7XZ2M8/VG3U0M83QBAnfKJ8hR5A70zUpH1mnQKDlzPp6/dDmeE2P9HMateFlWXZF0zg==", + "requires": { + "chalk": "^2.4.2", + "find": "^0.3.0", + "fs-extra": "^8.1.0", + "js-base64": "^2.5.1", + "jsonfile": "^5.0.0", + "lodash": "^4.17.14", + "moment": "^2.24.0", + "open": "^6.4.0", + "uuid": "^3.2.1" + }, + "dependencies": { + "find": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/find/-/find-0.3.0.tgz", + "integrity": "sha512-iSd+O4OEYV/I36Zl8MdYJO0xD82wH528SaCieTVHhclgiYNe9y+yPKSwK+A7/WsmHL1EZ+pYUJBXWTL5qofksw==", + "requires": { + "traverse-chain": "~0.1.0" + } + }, + "jsonfile": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-5.0.0.tgz", + "integrity": "sha512-NQRZ5CRo74MhMMC3/3r5g2k4fjodJ/wh8MxjFbCViWKFjxrnudWSY5vomh+23ZaXzAS7J3fBZIR2dV6WbmfM0w==", + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^0.1.2" + } + } + } + }, + "mute-stdout": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mute-stdout/-/mute-stdout-1.0.1.tgz", + "integrity": "sha512-kDcwXR4PS7caBpuRYYBUz9iVixUk3anO3f5OYFiIPwK/20vCzKCHyKoulbiDY1S53zD2bxUpxN/IJ+TnXjfvxg==" + }, + "mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" + }, + "mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "requires": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "named-js-regexp": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/named-js-regexp/-/named-js-regexp-1.3.5.tgz", + "integrity": "sha512-XO0DPujDP9IWpkt690iWLreKztb/VB811DGl5N3z7BfhkMJuiVZXOi6YN/fEB9qkvtMVTgSZDW8pzdVt8vj/FA==" + }, + "nan": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", + "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", + "optional": true + }, + "nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + } + }, + "next-tick": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", + "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=" + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" + }, + "no-case": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz", + "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==", + "requires": { + "lower-case": "^1.1.1" + } + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" + }, + "now-and-later": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/now-and-later/-/now-and-later-2.0.1.tgz", + "integrity": "sha512-KGvQ0cB70AQfg107Xvs/Fbu+dGmZoTRJp2TaPwcwQm3/7PteUyN2BCgk8KBMPGBUXZdVwyWS8fDCGFygBm19UQ==", + "requires": { + "once": "^1.3.2" + } + }, + "npm": { + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/npm/-/npm-6.10.3.tgz", + "integrity": "sha512-AH2uhSRaIMll7xz1JuLA6XbZu5k6DMSc77U6uWfuyBch4EzwpEc5dd54/OsX4Njioi7fSL7YmuPQbqKE2qiklw==", + "requires": { + "JSONStream": "^1.3.5", + "abbrev": "~1.1.1", + "ansicolors": "~0.3.2", + "ansistyles": "~0.1.3", + "aproba": "^2.0.0", + "archy": "~1.0.0", + "bin-links": "^1.1.2", + "bluebird": "^3.5.5", + "byte-size": "^5.0.1", + "cacache": "^12.0.2", + "call-limit": "^1.1.1", + "chownr": "^1.1.2", + "ci-info": "^2.0.0", + "cli-columns": "^3.1.2", + "cli-table3": "^0.5.1", + "cmd-shim": "~2.0.2", + "columnify": "~1.5.4", + "config-chain": "^1.1.12", + "debuglog": "*", + "detect-indent": "~5.0.0", + "detect-newline": "^2.1.0", + "dezalgo": "~1.0.3", + "editor": "~1.0.0", + "figgy-pudding": "^3.5.1", + "find-npm-prefix": "^1.0.2", + "fs-vacuum": "~1.2.10", + "fs-write-stream-atomic": "~1.0.10", + "gentle-fs": "^2.0.1", + "glob": "^7.1.4", + "graceful-fs": "^4.2.0", + "has-unicode": "~2.0.1", + "hosted-git-info": "^2.8.2", + "iferr": "^1.0.2", + "imurmurhash": "*", + "infer-owner": "^1.0.4", + "inflight": "~1.0.6", + "inherits": "^2.0.4", + "ini": "^1.3.5", + "init-package-json": "^1.10.3", + "is-cidr": "^3.0.0", + "json-parse-better-errors": "^1.0.2", + "lazy-property": "~1.0.0", + "libcipm": "^4.0.0", + "libnpm": "^3.0.1", + "libnpmaccess": "^3.0.2", + "libnpmhook": "^5.0.3", + "libnpmorg": "^1.0.1", + "libnpmsearch": "^2.0.2", + "libnpmteam": "^1.0.2", + "libnpx": "^10.2.0", + "lock-verify": "^2.1.0", + "lockfile": "^1.0.4", + "lodash._baseindexof": "*", + "lodash._baseuniq": "~4.6.0", + "lodash._bindcallback": "*", + "lodash._cacheindexof": "*", + "lodash._createcache": "*", + "lodash._getnative": "*", + "lodash.clonedeep": "~4.5.0", + "lodash.restparam": "*", + "lodash.union": "~4.6.0", + "lodash.uniq": "~4.5.0", + "lodash.without": "~4.4.0", + "lru-cache": "^5.1.1", + "meant": "~1.0.1", + "mississippi": "^3.0.0", + "mkdirp": "~0.5.1", + "move-concurrently": "^1.0.1", + "node-gyp": "^5.0.3", + "nopt": "~4.0.1", + "normalize-package-data": "^2.5.0", + "npm-audit-report": "^1.3.2", + "npm-cache-filename": "~1.0.2", + "npm-install-checks": "~3.0.0", + "npm-lifecycle": "^3.1.2", + "npm-package-arg": "^6.1.0", + "npm-packlist": "^1.4.4", + "npm-pick-manifest": "^2.2.3", + "npm-profile": "^4.0.2", + "npm-registry-fetch": "^4.0.0", + "npm-user-validate": "~1.0.0", + "npmlog": "~4.1.2", + "once": "~1.4.0", + "opener": "^1.5.1", + "osenv": "^0.1.5", + "pacote": "^9.5.4", + "path-is-inside": "~1.0.2", + "promise-inflight": "~1.0.1", + "qrcode-terminal": "^0.12.0", + "query-string": "^6.8.2", + "qw": "~1.0.1", + "read": "~1.0.7", + "read-cmd-shim": "~1.0.1", + "read-installed": "~4.0.3", + "read-package-json": "^2.0.13", + "read-package-tree": "^5.3.1", + "readable-stream": "^3.4.0", + "readdir-scoped-modules": "^1.1.0", + "request": "^2.88.0", + "retry": "^0.12.0", + "rimraf": "^2.6.3", + "safe-buffer": "^5.1.2", + "semver": "^5.7.0", + "sha": "^3.0.0", + "slide": "~1.1.6", + "sorted-object": "~2.0.1", + "sorted-union-stream": "~2.1.3", + "ssri": "^6.0.1", + "stringify-package": "^1.0.0", + "tar": "^4.4.10", + "text-table": "~0.2.0", + "tiny-relative-date": "^1.3.0", + "uid-number": "0.0.6", + "umask": "~1.1.0", + "unique-filename": "^1.1.1", + "unpipe": "~1.0.0", + "update-notifier": "^2.5.0", + "uuid": "^3.3.2", + "validate-npm-package-license": "^3.0.4", + "validate-npm-package-name": "~3.0.0", + "which": "^1.3.1", + "worker-farm": "^1.7.0", + "write-file-atomic": "^2.4.3" + }, + "dependencies": { + "JSONStream": { + "version": "1.3.5", + "bundled": true, + "requires": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + } + }, + "abbrev": { + "version": "1.1.1", + "bundled": true + }, + "agent-base": { + "version": "4.3.0", + "bundled": true, + "requires": { + "es6-promisify": "^5.0.0" + } + }, + "agentkeepalive": { + "version": "3.5.2", + "bundled": true, + "requires": { + "humanize-ms": "^1.2.1" + } + }, + "ajv": { + "version": "5.5.2", + "bundled": true, + "requires": { + "co": "^4.6.0", + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0" + } + }, + "ansi-align": { + "version": "2.0.0", + "bundled": true, + "requires": { + "string-width": "^2.0.0" + } + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true + }, + "ansi-styles": { + "version": "3.2.1", + "bundled": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "ansicolors": { + "version": "0.3.2", + "bundled": true + }, + "ansistyles": { + "version": "0.1.3", + "bundled": true + }, + "aproba": { + "version": "2.0.0", + "bundled": true + }, + "archy": { + "version": "1.0.0", + "bundled": true + }, + "are-we-there-yet": { + "version": "1.1.4", + "bundled": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "asap": { + "version": "2.0.6", + "bundled": true + }, + "asn1": { + "version": "0.2.4", + "bundled": true, + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "bundled": true + }, + "asynckit": { + "version": "0.4.0", + "bundled": true + }, + "aws-sign2": { + "version": "0.7.0", + "bundled": true + }, + "aws4": { + "version": "1.8.0", + "bundled": true + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "bundled": true, + "optional": true, + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "bin-links": { + "version": "1.1.2", + "bundled": true, + "requires": { + "bluebird": "^3.5.0", + "cmd-shim": "^2.0.2", + "gentle-fs": "^2.0.0", + "graceful-fs": "^4.1.11", + "write-file-atomic": "^2.3.0" + } + }, + "bluebird": { + "version": "3.5.5", + "bundled": true + }, + "boxen": { + "version": "1.3.0", + "bundled": true, + "requires": { + "ansi-align": "^2.0.0", + "camelcase": "^4.0.0", + "chalk": "^2.0.1", + "cli-boxes": "^1.0.0", + "string-width": "^2.0.0", + "term-size": "^1.2.0", + "widest-line": "^2.0.0" + } + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "buffer-from": { + "version": "1.0.0", + "bundled": true + }, + "builtins": { + "version": "1.0.3", + "bundled": true + }, + "byline": { + "version": "5.0.0", + "bundled": true + }, + "byte-size": { + "version": "5.0.1", + "bundled": true + }, + "cacache": { + "version": "12.0.2", + "bundled": true, + "requires": { + "bluebird": "^3.5.5", + "chownr": "^1.1.1", + "figgy-pudding": "^3.5.1", + "glob": "^7.1.4", + "graceful-fs": "^4.1.15", + "infer-owner": "^1.0.3", + "lru-cache": "^5.1.1", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.3", + "ssri": "^6.0.1", + "unique-filename": "^1.1.1", + "y18n": "^4.0.0" + } + }, + "call-limit": { + "version": "1.1.1", + "bundled": true + }, + "camelcase": { + "version": "4.1.0", + "bundled": true + }, + "capture-stack-trace": { + "version": "1.0.0", + "bundled": true + }, + "caseless": { + "version": "0.12.0", + "bundled": true + }, + "chalk": { + "version": "2.4.1", + "bundled": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "chownr": { + "version": "1.1.2", + "bundled": true + }, + "ci-info": { + "version": "2.0.0", + "bundled": true + }, + "cidr-regex": { + "version": "2.0.10", + "bundled": true, + "requires": { + "ip-regex": "^2.1.0" + } + }, + "cli-boxes": { + "version": "1.0.0", + "bundled": true + }, + "cli-columns": { + "version": "3.1.2", + "bundled": true, + "requires": { + "string-width": "^2.0.0", + "strip-ansi": "^3.0.1" + } + }, + "cli-table3": { + "version": "0.5.1", + "bundled": true, + "requires": { + "colors": "^1.1.2", + "object-assign": "^4.1.0", + "string-width": "^2.1.1" + } + }, + "cliui": { + "version": "4.1.0", + "bundled": true, + "requires": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "bundled": true + }, + "strip-ansi": { + "version": "4.0.0", + "bundled": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "clone": { + "version": "1.0.4", + "bundled": true + }, + "cmd-shim": { + "version": "2.0.2", + "bundled": true, + "requires": { + "graceful-fs": "^4.1.2", + "mkdirp": "~0.5.0" + } + }, + "co": { + "version": "4.6.0", + "bundled": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true + }, + "color-convert": { + "version": "1.9.1", + "bundled": true, + "requires": { + "color-name": "^1.1.1" + } + }, + "color-name": { + "version": "1.1.3", + "bundled": true + }, + "colors": { + "version": "1.3.3", + "bundled": true, + "optional": true + }, + "columnify": { + "version": "1.5.4", + "bundled": true, + "requires": { + "strip-ansi": "^3.0.0", + "wcwidth": "^1.0.0" + } + }, + "combined-stream": { + "version": "1.0.6", + "bundled": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "concat-map": { + "version": "0.0.1", + "bundled": true + }, + "concat-stream": { + "version": "1.6.2", + "bundled": true, + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "config-chain": { + "version": "1.1.12", + "bundled": true, + "requires": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, + "configstore": { + "version": "3.1.2", + "bundled": true, + "requires": { + "dot-prop": "^4.1.0", + "graceful-fs": "^4.1.2", + "make-dir": "^1.0.0", + "unique-string": "^1.0.0", + "write-file-atomic": "^2.0.0", + "xdg-basedir": "^3.0.0" + } + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true + }, + "copy-concurrently": { + "version": "1.0.5", + "bundled": true, + "requires": { + "aproba": "^1.1.1", + "fs-write-stream-atomic": "^1.0.8", + "iferr": "^0.1.5", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.0" + }, + "dependencies": { + "aproba": { + "version": "1.2.0", + "bundled": true + }, + "iferr": { + "version": "0.1.5", + "bundled": true + } + } + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true + }, + "create-error-class": { + "version": "3.0.2", + "bundled": true, + "requires": { + "capture-stack-trace": "^1.0.0" + } + }, + "cross-spawn": { + "version": "5.1.0", + "bundled": true, + "requires": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "dependencies": { + "lru-cache": { + "version": "4.1.5", + "bundled": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "yallist": { + "version": "2.1.2", + "bundled": true + } + } + }, + "crypto-random-string": { + "version": "1.0.0", + "bundled": true + }, + "cyclist": { + "version": "0.2.2", + "bundled": true + }, + "dashdash": { + "version": "1.14.1", + "bundled": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "debug": { + "version": "3.1.0", + "bundled": true, + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "bundled": true + } + } + }, + "debuglog": { + "version": "1.0.1", + "bundled": true + }, + "decamelize": { + "version": "1.2.0", + "bundled": true + }, + "decode-uri-component": { + "version": "0.2.0", + "bundled": true + }, + "deep-extend": { + "version": "0.5.1", + "bundled": true + }, + "defaults": { + "version": "1.0.3", + "bundled": true, + "requires": { + "clone": "^1.0.2" + } + }, + "define-properties": { + "version": "1.1.3", + "bundled": true, + "requires": { + "object-keys": "^1.0.12" + } + }, + "delayed-stream": { + "version": "1.0.0", + "bundled": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true + }, + "detect-indent": { + "version": "5.0.0", + "bundled": true + }, + "detect-newline": { + "version": "2.1.0", + "bundled": true + }, + "dezalgo": { + "version": "1.0.3", + "bundled": true, + "requires": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, + "dot-prop": { + "version": "4.2.0", + "bundled": true, + "requires": { + "is-obj": "^1.0.0" + } + }, + "dotenv": { + "version": "5.0.1", + "bundled": true + }, + "duplexer3": { + "version": "0.1.4", + "bundled": true + }, + "duplexify": { + "version": "3.6.0", + "bundled": true, + "requires": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "ecc-jsbn": { + "version": "0.1.2", + "bundled": true, + "optional": true, + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "editor": { + "version": "1.0.0", + "bundled": true + }, + "encoding": { + "version": "0.1.12", + "bundled": true, + "requires": { + "iconv-lite": "~0.4.13" + } + }, + "end-of-stream": { + "version": "1.4.1", + "bundled": true, + "requires": { + "once": "^1.4.0" + } + }, + "env-paths": { + "version": "1.0.0", + "bundled": true + }, + "err-code": { + "version": "1.1.2", + "bundled": true + }, + "errno": { + "version": "0.1.7", + "bundled": true, + "requires": { + "prr": "~1.0.1" + } + }, + "es-abstract": { + "version": "1.12.0", + "bundled": true, + "requires": { + "es-to-primitive": "^1.1.1", + "function-bind": "^1.1.1", + "has": "^1.0.1", + "is-callable": "^1.1.3", + "is-regex": "^1.0.4" + } + }, + "es-to-primitive": { + "version": "1.2.0", + "bundled": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "es6-promise": { + "version": "4.2.8", + "bundled": true + }, + "es6-promisify": { + "version": "5.0.0", + "bundled": true, + "requires": { + "es6-promise": "^4.0.3" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "bundled": true + }, + "execa": { + "version": "0.7.0", + "bundled": true, + "requires": { + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "dependencies": { + "get-stream": { + "version": "3.0.0", + "bundled": true + } + } + }, + "extend": { + "version": "3.0.2", + "bundled": true + }, + "extsprintf": { + "version": "1.3.0", + "bundled": true + }, + "fast-deep-equal": { + "version": "1.1.0", + "bundled": true + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "bundled": true + }, + "figgy-pudding": { + "version": "3.5.1", + "bundled": true + }, + "find-npm-prefix": { + "version": "1.0.2", + "bundled": true + }, + "find-up": { + "version": "2.1.0", + "bundled": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "flush-write-stream": { + "version": "1.0.3", + "bundled": true, + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.4" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "forever-agent": { + "version": "0.6.1", + "bundled": true + }, + "form-data": { + "version": "2.3.2", + "bundled": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "1.0.6", + "mime-types": "^2.1.12" + } + }, + "from2": { + "version": "2.3.0", + "bundled": true, + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "fs-minipass": { + "version": "1.2.6", + "bundled": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "fs-vacuum": { + "version": "1.2.10", + "bundled": true, + "requires": { + "graceful-fs": "^4.1.2", + "path-is-inside": "^1.0.1", + "rimraf": "^2.5.2" + } + }, + "fs-write-stream-atomic": { + "version": "1.0.10", + "bundled": true, + "requires": { + "graceful-fs": "^4.1.2", + "iferr": "^0.1.5", + "imurmurhash": "^0.1.4", + "readable-stream": "1 || 2" + }, + "dependencies": { + "iferr": { + "version": "0.1.5", + "bundled": true + }, + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true + }, + "function-bind": { + "version": "1.1.1", + "bundled": true + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + }, + "dependencies": { + "aproba": { + "version": "1.2.0", + "bundled": true + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + } + } + }, + "genfun": { + "version": "5.0.0", + "bundled": true + }, + "gentle-fs": { + "version": "2.0.1", + "bundled": true, + "requires": { + "aproba": "^1.1.2", + "fs-vacuum": "^1.2.10", + "graceful-fs": "^4.1.11", + "iferr": "^0.1.5", + "mkdirp": "^0.5.1", + "path-is-inside": "^1.0.2", + "read-cmd-shim": "^1.0.1", + "slide": "^1.1.6" + }, + "dependencies": { + "aproba": { + "version": "1.2.0", + "bundled": true + }, + "iferr": { + "version": "0.1.5", + "bundled": true + } + } + }, + "get-caller-file": { + "version": "1.0.2", + "bundled": true + }, + "get-stream": { + "version": "4.1.0", + "bundled": true, + "requires": { + "pump": "^3.0.0" + } + }, + "getpass": { + "version": "0.1.7", + "bundled": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "glob": { + "version": "7.1.4", + "bundled": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "global-dirs": { + "version": "0.1.1", + "bundled": true, + "requires": { + "ini": "^1.3.4" + } + }, + "got": { + "version": "6.7.1", + "bundled": true, + "requires": { + "create-error-class": "^3.0.0", + "duplexer3": "^0.1.4", + "get-stream": "^3.0.0", + "is-redirect": "^1.0.0", + "is-retry-allowed": "^1.0.0", + "is-stream": "^1.0.0", + "lowercase-keys": "^1.0.0", + "safe-buffer": "^5.0.1", + "timed-out": "^4.0.0", + "unzip-response": "^2.0.1", + "url-parse-lax": "^1.0.0" + }, + "dependencies": { + "get-stream": { + "version": "3.0.0", + "bundled": true + } + } + }, + "graceful-fs": { + "version": "4.2.0", + "bundled": true + }, + "har-schema": { + "version": "2.0.0", + "bundled": true + }, + "har-validator": { + "version": "5.1.0", + "bundled": true, + "requires": { + "ajv": "^5.3.0", + "har-schema": "^2.0.0" + } + }, + "has": { + "version": "1.0.3", + "bundled": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "3.0.0", + "bundled": true + }, + "has-symbols": { + "version": "1.0.0", + "bundled": true + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true + }, + "hosted-git-info": { + "version": "2.8.2", + "bundled": true, + "requires": { + "lru-cache": "^5.1.1" + } + }, + "http-cache-semantics": { + "version": "3.8.1", + "bundled": true + }, + "http-proxy-agent": { + "version": "2.1.0", + "bundled": true, + "requires": { + "agent-base": "4", + "debug": "3.1.0" + } + }, + "http-signature": { + "version": "1.2.0", + "bundled": true, + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "https-proxy-agent": { + "version": "2.2.2", + "bundled": true, + "requires": { + "agent-base": "^4.3.0", + "debug": "^3.1.0" + } + }, + "humanize-ms": { + "version": "1.2.1", + "bundled": true, + "requires": { + "ms": "^2.0.0" + } + }, + "iconv-lite": { + "version": "0.4.23", + "bundled": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "iferr": { + "version": "1.0.2", + "bundled": true + }, + "ignore-walk": { + "version": "3.0.1", + "bundled": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "import-lazy": { + "version": "2.1.0", + "bundled": true + }, + "imurmurhash": { + "version": "0.1.4", + "bundled": true + }, + "infer-owner": { + "version": "1.0.4", + "bundled": true + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "bundled": true + }, + "ini": { + "version": "1.3.5", + "bundled": true + }, + "init-package-json": { + "version": "1.10.3", + "bundled": true, + "requires": { + "glob": "^7.1.1", + "npm-package-arg": "^4.0.0 || ^5.0.0 || ^6.0.0", + "promzard": "^0.3.0", + "read": "~1.0.1", + "read-package-json": "1 || 2", + "semver": "2.x || 3.x || 4 || 5", + "validate-npm-package-license": "^3.0.1", + "validate-npm-package-name": "^3.0.0" + } + }, + "invert-kv": { + "version": "1.0.0", + "bundled": true + }, + "ip": { + "version": "1.1.5", + "bundled": true + }, + "ip-regex": { + "version": "2.1.0", + "bundled": true + }, + "is-callable": { + "version": "1.1.4", + "bundled": true + }, + "is-ci": { + "version": "1.1.0", + "bundled": true, + "requires": { + "ci-info": "^1.0.0" + }, + "dependencies": { + "ci-info": { + "version": "1.6.0", + "bundled": true + } + } + }, + "is-cidr": { + "version": "3.0.0", + "bundled": true, + "requires": { + "cidr-regex": "^2.0.10" + } + }, + "is-date-object": { + "version": "1.0.1", + "bundled": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "is-installed-globally": { + "version": "0.1.0", + "bundled": true, + "requires": { + "global-dirs": "^0.1.0", + "is-path-inside": "^1.0.0" + } + }, + "is-npm": { + "version": "1.0.0", + "bundled": true + }, + "is-obj": { + "version": "1.0.1", + "bundled": true + }, + "is-path-inside": { + "version": "1.0.1", + "bundled": true, + "requires": { + "path-is-inside": "^1.0.1" + } + }, + "is-redirect": { + "version": "1.0.0", + "bundled": true + }, + "is-regex": { + "version": "1.0.4", + "bundled": true, + "requires": { + "has": "^1.0.1" + } + }, + "is-retry-allowed": { + "version": "1.1.0", + "bundled": true + }, + "is-stream": { + "version": "1.1.0", + "bundled": true + }, + "is-symbol": { + "version": "1.0.2", + "bundled": true, + "requires": { + "has-symbols": "^1.0.0" + } + }, + "is-typedarray": { + "version": "1.0.0", + "bundled": true + }, + "isarray": { + "version": "1.0.0", + "bundled": true + }, + "isexe": { + "version": "2.0.0", + "bundled": true + }, + "isstream": { + "version": "0.1.2", + "bundled": true + }, + "jsbn": { + "version": "0.1.1", + "bundled": true, + "optional": true + }, + "json-parse-better-errors": { + "version": "1.0.2", + "bundled": true + }, + "json-schema": { + "version": "0.2.3", + "bundled": true + }, + "json-schema-traverse": { + "version": "0.3.1", + "bundled": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "bundled": true + }, + "jsonparse": { + "version": "1.3.1", + "bundled": true + }, + "jsprim": { + "version": "1.4.1", + "bundled": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "latest-version": { + "version": "3.1.0", + "bundled": true, + "requires": { + "package-json": "^4.0.0" + } + }, + "lazy-property": { + "version": "1.0.0", + "bundled": true + }, + "lcid": { + "version": "1.0.0", + "bundled": true, + "requires": { + "invert-kv": "^1.0.0" + } + }, + "libcipm": { + "version": "4.0.0", + "bundled": true, + "requires": { + "bin-links": "^1.1.2", + "bluebird": "^3.5.1", + "figgy-pudding": "^3.5.1", + "find-npm-prefix": "^1.0.2", + "graceful-fs": "^4.1.11", + "ini": "^1.3.5", + "lock-verify": "^2.0.2", + "mkdirp": "^0.5.1", + "npm-lifecycle": "^3.0.0", + "npm-logical-tree": "^1.2.1", + "npm-package-arg": "^6.1.0", + "pacote": "^9.1.0", + "read-package-json": "^2.0.13", + "rimraf": "^2.6.2", + "worker-farm": "^1.6.0" + } + }, + "libnpm": { + "version": "3.0.1", + "bundled": true, + "requires": { + "bin-links": "^1.1.2", + "bluebird": "^3.5.3", + "find-npm-prefix": "^1.0.2", + "libnpmaccess": "^3.0.2", + "libnpmconfig": "^1.2.1", + "libnpmhook": "^5.0.3", + "libnpmorg": "^1.0.1", + "libnpmpublish": "^1.1.2", + "libnpmsearch": "^2.0.2", + "libnpmteam": "^1.0.2", + "lock-verify": "^2.0.2", + "npm-lifecycle": "^3.0.0", + "npm-logical-tree": "^1.2.1", + "npm-package-arg": "^6.1.0", + "npm-profile": "^4.0.2", + "npm-registry-fetch": "^4.0.0", + "npmlog": "^4.1.2", + "pacote": "^9.5.3", + "read-package-json": "^2.0.13", + "stringify-package": "^1.0.0" + } + }, + "libnpmaccess": { + "version": "3.0.2", + "bundled": true, + "requires": { + "aproba": "^2.0.0", + "get-stream": "^4.0.0", + "npm-package-arg": "^6.1.0", + "npm-registry-fetch": "^4.0.0" + } + }, + "libnpmconfig": { + "version": "1.2.1", + "bundled": true, + "requires": { + "figgy-pudding": "^3.5.1", + "find-up": "^3.0.0", + "ini": "^1.3.5" + }, + "dependencies": { + "find-up": { + "version": "3.0.0", + "bundled": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "bundled": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "2.2.0", + "bundled": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "bundled": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "bundled": true + } + } + }, + "libnpmhook": { + "version": "5.0.3", + "bundled": true, + "requires": { + "aproba": "^2.0.0", + "figgy-pudding": "^3.4.1", + "get-stream": "^4.0.0", + "npm-registry-fetch": "^4.0.0" + } + }, + "libnpmorg": { + "version": "1.0.1", + "bundled": true, + "requires": { + "aproba": "^2.0.0", + "figgy-pudding": "^3.4.1", + "get-stream": "^4.0.0", + "npm-registry-fetch": "^4.0.0" + } + }, + "libnpmpublish": { + "version": "1.1.2", + "bundled": true, + "requires": { + "aproba": "^2.0.0", + "figgy-pudding": "^3.5.1", + "get-stream": "^4.0.0", + "lodash.clonedeep": "^4.5.0", + "normalize-package-data": "^2.4.0", + "npm-package-arg": "^6.1.0", + "npm-registry-fetch": "^4.0.0", + "semver": "^5.5.1", + "ssri": "^6.0.1" + } + }, + "libnpmsearch": { + "version": "2.0.2", + "bundled": true, + "requires": { + "figgy-pudding": "^3.5.1", + "get-stream": "^4.0.0", + "npm-registry-fetch": "^4.0.0" + } + }, + "libnpmteam": { + "version": "1.0.2", + "bundled": true, + "requires": { + "aproba": "^2.0.0", + "figgy-pudding": "^3.4.1", + "get-stream": "^4.0.0", + "npm-registry-fetch": "^4.0.0" + } + }, + "libnpx": { + "version": "10.2.0", + "bundled": true, + "requires": { + "dotenv": "^5.0.1", + "npm-package-arg": "^6.0.0", + "rimraf": "^2.6.2", + "safe-buffer": "^5.1.0", + "update-notifier": "^2.3.0", + "which": "^1.3.0", + "y18n": "^4.0.0", + "yargs": "^11.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "bundled": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "lock-verify": { + "version": "2.1.0", + "bundled": true, + "requires": { + "npm-package-arg": "^6.1.0", + "semver": "^5.4.1" + } + }, + "lockfile": { + "version": "1.0.4", + "bundled": true, + "requires": { + "signal-exit": "^3.0.2" + } + }, + "lodash._baseindexof": { + "version": "3.1.0", + "bundled": true + }, + "lodash._baseuniq": { + "version": "4.6.0", + "bundled": true, + "requires": { + "lodash._createset": "~4.0.0", + "lodash._root": "~3.0.0" + } + }, + "lodash._bindcallback": { + "version": "3.0.1", + "bundled": true + }, + "lodash._cacheindexof": { + "version": "3.0.2", + "bundled": true + }, + "lodash._createcache": { + "version": "3.1.2", + "bundled": true, + "requires": { + "lodash._getnative": "^3.0.0" + } + }, + "lodash._createset": { + "version": "4.0.3", + "bundled": true + }, + "lodash._getnative": { + "version": "3.9.1", + "bundled": true + }, + "lodash._root": { + "version": "3.0.1", + "bundled": true + }, + "lodash.clonedeep": { + "version": "4.5.0", + "bundled": true + }, + "lodash.restparam": { + "version": "3.6.1", + "bundled": true + }, + "lodash.union": { + "version": "4.6.0", + "bundled": true + }, + "lodash.uniq": { + "version": "4.5.0", + "bundled": true + }, + "lodash.without": { + "version": "4.4.0", + "bundled": true + }, + "lowercase-keys": { + "version": "1.0.1", + "bundled": true + }, + "lru-cache": { + "version": "5.1.1", + "bundled": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "make-dir": { + "version": "1.3.0", + "bundled": true, + "requires": { + "pify": "^3.0.0" + } + }, + "make-fetch-happen": { + "version": "5.0.0", + "bundled": true, + "requires": { + "agentkeepalive": "^3.4.1", + "cacache": "^12.0.0", + "http-cache-semantics": "^3.8.1", + "http-proxy-agent": "^2.1.0", + "https-proxy-agent": "^2.2.1", + "lru-cache": "^5.1.1", + "mississippi": "^3.0.0", + "node-fetch-npm": "^2.0.2", + "promise-retry": "^1.1.1", + "socks-proxy-agent": "^4.0.0", + "ssri": "^6.0.0" + } + }, + "meant": { + "version": "1.0.1", + "bundled": true + }, + "mem": { + "version": "1.1.0", + "bundled": true, + "requires": { + "mimic-fn": "^1.0.0" + } + }, + "mime-db": { + "version": "1.35.0", + "bundled": true + }, + "mime-types": { + "version": "2.1.19", + "bundled": true, + "requires": { + "mime-db": "~1.35.0" + } + }, + "mimic-fn": { + "version": "1.2.0", + "bundled": true + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "bundled": true + }, + "minipass": { + "version": "2.3.3", + "bundled": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + }, + "dependencies": { + "yallist": { + "version": "3.0.2", + "bundled": true + } + } + }, + "minizlib": { + "version": "1.2.1", + "bundled": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "mississippi": { + "version": "3.0.0", + "bundled": true, + "requires": { + "concat-stream": "^1.5.0", + "duplexify": "^3.4.2", + "end-of-stream": "^1.1.0", + "flush-write-stream": "^1.0.0", + "from2": "^2.1.0", + "parallel-transform": "^1.1.0", + "pump": "^3.0.0", + "pumpify": "^1.3.3", + "stream-each": "^1.1.0", + "through2": "^2.0.0" + } + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "requires": { + "minimist": "0.0.8" + } + }, + "move-concurrently": { + "version": "1.0.1", + "bundled": true, + "requires": { + "aproba": "^1.1.1", + "copy-concurrently": "^1.0.0", + "fs-write-stream-atomic": "^1.0.8", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.3" + }, + "dependencies": { + "aproba": { + "version": "1.2.0", + "bundled": true + } + } + }, + "ms": { + "version": "2.1.1", + "bundled": true + }, + "mute-stream": { + "version": "0.0.7", + "bundled": true + }, + "node-fetch-npm": { + "version": "2.0.2", + "bundled": true, + "requires": { + "encoding": "^0.1.11", + "json-parse-better-errors": "^1.0.0", + "safe-buffer": "^5.1.1" + } + }, + "node-gyp": { + "version": "5.0.3", + "bundled": true, + "requires": { + "env-paths": "^1.0.0", + "glob": "^7.0.3", + "graceful-fs": "^4.1.2", + "mkdirp": "^0.5.0", + "nopt": "2 || 3", + "npmlog": "0 || 1 || 2 || 3 || 4", + "request": "^2.87.0", + "rimraf": "2", + "semver": "~5.3.0", + "tar": "^4.4.8", + "which": "1" + }, + "dependencies": { + "nopt": { + "version": "3.0.6", + "bundled": true, + "requires": { + "abbrev": "1" + } + }, + "semver": { + "version": "5.3.0", + "bundled": true + } + } + }, + "nopt": { + "version": "4.0.1", + "bundled": true, + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "normalize-package-data": { + "version": "2.5.0", + "bundled": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + }, + "dependencies": { + "resolve": { + "version": "1.10.0", + "bundled": true, + "requires": { + "path-parse": "^1.0.6" + } + } + } + }, + "npm-audit-report": { + "version": "1.3.2", + "bundled": true, + "requires": { + "cli-table3": "^0.5.0", + "console-control-strings": "^1.1.0" + } + }, + "npm-bundled": { + "version": "1.0.6", + "bundled": true + }, + "npm-cache-filename": { + "version": "1.0.2", + "bundled": true + }, + "npm-install-checks": { + "version": "3.0.0", + "bundled": true, + "requires": { + "semver": "^2.3.0 || 3.x || 4 || 5" + } + }, + "npm-lifecycle": { + "version": "3.1.2", + "bundled": true, + "requires": { + "byline": "^5.0.0", + "graceful-fs": "^4.1.15", + "node-gyp": "^5.0.2", + "resolve-from": "^4.0.0", + "slide": "^1.1.6", + "uid-number": "0.0.6", + "umask": "^1.1.0", + "which": "^1.3.1" + } + }, + "npm-logical-tree": { + "version": "1.2.1", + "bundled": true + }, + "npm-package-arg": { + "version": "6.1.0", + "bundled": true, + "requires": { + "hosted-git-info": "^2.6.0", + "osenv": "^0.1.5", + "semver": "^5.5.0", + "validate-npm-package-name": "^3.0.0" + } + }, + "npm-packlist": { + "version": "1.4.4", + "bundled": true, + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" + } + }, + "npm-pick-manifest": { + "version": "2.2.3", + "bundled": true, + "requires": { + "figgy-pudding": "^3.5.1", + "npm-package-arg": "^6.0.0", + "semver": "^5.4.1" + } + }, + "npm-profile": { + "version": "4.0.2", + "bundled": true, + "requires": { + "aproba": "^1.1.2 || 2", + "figgy-pudding": "^3.4.1", + "npm-registry-fetch": "^4.0.0" + } + }, + "npm-registry-fetch": { + "version": "4.0.0", + "bundled": true, + "requires": { + "JSONStream": "^1.3.4", + "bluebird": "^3.5.1", + "figgy-pudding": "^3.4.1", + "lru-cache": "^5.1.1", + "make-fetch-happen": "^5.0.0", + "npm-package-arg": "^6.1.0" + } + }, + "npm-run-path": { + "version": "2.0.2", + "bundled": true, + "requires": { + "path-key": "^2.0.0" + } + }, + "npm-user-validate": { + "version": "1.0.0", + "bundled": true + }, + "npmlog": { + "version": "4.1.2", + "bundled": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true + }, + "oauth-sign": { + "version": "0.9.0", + "bundled": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true + }, + "object-keys": { + "version": "1.0.12", + "bundled": true + }, + "object.getownpropertydescriptors": { + "version": "2.0.3", + "bundled": true, + "requires": { + "define-properties": "^1.1.2", + "es-abstract": "^1.5.1" + } + }, + "once": { + "version": "1.4.0", + "bundled": true, + "requires": { + "wrappy": "1" + } + }, + "opener": { + "version": "1.5.1", + "bundled": true + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true + }, + "os-locale": { + "version": "2.1.0", + "bundled": true, + "requires": { + "execa": "^0.7.0", + "lcid": "^1.0.0", + "mem": "^1.1.0" + } + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true + }, + "osenv": { + "version": "0.1.5", + "bundled": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "p-finally": { + "version": "1.0.0", + "bundled": true + }, + "p-limit": { + "version": "1.2.0", + "bundled": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "bundled": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "bundled": true + }, + "package-json": { + "version": "4.0.1", + "bundled": true, + "requires": { + "got": "^6.7.1", + "registry-auth-token": "^3.0.1", + "registry-url": "^3.0.3", + "semver": "^5.1.0" + } + }, + "pacote": { + "version": "9.5.4", + "bundled": true, + "requires": { + "bluebird": "^3.5.3", + "cacache": "^12.0.0", + "figgy-pudding": "^3.5.1", + "get-stream": "^4.1.0", + "glob": "^7.1.3", + "lru-cache": "^5.1.1", + "make-fetch-happen": "^5.0.0", + "minimatch": "^3.0.4", + "minipass": "^2.3.5", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "normalize-package-data": "^2.4.0", + "npm-package-arg": "^6.1.0", + "npm-packlist": "^1.1.12", + "npm-pick-manifest": "^2.2.3", + "npm-registry-fetch": "^4.0.0", + "osenv": "^0.1.5", + "promise-inflight": "^1.0.1", + "promise-retry": "^1.1.1", + "protoduck": "^5.0.1", + "rimraf": "^2.6.2", + "safe-buffer": "^5.1.2", + "semver": "^5.6.0", + "ssri": "^6.0.1", + "tar": "^4.4.8", + "unique-filename": "^1.1.1", + "which": "^1.3.1" + }, + "dependencies": { + "minipass": { + "version": "2.3.5", + "bundled": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + } + } + }, + "parallel-transform": { + "version": "1.1.0", + "bundled": true, + "requires": { + "cyclist": "~0.2.2", + "inherits": "^2.0.3", + "readable-stream": "^2.1.5" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "path-exists": { + "version": "3.0.0", + "bundled": true + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true + }, + "path-is-inside": { + "version": "1.0.2", + "bundled": true + }, + "path-key": { + "version": "2.0.1", + "bundled": true + }, + "path-parse": { + "version": "1.0.6", + "bundled": true + }, + "performance-now": { + "version": "2.1.0", + "bundled": true + }, + "pify": { + "version": "3.0.0", + "bundled": true + }, + "prepend-http": { + "version": "1.0.4", + "bundled": true + }, + "process-nextick-args": { + "version": "2.0.0", + "bundled": true + }, + "promise-inflight": { + "version": "1.0.1", + "bundled": true + }, + "promise-retry": { + "version": "1.1.1", + "bundled": true, + "requires": { + "err-code": "^1.0.0", + "retry": "^0.10.0" + }, + "dependencies": { + "retry": { + "version": "0.10.1", + "bundled": true + } + } + }, + "promzard": { + "version": "0.3.0", + "bundled": true, + "requires": { + "read": "1" + } + }, + "proto-list": { + "version": "1.2.4", + "bundled": true + }, + "protoduck": { + "version": "5.0.1", + "bundled": true, + "requires": { + "genfun": "^5.0.0" + } + }, + "prr": { + "version": "1.0.1", + "bundled": true + }, + "pseudomap": { + "version": "1.0.2", + "bundled": true + }, + "psl": { + "version": "1.1.29", + "bundled": true + }, + "pump": { + "version": "3.0.0", + "bundled": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "pumpify": { + "version": "1.5.1", + "bundled": true, + "requires": { + "duplexify": "^3.6.0", + "inherits": "^2.0.3", + "pump": "^2.0.0" + }, + "dependencies": { + "pump": { + "version": "2.0.1", + "bundled": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + } + } + }, + "punycode": { + "version": "1.4.1", + "bundled": true + }, + "qrcode-terminal": { + "version": "0.12.0", + "bundled": true + }, + "qs": { + "version": "6.5.2", + "bundled": true + }, + "query-string": { + "version": "6.8.2", + "bundled": true, + "requires": { + "decode-uri-component": "^0.2.0", + "split-on-first": "^1.0.0", + "strict-uri-encode": "^2.0.0" + } + }, + "qw": { + "version": "1.0.1", + "bundled": true + }, + "rc": { + "version": "1.2.7", + "bundled": true, + "requires": { + "deep-extend": "^0.5.1", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "bundled": true + } + } + }, + "read": { + "version": "1.0.7", + "bundled": true, + "requires": { + "mute-stream": "~0.0.4" + } + }, + "read-cmd-shim": { + "version": "1.0.1", + "bundled": true, + "requires": { + "graceful-fs": "^4.1.2" + } + }, + "read-installed": { + "version": "4.0.3", + "bundled": true, + "requires": { + "debuglog": "^1.0.1", + "graceful-fs": "^4.1.2", + "read-package-json": "^2.0.0", + "readdir-scoped-modules": "^1.0.0", + "semver": "2 || 3 || 4 || 5", + "slide": "~1.1.3", + "util-extend": "^1.0.1" + } + }, + "read-package-json": { + "version": "2.0.13", + "bundled": true, + "requires": { + "glob": "^7.1.1", + "graceful-fs": "^4.1.2", + "json-parse-better-errors": "^1.0.1", + "normalize-package-data": "^2.0.0", + "slash": "^1.0.0" + } + }, + "read-package-tree": { + "version": "5.3.1", + "bundled": true, + "requires": { + "read-package-json": "^2.0.0", + "readdir-scoped-modules": "^1.0.0", + "util-promisify": "^2.1.0" + } + }, + "readable-stream": { + "version": "3.4.0", + "bundled": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "readdir-scoped-modules": { + "version": "1.1.0", + "bundled": true, + "requires": { + "debuglog": "^1.0.1", + "dezalgo": "^1.0.0", + "graceful-fs": "^4.1.2", + "once": "^1.3.0" + } + }, + "registry-auth-token": { + "version": "3.3.2", + "bundled": true, + "requires": { + "rc": "^1.1.6", + "safe-buffer": "^5.0.1" + } + }, + "registry-url": { + "version": "3.1.0", + "bundled": true, + "requires": { + "rc": "^1.0.1" + } + }, + "request": { + "version": "2.88.0", + "bundled": true, + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + } + }, + "require-directory": { + "version": "2.1.1", + "bundled": true + }, + "require-main-filename": { + "version": "1.0.1", + "bundled": true + }, + "resolve-from": { + "version": "4.0.0", + "bundled": true + }, + "retry": { + "version": "0.12.0", + "bundled": true + }, + "rimraf": { + "version": "2.6.3", + "bundled": true, + "requires": { + "glob": "^7.1.3" + } + }, + "run-queue": { + "version": "1.0.3", + "bundled": true, + "requires": { + "aproba": "^1.1.1" + }, + "dependencies": { + "aproba": { + "version": "1.2.0", + "bundled": true + } + } + }, + "safe-buffer": { + "version": "5.1.2", + "bundled": true + }, + "safer-buffer": { + "version": "2.1.2", + "bundled": true + }, + "semver": { + "version": "5.7.0", + "bundled": true + }, + "semver-diff": { + "version": "2.1.0", + "bundled": true, + "requires": { + "semver": "^5.0.3" + } + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true + }, + "sha": { + "version": "3.0.0", + "bundled": true, + "requires": { + "graceful-fs": "^4.1.2" + } + }, + "shebang-command": { + "version": "1.2.0", + "bundled": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "bundled": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true + }, + "slash": { + "version": "1.0.0", + "bundled": true + }, + "slide": { + "version": "1.1.6", + "bundled": true + }, + "smart-buffer": { + "version": "4.0.2", + "bundled": true + }, + "socks": { + "version": "2.3.2", + "bundled": true, + "requires": { + "ip": "^1.1.5", + "smart-buffer": "4.0.2" + } + }, + "socks-proxy-agent": { + "version": "4.0.2", + "bundled": true, + "requires": { + "agent-base": "~4.2.1", + "socks": "~2.3.2" + }, + "dependencies": { + "agent-base": { + "version": "4.2.1", + "bundled": true, + "requires": { + "es6-promisify": "^5.0.0" + } + } + } + }, + "sorted-object": { + "version": "2.0.1", + "bundled": true + }, + "sorted-union-stream": { + "version": "2.1.3", + "bundled": true, + "requires": { + "from2": "^1.3.0", + "stream-iterate": "^1.1.0" + }, + "dependencies": { + "from2": { + "version": "1.3.0", + "bundled": true, + "requires": { + "inherits": "~2.0.1", + "readable-stream": "~1.1.10" + } + }, + "isarray": { + "version": "0.0.1", + "bundled": true + }, + "readable-stream": { + "version": "1.1.14", + "bundled": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "string_decoder": { + "version": "0.10.31", + "bundled": true + } + } + }, + "spdx-correct": { + "version": "3.0.0", + "bundled": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.1.0", + "bundled": true + }, + "spdx-expression-parse": { + "version": "3.0.0", + "bundled": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.3", + "bundled": true + }, + "split-on-first": { + "version": "1.1.0", + "bundled": true + }, + "sshpk": { + "version": "1.14.2", + "bundled": true, + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "ssri": { + "version": "6.0.1", + "bundled": true, + "requires": { + "figgy-pudding": "^3.5.1" + } + }, + "stream-each": { + "version": "1.2.2", + "bundled": true, + "requires": { + "end-of-stream": "^1.1.0", + "stream-shift": "^1.0.0" + } + }, + "stream-iterate": { + "version": "1.2.0", + "bundled": true, + "requires": { + "readable-stream": "^2.1.5", + "stream-shift": "^1.0.0" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "stream-shift": { + "version": "1.0.0", + "bundled": true + }, + "strict-uri-encode": { + "version": "2.0.0", + "bundled": true + }, + "string-width": { + "version": "2.1.1", + "bundled": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "bundled": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "bundled": true + }, + "strip-ansi": { + "version": "4.0.0", + "bundled": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "string_decoder": { + "version": "1.2.0", + "bundled": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "stringify-package": { + "version": "1.0.0", + "bundled": true + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-eof": { + "version": "1.0.0", + "bundled": true + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true + }, + "supports-color": { + "version": "5.4.0", + "bundled": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "tar": { + "version": "4.4.10", + "bundled": true, + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.3.5", + "minizlib": "^1.2.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.3" + }, + "dependencies": { + "minipass": { + "version": "2.3.5", + "bundled": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + }, + "yallist": { + "version": "3.0.3", + "bundled": true + } + } + }, + "term-size": { + "version": "1.2.0", + "bundled": true, + "requires": { + "execa": "^0.7.0" + } + }, + "text-table": { + "version": "0.2.0", + "bundled": true + }, + "through": { + "version": "2.3.8", + "bundled": true + }, + "through2": { + "version": "2.0.3", + "bundled": true, + "requires": { + "readable-stream": "^2.1.5", + "xtend": "~4.0.1" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "timed-out": { + "version": "4.0.1", + "bundled": true + }, + "tiny-relative-date": { + "version": "1.3.0", + "bundled": true + }, + "tough-cookie": { + "version": "2.4.3", + "bundled": true, + "requires": { + "psl": "^1.1.24", + "punycode": "^1.4.1" + } + }, + "tunnel-agent": { + "version": "0.6.0", + "bundled": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "bundled": true, + "optional": true + }, + "typedarray": { + "version": "0.0.6", + "bundled": true + }, + "uid-number": { + "version": "0.0.6", + "bundled": true + }, + "umask": { + "version": "1.1.0", + "bundled": true + }, + "unique-filename": { + "version": "1.1.1", + "bundled": true, + "requires": { + "unique-slug": "^2.0.0" + } + }, + "unique-slug": { + "version": "2.0.0", + "bundled": true, + "requires": { + "imurmurhash": "^0.1.4" + } + }, + "unique-string": { + "version": "1.0.0", + "bundled": true, + "requires": { + "crypto-random-string": "^1.0.0" + } + }, + "unpipe": { + "version": "1.0.0", + "bundled": true + }, + "unzip-response": { + "version": "2.0.1", + "bundled": true + }, + "update-notifier": { + "version": "2.5.0", + "bundled": true, + "requires": { + "boxen": "^1.2.1", + "chalk": "^2.0.1", + "configstore": "^3.0.0", + "import-lazy": "^2.1.0", + "is-ci": "^1.0.10", + "is-installed-globally": "^0.1.0", + "is-npm": "^1.0.0", + "latest-version": "^3.0.0", + "semver-diff": "^2.0.0", + "xdg-basedir": "^3.0.0" + } + }, + "url-parse-lax": { + "version": "1.0.0", + "bundled": true, + "requires": { + "prepend-http": "^1.0.1" + } + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true + }, + "util-extend": { + "version": "1.0.3", + "bundled": true + }, + "util-promisify": { + "version": "2.1.0", + "bundled": true, + "requires": { + "object.getownpropertydescriptors": "^2.0.3" + } + }, + "uuid": { + "version": "3.3.2", + "bundled": true + }, + "validate-npm-package-license": { + "version": "3.0.4", + "bundled": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "validate-npm-package-name": { + "version": "3.0.0", + "bundled": true, + "requires": { + "builtins": "^1.0.3" + } + }, + "verror": { + "version": "1.10.0", + "bundled": true, + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "wcwidth": { + "version": "1.0.1", + "bundled": true, + "requires": { + "defaults": "^1.0.3" + } + }, + "which": { + "version": "1.3.1", + "bundled": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "bundled": true + }, + "wide-align": { + "version": "1.1.2", + "bundled": true, + "requires": { + "string-width": "^1.0.2" + }, + "dependencies": { + "string-width": { + "version": "1.0.2", + "bundled": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + } + } + }, + "widest-line": { + "version": "2.0.0", + "bundled": true, + "requires": { + "string-width": "^2.1.1" + } + }, + "worker-farm": { + "version": "1.7.0", + "bundled": true, + "requires": { + "errno": "~0.1.7" + } + }, + "wrap-ansi": { + "version": "2.1.0", + "bundled": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + }, + "dependencies": { + "string-width": { + "version": "1.0.2", + "bundled": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true + }, + "write-file-atomic": { + "version": "2.4.3", + "bundled": true, + "requires": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" + } + }, + "xdg-basedir": { + "version": "3.0.0", + "bundled": true + }, + "xtend": { + "version": "4.0.1", + "bundled": true + }, + "y18n": { + "version": "4.0.0", + "bundled": true + }, + "yallist": { + "version": "3.0.3", + "bundled": true + }, + "yargs": { + "version": "11.0.0", + "bundled": true, + "requires": { + "cliui": "^4.0.0", + "decamelize": "^1.1.1", + "find-up": "^2.1.0", + "get-caller-file": "^1.0.1", + "os-locale": "^2.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1", + "yargs-parser": "^9.0.2" + }, + "dependencies": { + "y18n": { + "version": "3.2.1", + "bundled": true + } + } + }, + "yargs-parser": { + "version": "9.0.2", + "bundled": true, + "requires": { + "camelcase": "^4.1.0" + } + } + } + }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "requires": { + "path-key": "^2.0.0" + } + }, + "nth-check": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", + "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", + "requires": { + "boolbase": "~1.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "requires": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + }, + "object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "requires": { + "isobject": "^3.0.0" + } + }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + } + }, + "object.defaults": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz", + "integrity": "sha1-On+GgzS0B96gbaFtiNXNKeQ1/s8=", + "requires": { + "array-each": "^1.0.1", + "array-slice": "^1.0.0", + "for-own": "^1.0.0", + "isobject": "^3.0.0" + } + }, + "object.map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object.map/-/object.map-1.0.1.tgz", + "integrity": "sha1-z4Plncj8wK1fQlDh94s7gb2AHTc=", + "requires": { + "for-own": "^1.0.0", + "make-iterator": "^1.0.0" + } + }, + "object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "requires": { + "isobject": "^3.0.1" + } + }, + "object.reduce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object.reduce/-/object.reduce-1.0.1.tgz", + "integrity": "sha1-b+NI8qx/oPlcpiEiZZkJaCW7A60=", + "requires": { + "for-own": "^1.0.0", + "make-iterator": "^1.0.0" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "one-time": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-0.0.4.tgz", + "integrity": "sha1-+M33eISCb+Tf+T46nMN7HkSAdC4=" + }, + "open": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/open/-/open-6.4.0.tgz", + "integrity": "sha512-IFenVPgF70fSm1keSd2iDBIDIBZkroLeuffXq+wKTzTJlBpesFWojV9lb8mzOfaAzM1sr7HQHuO0vtV0zYekGg==", + "requires": { + "is-wsl": "^1.1.0" + } + }, + "opn": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/opn/-/opn-5.3.0.tgz", + "integrity": "sha512-bYJHo/LOmoTd+pfiYhfZDnf9zekVJrY+cnS2a5F2x+w5ppvTqObojTP7WiFG+kVZs9Inw+qQ/lw7TroWwhdd2g==", + "requires": { + "is-wsl": "^1.1.0" + } + }, + "ordered-read-streams": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz", + "integrity": "sha1-d8DLN8QVJdZBZtmQ/61+xqDhNj4=", + "requires": { + "readable-stream": "^2.0.1" + } + }, + "os": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/os/-/os-0.1.1.tgz", + "integrity": "sha1-IIhF6J4ZOtTZcUdLk5R3NqVtE/M=" + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" + }, + "os-locale": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", + "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", + "requires": { + "lcid": "^1.0.0" + } + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" + }, + "osenv": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" + }, + "p-limit": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", + "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" + }, + "pad-right": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/pad-right/-/pad-right-0.2.2.tgz", + "integrity": "sha1-b7ySQEXSRPKiokRQMGDTv8YAl3Q=", + "requires": { + "repeat-string": "^1.5.2" + } + }, + "parse-filepath": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz", + "integrity": "sha1-pjISf1Oq89FYdvWHLz/6x2PWyJE=", + "requires": { + "is-absolute": "^1.0.0", + "map-cache": "^0.2.0", + "path-root": "^0.1.1" + } + }, + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "requires": { + "error-ex": "^1.2.0" + } + }, + "parse-node-version": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", + "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==" + }, + "parse-passwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", + "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=" + }, + "parse-semver": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/parse-semver/-/parse-semver-1.1.1.tgz", + "integrity": "sha1-mkr9bfBj3Egm+T+6SpnPIj9mbLg=", + "requires": { + "semver": "^5.1.0" + } + }, + "parse5": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-3.0.3.tgz", + "integrity": "sha512-rgO9Zg5LLLkfJF9E6CCmXlSE4UVceloys8JrFqCcHloC3usd/kJCyPDwH2SOlzix2j3xaP9sUX3e8+kvkuleAA==", + "requires": { + "@types/node": "*" + } + }, + "pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=" + }, + "path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=" + }, + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "requires": { + "pinkie-promise": "^2.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" + }, + "path-root": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz", + "integrity": "sha1-mkpoFMrBwM1zNgqV8yCDyOpHRbc=", + "requires": { + "path-root-regex": "^0.1.0" + } + }, + "path-root-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz", + "integrity": "sha1-v8zcjfWxLcUsi0PsONGNcsBLqW0=" + }, + "path-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "requires": { + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "pathval": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", + "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=" + }, + "pause-stream": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", + "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=", + "requires": { + "through": "~2.3" + } + }, + "pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=" + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=" + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "requires": { + "pinkie": "^2.0.0" + } + }, + "plugin-error": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-1.0.1.tgz", + "integrity": "sha512-L1zP0dk7vGweZME2i+EeakvUNqSrdiI3F91TwEoYiGrAfUXmVv6fJIq4g82PAXxNsWOp0J7ZqQy/3Szz0ajTxA==", + "requires": { + "ansi-colors": "^1.0.1", + "arr-diff": "^4.0.0", + "arr-union": "^3.1.0", + "extend-shallow": "^3.0.2" + } + }, + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=" + }, + "prettier": { + "version": "1.18.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.18.2.tgz", + "integrity": "sha512-OeHeMc0JhFE9idD4ZdtNibzY0+TPHSpSSb9h8FqtP+YnoZZ1sl8Vc9b1sasjfymH3SonAF4QcA2+mzHPhMvIiw==" + }, + "pretty-hrtime": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", + "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=" + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==" + }, + "proxy-from-env": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.0.0.tgz", + "integrity": "sha1-M8UDmPcOp+uW0h97gXYwpVeRx+4=" + }, + "psl": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.3.0.tgz", + "integrity": "sha512-avHdspHO+9rQTLbv1RO+MPYeP/SzsCoxofjVnHanETfQhTJrmB0HlDoW+EiN/R+C0BZ+gERab9NY0lPN2TxNag==" + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "pumpify": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", + "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", + "requires": { + "duplexify": "^3.6.0", + "inherits": "^2.0.3", + "pump": "^2.0.0" + }, + "dependencies": { + "pump": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + } + } + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + }, + "puppeteer-core": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-1.19.0.tgz", + "integrity": "sha512-ZPbbjUymorIJomHBvdZX5+2gciUmQtAdepCrkweHH6rMJr96xd/dXzHgmYEOBMatH44SmJrcMtWkgsLHJqT89g==", + "requires": { + "debug": "^4.1.0", + "extract-zip": "^1.6.6", + "https-proxy-agent": "^2.2.1", + "mime": "^2.0.3", + "progress": "^2.0.1", + "proxy-from-env": "^1.0.0", + "rimraf": "^2.6.1", + "ws": "^6.1.0" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + }, + "queue": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/queue/-/queue-4.5.1.tgz", + "integrity": "sha512-AMD7w5hRXcFSb8s9u38acBZ+309u6GsiibP4/0YacJeaurRshogB7v/ZcVPxP5gD5+zIw6ixRHdutiYUJfwKHw==", + "requires": { + "inherits": "~2.0.0" + } + }, + "read": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", + "integrity": "sha1-s9oZvQUkMal2cdRKQmNK33ELQMQ=", + "requires": { + "mute-stream": "~0.0.4" + } + }, + "read-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "requires": { + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" + } + }, + "read-pkg-up": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", + "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "requires": { + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" + } + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "requires": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" + } + }, + "rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", + "requires": { + "resolve": "^1.1.6" + } + }, + "regenerator-runtime": { + "version": "0.13.3", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz", + "integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw==" + }, + "regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "requires": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + } + }, + "remove-bom-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz", + "integrity": "sha512-8v2rWhaakv18qcvNeli2mZ/TMTL2nEyAKRvzo1WtnZBl15SHyEhrCu2/xKlJyUFKHiHgfXIyuY6g2dObJJycXQ==", + "requires": { + "is-buffer": "^1.1.5", + "is-utf8": "^0.2.1" + } + }, + "remove-bom-stream": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/remove-bom-stream/-/remove-bom-stream-1.2.0.tgz", + "integrity": "sha1-BfGlk/FuQuH7kOv1nejlaVJflSM=", + "requires": { + "remove-bom-buffer": "^3.0.0", + "safe-buffer": "^5.1.0", + "through2": "^2.0.3" + } + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=" + }, + "repeat-element": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", + "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==" + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" + }, + "replace-ext": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", + "integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=" + }, + "replace-homedir": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/replace-homedir/-/replace-homedir-1.0.0.tgz", + "integrity": "sha1-6H9tUTuSjd6AgmDBK+f+xv9ueYw=", + "requires": { + "homedir-polyfill": "^1.0.1", + "is-absolute": "^1.0.0", + "remove-trailing-separator": "^1.1.0" + } + }, + "request": { + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "dependencies": { + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + } + } + }, + "request-progress": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-3.0.0.tgz", + "integrity": "sha1-TKdUCBx/7GP1BeT6qCWqBs1mnb4=", + "requires": { + "throttleit": "^1.0.0" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" + }, + "require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=" + }, + "resolve": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz", + "integrity": "sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==", + "requires": { + "path-parse": "^1.0.6" + } + }, + "resolve-dir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", + "integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=", + "requires": { + "expand-tilde": "^2.0.0", + "global-modules": "^1.0.0" + } + }, + "resolve-options": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/resolve-options/-/resolve-options-1.1.0.tgz", + "integrity": "sha1-MrueOcBtZzONyTeMDW1gdFZq0TE=", + "requires": { + "value-or-function": "^3.0.0" + } + }, + "resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=" + }, + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==" + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "requires": { + "glob": "^7.1.3" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "requires": { + "ret": "~0.1.10" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "seed-random": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/seed-random/-/seed-random-2.2.0.tgz", + "integrity": "sha1-KpsZ4lCoFwmSMaW5mk2vgLf77VQ=" + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + }, + "semver-greatest-satisfied-range": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-1.1.0.tgz", + "integrity": "sha1-E+jCZYq5aRywzXEJMkAoDTb3els=", + "requires": { + "sver-compat": "^1.5.0" + } + }, + "serialize-error": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-3.0.0.tgz", + "integrity": "sha512-+y3nkkG/go1Vdw+2f/+XUXM1DXX1XcxTl99FfiD/OEPUNw4uo0i6FKABfTAN5ZcgGtjTRZcEbxcE/jtXbEY19A==" + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + }, + "set-value": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" + }, + "simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", + "requires": { + "is-arrayish": "^0.3.1" + }, + "dependencies": { + "is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" + } + } + }, + "snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "requires": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "requires": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "requires": { + "kind-of": "^3.2.0" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + }, + "source-map-resolve": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", + "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", + "requires": { + "atob": "^2.1.1", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "source-map-url": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=" + }, + "sparkles": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.1.tgz", + "integrity": "sha512-dSO0DDYUahUt/0/pD/Is3VIm5TGJjludZ0HVymmhYF6eNA53PVLhnUk0znSYbH8IYBuJdCE+1luR22jNLMaQdw==" + }, + "spdx-correct": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", + "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", + "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==" + }, + "spdx-expression-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", + "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", + "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==" + }, + "split": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", + "integrity": "sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8=", + "requires": { + "through": "2" + } + }, + "split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "requires": { + "extend-shallow": "^3.0.0" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" + }, + "sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "stack-chain": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/stack-chain/-/stack-chain-2.0.0.tgz", + "integrity": "sha512-GGrHXePi305aW7XQweYZZwiRwR7Js3MWoK/EHzzB9ROdc75nCnjSJVi21rdAGxFl+yCx2L2qdfl5y7NO4lTyqg==" + }, + "stack-generator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/stack-generator/-/stack-generator-2.0.3.tgz", + "integrity": "sha512-kdzGoqrnqsMxOEuXsXyQTmvWXZmG0f3Ql2GDx5NtmZs59sT2Bt9Vdyq0XdtxUi58q/+nxtbF9KOQ9HkV1QznGg==", + "requires": { + "stackframe": "^1.0.4" + } + }, + "stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=" + }, + "stackframe": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.0.4.tgz", + "integrity": "sha512-to7oADIniaYwS3MhtCa/sQhrxidCCQiF/qp4/m5iN3ipf0Y7Xlri0f6eG29r08aL7JYl8n32AF3Q5GYBZ7K8vw==" + }, + "stacktrace-gps": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/stacktrace-gps/-/stacktrace-gps-3.0.2.tgz", + "integrity": "sha512-9o+nWhiz5wFnrB3hBHs2PTyYrS60M1vvpSzHxwxnIbtY2q9Nt51hZvhrG1+2AxD374ecwyS+IUwfkHRE/2zuGg==", + "requires": { + "source-map": "0.5.6", + "stackframe": "^1.0.4" + }, + "dependencies": { + "source-map": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", + "integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI=" + } + } + }, + "stacktrace-js": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/stacktrace-js/-/stacktrace-js-2.0.0.tgz", + "integrity": "sha1-d2ymRqlbxsayuQd2U2p/xyxt21g=", + "requires": { + "error-stack-parser": "^2.0.1", + "stack-generator": "^2.0.1", + "stacktrace-gps": "^3.0.1" + } + }, + "stat-mode": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/stat-mode/-/stat-mode-0.3.0.tgz", + "integrity": "sha512-QjMLR0A3WwFY2aZdV0okfFEJB5TRjkggXZjxP3A1RsWsNHNu3YPv8btmtc6iCFZ0Rul3FE93OYogvhOUClU+ng==" + }, + "static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "requires": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "stream-combiner": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", + "integrity": "sha1-TV5DPBhSYd3mI8o/RMWGvPXErRQ=", + "requires": { + "duplexer": "~0.1.1" + } + }, + "stream-exhaust": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stream-exhaust/-/stream-exhaust-1.0.2.tgz", + "integrity": "sha512-b/qaq/GlBK5xaq1yrK9/zFcyRSTNxmcZwFLGSTG0mXgZl/4Z6GgiyYOXOvY7N3eEvFRAG1bkDRz5EPGSvPYQlw==" + }, + "stream-shift": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", + "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=" + }, + "streamfilter": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/streamfilter/-/streamfilter-3.0.0.tgz", + "integrity": "sha512-kvKNfXCmUyC8lAXSSHCIXBUlo/lhsLcCU/OmzACZYpRUdtKIH68xYhm/+HI15jFJYtNJGYtCgn2wmIiExY1VwA==", + "requires": { + "readable-stream": "^3.0.6" + }, + "dependencies": { + "readable-stream": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz", + "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, + "streamifier": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/streamifier/-/streamifier-0.1.1.tgz", + "integrity": "sha1-l+mNj6TRBdYqJpHR3AfoINuN/E8=" + }, + "string-argv": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.1.1.tgz", + "integrity": "sha512-El1Va5ehZ0XTj3Ekw4WFidXvTmt9SrC0+eigdojgtJMVtPkF0qbBe9fyNSl9eQf+kUHnTSQxdQYzuHfZy8V+DQ==" + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "requires": { + "is-utf8": "^0.2.0" + } + }, + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + }, + "sver-compat": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/sver-compat/-/sver-compat-1.5.0.tgz", + "integrity": "sha1-PPh9/rTQe0o/FIJ7wYaz/QxkXNg=", + "requires": { + "es6-iterator": "^2.0.1", + "es6-symbol": "^3.1.1" + } + }, + "tar": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.2.tgz", + "integrity": "sha512-FCEhQ/4rE1zYv9rYXJw/msRqsnmlje5jHP6huWeBZ704jUTy02c5AZyWujpMR1ax6mVw9NyJMfuK2CMDWVIfgA==", + "requires": { + "block-stream": "*", + "fstream": "^1.0.12", + "inherits": "2" + } + }, + "text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==" + }, + "thenify": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.0.tgz", + "integrity": "sha1-5p44obq+lpsBCCB5eLn2K4hgSDk=", + "requires": { + "any-promise": "^1.0.0" + } + }, + "thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY=", + "requires": { + "thenify": ">= 3.1.0 < 4" + } + }, + "throttleit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.0.tgz", + "integrity": "sha1-nnhYNtr0Z0MUWlmEtiaNgoUorGw=" + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "through2-filter": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/through2-filter/-/through2-filter-3.0.0.tgz", + "integrity": "sha512-jaRjI2WxN3W1V8/FMZ9HKIBXixtiqs3SQSX4/YGIiP3gL6djW48VoZq9tDqeCWs3MT8YY5wb/zli8VW8snY1CA==", + "requires": { + "through2": "~2.0.0", + "xtend": "~4.0.0" + } + }, + "time-stamp": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/time-stamp/-/time-stamp-1.1.0.tgz", + "integrity": "sha1-dkpaEa9QVhkhsTPztE5hhofg9cM=" + }, + "title-case": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/title-case/-/title-case-2.1.1.tgz", + "integrity": "sha1-PhJyFtpY0rxb7PE3q5Ha46fNj6o=", + "requires": { + "no-case": "^2.2.0", + "upper-case": "^1.0.3" + } + }, + "tmp": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.1.0.tgz", + "integrity": "sha512-J7Z2K08jbGcdA1kkQpJSqLF6T0tdQqpR2pnSUXsIchbPdTI9v3e85cLW0d6WDhwuAleOV71j2xWs8qMPfK7nKw==", + "requires": { + "rimraf": "^2.6.3" + } + }, + "to-absolute-glob": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz", + "integrity": "sha1-GGX0PZ50sIItufFFt4z/fQ98hJs=", + "requires": { + "is-absolute": "^1.0.0", + "is-negated-glob": "^1.0.0" + } + }, + "to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "requires": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + }, + "to-through": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-through/-/to-through-2.0.0.tgz", + "integrity": "sha1-/JKtq6ByZHvAtn1rA2ZKoZUJOvY=", + "requires": { + "through2": "^2.0.3" + } + }, + "tough-cookie": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "requires": { + "psl": "^1.1.24", + "punycode": "^1.4.1" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + } + } + }, + "traverse-chain": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/traverse-chain/-/traverse-chain-0.1.0.tgz", + "integrity": "sha1-YdvC1Ttp/2CRoSoWj9fUMxB+QPE=" + }, + "triple-beam": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz", + "integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==" + }, + "tslib": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", + "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==" + }, + "tslint": { + "version": "5.18.0", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.18.0.tgz", + "integrity": "sha512-Q3kXkuDEijQ37nXZZLKErssQVnwCV/+23gFEMROi8IlbaBG6tXqLPQJ5Wjcyt/yHPKBC+hD5SzuGaMora+ZS6w==", + "requires": { + "@babel/code-frame": "^7.0.0", + "builtin-modules": "^1.1.1", + "chalk": "^2.3.0", + "commander": "^2.12.1", + "diff": "^3.2.0", + "glob": "^7.1.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "resolve": "^1.3.2", + "semver": "^5.3.0", + "tslib": "^1.8.0", + "tsutils": "^2.29.0" + } + }, + "tslint-config-prettier": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/tslint-config-prettier/-/tslint-config-prettier-1.18.0.tgz", + "integrity": "sha512-xPw9PgNPLG3iKRxmK7DWr+Ea/SzrvfHtjFt5LBl61gk2UBG/DB9kCXRjv+xyIU1rUtnayLeMUVJBcMX8Z17nDg==" + }, + "tslint-eslint-rules": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/tslint-eslint-rules/-/tslint-eslint-rules-5.4.0.tgz", + "integrity": "sha512-WlSXE+J2vY/VPgIcqQuijMQiel+UtmXS+4nvK4ZzlDiqBfXse8FAvkNnTcYhnQyOTW5KFM+uRRGXxYhFpuBc6w==", + "requires": { + "doctrine": "0.7.2", + "tslib": "1.9.0", + "tsutils": "^3.0.0" + }, + "dependencies": { + "tslib": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.0.tgz", + "integrity": "sha512-f/qGG2tUkrISBlQZEjEqoZ3B2+npJjIf04H1wuAv9iA8i04Icp+61KRXxFdha22670NJopsZCIjhC3SnjPRKrQ==" + }, + "tsutils": { + "version": "3.17.1", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.17.1.tgz", + "integrity": "sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g==", + "requires": { + "tslib": "^1.8.1" + } + } + } + }, + "tslint-microsoft-contrib": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tslint-microsoft-contrib/-/tslint-microsoft-contrib-6.2.0.tgz", + "integrity": "sha512-6tfi/2tHqV/3CL77pULBcK+foty11Rr0idRDxKnteTaKm6gWF9qmaCNU17HVssOuwlYNyOmd9Jsmjd+1t3a3qw==", + "requires": { + "tsutils": "^2.27.2 <2.29.0" + }, + "dependencies": { + "tsutils": { + "version": "2.28.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.28.0.tgz", + "integrity": "sha512-bh5nAtW0tuhvOJnx1GLRn5ScraRLICGyJV5wJhtRWOLsxW70Kk5tZtpK3O/hW6LDnqKS9mlUMPZj9fEMJ0gxqA==", + "requires": { + "tslib": "^1.8.1" + } + } + } + }, + "tslint-plugin-prettier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/tslint-plugin-prettier/-/tslint-plugin-prettier-2.0.1.tgz", + "integrity": "sha512-4FX9JIx/1rKHIPJNfMb+ooX1gPk5Vg3vNi7+dyFYpLO+O57F4g+b/fo1+W/G0SUOkBLHB/YKScxjX/P+7ZT/Tw==", + "requires": { + "eslint-plugin-prettier": "^2.2.0", + "lines-and-columns": "^1.1.6", + "tslib": "^1.7.1" + } + }, + "tsutils": { + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", + "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", + "requires": { + "tslib": "^1.8.1" + } + }, + "tunnel": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.4.tgz", + "integrity": "sha1-LTeFoVjBdMmhbcLARuxfxfF0IhM=" + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + }, + "type": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/type/-/type-1.0.3.tgz", + "integrity": "sha512-51IMtNfVcee8+9GJvj0spSuFcZHe9vSib6Xtgsny1Km9ugyz2mbS08I3rsUIRYgJohFRFU1160sgRodYz378Hg==" + }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==" + }, + "typed-rest-client": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/typed-rest-client/-/typed-rest-client-1.2.0.tgz", + "integrity": "sha512-FrUshzZ1yxH8YwGR29PWWnfksLEILbWJydU7zfIRkyH7kAEzB62uMAl2WY6EyolWpLpVHeJGgQm45/MaruaHpw==", + "requires": { + "tunnel": "0.0.4", + "underscore": "1.8.3" + } + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" + }, + "typescript": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.5.3.tgz", + "integrity": "sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g==" + }, + "uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==" + }, + "unc-path-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", + "integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo=" + }, + "underscore": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz", + "integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=" + }, + "undertaker": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/undertaker/-/undertaker-1.2.1.tgz", + "integrity": "sha512-71WxIzDkgYk9ZS+spIB8iZXchFhAdEo2YU8xYqBYJ39DIUIqziK78ftm26eecoIY49X0J2MLhG4hr18Yp6/CMA==", + "requires": { + "arr-flatten": "^1.0.1", + "arr-map": "^2.0.0", + "bach": "^1.0.0", + "collection-map": "^1.0.0", + "es6-weak-map": "^2.0.1", + "last-run": "^1.1.0", + "object.defaults": "^1.0.0", + "object.reduce": "^1.0.0", + "undertaker-registry": "^1.0.0" + } + }, + "undertaker-registry": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/undertaker-registry/-/undertaker-registry-1.0.1.tgz", + "integrity": "sha1-XkvaMI5KiirlhPm5pDWaSZglzFA=" + }, + "union-value": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "requires": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^2.0.1" + } + }, + "unique-stream": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-2.3.1.tgz", + "integrity": "sha512-2nY4TnBE70yoxHkDli7DMazpWiP7xMdCYqU2nBRO0UB+ZpEkGsSija7MvmvnZFUeC+mrgiUfcHSr3LmRFIg4+A==", + "requires": { + "json-stable-stringify-without-jsonify": "^1.0.1", + "through2-filter": "^3.0.0" + } + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" + }, + "unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "requires": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "requires": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=" + } + } + }, + "upath": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.2.tgz", + "integrity": "sha512-kXpym8nmDmlCBr7nKdIx8P2jNBa+pBpIUFRnKJ4dr8htyYGJFokkr2ZvERRtUN+9SY+JqXouNgUPtv6JQva/2Q==" + }, + "upper-case": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz", + "integrity": "sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg=" + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "requires": { + "punycode": "^2.1.0" + } + }, + "urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=" + }, + "url-join": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-1.1.0.tgz", + "integrity": "sha1-dBxsL0WWxIMNZxhGCSDQySIC3Hg=" + }, + "use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==" + }, + "util-arity": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/util-arity/-/util-arity-1.1.0.tgz", + "integrity": "sha1-WdAa8f2z/t4KxOYysKtfbOl8kzA=" + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" + }, + "v8flags": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.1.3.tgz", + "integrity": "sha512-amh9CCg3ZxkzQ48Mhcb8iX7xpAfYJgePHxWMQCBWECpOSqJUXgY26ncA61UTV0BkPqfhcy6mzwCIoP4ygxpW8w==", + "requires": { + "homedir-polyfill": "^1.0.1" + } + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "value-or-function": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/value-or-function/-/value-or-function-3.0.0.tgz", + "integrity": "sha1-HCQ6ULWVwb5Up1S/7OhWO5/42BM=" + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "vinyl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.0.tgz", + "integrity": "sha512-MBH+yP0kC/GQ5GwBqrTPTzEfiiLjta7hTtvQtbxBgTeSXsmKQRQecjibMbxIXzVT3Y9KJK+drOz1/k+vsu8Nkg==", + "requires": { + "clone": "^2.1.1", + "clone-buffer": "^1.0.0", + "clone-stats": "^1.0.0", + "cloneable-readable": "^1.0.0", + "remove-trailing-separator": "^1.0.1", + "replace-ext": "^1.0.0" + } + }, + "vinyl-fs": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-3.0.3.tgz", + "integrity": "sha512-vIu34EkyNyJxmP0jscNzWBSygh7VWhqun6RmqVfXePrOwi9lhvRs//dOaGOTRUQr4tx7/zd26Tk5WeSVZitgng==", + "requires": { + "fs-mkdirp-stream": "^1.0.0", + "glob-stream": "^6.1.0", + "graceful-fs": "^4.0.0", + "is-valid-glob": "^1.0.0", + "lazystream": "^1.0.0", + "lead": "^1.0.0", + "object.assign": "^4.0.4", + "pumpify": "^1.3.5", + "readable-stream": "^2.3.3", + "remove-bom-buffer": "^3.0.0", + "remove-bom-stream": "^1.2.0", + "resolve-options": "^1.1.0", + "through2": "^2.0.0", + "to-through": "^2.0.0", + "value-or-function": "^3.0.0", + "vinyl": "^2.0.0", + "vinyl-sourcemap": "^1.1.0" + } + }, + "vinyl-sourcemap": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/vinyl-sourcemap/-/vinyl-sourcemap-1.1.0.tgz", + "integrity": "sha1-kqgAWTo4cDqM2xHYswCtS+Y7PhY=", + "requires": { + "append-buffer": "^1.0.2", + "convert-source-map": "^1.5.0", + "graceful-fs": "^4.1.6", + "normalize-path": "^2.1.1", + "now-and-later": "^2.0.0", + "remove-bom-buffer": "^3.0.0", + "vinyl": "^2.0.0" + }, + "dependencies": { + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "requires": { + "remove-trailing-separator": "^1.0.1" + } + } + } + }, + "vsce": { + "version": "1.66.0", + "resolved": "https://registry.npmjs.org/vsce/-/vsce-1.66.0.tgz", + "integrity": "sha512-Zf4+WD4PhEcOr7jkU08SI9lwFqDhmhk73YOCGQ/tNLaBy+PnnX4eSdqj9LdzDLuI2dsyomJLXzDSNgxuaInxCQ==", + "requires": { + "azure-devops-node-api": "^7.2.0", + "chalk": "^2.4.2", + "cheerio": "^1.0.0-rc.1", + "commander": "^2.8.1", + "denodeify": "^1.2.1", + "didyoumean": "^1.2.1", + "glob": "^7.0.6", + "lodash": "^4.17.10", + "markdown-it": "^8.3.1", + "mime": "^1.3.4", + "minimatch": "^3.0.3", + "osenv": "^0.1.3", + "parse-semver": "^1.1.1", + "read": "^1.0.7", + "semver": "^5.1.0", + "tmp": "0.0.29", + "typed-rest-client": "1.2.0", + "url-join": "^1.1.0", + "yauzl": "^2.3.1", + "yazl": "^2.2.2" + }, + "dependencies": { + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, + "tmp": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.29.tgz", + "integrity": "sha1-8lEl/w3Z2jzLDC3Tce4SiLuRKMA=", + "requires": { + "os-tmpdir": "~1.0.1" + } + } + } + }, + "vscode-uri": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-2.0.3.tgz", + "integrity": "sha512-4D3DI3F4uRy09WNtDGD93H9q034OHImxiIcSq664Hq1Y1AScehlP3qqZyTkX/RWxeu0MRMHGkrxYqm2qlDF/aw==" + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", + "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=" + }, + "window-size": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.2.0.tgz", + "integrity": "sha1-tDFbtCFKPXBY6+7okuE/ok2YsHU=" + }, + "winston": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.2.1.tgz", + "integrity": "sha512-zU6vgnS9dAWCEKg/QYigd6cgMVVNwyTzKs81XZtTFuRwJOcDdBg7AU0mXVyNbs7O5RH2zdv+BdNZUlx7mXPuOw==", + "requires": { + "async": "^2.6.1", + "diagnostics": "^1.1.1", + "is-stream": "^1.1.0", + "logform": "^2.1.1", + "one-time": "0.0.4", + "readable-stream": "^3.1.1", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.3.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz", + "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, + "winston-transport": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.3.0.tgz", + "integrity": "sha512-B2wPuwUi3vhzn/51Uukcao4dIduEiPOcOt9HJ3QeaXgkJ5Z7UwpBzxS4ZGNHtrxrUvTwemsQiSys0ihOf8Mp1A==", + "requires": { + "readable-stream": "^2.3.6", + "triple-beam": "^1.2.0" + } + }, + "wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "ws": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz", + "integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==", + "requires": { + "async-limiter": "~1.0.0" + } + }, + "xml": { + "version": "0.0.12", + "resolved": "https://registry.npmjs.org/xml/-/xml-0.0.12.tgz", + "integrity": "sha1-8Is0cQmRK+AChXhfRvFa2OUKX2c=" + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + }, + "y18n": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", + "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=" + }, + "yargs": { + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.0.tgz", + "integrity": "sha512-2eehun/8ALW8TLoIl7MVaRUrg+yCnenu8B4kBlRxj3GJGDKU1Og7sMXPNm1BYyM1DOJmTZ4YeN/Nwxv+8XJsUA==", + "requires": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.1" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" + }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "requires": { + "locate-path": "^3.0.0" + } + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "requires": { + "ansi-regex": "^4.1.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" + }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + } + }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==" + }, + "yargs-parser": { + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.1.tgz", + "integrity": "sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==", + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, + "yargs-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-3.2.0.tgz", + "integrity": "sha1-UIE1XRnZ0MjF2BrakIy05tGGZk8=", + "requires": { + "camelcase": "^3.0.0", + "lodash.assign": "^4.1.0" + } + }, + "yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", + "requires": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "yazl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/yazl/-/yazl-2.5.1.tgz", + "integrity": "sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw==", + "requires": { + "buffer-crc32": "~0.2.3" + } + } + } +} diff --git a/uitests/package.json b/uitests/package.json new file mode 100644 index 000000000000..95378cd55403 --- /dev/null +++ b/uitests/package.json @@ -0,0 +1,68 @@ +{ + "name": "uitests", + "version": "1.0.0", + "main": "./out/index", + "scripts": { + "compile": "tsc -watch -p ./", + "compileNoWatch": "tsc -p ./", + "download": "node ./out/index download", + "_install": "node ./out/index install", + "test": "node ./out/index test", + "lint": "tslint --project tsconfig.json" + }, + "dependencies": { + "@types/chai": "^4.1.7", + "@types/chai-arrays": "^1.0.2", + "@types/chai-as-promised": "^7.1.0", + "@types/cucumber": "^4.0.7", + "@types/fs-extra": "^8.0.0", + "@types/gulp": "^4.0.6", + "@types/progress": "^2.0.3", + "@types/puppeteer-core": "^1.9.0", + "@types/request": "^2.48.2", + "@types/rimraf": "^2.0.2", + "@types/tmp": "^0.1.0", + "@types/yargs": "^13.0.0", + "chai": "^4.2.0", + "chai-array": "0.0.2", + "chai-as-promised": "^7.1.1", + "clipboardy": "^2.1.0", + "colors": "^1.3.3", + "cucumber": "^5.1.0", + "cucumber-html-reporter": "^5.0.0", + "cucumber-junit": "^1.7.1", + "cucumber-pretty": "^1.5.2", + "fs-extra": "^8.1.0", + "glob": "^7.1.4", + "gulp": "^4.0.2", + "gulp-chmod": "^3.0.0", + "gulp-filter": "^6.0.0", + "gulp-gunzip": "^1.1.0", + "gulp-untar": "0.0.8", + "gulp-vinyl-zip": "^2.1.2", + "jsonc-parser": "^2.1.0", + "multiple-cucumber-html-reporter": "^1.12.0", + "named-js-regexp": "^1.3.5", + "npm": "^6.10.3", + "prettier": "^1.18.2", + "progress": "^2.0.3", + "puppeteer-core": "^1.19.0", + "request": "^2.88.0", + "request-progress": "^3.0.0", + "rimraf": "^2.6.3", + "source-map-support": "^0.5.12", + "tmp": "^0.1.0", + "tslint": "^5.18.0", + "tslint-config-prettier": "^1.18.0", + "tslint-eslint-rules": "^5.4.0", + "tslint-microsoft-contrib": "^6.2.0", + "tslint-plugin-prettier": "^2.0.1", + "typescript": "^3.5.3", + "vinyl-fs": "^3.0.3", + "vsce": "^1.66.0", + "vscode-uri": "^2.0.3", + "winston": "^3.2.1", + "winston-transport": "^4.3.0", + "yargs": "^13.3.0" + } +} diff --git a/uitests/src/constants.ts b/uitests/src/constants.ts new file mode 100644 index 000000000000..3bc095c6296b --- /dev/null +++ b/uitests/src/constants.ts @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import * as path from 'path'; +import { RetryOptions } from './helpers'; + +export const isCI = process.env.TRAVIS === 'true' || process.env.TF_BUILD !== undefined; +export const uitestsRootPath = path.join(__dirname, '..'); +export const featurePath = path.join(uitestsRootPath, 'features'); +export const vscodeTestPath = path.join(uitestsRootPath, '.vscode test'); + +// Assume 1 minute is enough for extension to get activated. +// Remember, activation of extension is slow on Windows. +export const extensionActivationTimeout = 60_000; +export const maxStepTimeout = 150_000; +export const maxHookTimeout = 240_000; + +// Tooltip of the Statusbar created the Bootstrap extension to indicate it has activated. +export const pyBootstrapTooltip = 'Py'; +// Tooltip of the Statusbar created by the Bootstrap extension when Python Extension has activated. +export const pyBootstrapActivatedStatusBarTooltip = 'Py2'; + +export const RetryMax30Seconds: RetryOptions = { timeout: 30_000, interval: 100 }; +export const RetryMax20Seconds: RetryOptions = { timeout: 20_000, interval: 100 }; +export const RetryMax10Seconds: RetryOptions = { timeout: 10_000, interval: 100 }; +export const RetryMax5Seconds: RetryOptions = { timeout: 5_000, interval: 100 }; +export const RetryMax2Seconds: RetryOptions = { timeout: 2_000, interval: 100 }; +export const RetryMax5Times: RetryOptions = { count: 5, interval: 100 }; +export const RetryMax2Times: RetryOptions = { count: 2, interval: 100 }; + +export const CucumberRetryMax30Seconds: {} = { wrapperOptions: { retry: RetryMax30Seconds } }; +export const CucumberRetryMax20Seconds: {} = { wrapperOptions: { retry: RetryMax20Seconds } }; +export const CucumberRetryMax10Seconds: {} = { wrapperOptions: { retry: RetryMax10Seconds } }; +export const CucumberRetryMax5Seconds: {} = { wrapperOptions: { retry: RetryMax5Seconds } }; +export const CucumberRetryMax2Seconds: {} = { wrapperOptions: { retry: RetryMax2Seconds } }; +export const CucumberRetryMax5Times: {} = { wrapperOptions: { retry: RetryMax5Times } }; +export const CucumberRetryMax2Times: {} = { wrapperOptions: { retry: RetryMax2Times } }; + +export type localizationKeys = 'debug.selectConfigurationTitle'; diff --git a/uitests/src/helpers/extensions.ts b/uitests/src/helpers/extensions.ts new file mode 100644 index 000000000000..612e91fbdbc4 --- /dev/null +++ b/uitests/src/helpers/extensions.ts @@ -0,0 +1,140 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +/** + * @typedef {Object} SplitLinesOptions + * @property {boolean} [trim=true] - Whether to trim the lines. + * @property {boolean} [removeEmptyEntries=true] - Whether to remove empty entries. + */ + +// https://stackoverflow.com/questions/39877156/how-to-extend-string-prototype-and-use-it-next-in-typescript +// tslint:disable-next-line:interface-name + +/** + * @typedef {Object} SplitLinesOptions + * @property {boolean} [trim=true] - Whether to trim the lines. + * @property {boolean} [removeEmptyEntries=true] - Whether to remove empty entries. + */ + +// https://stackoverflow.com/questions/39877156/how-to-extend-string-prototype-and-use-it-next-in-typescript +// tslint:disable-next-line:interface-name +declare interface String { + /** + * Split a string using the cr and lf characters and return them as an array. + * By default lines are trimmed and empty lines are removed. + * @param {SplitLinesOptions=} splitOptions - Options used for splitting the string. + */ + splitLines(splitOptions?: { trim: boolean; removeEmptyEntries?: boolean }): string[]; + /** + * Appropriately formats a string so it can be used as an argument for a command in a shell. + * E.g. if an argument contains a space, then it will be enclosed within double quotes. + */ + toCommandArgument(): string; + /** + * Appropriately formats a a file path so it can be used as an argument for a command in a shell. + * E.g. if an argument contains a space, then it will be enclosed within double quotes. + */ + fileToCommandArgument(): string; + /** + * String.format() implementation. + * Tokens such as {0}, {1} will be replaced with corresponding positional arguments. + */ + format(...args: string[]): string; + + /** + * String.trimQuotes implementation + * Removes leading and trailing quotes from a string + */ + trimQuotes(): string; + + /** + * Replaces characters such as 160 with 32. + * When we get string content of html elements, we get char code 160 instead of 32. + */ + normalize(): string; +} + +// Standard normalize. +const oldNormalize = String.prototype.normalize; +/** + * Replaces characters such as 160 with 32. + * When we get string content of html elements, we get char code 160 instead of 32. + */ +// tslint:disable-next-line: no-any +String.prototype.normalize = function(this: string, ...args: []): string { + const normalized = this.replace(new RegExp(String.fromCharCode(160), 'g'), String.fromCharCode(32)); + return typeof oldNormalize === 'function' ? oldNormalize.apply(normalized, args) : normalized; +}; + +/** + * Split a string using the cr and lf characters and return them as an array. + * By default lines are trimmed and empty lines are removed. + * @param {SplitLinesOptions=} splitOptions - Options used for splitting the string. + */ +String.prototype.splitLines = function(this: string, splitOptions: { trim: boolean; removeEmptyEntries: boolean } = { removeEmptyEntries: true, trim: true }): string[] { + let lines = this.split(/\r?\n/g); + if (splitOptions && splitOptions.trim) { + lines = lines.map(line => line.trim()); + } + if (splitOptions && splitOptions.removeEmptyEntries) { + lines = lines.filter(line => line.length > 0); + } + return lines; +}; + +/** + * Appropriately formats a string so it can be used as an argument for a command in a shell. + * E.g. if an argument contains a space, then it will be enclosed within double quotes. + * @param {String} value. + */ +String.prototype.toCommandArgument = function(this: string): string { + if (!this) { + return this; + } + return this.indexOf(' ') >= 0 && !this.startsWith('"') && !this.endsWith('"') ? `"${this}"` : this.toString(); +}; + +/** + * Appropriately formats a a file path so it can be used as an argument for a command in a shell. + * E.g. if an argument contains a space, then it will be enclosed within double quotes. + */ +String.prototype.fileToCommandArgument = function(this: string): string { + if (!this) { + return this; + } + return this.toCommandArgument().replace(/\\/g, '/'); +}; + +/** + * String.trimQuotes implementation + * Removes leading and trailing quotes from a string + */ +String.prototype.trimQuotes = function(this: string): string { + if (!this) { + return this; + } + return this.replace(/(^['"])|(['"]$)/g, ''); +}; + +// tslint:disable-next-line:interface-name +declare interface Promise { + /** + * Catches task error and ignores them. + */ + ignoreErrors(): void; +} + +/** + * Explicitly tells that promise should be run asynchonously. + */ +Promise.prototype.ignoreErrors = function(this: Promise) { + // tslint:disable-next-line:no-empty + this.catch(() => {}); +}; + +if (!String.prototype.format) { + String.prototype.format = function(this: string) { + const args = arguments; + return this.replace(/{(\d+)}/g, (match, number) => (args[number] === undefined ? match : args[number])); + }; +} diff --git a/uitests/src/helpers/http.ts b/uitests/src/helpers/http.ts new file mode 100644 index 000000000000..62363cc25cb8 --- /dev/null +++ b/uitests/src/helpers/http.ts @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +// tslint:disable: import-name no-console no-var-requires no-require-imports + +import * as fs from 'fs-extra'; +import ProgressBar from 'progress'; +import * as request from 'request'; +const progress = require('request-progress'); +const progressBar = require('progress') as typeof ProgressBar; + +export async function downloadFile(url: string, targetFile: string, downloadMessage = 'Downloading') { + return new Promise((resolve, reject) => { + const bar = new progressBar(`${downloadMessage} [:bar]`, { + complete: '=', + incomplete: ' ', + width: 20, + total: 100 + }); + progress(request(url)) + .on('progress', (state: { percent: number }) => bar.update(state.percent)) + .on('error', reject) + .on('end', () => { + bar.update(100); + resolve(); + }) + .pipe(fs.createWriteStream(targetFile)); + }); +} diff --git a/uitests/src/helpers/index.ts b/uitests/src/helpers/index.ts new file mode 100644 index 000000000000..f1f9278eeb2a --- /dev/null +++ b/uitests/src/helpers/index.ts @@ -0,0 +1,9 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +export * from './http'; +export * from './misc'; +export * from './unzip'; +export * from './types'; diff --git a/uitests/src/helpers/logger.ts b/uitests/src/helpers/logger.ts new file mode 100644 index 000000000000..071ccb1e7cbd --- /dev/null +++ b/uitests/src/helpers/logger.ts @@ -0,0 +1,196 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +// tslint:disable: no-any + +import * as util from 'util'; +import { createLogger, format, transports } from 'winston'; +import * as Transport from 'winston-transport'; +import { getOSType, OSType, StopWatch } from './misc'; + +const formatter = format.printf(({ level, message, timestamp }) => { + // Pascal casing og log level, so log files get highlighted when viewing in VSC and other editors. + return `${level.substring(0, 1).toUpperCase()}${level.substring(1)} ${timestamp}: ${message}`; +}); + +const consoleFormat = format.combine( + format.colorize({ all: true }), + format.timestamp({ + format: 'YYYY-MM-DD HH:mm:ss' + }), + formatter +); + +const fileFormat = format.combine( + format.timestamp({ + format: 'YYYY-MM-DD HH:mm:ss' + }), + formatter +); + +const getFormattedMessage = (...args: {}[]) => (args.length === 0 ? '' : util.format(args[0], ...args.slice(1))); +let logger = createLogger({ + format: consoleFormat, + level: 'debug', + transports: [new transports.Console({ format: consoleFormat })] +}); + +export function info(message: string, ...args: any[]) { + logger.info(getFormattedMessage(message, ...args)); +} +export function debug(message: string, ...args: any[]) { + logger.debug(getFormattedMessage(message, ...args)); +} +export function warn(message: string, ...args: any[]) { + logger.warn(getFormattedMessage(message, ...args)); +} +export function error(message: string, ...args: any[]) { + logger.error(getFormattedMessage(message, ...args)); +} +export function initialize(verbose: boolean, filename?: string) { + const level = verbose ? 'debug' : 'info'; + const loggerTransports: Transport[] = [new transports.Console({ format: consoleFormat })]; + if (filename && getOSType() !== OSType.Windows) { + // Don't log to a file on windows, cuz it sucks. + // We delete the file mid testing, but the file logger on windows craps out when the file is deleted. + loggerTransports.push(new transports.File({ format: fileFormat, filename: filename })); + } + logger = createLogger({ level, transports: loggerTransports }); +} + +/** + * What do we want to log. + * @export + * @enum {number} + */ +export enum LogOptions { + None = 0, + Arguments = 1, + ReturnValue = 2 +} + +// tslint:disable-next-line:no-any +function argsToLogString(args: any[]): string { + try { + return (args || []) + .map((item, index) => { + if (item === undefined) { + return `Arg ${index + 1}: undefined`; + } + if (item === null) { + return `Arg ${index + 1}: null`; + } + try { + if (item && item.fsPath) { + return `Arg ${index + 1}: `; + } + return `Arg ${index + 1}: ${JSON.stringify(item)}`; + } catch { + return `Arg ${index + 1}: `; + } + }) + .join(', '); + } catch { + return ''; + } +} + +// tslint:disable-next-line:no-any +function returnValueToLogString(returnValue: any): string { + const returnValueMessage = 'Return Value: '; + if (returnValue === undefined) { + return `${returnValueMessage}undefined`; + } + if (returnValue === null) { + return `${returnValueMessage}null`; + } + try { + return `${returnValueMessage}${JSON.stringify(returnValue)}`; + } catch { + return `${returnValueMessage}`; + } +} +enum LogLevel { + Information = 'Information', + Error = 'Error', + Warning = 'Warning' +} + +export function debugDecorator(message: string, options: LogOptions = LogOptions.Arguments | LogOptions.ReturnValue) { + return trace(message, options); +} +export function errorDecorator(message: string) { + return trace(message, LogOptions.Arguments | LogOptions.ReturnValue, LogLevel.Error); +} +export function infoDecorator(message: string) { + return trace(message); +} +export function warnDecorator(message: string) { + return trace(message, LogOptions.Arguments | LogOptions.ReturnValue, LogLevel.Warning); +} + +function trace(message: string, options: LogOptions = LogOptions.None, logLevel?: LogLevel) { + // tslint:disable-next-line:no-function-expression no-any + return function(_: Object, __: string, descriptor: TypedPropertyDescriptor) { + const originalMethod = descriptor.value; + // tslint:disable-next-line:no-function-expression no-any + descriptor.value = function(...args: any[]) { + const className = _ && _.constructor ? _.constructor.name : ''; + // tslint:disable-next-line:no-any + function writeSuccess(elapsedTime: number, returnValue: any) { + if (logLevel === LogLevel.Error) { + return; + } + writeToLog(elapsedTime, returnValue); + } + function writeError(elapsedTime: number, ex: Error) { + writeToLog(elapsedTime, undefined, ex); + } + // tslint:disable-next-line:no-any + function writeToLog(elapsedTime: number, returnValue?: any, ex?: Error) { + const messagesToLog = [message]; + messagesToLog.push(`Class name = ${className}, completed in ${elapsedTime}ms`); + if ((options && LogOptions.Arguments) === LogOptions.Arguments) { + messagesToLog.push(argsToLogString(args)); + } + if ((options & LogOptions.ReturnValue) === LogOptions.ReturnValue) { + messagesToLog.push(returnValueToLogString(returnValue)); + } + if (ex) { + error(messagesToLog.join(', '), ex); + } else { + info(messagesToLog.join(', ')); + } + } + const timer = new StopWatch(); + try { + trace(`Before ${message}`, options, logLevel); + // tslint:disable-next-line:no-invalid-this no-unsafe-any + const result = originalMethod.apply(this, args); + // If method being wrapped returns a promise then wait for it. + // tslint:disable-next-line:no-unsafe-any + if (result && typeof result.then === 'function' && typeof result.catch === 'function') { + // tslint:disable-next-line:prefer-type-cast + (result as Promise) + .then(data => { + writeSuccess(timer.elapsedTime, data); + return data; + }) + .catch(ex => { + writeError(timer.elapsedTime, ex); + }); + } else { + writeSuccess(timer.elapsedTime, result); + } + return result; + } catch (ex) { + writeError(timer.elapsedTime, ex); + throw ex; + } + }; + + return descriptor; + }; +} diff --git a/uitests/src/helpers/misc.ts b/uitests/src/helpers/misc.ts new file mode 100644 index 000000000000..cd1f02c0f15d --- /dev/null +++ b/uitests/src/helpers/misc.ts @@ -0,0 +1,118 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { warn } from './logger'; +import { RetryCounterOptions, RetryOptions, RetryTimeoutOptions } from './types'; + +export enum OSType { + OSX = 'OSX', + Linux = 'Linux', + Windows = 'Windows' +} + +export function getOSType(): OSType { + if (/^win/.test(process.platform)) { + return OSType.Windows; + } else if (/^darwin/.test(process.platform)) { + return OSType.OSX; + } else if (/^linux/.test(process.platform)) { + return OSType.Linux; + } else { + throw new Error('Unknown OS'); + } +} + +export function sleep(timeout: number): Promise { + return new Promise(resolve => setTimeout(resolve, timeout)); +} + +export function noop() { + // Do nothing. +} + +export class StopWatch { + private started = new Date().getTime(); + public get elapsedTime() { + return new Date().getTime() - this.started; + } + public reset() { + this.started = new Date().getTime(); + } + public log(message: string): void { + // tslint:disable-next-line: no-console + console.log(`${this.elapsedTime}: ${message}`); + } +} + +// tslint:disable-next-line: no-any +type AnyAsyncFunction = (...args: any[]) => Promise; +type Unpacked = T extends Promise ? U : T; +// tslint:disable-next-line: no-any +/** + * Wrap a function to ensure it gets retried if there are any errors. + * @example The following example will run the inner function for a max of 10ms (will fail after 10ms as it will always throw an exception). + * retryWrapper(async ()=> { console.log('Hello'); throw new Error('kaboom');}, {timeout: 10}); + * + * @export + * @template T + * @param {({} | any)} this + * @param {RetryOptions} options + * @param {T} fn + * @param {...{}[]} args + * @returns {Promise>>} + */ +export async function retryWrapper( + // tslint:disable-next-line: no-any + this: {} | any, + options: RetryOptions, + fn: T, + ...args: {}[] +): Promise>> { + const watch = new StopWatch(); + const interval = options.interval || 100; + const iterations = (options as RetryTimeoutOptions).timeout ? (options as RetryTimeoutOptions).timeout / interval : (options as RetryCounterOptions).count; + const timeout = (options as RetryTimeoutOptions).timeout || (options as RetryCounterOptions).count * interval; + + let lastEx: Error | undefined; + + // tslint:disable-next-line: prefer-array-literal + for (const _ of [...new Array(iterations)]) { + try { + return await (fn as Function).apply(this, args); + } catch (ex) { + lastEx = ex; + if (watch.elapsedTime > timeout) { + break; + } + await sleep(interval); + continue; + } + } + if (options.logFailures !== false) { + const customMessage = options.errorMessage ? `, ${options.errorMessage}` : ''; + warn(`Timeout after ${timeout}${customMessage}. Options ${JSON.stringify(options)}`, lastEx); + } + throw lastEx; +} + +/** + * Retry decorator. + * + * @export + * @param {RetryOptions} [options={ timeout: 5_000, interval: 100 }] + * @returns + */ +export function retry(options: RetryOptions = { timeout: 5_000, interval: 100 }) { + // tslint:disable-next-line: no-any no-function-expression + return function(_target: any, _propertyKey: string, descriptor: PropertyDescriptor) { + const originalMethod = descriptor.value!; + descriptor.value = async function(this: {}): Promise<{}> { + const args = [].slice.call(arguments) as {}[]; + return retryWrapper.bind(this)(options, originalMethod as AnyAsyncFunction, ...args); + }; + + return descriptor; + }; +} diff --git a/uitests/src/helpers/python.ts b/uitests/src/helpers/python.ts new file mode 100644 index 000000000000..72691752542d --- /dev/null +++ b/uitests/src/helpers/python.ts @@ -0,0 +1,52 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { exec } from 'child_process'; +import { debug } from './logger'; +import { sleep } from './misc'; + +export async function isPackageInstalled(pythonPath: string, moduleName: string): Promise { + const cmd = `${pythonPath.toCommandArgument()} -c "import ${moduleName};print('Hello World')"`; + debug(`Executing command = ${cmd}`); + return new Promise(resolve => { + exec(cmd, (ex, stdout: string, stdErr: string) => { + if (ex || stdErr) { + debug(`Executing command = ${cmd}, error: `, ex, stdErr); + return resolve(false); + } + debug(`Executing command = ${cmd}, output: `, stdout); + resolve(stdout.trim() === 'Hello World'); + }); + }); +} + +export async function installPackage(pythonPath: string, moduleName: string): Promise { + await installOrUninstallPackage(pythonPath, moduleName, true); +} +export async function uninstallModule(pythonPath: string, moduleName: string): Promise { + await installOrUninstallPackage(pythonPath, moduleName, false); +} +export async function installOrUninstallPackage(pythonPath: string, moduleName: string, install: boolean = true): Promise { + const installCmd = install ? 'install' : 'uninstall'; + const extraArgs = install ? [] : ['-y']; + const cmd = `${pythonPath.toCommandArgument()} -m pip ${installCmd} ${moduleName} -q --disable-pip-version-check ${extraArgs.join(' ')}`; + // tslint:disable-next-line: no-unnecessary-callback-wrapper + return new Promise(resolve => exec(cmd.trim(), () => resolve())); +} +export async function ensurePackageIsInstalled(pythonPath: string, moduleName: string): Promise { + const installed = await isPackageInstalled(pythonPath, moduleName); + if (!installed) { + await installPackage(pythonPath, moduleName); + await sleep(1000); + } +} +export async function ensurePackageIsNotInstalled(pythonPath: string, moduleName: string): Promise { + const installed = await isPackageInstalled(pythonPath, moduleName); + debug(`Module ${moduleName} is installed = ${installed}`); + if (installed) { + await uninstallModule(pythonPath, moduleName); + await sleep(1000); + } +} diff --git a/uitests/src/helpers/report.ts b/uitests/src/helpers/report.ts new file mode 100644 index 000000000000..3376ec566f8b --- /dev/null +++ b/uitests/src/helpers/report.ts @@ -0,0 +1,99 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import * as fs from 'fs-extra'; +import * as path from 'path'; +import { ITestOptions } from '../types'; +import { getOSType, OSType } from './misc'; + +// tslint:disable: no-var-requires no-require-imports no-any +const report = require('multiple-cucumber-html-reporter'); +const cucumberJunit = require('cucumber-junit'); +const reporter = require('cucumber-html-reporter'); + +const OS = { + [OSType.Linux]: '🐧 Linux', + [OSType.OSX]: '🍎 Mac', + [OSType.Windows]: '🖥 Win' +}; + +export async function generateJUnitReport(options: ITestOptions, cucumberReportJsonFilePath: string) { + const content = await fs.readFile(cucumberReportJsonFilePath); + const xml = cucumberJunit(content, { strict: true }); + await fs.writeFile(path.join(options.reportsPath, 'report.xml'), xml); +} + +function getMetadata(options: ITestOptions) { + return [ + { name: 'OS', value: OS[getOSType()] }, + { name: 'VS Code', value: options.channel }, + { name: 'Build', value: process.env.AgentJobName }, + { name: 'Python', value: process.env.PYTHON_VERSION } + ]; +} + +/** + * Add metadata into the JSON report. + * Useful for later (when we merge all reports into one and generate html reports). + * + * @export + * @param {ITestOptions} options + * @param {string} cucumberReportJsonFilePath + */ +export async function addReportMetadata(options: ITestOptions, cucumberReportJsonFilePath: string) { + const metadata = getMetadata(options); + + // Write custom metadata (make it part of Json report for later use). + // This way cucumber report has the data. + const reportData = JSON.parse(await fs.readFile(cucumberReportJsonFilePath, 'utf8')); + for (const item of reportData) { + item.metadata = JSON.parse(JSON.stringify(metadata)); + } + await fs.writeFile(cucumberReportJsonFilePath, JSON.stringify(reportData)); +} + +/** + * Generate HTML report. + * (store metadata into cucumber json report for when multiple reports are merged into one). + * + * @export + * @param {ITestOptions} options + * @param {string} cucumberReportJsonFilePath + */ +export async function generateHtmlReport(options: ITestOptions, cucumberReportJsonFilePath: string) { + // Generate the report. + const htmlFile = path.join(options.reportsPath, 'index.html'); + const reportOptions = { + name: 'Python VS Code', + brandTitle: 'UI Tests', + theme: 'hierarchy', + jsonFile: cucumberReportJsonFilePath, + output: htmlFile, + reportSuiteAsScenarios: true, + launchReport: false, + metadata: {} + }; + getMetadata(options).forEach(item => ((reportOptions.metadata as any)[item.name] = item.value)); + reporter.generate(reportOptions); +} + +/** + * Merge multiple cucumber reports into one. + * (we expect metadata to be stored in cucumber json). + * + * @export + * @param {string} cucumberReportsPath + * @param {string} outputDir + */ +export async function mergeAndgenerateHtmlReport(cucumberReportsPath: string, outputDir: string) { + report.generate({ + jsonDir: cucumberReportsPath, + reportPath: outputDir, + pageTitle: 'Python VS Code', + reportName: 'UI Tests', + customMetadata: true, + displayDuration: true + }); +} diff --git a/uitests/src/helpers/types.ts b/uitests/src/helpers/types.ts new file mode 100644 index 000000000000..a01287e29249 --- /dev/null +++ b/uitests/src/helpers/types.ts @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +export type RetryTimeoutOptions = { + /** + * Number of times to try. + * + * @type {number} + */ + timeout: number; + /** + * Time in ms to wait before retrying (generally defaults to 100ms). + * + * @type {number} + */ + interval?: number; + errorMessage?: string; + /** + * If true, then do not log failures. + * Defaults to true. + * + * @type {boolean} + */ + logFailures?: boolean; +}; +export type RetryCounterOptions = { + /** + * Number of times to try. + * + * @type {number} + */ + count: number; + /** + * Time in ms to wait before retrying (generally defaults to 100ms). + * + * @type {number} + */ + interval?: number; + errorMessage?: string; + /** + * If true, then do not log failures. + * Defaults to true. + * + * @type {boolean} + */ + logFailures?: boolean; +}; +export type RetryOptions = RetryTimeoutOptions | RetryCounterOptions; diff --git a/uitests/src/helpers/unzip.ts b/uitests/src/helpers/unzip.ts new file mode 100644 index 000000000000..70189c589d97 --- /dev/null +++ b/uitests/src/helpers/unzip.ts @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +// tslint:disable: no-var-requires no-require-imports no-default-export no-console + +const gulp = require('gulp'); +const vzip = require('gulp-vinyl-zip'); +const vfs = require('vinyl-fs'); +const untar = require('gulp-untar'); +const gunzip = require('gulp-gunzip'); +const chmod = require('gulp-chmod'); +const filter = require('gulp-filter'); +import * as fs from 'fs-extra'; +import * as glob from 'glob'; +import * as path from 'path'; +import { debug } from './logger'; + +export function unzipVSCode(zipFile: string, targetDir: string) { + debug(`Unzip VSCode ${zipFile} into ${targetDir}`); + const fn = zipFile.indexOf('.gz') > 0 || zipFile.indexOf('.tag') > 0 ? unzipTarGz : unzipFile; + return fn(zipFile, targetDir); +} + +export async function unzipFile(zipFile: string, targetFolder: string) { + debug(`Unzip (unzipFile) ${zipFile} into ${targetFolder}`); + await fs.ensureDir(targetFolder); + return new Promise((resolve, reject) => { + gulp.src(zipFile) + .pipe(vzip.src()) + .pipe(vfs.dest(targetFolder)) + .on('end', resolve) + .on('error', reject); + }); +} + +export async function unzipTarGz(zipFile: string, targetFolder: string) { + debug(`Unzip (unzipTarGz) ${zipFile} into ${targetFolder}`); + const fileToFixPermissions = ['VSCode-linux-x64/code', 'VSCode-linux-x64/code-insiders', 'VSCode-linux-x64/resources/app/node_modules*/vscode-ripgrep/**/rg']; + await fs.ensureDir(targetFolder); + await new Promise((resolve, reject) => { + const gulpFilter = filter(fileToFixPermissions, { restore: true }); + gulp.src(zipFile) + .pipe(gunzip()) + .pipe(untar()) + .pipe(gulpFilter) + .pipe(chmod(493)) // 0o755 + .pipe(gulpFilter.restore) + .pipe(vfs.dest(targetFolder)) + .on('end', resolve) + .on('error', reject); + }); + + for (const fileGlob of fileToFixPermissions) { + const files = await new Promise((resolve, reject) => { + glob(path.join(targetFolder, fileGlob), (ex, items) => (ex ? reject(ex) : resolve(items))); + }); + await Promise.all(files.map(file => fs.chmod(file, '755'))); + } +} diff --git a/uitests/src/index.ts b/uitests/src/index.ts new file mode 100644 index 000000000000..58ae67b86f7f --- /dev/null +++ b/uitests/src/index.ts @@ -0,0 +1,168 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import * as cp from 'child_process'; +import * as path from 'path'; +import * as yargs from 'yargs'; +import { sleep } from './helpers'; +import { info, initialize } from './helpers/logger'; +import { mergeAndgenerateHtmlReport } from './helpers/report'; +import { downloadVSCode, getTestOptions, installExtensions, TestOptions, waitForPythonExtensionToActivate } from './setup'; +import { start } from './testRunner'; +import { Channel } from './types'; +import { Application } from './vscode'; + +// tslint:disable: no-console + +const channels: Channel[] = ['insider', 'stable']; +const channelOption = { + describe: 'VS Code Channel', + default: 'stable' as Channel, + choices: channels +}; +const destinationOption = { + describe: 'Destination for download path', + default: './.vscode test' +}; +const enableVerboseLogging = { + describe: 'Enable verbose (debug) logging', + default: false +}; + +// tslint:disable-next-line: no-unused-expression +const parsedArgs = yargs + .command({ + command: 'download', + describe: 'Downloads VS Code', + builder: (args: yargs.Argv) => + args + .option('channel', channelOption) + .option('destination', destinationOption) + .option('verbose', enableVerboseLogging), + handler: async argv => { + initialize(argv.verbose); + downloadVSCode(argv.channel, path.resolve(argv.destination)).catch(console.error); + } + }) + .command({ + command: 'install', + describe: 'Installs the extensions into VS Code', + builder: (args: yargs.Argv) => + args + .option('channel', channelOption) + .option('destination', destinationOption) + .option('verbose', enableVerboseLogging) + .option('vsix', { + describe: 'Path to Python Extension', + default: './ms-python-insiders.vsix' + }), + handler: async argv => { + initialize(argv.verbose); + await installExtensions(argv.channel, path.resolve(argv.destination), path.resolve(argv.vsix)); + } + }) + .command({ + command: 'launch', + describe: 'Launches VS Code', + builder: (args: yargs.Argv) => + args + .option('channel', channelOption) + .option('destination', destinationOption) + .option('verbose', enableVerboseLogging) + .option('timeout', { + alias: 't', + describe: 'Timeout (ms) before closing VS Code', + default: 5 * 60 * 1_000 + }), + handler: async argv => { + initialize(argv.verbose); + const options = getTestOptions(argv.channel, path.resolve(argv.destination), 'python', argv.verbose); + const app = new Application(options); + info(app.channel); + await (app.options as TestOptions).initilize(); + await app + .start() + .then(() => info('VS Code successfully launched')) + .catch(console.error.bind(console, 'Failed to launch VS Code')); + await waitForPythonExtensionToActivate(60_000, app); + await sleep(100_000); + await app.quickopen.runCommand('View: Close Editor'); + } + }) + .command({ + command: 'test', + describe: "Runs the UI Tests (Arguments after '--' are cucumberjs args)", + builder: (args: yargs.Argv) => + args + .option('channel', channelOption) + .option('destination', destinationOption) + .option('verbose', enableVerboseLogging) + .option('pythonPath', { + describe: 'Destination for download path', + default: process.env.CI_PYTHON_PATH || 'python' + }) + .example('test', ' # (Runs all tests in stable)') + .example('test', '--channel=insider # (Runs all tests in insiders)') + .example('test', '--channel=insider --pythonPath=c:/python/python.exe # (Runs all tests in insiders)') + .example('test', "-- --tags=@wip # (Runs tests in stable with with tags @wip. Arguments after '--' are cucumberjs args.)") + .example('test', "-- --tags='@smoke and @terminal' # (Runs tests in stable with tags '@smoke and @terminal')"), + handler: async argv => { + initialize(argv.verbose); + const cucumberArgs = argv._.slice(1); + const pythonPath = + argv.pythonPath === 'python' + ? cp + .execSync('python -c "import sys;print(sys.executable)"') + .toString() + .trim() + : argv.pythonPath; + await start(argv.channel, path.resolve(argv.destination), argv.verbose, pythonPath, cucumberArgs).catch(ex => { + console.error('UI Tests Failed', ex); + process.exit(1); // Required for CLI to fail on CI servers. + }); + } + }) + .command({ + command: 'report', + describe: 'Merges multiple cucumber JSON reports and generates a single HTML report', + builder: (args: yargs.Argv) => + args + .option('jsonDir', { + describe: 'Directory containing the Cucumber JSON reports', + demandOption: true + }) + .option('htmlOutput', { + describe: 'Target directory for HTML report', + default: path.join(process.cwd(), '.vscode test', 'reports') + }), + handler: argv => mergeAndgenerateHtmlReport(argv.jsonDir as string, argv.htmlOutput) + }) + .command({ + command: 'steps', + describe: 'List all of the Steps (with arguments and all usages)', + builder: (args: yargs.Argv) => + args + .option('format', { + describe: 'Where should the steps be displayed as plain text or JSON', + default: 'text', + choices: ['text', 'json'] + }) + .option('file', { + describe: 'Whether to print output to a file' + }) + .example('steps', '# Lists all steps'), + handler: argv => { + console.log('test', argv); + } + }) + .demandCommand() + .help() + .version(false).argv; + +// argv needs to be retained by compiler. +// Hence we need a bogus use of the .argv value. +if (parsedArgs._.length === 0) { + console.log(parsedArgs); +} diff --git a/uitests/src/selectors.ts b/uitests/src/selectors.ts new file mode 100644 index 000000000000..45819dec145d --- /dev/null +++ b/uitests/src/selectors.ts @@ -0,0 +1,313 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { pyBootstrapActivatedStatusBarTooltip, pyBootstrapTooltip } from './constants'; +import { Channel } from './types'; + +export enum Selector { + /** + * Selector for the Bootstrap extensions statubar item. + */ + 'PyBootstrapStatusBar', + /** + * Selector for Python extension statusbar item . + */ + 'PythonExtensionStatusBar', + + /** + * Selector for the VSC statubar item displaying the line & column. + * This is the item on the bottom right e.g. `Ln 12, Col 56`. + */ + 'ColumnLineNumbnerStatusBar', + /** + * Selector for the statusbar created by Bootstrap extensions when Python Extension gets activated. + * (basically if this status bar item exists, then Python Extension has activated). + */ + 'PyBootstrapActivatedStatusBar', + /** + * Selector for our custom statubar item (for the uitests) displaying the line & column. + * This is the item on the bottom left display line & column as `12,4`. + */ + 'CurrentEditorLineColumnStatusBar', + /** + * Selector for Explorer Activity Bar + */ + 'ExplorerActivityBar', + /** + * Selector for Debug Activity Bar + */ + 'DebugActivityBar', + /** + * Input in the dropdown of the Debug Configuration picker. + */ + 'DebugConfigurationPickerDropDownInput', + /** + * The visibility of this indicates the debugger has started. + */ + 'DebugToolbar', + /** + * Selector for an icon in the debug toolbar + */ + 'DebugToolbarIcon', + 'MaximizePanel', + 'MinimizePanel', + /** + * Selector for individual lines in the visible output panel. + */ + 'IndividualLinesInOutputPanel', + /** + * Individual notification. + */ + 'Notification', + /** + * Individual notification (type = error). + */ + 'NotificationError', + 'IndividualNotification', + /** + * Message displayed in the nth Individual notification. + */ + 'NthNotificationMessage', + /** + * The (x) for the nth Individual notification. + */ + 'CloseButtonInNthNotification', + /** + * The selector for a button in the nth Individual notification. + */ + 'ButtonInNthNotification', + /** + * The number of problems (this is a number next to `Problems` text in the panel). + */ + 'ProblemsBadge', + /** + * Selector to check whether problems panel is visible. + */ + 'ProblemsPanel', + /** + * Selector for the file name in a problem in the problems panel. + */ + 'FileNameInProblemsPanel', + /** + * Selector for the problem message in a problem in the problems panel. + */ + 'ProblemMessageInProblemsPanel', + /** + * Quick input container + */ + 'QuickInput', + /** + * Input box in the Quick Input + */ + 'QuickInputInput', + /** + * Input box in the quick open dropdown + */ + 'QuickOpenInput', + /** + * Selector for when quick open has been hidden. + */ + 'QuickOpenHidden', + /** + * Selector for individual items displayed in the quick open dropdown + */ + 'QuickOpenEntryLabel', + 'QuickOpenEntryLineLabel', + /** + * Selector for individual items that are focused and displayed in the quick open dropdown + */ + 'QuickOpenEntryLabelFocused', + 'QuickOpenEntryLabelFocused2', + /** + * Selector for the test activitybar/test explorer. + */ + 'TestActivityBar', + /** + * Selector to check visibility of the test explorer icon in the activity bar. + */ + 'TestActivityIcon', + /** + * Icon in toolbar of test explorer. + */ + 'TestExplorerToolbarcon', + /** + * Selector for a node in the test explorer. + */ + 'TestExplorerNode', + /** + * Selector for the nth node in the test explorer. + */ + 'NthTestExplorerNode', + /** + * Selector for a label in the nth node of a test explorer. + */ + 'NthTestExplorerNodeLabel', + /** + * Selector for the icon in the nth node of a test explorer. + * Used to get details of the icon (backgroundImage) of the displayed icon. + */ + 'NthTestExplorerNodeIcon', + /** + * Selector for the treeview container of the test explorer. + * This is used to set focus to the test explorer tree view and press keys for navigation in tree view. + */ + 'TestExplorerTreeViewContainer', + /** + * Selector for the items in the auto completion list. + */ + 'AutoCompletionListItem' +} + +// Selector for container of notifications. +const messageBoxContainer = 'div.notifications-toasts.visible div.notification-toast-container'; +// Selector for individual notification message. +const messageBoxSelector = `${messageBoxContainer} div.notification-list-item-message span`; +const quickOpen = 'div.monaco-quick-open-widget'; +// Selector +// tslint:disable: no-unnecessary-class +export class QuickOpen { + public static QUICK_OPEN = 'div.monaco-quick-open-widget'; + public static QUICK_OPEN_HIDDEN = 'div.monaco-quick-open-widget[aria-hidden="true"]'; + public static QUICK_OPEN_INPUT = `${QuickOpen.QUICK_OPEN} .quick-open-input input`; + public static QUICK_OPEN_FOCUSED_ELEMENT = `${QuickOpen.QUICK_OPEN} .quick-open-tree .monaco-tree-row.focused .monaco-highlighted-label`; + public static QUICK_OPEN_ENTRY_SELECTOR = 'div[aria-label="Quick Picker"] .monaco-tree-rows.show-twisties .monaco-tree-row .quick-open-entry'; + public static QUICK_OPEN_ENTRY_LABEL_SELECTOR = 'div[aria-label="Quick Picker"] .monaco-tree-rows.show-twisties .monaco-tree-row .quick-open-entry .label-name'; + public static QUICK_OPEN_ENTRY_LINE_LABEL_SELECTOR = + 'div[aria-label="Quick Picker"] .monaco-tree-rows.show-twisties .monaco-tree-row.focused .quick-open-entry .monaco-label-description-container .label-name .monaco-highlighted-label span'; +} + +class QuickInput { + public static QUICK_INPUT = '.quick-input-widget'; + public static QUICK_INPUT_INPUT = `${QuickInput.QUICK_INPUT} .quick-input-box input`; + public static QUICK_INPUT_FOCUSED_ELEMENT = `${QuickInput.QUICK_INPUT} .quick-open-tree .monaco-tree-row.focused .monaco-highlighted-label`; +} + +const selectors: Record = { + [Selector.PythonExtensionStatusBar]: { + stable: ".statusbar-item[id='ms-python.python']" + }, + [Selector.PyBootstrapStatusBar]: { + stable: `.part.statusbar *[title='${pyBootstrapTooltip}'] a` + }, + [Selector.PyBootstrapActivatedStatusBar]: { + stable: `.part.statusbar *[title='${pyBootstrapActivatedStatusBarTooltip}'] a` + }, + [Selector.CurrentEditorLineColumnStatusBar]: { + stable: ".part.statusbar *[title='PyLine'] a" + }, + [Selector.ColumnLineNumbnerStatusBar]: { + stable: 'div.statusbar-item[title="Go to Line"] a' + }, + [Selector.ExplorerActivityBar]: { + stable: '.composite.viewlet.explorer-viewlet' + }, + [Selector.DebugActivityBar]: { + stable: '.composite.viewlet.debug-viewlet' + }, + [Selector.DebugToolbar]: { + stable: 'div.debug-toolbar' + }, + [Selector.DebugToolbarIcon]: { + stable: 'div.debug-toolbar .action-item .action-label.icon' + }, + [Selector.DebugConfigurationPickerDropDownInput]: { + stable: '.quick-input-widget .quick-input-title' + }, + [Selector.MaximizePanel]: { + stable: '.part.panel.bottom a.icon.maximize-panel-action' + }, + [Selector.MinimizePanel]: { + stable: '.part.panel.bottom a.icon.minimize-panel-action' + }, + [Selector.IndividualLinesInOutputPanel]: { + stable: '.part.panel.bottom .view-lines .view-line span span' + }, + [Selector.Notification]: { + stable: '.notifications-toasts.visible .notification-toast-container .notification-list-item.expanded' + }, + [Selector.NotificationError]: { + stable: '.notifications-toasts.visible .notification-toast-container .notification-list-item.expanded .notification-list-item-icon.icon-error' + }, + [Selector.NthNotificationMessage]: { + stable: '.notifications-toasts.visible .notification-toast-container:nth-child({0}) .notification-list-item.expanded div.notification-list-item-message span' + }, + [Selector.IndividualNotification]: { + stable: messageBoxSelector + }, + [Selector.CloseButtonInNthNotification]: { + stable: '.notifications-toasts.visible .notification-toast-container:nth-child({0}) .notification-list-item.expanded .action-label.icon.clear-notification-action' + }, + [Selector.ButtonInNthNotification]: { + stable: ".notifications-toasts.visible .notification-toast-container:nth-child({0}) .notification-list-item.expanded .monaco-button.monaco-text-button[title='{1}']" + }, + [Selector.ProblemsBadge]: { + stable: '.part.panel.bottom .action-item.checked .badge-content' + }, + [Selector.FileNameInProblemsPanel]: { + stable: '.part.panel.bottom .content .tree-container .monaco-tl-row .file-icon .label-name span span' + }, + [Selector.ProblemMessageInProblemsPanel]: { + stable: '.part.panel.bottom .content .tree-container .monaco-tl-row .marker-message-details' + }, + [Selector.QuickOpenInput]: { + stable: `${quickOpen} .quick-open-input input` + }, + [Selector.QuickOpenEntryLabel]: { + stable: 'div[aria-label="Quick Picker"] .monaco-tree-rows.show-twisties .monaco-tree-row .quick-open-entry .label-name' + }, + [Selector.QuickOpenEntryLabelFocused]: { + stable: 'div[aria-label="Quick Picker"] .monaco-tree-rows.show-twisties .monaco-tree-row.focused .quick-open-entry .label-name .monaco-highlighted-label .highlight' + }, + [Selector.QuickOpenEntryLineLabel]: { + stable: QuickOpen.QUICK_OPEN_ENTRY_LINE_LABEL_SELECTOR + }, + [Selector.QuickOpenEntryLabelFocused2]: { + stable: '.monaco-tree-row.focused .monaco-icon-label-description-container .monaco-highlighted-label' + }, + [Selector.QuickInputInput]: { + stable: QuickInput.QUICK_INPUT_INPUT + }, + [Selector.QuickInput]: { + stable: QuickInput.QUICK_INPUT + }, + [Selector.TestActivityBar]: { + stable: '.composite.viewlet[id="workbench.view.extension.test"]' + }, + [Selector.TestActivityIcon]: { + stable: ".activitybar.left .actions-container a[title='Test']" + }, + [Selector.TestExplorerToolbarcon]: { + stable: "div[id='workbench.parts.sidebar'] .action-item a[title='{0}']" + }, + [Selector.NthTestExplorerNodeLabel]: { + stable: 'div[id="workbench.view.extension.test"] div.monaco-tree-row:nth-child({0}) a.label-name' + }, + [Selector.NthTestExplorerNodeIcon]: { + stable: 'div[id="workbench.view.extension.test"] div.monaco-tree-row:nth-child({0}) .custom-view-tree-node-item-icon' + }, + [Selector.NthTestExplorerNode]: { + stable: 'div[id="workbench.view.extension.test"] div.monaco-tree-row:nth-child({0})' + }, + [Selector.TestExplorerNode]: { + stable: 'div[id="workbench.view.extension.test"] .tree-explorer-viewlet-tree-view div.monaco-tree-row' + }, + [Selector.TestExplorerTreeViewContainer]: { + stable: "div[id='workbench.view.extension.test'] .monaco-tree" + }, + [Selector.QuickOpenHidden]: { + stable: QuickOpen.QUICK_OPEN_HIDDEN + }, + [Selector.AutoCompletionListItem]: { + stable: '.editor-widget.suggest-widget.visible .monaco-list-row a.label-name .monaco-highlighted-label' + }, + [Selector.ProblemsPanel]: { + stable: '.part.panel.bottom .composite.panel.markers-panel' + } +}; + +export function getSelector(selector: Selector, channel: Channel): string { + const channelSelector = selectors[selector]; + return channelSelector[channel] || selectors[selector].stable; +} diff --git a/uitests/src/setup/bootstrap.ts b/uitests/src/setup/bootstrap.ts new file mode 100644 index 000000000000..f6ece81c2f7d --- /dev/null +++ b/uitests/src/setup/bootstrap.ts @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { spawnSync } from 'child_process'; +import * as fs from 'fs-extra'; +import * as path from 'path'; +import { uitestsRootPath } from '../constants'; +import { debug } from '../helpers/logger'; + +/** + * Gets the path to the bootstrap extension. + * + * @export + * @returns + */ +export async function getExtensionPath() { + const sourceDir = path.join(uitestsRootPath, 'bootstrap'); + const extensionPath = path.join(sourceDir, 'bootstrap.vsix'); + if (await fs.pathExists(extensionPath)) { + debug(`Reusing existing bootstrap extension ${extensionPath}`); + return extensionPath; + } + return new Promise((resolve, reject) => { + debug(`Building bootstrap extension ${extensionPath}`); + const args = ['vsce', 'package', '--out', extensionPath]; + const result = spawnSync('npx', args, { cwd: path.join(sourceDir, 'extension') }); + const stdErr = (result.stderr || '').toString().trim(); + if (stdErr.length > 0) { + return reject(new Error(`Failed to build bootstrap extension. Error: ${result.stderr.toString()}`)); + } + debug(`Built bootstrap extension ${extensionPath}`); + resolve(extensionPath); + }); +} diff --git a/uitests/src/setup/downloader.ts b/uitests/src/setup/downloader.ts new file mode 100644 index 000000000000..71a25a584479 --- /dev/null +++ b/uitests/src/setup/downloader.ts @@ -0,0 +1,130 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import * as fs from 'fs-extra'; +import * as path from 'path'; +import * as tmp from 'tmp'; +import { downloadFile, getOSType, OSType, unzipVSCode } from '../helpers'; +import { info } from '../helpers/logger'; +import { Channel } from '../types'; + +function getDownloadPlatform() { + switch (process.platform) { + case 'darwin': + return 'darwin'; + case 'win32': + return 'win32-archive'; + default: + return 'linux-x64'; + } +} + +const DownloadChannel = { + stable: 'stable', + insider: 'insider' +}; + +/** + * Gets the download url for VS Code. + * Its possible to hard code the VS Code version number in here for stable versions of VS Code. + * This would be useful to ensure our CI tests always pass. + * E.g. if VSC updates CSS in insiders and release a new version tomorrow, then if we haven't had + * the time to account for the CSS changes, then UI tests will fail. + * Solution is to tie the UI tests to a specific version of VS Code. + * + * @export + * @param {Channel} channel + * @returns + */ +export async function getVSCodeDownloadUrl(channel: Channel) { + const downloadPlatform = getDownloadPlatform(); + return `https://update.code.visualstudio.com/latest/${downloadPlatform}/${DownloadChannel[channel]}`; +} + +export function getVSCodeExecutablePath(channel: Channel, testDir: string) { + if (process.platform === 'win32') { + return path.join(testDir, channel, channel === 'stable' ? 'Code.exe' : 'Code - Insiders.exe'); + } else if (process.platform === 'darwin') { + return path.join(testDir, channel, channel === 'stable' ? 'Visual Studio Code.app/Contents/MacOS/Electron' : 'Visual Studio Code - Insiders.app/Contents/MacOS/Electron'); + } else { + return path.join(testDir, channel, channel === 'stable' ? 'VSCode-linux-x64/code' : 'VSCode-linux-x64/code-insiders'); + } +} + +/** + * Returns the path to the VS Code Electron executable. + * + * @export + * @param {Channel} channel + * @param {string} testDir + * @returns + */ +export function getVSCodeElectronPath(channel: Channel, testDir: string) { + if (process.platform === 'win32') { + return path.join(testDir, channel, channel === 'stable' ? 'Code.exe' : 'Code - Insiders.exe'); + } else if (process.platform === 'darwin') { + return path.join(testDir, channel, channel === 'stable' ? 'Visual Studio Code.app/Contents/MacOS/Electron' : 'Visual Studio Code - Insiders.app/Contents/MacOS/Electron'); + } else { + return path.join(testDir, channel, channel === 'stable' ? 'VSCode-linux-x64/code' : 'VSCode-linux-x64/code-insiders'); + } +} + +/** + * Returns the root directory of the VS Code application. + * + * @export + * @param {Channel} channel + * @param {string} testDir + * @returns + */ +export function getVSCodeDirectory(channel: Channel, testDir: string) { + if (process.platform === 'win32') { + return path.join(testDir, channel); + } else if (process.platform === 'darwin') { + return path.join(testDir, channel, channel === 'stable' ? 'Visual Studio Code.app' : 'Visual Studio Code - Insiders.app'); + } else { + return path.join(testDir, channel, channel === 'stable' ? 'VSCode-linux-x64' : 'VSCode-linux-x64'); + } +} + +/** + * Download destination for VS Code. + * If the channel is stable, then this is typically of the form `./.vscode test/stable` else `./.vscode test/insider`. + * Where `.vscode test` is the value of the argument `testDir`. + * + * @param {Channel} channel + * @param {string} testDir + * @returns + */ +function getVSCodeDestinationDirectory(channel: Channel, testDir: string) { + return path.join(testDir, channel === 'stable' ? 'stable' : 'insider'); +} + +async function hasVSCBeenDownloaded(channel: Channel, testDir: string) { + const vscodeDir = getVSCodeDestinationDirectory(channel, testDir); + return fs.pathExists(vscodeDir); +} + +export async function downloadVSCode(channel: Channel, testDir: string) { + if (await hasVSCBeenDownloaded(channel, testDir)) { + info('VS Code already downloaded.'); + return; + } + const targetDir = getVSCodeDestinationDirectory(channel, testDir); + const url = await getVSCodeDownloadUrl(channel); + const ostype = getOSType(); + const filePostfix = ostype === OSType.Linux ? 'vscode.tar.gz' : 'vscode.zip'; + const targetFile = await new Promise((resolve, reject) => { + tmp.tmpName({ postfix: filePostfix }, (ex, fileName) => { + if (ex) { + return reject(ex); + } + resolve(fileName); + }); + }); + await downloadFile(url, targetFile, `Downloading VS Code ${channel === 'stable' ? 'Stable' : 'Insider'}`); + await unzipVSCode(targetFile, targetDir); + info('VS Code successfully downloaded.'); +} diff --git a/uitests/src/setup/driver.ts b/uitests/src/setup/driver.ts new file mode 100644 index 000000000000..caf24847aaf1 --- /dev/null +++ b/uitests/src/setup/driver.ts @@ -0,0 +1,324 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { ChildProcess } from 'child_process'; +import { EventEmitter } from 'events'; +import { Browser, ClickOptions, ElementHandle, launch, Page, UnwrapElementHandle, WrapElementHandle } from 'puppeteer-core'; +import { URI } from 'vscode-uri'; +import { isCI } from '../constants'; +import { noop, RetryOptions, retryWrapper, sleep } from '../helpers'; +import { debug, warn } from '../helpers/logger'; +import { getSelector, Selector } from '../selectors'; +import { ElementsSelectorPredicate, IDriver, ITestOptions, SelectorRetryOptions, WaitForSelectorOptions, WaitForSelectorOptionsHidden } from '../types'; +import { getVSCodeElectronPath } from './downloader'; + +// Time to wait for UI to react to user typing in a textbox. +// If its too low (then VSC UI won't have enough time to react the keys being typed into the input boxes). +// 100ms seems to be the sweetspot (any slower, then UI tests will be slow). +// Right now using 100ms seems to be enough, 50ms might be enough as well, but 100ms works. +const waitTimeoutAfterTypging = 100; + +/* + Hacky way to translate control keys into puppeteer keys. + Better way would be to wrap this up with a class. +(plenty of places to get inspiration from .NET, Java, Flex, etc)... + Current approach is quite sloppy. +*/ +const KeyTranslations: Record = { + alt: 'Alt', + control: 'Control', + ctrl: 'Control', + shift: 'Shift', + space: 'Space', + Escape: 'Escape', + escape: 'Escape', + esc: 'Escape', + Enter: 'Enter', + enter: 'Enter', + down: 'ArrowDown', + right: 'ArrowRight', + left: 'ArrowLeft', + tab: 'Tab' +}; + +/** + * Given a key (control key or standard alphanumeric character), + * convert them into a key understoon by puppeteer. + * + * @param {string} key + * @returns {string} + */ +function normalizeKey(key: string): string { + return key in KeyTranslations ? KeyTranslations[key] : key; +} + +/** + * This is what loads VS Code. + * VS Code is launched using puppeteer and provides the ability to run CSS queries against the dom and perform UI actions. + * This is the heart of the UI test. + * + * @export + * @class Driver + * @extends {EventEmitter} + * @implements {IDriver} + */ +export class Driver extends EventEmitter implements IDriver { + public get isAlive(): boolean { + return this.process && !this.process.killed ? true : false; + } + private process?: ChildProcess; + private browser!: Browser; + private pages!: Page[]; + private mainPage!: Page; + private readonly options: ITestOptions; + constructor(options: ITestOptions) { + super(); + this.options = options; + } + /** + * Given the `SelectorRetryOptions`, and an error message, convert it into `RetryOptions`. + * This will be used to retry querying the UI using the `retryWrapper` or `retry` decorator. + * + * @private + * @static + * @param {SelectorRetryOptions} options + * @param {string} fallbackErrorMessage + * @returns {RetryOptions} + * @memberof Driver + */ + private static toRetryOptions(options: SelectorRetryOptions, fallbackErrorMessage: string): RetryOptions { + if ('retryTimeout' in options) { + return { + timeout: options.retryTimeout, + errorMessage: options.errorMessage || fallbackErrorMessage, + logFailures: options.logFailures + }; + } else { + return { + count: options.retryCount, + errorMessage: options.errorMessage || fallbackErrorMessage, + logFailures: options.logFailures + }; + } + } + /** + * Starts VS Code. + * + * @returns {Promise} + * @memberof Driver + */ + public async start(): Promise { + if (this.process) { + debug('Killing existing instance before starting VS Code'); + await this.exit().catch(warn); + } + const electronPath = getVSCodeElectronPath(this.options.channel, this.options.testPath); + // If on CI, run in headless mode. + const ciArgs = isCI ? ['--headless'] : []; + const args = [ + ...ciArgs, + `--user-data-dir=${this.options.userDataPath}`, + `--extensions-dir=${this.options.extensionsPath}`, + '--skip-getting-started', + '--skip-release-notes', + '--sticky-quickopen', + '--disable-telemetry', + '--disable-updates', + '--disable-crash-reporter', + '--no-sandbox', + '--no-first-run', + '--disable-dev-shm-usage', + '--disable-setuid-sandbox', + `--folder-uri=${URI.file(this.options.workspacePathOrFolder)}` + ]; + debug(`Launching via puppeteer with electron path ${electronPath} & args ${args.join('\n')}`); + this.browser = await launch({ + executablePath: electronPath, + args, + headless: true, + devtools: false, + // This must be set to `null`, else VSC UI resizes in a funky way. + // tslint:disable-next-line: no-null-keyword + defaultViewport: null, + // This must be set to ensure puppeteer doesn't send default (additional) args. + ignoreDefaultArgs: true + }); + this.process = this.browser.process(); + this.process.on('exit', this.emit.bind(this, 'exit')); + + debug(`Launched with process ${this.process.pid}`); + + this.pages = await this.browser.pages(); + this.pages.forEach(page => { + page.on('error', error => warn('One of the pages have errored', error)); + }); + this.mainPage = this.pages[0]; + // We know it will take at least 1 second, so lets wait for 1 second, no point trying before then. + await sleep(1000); + + // Wait for bootstrap extension to load (when this extension is ready, that means VSC is ready for user interaction). + // Based on assumption that if extensions have been activated, then VSC is ready for user interaction. + // Note: This extension loads very quickly (nothing in activation method to slow activation). + debug('Wait for bootstrap extension to actiavte'); + await this.waitForSelector(getSelector(Selector.PyBootstrapStatusBar, this.options.channel), { + timeout: 15_000, + visible: true + }); + debug('VS Code successfully launched'); + } + public async captureScreenshot(filename: string): Promise { + return this.mainPage.screenshot({ path: filename }); + } + public async exit(): Promise { + if (!this.process) { + return; + } + this.removeAllListeners(); + debug('Shutting down vscode driver'); + await this.browser.close().catch(warn); + try { + if (this.process.connected && this.process) { + // If exiting failed, kill the underlying process. + process.kill(this.process.pid); + } + } catch { + noop(); + } + this.process = undefined; + } + public async waitForSelector(selector: string, options?: WaitForSelectorOptions): Promise; + public async waitForSelector(selector: string, options?: WaitForSelectorOptionsHidden): Promise; + public async waitForSelector( + selector: string, + options?: WaitForSelectorOptions | WaitForSelectorOptionsHidden + // tslint:disable-next-line: no-any + ): Promise { + if (options && 'hidden' in options && options.hidden === true) { + // We expect selector to be available. + return this.mainPage.waitForSelector(selector, { timeout: 3000, ...options }); + } + // We expect selector to be available. + return this.mainPage.waitForSelector(selector, { visible: true, timeout: 3000, ...options }); + } + // tslint:disable-next-line: no-any + public async $(selector: string, options?: SelectorRetryOptions): Promise { + if (!options) { + return this.mainPage.$(selector).then(ele => (ele ? Promise.resolve(ele) : Promise.reject(new Error(`Element not found with selector '${selector}'`)))); + } + const wrapper = async (): Promise => { + const ele = await this.mainPage.$(selector); + if (ele) { + return ele; + } + debug(`Element not found for selector '${selector}', will retry.`); + throw new Error('Element not found, keep retrying'); + }; + return retryWrapper(Driver.toRetryOptions(options, `Failed to find for selector '${selector}'`), wrapper); + } + public async $$(selector: string, options?: SelectorRetryOptions & { predicate?: ElementsSelectorPredicate }): Promise { + if (!options) { + return this.mainPage.$$(selector); + } + const wrapper = async (): Promise => { + let eles = await this.mainPage.$$(selector); + if (eles.length > 0 && options.predicate) { + eles = options.predicate(eles); + } + if (eles.length > 0) { + return eles; + } + debug(`Elements not found for selector '${selector}', will retry.`); + throw new Error('Elements not found, keep retrying'); + }; + + return retryWrapper(Driver.toRetryOptions(options, `Failed to find for selector '${selector}'`), wrapper); + } + public $eval(selector: string, pageFunction: (element: Element) => R | Promise): Promise>; + public $eval(selector: string, pageFunction: (element: Element, x1: UnwrapElementHandle) => R | Promise, x1: X1): Promise>; + // tslint:disable-next-line: no-any + public $eval(selector: any, pageFunction: any, x1?: any) { + if (arguments.length === 3) { + return this.mainPage.$eval(selector, pageFunction, x1); + } + return this.mainPage.$eval(selector, pageFunction); + } + + public $$eval(selector: string, pageFunction: (elements: Element[]) => R | Promise): Promise>; + public $$eval(selector: string, pageFunction: (elements: Element[], x1: UnwrapElementHandle) => R | Promise, x1: X1): Promise>; + // tslint:disable-next-line: no-any + public $$eval(selector: any, pageFunction: any, x1?: any) { + return this.mainPage.$$eval(selector, pageFunction, x1); + } + + public async click(selector: string, options?: ClickOptions & SelectorRetryOptions): Promise { + if (!options || (!('retryTimeout' in options) && !('retryCount' in options))) { + return this.mainPage.click(selector, options); + } + const wrapper = async (): Promise => { + // Click will throw an error if selector is invalid or element is not found. + await this.mainPage.click(selector, options).catch(ex => { + debug(`Element not found for selector '${selector}', will retry.`); + return Promise.reject(ex); + }); + }; + + return retryWrapper(Driver.toRetryOptions(options, `Failed to click for selector '${selector}'`), wrapper); + } + public async focus(selector: string): Promise { + // Ensure element exists before setting focus. + await this.waitForSelector(selector, { timeout: 500, visible: true }); + return this.mainPage.focus(selector); + } + public async hover(selector: string): Promise { + // Ensure element exists before hovering over it. + await this.waitForSelector(selector, { timeout: 500, visible: true }); + return this.mainPage.hover(selector); + } + public async type(selector: string, text: string, options?: { delay: number }): Promise { + // Focus the element before typing into it. + await this.focus(selector); + await this.mainPage.type(selector, text, options); + // Wait for text to be typed in (sometimes having this delay helps). + // Not doing this sometimes results in value not being entered in input box. + // Hopefully we don't need bigger delays on CI. + // Cause is the fact that typing into thie textbox causes vscode to filter + // the dropdown list. If we don't waait long enough, then an item isn't selected + // in the dropdown list, meaning the necessary action isn't performed. + // Works much like an html dropdown, we need to wait for UI to react to the input + // before we can hit the enter key. + // We don't need this delay when selecting files from quickopen or selecting + // commands from quick open, as we wait for those items to get highlighted in the dropdown. + // Here we're not waiting for someting to get highlighted, that's where the problem lies. + await sleep(waitTimeoutAfterTypging); + } + public async press(keys: string, options?: { delay: number }): Promise { + debug(`Press key combination ${keys}`); + const individualKeys = keys.split('+').map(normalizeKey); + try { + const pressUpControlKeys: string[] = []; + for (const key of individualKeys) { + if (['Control', 'Shift'].includes(key)) { + debug(`Down ${key}`); + await this.mainPage.keyboard.down(key); + pressUpControlKeys.push(key); + continue; + } + debug(`Press ${key}`); + await this.mainPage.keyboard.press(key, options); + } + while (pressUpControlKeys.length) { + const key = pressUpControlKeys.shift(); + if (key) { + debug(`Up ${key}`); + await this.mainPage.keyboard.up(key); + } + } + } finally { + await sleep(waitTimeoutAfterTypging); + } + // Key(s) was pressed, lets wait for UI to react to this. + await sleep(waitTimeoutAfterTypging); + } +} diff --git a/uitests/src/setup/environment.ts b/uitests/src/setup/environment.ts new file mode 100644 index 000000000000..c18f82ec1f24 --- /dev/null +++ b/uitests/src/setup/environment.ts @@ -0,0 +1,139 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { spawnSync } from 'child_process'; +import { HookScenarioResult, pickle } from 'cucumber'; +import * as path from 'path'; +import { sleep } from '../helpers'; +import { debug } from '../helpers/logger'; +import { IApplication } from '../types'; + +/** + * Dismiss messages that are not required. + * E.g. attempt to dismiss messages such that they never appear. + */ +export async function dismissMessages(app: IApplication) { + const messages = [ + { content: 'Try out Preview of our new Python Language Server', buttonText: 'No thanks' }, + { content: 'Tip: you can change the Python interpreter used by the', buttonText: 'Got it!' }, + { content: 'Help improve VS Code by allowing' }, + { content: 'Linter pylint is not installed', buttonText: 'Do not show again' }, + { content: 'Would you like to run code in the', buttonText: 'No' } + ]; + await app.notifications.dismiss(messages, 1000); +} + +/** + * When we close VS Code and reopen it, the un saved files are still left open in VSC. + * We need to close them before shutting down VS Code. + * + * @export + * @returns + */ +export async function clearWorkspace(app: IApplication) { + if (!app.isAlive) { + debug('Not clearing workspace as application is not alive'); + return; + } + const commands = [ + // Custom command in our bootstrap extension. + // We can use the command `Debug: Stop` from the command palette only if a debug session is active. + // Using this approach we can send a command regardless, easy. + // 'Stop Debugging Python', + // Assume we have a max of 2 editors, revert changes and close all of them. + // Hence execute this command twice. + 'View: Revert and Close Editor', + 'View: Revert and Close Editor', + // 'Terminal: Kill the Active Terminal Instance', + 'Debug: Remove All Breakpoints', + // Clear this, else when trying to open files, VSC will list files in file picker dropdown that don't exist. + // This will cause serious issues. + // Assume in a test we had a file named `abc.py`. + // Next test we create a file named `ab.py`. At this point, VSC will remember the file from previous session and will display `abc.py`. + // Thats a serious problem. + 'File: Clear Recently Opened', + // Same reason as clearing `Recently Opened` + 'Clear Editor History', + // Same reason as clearing `Recently Opened` + // We don't want the command history to be polluted (we don't care about previous sessions). + 'Clear Command History', + 'View: Close All Editors', + 'Notifications: Clear All Notifications', + 'View: Close Panel' + ]; + + for (const command of commands) { + await app.quickopen.runCommand(command); + } + // Wait for UI to get updated (closing editors, closing panel, etc). + await sleep(200); +} + +/** + * Gets the git repo that needs to be downloaded for given tags. + * + * @param {pickle.Tag[]} tags + * @returns {({ url: string; subDirectory?: string } | undefined)} + */ +export function getGitRepo(tags: pickle.Tag[]): { url: string; subDirectory?: string } | undefined { + const tagWithUrl = tags.find(tag => tag.name.toLowerCase().startsWith('@https://github.com/')); + const url = tagWithUrl ? tagWithUrl.name.substring(1) : undefined; + if (!url) { + return; + } + if (url.toLowerCase().endsWith('.git')) { + return { url }; + } + const repoParts = url.substring('https://github.com/'.length).split('/'); + let subDirectory: string | undefined; + if (repoParts.length > 2) { + subDirectory = repoParts.filter((_, i) => i > 1).join('/'); + } + return { + url: `https://github.com/${repoParts[0]}/${repoParts[1]}`, + subDirectory + }; +} + +/** + * Clones the git repo into the provided directory. + * @param {{ url: string; subDirectory?: string }} repo + * @param {string} cwd + * @returns {Promise} + */ +async function cloneGitRepo({ url }: { url: string }, cwd: string): Promise { + debug(`Clone git repo ${url}`); + await new Promise((resolve, reject) => { + const proc = spawnSync('git', ['clone', url, '.'], { cwd }); + return proc.error ? reject(proc.error) : resolve(); + }); +} + +/** + * Initializes the workspace folder with the required code (downloads the git repo if required). + * Returns the new workspace folder. + * + * @export + * @param {HookScenarioResult} scenario + * @param {string} workspaceFolder + * @returns {(Promise)} + */ +export async function initializeWorkspace(scenario: HookScenarioResult, workspaceFolder: string): Promise { + const repo = getGitRepo(scenario.pickle.tags); + if (!repo) { + debug('initializeWorkspace without a repo'); + return; + } + debug(`initializeWorkspace for ${repo.url}`); + await cloneGitRepo(repo, workspaceFolder); + + // Its possible source_repo is https://github.com/Microsoft/vscode-python/tree/master/build + // Meaning, we want to glon https://github.com/Microsoft/vscode-python + // and want the workspace folder to be tree / master / build when cloned. + if (repo.subDirectory) { + debug(`initializeWorkspace for ${repo.url} in subdirectory ${repo.subDirectory}`); + return path.join(workspaceFolder, ...repo.subDirectory.replace(/\\/g, '/').split('/')); + } +} diff --git a/uitests/src/setup/index.ts b/uitests/src/setup/index.ts new file mode 100644 index 000000000000..7d2339dc47b9 --- /dev/null +++ b/uitests/src/setup/index.ts @@ -0,0 +1,8 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +export * from './bootstrap'; +export * from './downloader'; +export * from './setup'; diff --git a/uitests/src/setup/setup.ts b/uitests/src/setup/setup.ts new file mode 100644 index 000000000000..d4263ae67395 --- /dev/null +++ b/uitests/src/setup/setup.ts @@ -0,0 +1,256 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import * as cp from 'child_process'; +import { HookScenarioResult } from 'cucumber'; +import * as fs from 'fs-extra'; +import * as path from 'path'; +import * as rimraf from 'rimraf'; +import * as tmp from 'tmp'; +import { isCI } from '../constants'; +import { noop, sleep, unzipFile } from '../helpers'; +import { debug, info, initialize as initializeLogger, warn } from '../helpers/logger'; +import { Selector } from '../selectors'; +import { Channel, IApplication, ITestOptions } from '../types'; +import { getExtensionPath as getBootstrapExtensionPath } from './bootstrap'; + +// tslint:disable: no-console + +export class TestOptions implements ITestOptions { + /** + * Make static, as we might have a couple of runs of same tests. + * We will use this to ensure we have a unique name (counter increases per process session, hence no conflicts). + * + * @private + * @static + * @memberof TestOptions + */ + private static workspaceCounter = 0; + private _reportsPath?: string; + private _workspacePathOrFolder!: string; + get extensionsPath(): string { + return path.join(this.testPath, 'extensions'); + } + get userDataPath(): string { + return path.join(this.testPath, 'user'); + } + get userSettingsFilePath(): string { + return path.join(this.userDataPath, 'User', 'settings.json'); + } + get screenshotsPath(): string { + return path.join(this._reportsPath || this.testPath, 'screenshots'); + } + get rootReportsPath(): string { + return path.join(this.testPath, 'reports'); + } + get reportsPath(): string { + return this._reportsPath || this.rootReportsPath; + } + get logsPath(): string { + return path.join(this._reportsPath || this.testPath, 'logs'); + } + get workspacePathOrFolder(): string { + return this._workspacePathOrFolder; + } + constructor( + public readonly channel: Channel, + public readonly testPath: string, + public readonly tempPath: string, + public readonly verbose: boolean, + public readonly pythonPath: string = 'python' + ) { + this._workspacePathOrFolder = path.join(this.tempPath, 'workspace folder'); + } + /** + * Initialize paths for the tests. + * + * @memberof TestOptions + */ + public async initilize() { + this._workspacePathOrFolder = this._workspacePathOrFolder || path.join(this.tempPath, `workspace folder${(TestOptions.workspaceCounter += 1)}`); + await Promise.all([ + new Promise(resolve => rimraf(this.tempPath, resolve)).catch(warn.bind(warn, 'Failed to empty temp dir in updateForScenario')), + new Promise(resolve => rimraf(this._workspacePathOrFolder, resolve)).catch(warn.bind(warn, 'Failed to create workspace directory')) + ]); + await Promise.all([ + fs.ensureDir(this.tempPath), + fs.ensureDir(this._workspacePathOrFolder), + fs.ensureDir(this.screenshotsPath), + fs.ensureDir(this.rootReportsPath), + fs.ensureDir(this.reportsPath) + ]); + // Set variables for logging to be enabled within extension. + process.env.TF_BUILD = 'true'; + // Where are the Python extension logs written to. + process.env.VSC_PYTHON_LOG_FILE = path.join(this.logsPath, 'pvsc.log'); + // Ensure PTVSD logs are in the reports directory, + // This way they are available for analyzing. + process.env.PTVSD_LOG_DIR = this.logsPath; + } + /** + * Update the options for the tests based on the provided scenario. + * Initialize paths where various logs and screenshots related to a test run will be stored. + * Path provided must be a relative path. As it will be created in the reports directory. + * + * @param {HookScenarioResult} scenario + * @returns + * @memberof TestOptions + */ + public async updateForScenario(scenario: HookScenarioResult) { + const location = scenario.pickle.locations[0].line; + this._reportsPath = path.join(this.rootReportsPath, `${scenario.pickle.name}:${location}:_${TestOptions.workspaceCounter}`.replace(/[^a-z0-9\-]/gi, '_')); + this._workspacePathOrFolder = path.join(this.tempPath, `workspace folder${(TestOptions.workspaceCounter += 1)}`); + // await Promise.all([ + // fs.ensureDir(scenarioLogsPath), + // fs.emptyDir(this.tempPath).catch(warn.bind(warn, 'Failed to empty temp dir in updateForScenario')), + // fs.emptyDir(this._workspacePathOrFolder).catch(noop) + // ]); + // await fs.ensureDir(this._workspacePathOrFolder); + // process.env.VSC_PYTHON_LOG_FILE = path.join(scenarioLogsPath, 'pvsc.log'); + // // Ensure PTVSD logs are in the reports directory, + // // This way they are available for analyzing. + // process.env.PTVSD_LOG_DIR = scenarioLogsPath; + await this.initilize(); + } + public udpateWorkspaceFolder(workspaceFolder: string) { + this._workspacePathOrFolder = workspaceFolder; + } +} + +/** + * Get options for the UI Tests. + * + * @export + * @returns {TestOptions} + */ +export function getTestOptions(channel: Channel, testDir: string, pythonPath: string = 'python', verboseLogging: boolean = false): ITestOptions { + pythonPath = + pythonPath || + cp + .execSync('python -c "import sys;print(sys.executable)"') + .toString() + .trim(); + const options = new TestOptions(channel, testDir, path.join(testDir, 'temp folder'), verboseLogging, pythonPath); + [options.tempPath, options.userDataPath, options.logsPath, options.screenshotsPath, options.workspacePathOrFolder].forEach(dir => { + try { + rimraf.sync(dir); + } catch { + // Ignore. + } + }); + + [ + options.testPath, + options.extensionsPath, + options.userDataPath, + options.screenshotsPath, + options.reportsPath, + options.logsPath, + options.workspacePathOrFolder, + options.tempPath, + path.dirname(options.userSettingsFilePath) + ].map(dir => { + try { + fs.ensureDirSync(dir); + } catch { + // Ignore + } + }); + + initializeLogger(verboseLogging, path.join(options.logsPath, 'uitests.log')); + + return options; +} + +export async function installExtensions(channel: Channel, testDir: string, vsixPath: string): Promise { + const options = getTestOptions(channel, testDir); + await installExtension(options.extensionsPath, 'ms-python.python', vsixPath); + const bootstrapExension = await getBootstrapExtensionPath(); + await installExtension(options.extensionsPath, 'ms-python.bootstrap', bootstrapExension); + info('Installed extensions'); +} + +export async function restoreDefaultUserSettings(options: ITestOptions) { + await initializeDefaultUserSettings(options, getExtensionSpecificUserSettingsForAllTests()); +} + +function getExtensionSpecificUserSettingsForAllTests(): { [key: string]: {} } { + return { + // Log everything in LS server, to ensure they are captured in reports. + // Found under.vscode test/reports/user/logs/xxx/exthostx/output_logging_xxx/x-Python.log + // These are logs created by VSC. + // Enabling this makes it difficult to look for text in the panel(there's too much content). + // "python.analysis.logLevel": "Trace", + 'python.venvFolders': ['envs', '.pyenv', '.direnv', '.local/share/virtualenvs'], + // Disable pylint(we don't want this message) + 'python.linting.pylintEnabled': false + }; +} +async function initializeDefaultUserSettings(opts: ITestOptions, additionalSettings: { [key: string]: {} } = {}) { + const settingsToAdd: { [key: string]: {} } = { + 'python.pythonPath': opts.pythonPath, + // We dont need these(avoid VSC from displaying prompts). + 'telemetry.enableTelemetry': false, + // We don't want insiders getting installed (at all). + // That'll break everything. + 'python.insidersChannel': 'off', + // We don't want extensions getting updated/installed automatically. + 'extensions.autoUpdate': false, + 'telemetry.enableCrashReporter': false, + // Download latest (upon first load), do not update while tests are running. + 'python.autoUpdateLanguageServer': false, + // Disable process logging (src/client/common/process/logger.ts). + // Minimal logging in output channel (cuz we look for specific text in output channel). + 'python.enableProcessLogging': false, + // Minimal logging in output channel (cuz we look for specific text in output channel). + 'python.analysis.logLevel': 'Error', + 'debug.showInStatusBar': 'never', // Save some more room in statusbar. + // We don't want VSC to complete the brackets. + // When sending text to editors, such as json files, VSC will automatically complete brackets. + //And that messes up with the text thats being sent to the editor. + 'editor.autoClosingBrackets': 'never', + ...additionalSettings + }; + + // Maximize the window and reduce font size only on CI. + if (isCI) { + // Start VS Code maximized(good for screenshots and the like). + // Also more realestate(capturing logs, etc). + settingsToAdd['window.newWindowDimensions'] = 'maximized'; + } + + await initializeUserSettings(opts, settingsToAdd); +} + +export async function waitForPythonExtensionToActivate(timeout: number, app: IApplication) { + debug('Start activating Python Extension'); + await app.quickopen.runCommand('Activate Python Extension'); + // We know it will take at least 1 second, so lets wait for 1 second, no point trying before then. + await sleep(1000); + const selector = app.getCSSSelector(Selector.PyBootstrapActivatedStatusBar); + await app.driver.waitForSelector(selector, { timeout, visible: true }); + debug('Python Extension activation completed'); +} + +async function initializeUserSettings(opts: ITestOptions, settings: { [key: string]: {} }) { + debug(`initializeUserSettings ${opts.userSettingsFilePath} with ${JSON.stringify(settings)}`); + await fs.mkdirp(path.dirname(opts.userSettingsFilePath)).catch(noop); + return fs.writeFile(opts.userSettingsFilePath, JSON.stringify(settings, undefined, 4), 'utf8'); +} + +async function installExtension(extensionsDir: string, extensionName: string, vsixPath: string) { + await new Promise(resolve => rimraf(path.join(extensionsDir, extensionName), resolve)).catch(noop); + const tmpDir = await new Promise((resolve, reject) => { + tmp.dir((ex: Error, dir: string) => { + if (ex) { + return reject(ex); + } + resolve(dir); + }); + }); + await unzipFile(vsixPath, tmpDir); + await fs.copy(path.join(tmpDir, 'extension'), path.join(extensionsDir, extensionName)); + await new Promise(resolve => rimraf(tmpDir, resolve)).catch(noop); +} diff --git a/uitests/src/steps/commands.ts b/uitests/src/steps/commands.ts new file mode 100644 index 000000000000..63213cee20e3 --- /dev/null +++ b/uitests/src/steps/commands.ts @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +// tslint:disable: no-invalid-this + +import { Then, When } from 'cucumber'; + +When('I select the command {string}', async function(command: string) { + await this.app.quickopen.runCommand(command); +}); + +Then('select the command {string}', async function(command: string) { + await this.app.quickopen.runCommand(command); +}); diff --git a/uitests/src/steps/core.ts b/uitests/src/steps/core.ts new file mode 100644 index 000000000000..ec1828c2e483 --- /dev/null +++ b/uitests/src/steps/core.ts @@ -0,0 +1,92 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +// tslint:disable: no-invalid-this + +import * as colors from 'colors'; +import { Given, Then, When } from 'cucumber'; +import { extensionActivationTimeout } from '../constants'; +import { noop, sleep } from '../helpers'; +import { waitForPythonExtensionToActivate } from '../setup'; + +Then('do nothing', noop); + +Then('kaboom', () => { + throw new Error('Kaboom'); +}); + +Then('wip', noop); + +Then('Step {string}', async (_step: string) => { + noop(); +}); + +Given('VS Code is opened for the first time', async function() { + await this.app.exit(); + await this.app.start(true); +}); + +When('I open VS Code for the first time', async function() { + await this.app.exit(); + await this.app.start(true); +}); + +Given('VS Code is closed', function() { + return this.app.exit(); +}); + +When('I close VS Code', function() { + return this.app.exit(); +}); + +When('I start VS Code', function() { + return this.app.start(); +}); + +When('I reload VS Code', function() { + return this.app.reload(); +}); + +When('I wait for a maximum of {int} seconds for the Python extension to get activated', async function(seconds: number) { + await waitForPythonExtensionToActivate(seconds * 1000, this.app); +}); + +When('I wait for the Python extension to activate', async function() { + await waitForPythonExtensionToActivate(extensionActivationTimeout, this.app); +}); + +When('the Python extension has activated', async function() { + await waitForPythonExtensionToActivate(extensionActivationTimeout, this.app); +}); + +Given('the Python extension has been activated', async function() { + await waitForPythonExtensionToActivate(extensionActivationTimeout, this.app); +}); + +When('I wait for {int} second(s)', async (seconds: number) => sleep(seconds * 1000)); + +Then('wait for {int} millisecond(s)', sleep); + +When('I wait for {int} millisecond(s)', sleep); + +Then('wait for {int} second(s)', (seconds: number) => sleep(seconds * 1000)); + +Then('take a screenshot', async function() { + // await sleep(500); + await this.app.captureScreenshot(`take_a_screenshot_${new Date().getTime().toString()}`); +}); + +// tslint:disable-next-line: no-console +Then('log the message {string}', (message: string) => console.info(colors.green(message))); + +When(/^I press (.*)$/, async function(key: string) { + await this.app.driver.press(key); +}); + +When('I press {word} {int} times', async function(key: string, counter: number) { + for (let i = 0; i <= counter; i += 1) { + await this.app.driver.press(key); + } +}); diff --git a/uitests/src/steps/debugger.ts b/uitests/src/steps/debugger.ts new file mode 100644 index 000000000000..69b9b54d4478 --- /dev/null +++ b/uitests/src/steps/debugger.ts @@ -0,0 +1,113 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +// tslint:disable: no-invalid-this + +import { Then, When } from 'cucumber'; +import { CucumberRetryMax5Seconds } from '../constants'; + +Then('the Python Debug Configuration picker is displayed', async function() { + await this.app.debugger.waitForConfigPicker(); +}); +When('I select the debug configuration {string}', async function(configItem: string) { + await this.app.debugger.selectConfiguration(configItem); +}); + +Then('the debugger starts', async function() { + await this.app.debugger.waitUntilStarted(); +}); + +Then('the debugger pauses', async function() { + await this.app.debugger.waitUntilPaused(); +}); + +Then('the debugger stops', async function() { + await this.app.debugger.waitUntilStopped(); +}); + +Then('the debugger will stop within {int} seconds', async function(timeoutSeconds: number) { + await this.app.debugger.waitUntilStopped(timeoutSeconds * 1000); +}); +Then('the current stack frame is at line {int} in {string}', CucumberRetryMax5Seconds, async function(line: number, fileName: string) { + await this.app.documents.waitForActiveEditor(fileName); + await this.app.documents.waitForPosition({ line }); +}); + +When('I add a breakpoint to line {int}', async function(line: number) { + await this.app.debugger.setBreakpointOnLine(line); +}); +When('I add a breakpoint to line {int} in {string}', async function(line: number, fileName: string) { + await this.app.quickopen.openFile(fileName); + await this.app.debugger.setBreakpointOnLine(line); +}); + +// Given('the debug sidebar is open', async function() { +// await this.app.debugger.openDebugViewlet(); +// }); + +// When('I configure the debugger', async function() { +// await this.app.debugger.configure(); +// }); + +// When('stopOnEntry is true in launch.json', async function() { +// await updateDebugConfiguration('stopOnEntry', true, context.app.workspacePathOrFolder, 0); +// }); + +// When('stopOnEntry is false in launch.json', async function() { +// await updateDebugConfiguration('stopOnEntry', false, context.app.workspacePathOrFolder, 0); +// }); + +// Then('debugger starts', async function() { +// await sleep(200); +// await this.app.debugger.debuggerHasStarted(); +// }); + +// When('I open the debug console', async function() { +// // await this.app.debugger.openDebugConsole(); +// await context.app.workbench.quickopen.runCommand('View: Debug Console'); +// }); + +// Then('number of variables in variable window is {int}', async function(count: number) { +// await this.app.debugger.waitForVariableCount(count, count); +// }); + +// When('I step over', async function() { +// // await this.app.debugger.stepOver(); +// await context.app.workbench.quickopen.runCommand('Debug: Step Over'); +// }); + +// When('I step in', async function() { +// // await this.app.debugger.stepIn(); +// await context.app.workbench.quickopen.runCommand('Debug: Step Into'); +// }); + +// When('I continue', async function() { +// // await this.app.debugger.continue(); +// await context.app.workbench.quickopen.runCommand('Debug: Continue'); +// }); + +// Then('stack frame for file {string} is displayed', async function(file: string) { +// await this.app.debugger.waitForStackFrame( +// sf => sf.name.indexOf(file) >= 0, +// 'looking for main.py' +// ); +// }); + +// Then('debugger stops', async function() { +// await this.app.debugger.debuggerHasStopped(); +// }); + +// Then('stack frame for file {string} and line {int} is displayed', async function(file: string, line: number) { +// await this.app.debugger.waitForStackFrame( +// sf => sf.name.indexOf(file) >= 0 && sf.lineNumber === line, +// 'looking for main.py' +// ); +// }); + +// Then('the text {string} is displayed in the debug console', async function(text: string) { +// await this.app.debugger.waitForOutput(output => { +// return output.some(line => line.indexOf(text) >= 0); +// }); +// }); diff --git a/uitests/src/steps/documents.ts b/uitests/src/steps/documents.ts new file mode 100644 index 000000000000..19cfe1e18b8e --- /dev/null +++ b/uitests/src/steps/documents.ts @@ -0,0 +1,226 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +// tslint:disable: no-invalid-this + +import * as assert from 'assert'; +import { expect } from 'chai'; +import { Given, Then, When } from 'cucumber'; +import * as fs from 'fs-extra'; +import * as path from 'path'; +import { CucumberRetryMax10Seconds, CucumberRetryMax5Seconds } from '../constants'; +import { noop, retryWrapper, sleep } from '../helpers'; +import { IApplication } from '../types'; + +// tslint:disable-next-line: no-var-requires no-require-imports +const clipboardy = require('clipboardy'); + +// const autoCompletionListItemSlector = '.editor-widget.suggest-widget.visible .monaco-list-row a.label-name .monaco-highlighted-label'; + +When('I create a new file', async function() { + await this.app.documents.createNewUntitledFile(); +}); + +// Create a file in the editor by opening an editor and pasting the code. +// Sending text to the editor is the same as manually typging code. +// This can cause issues, e.g. vsc will auto complete brackets, etc... +// Easiest option, paste the text into the editor. +When('I create a new file with the following content', async function(contents: string) { + await this.app.documents.createNewUntitledFile(); + await clipboardy.write(contents); + await this.app.quickopen.runCommand('Paste'); + // Wait for text to get pasted and UI to get updated. + await sleep(200); +}); + +Given('a file named {string} is created with the following content', async function(filename: string, contents: string) { + const fullpath = path.join(this.app.workspacePathOrFolder, filename); + await fs.ensureDir(path.dirname(fullpath)); + await fs.writeFile(fullpath, contents); + // Ensure VS Code has had time to refresh to explorer and is aware of the file. + // Else if we later attempt to open this file, VSC might not be aware of it and woudn't display anything in the `quick open` dropdown. + const openRecentlyCreatedDocument = async () => { + await this.app.documents.refreshExplorer(); + await this.app.quickopen.openFile(path.basename(filename)); + await this.app.quickopen.runCommand('View: Close Editor'); + }; + + await retryWrapper({ timeout: 5000 }, openRecentlyCreatedDocument); +}); + +When('I change the language of the file to {string}', async function(language: string) { + await this.app.quickopen.runCommand('Change Language Mode'); + await this.app.quickinput.select({ value: language }); +}); + +When('I go to line {int}', async function(line: number) { + await this.app.documents.gotToPosition({ line }); +}); + +When('I go to line {int}, column {int}', async function(line: number, column: number) { + await this.app.documents.gotToPosition({ line, column }); +}); + +Given('the file {string} is open', async function(file: string) { + await this.app.quickopen.openFile(file); +}); + +When('I open the file {string}', async function(file: string) { + await this.app.quickopen.openFile(file); +}); + +// Wait for some time, possible UI hasn't been updated. +// Its been observed that 2 seconds isn't enough on Mac for Jedi/LS (go to definition). +Then('the cursor is on line {int}', CucumberRetryMax10Seconds, async function(lineNumber: number) { + const { line } = await this.app.documents.getCurrentPosition(); + assert.equal(line, lineNumber, `Line number ${line} is not same as expected ${lineNumber}`); +}); + +// Wait for some time, possible UI hasn't been updated. +// Its been observed that 2 seconds isn't enough on Mac for Jedi/LS (go to definition). +Then('auto completion list contains the item {string}', CucumberRetryMax5Seconds, async function(label: string) { + // tslint:disable-next-line: no-console + const labels = await this.app.documents.getAutoCompletionList(); + expect(labels).to.contain(label, `Label '${label}' not found in [${labels.join(',')}]`); +}); + +Then('the file {string} will be opened', async function(file: string) { + await this.app.documents.waitUntilFileOpened(file); +}); + +Then('the file {string} is opened', async function(file: string) { + await this.app.documents.waitUntilFileOpened(file); +}); + +// Then('a file named {string} is created with the following content', async (fileName: string, contents: string) => { +// const fullFilePath = path.join(context.app.workspacePathOrFolder, fileName); +// await fs.mkdirp(path.dirname(fullFilePath)).catch(noop); +// await fs.writeFile(fullFilePath, contents); +// await sleep(1000); +// }); + +// When('the file {string} has the following content', async (fileName: string, contents: string) => { +// const fullFilePath = path.join(context.app.workspacePathOrFolder, fileName); +// await fs.mkdirp(path.dirname(fullFilePath)).catch(noop); +// await fs.writeFile(fullFilePath, contents); +// await sleep(1000); +// }); + +Given('a file named {string} does not exist', async function(fileName: string) { + const fullFilePath = path.join(this.app.workspacePathOrFolder, fileName); + await fs.unlink(fullFilePath).catch(noop); +}); + +Given('the file {string} does not exist', async function(fileName: string) { + const fullFilePath = path.join(this.app.workspacePathOrFolder, fileName); + await fs.unlink(fullFilePath).catch(noop); + await sleep(1000); +}); + +// Then('a file named {string} exists', async (fileName: string) => { +// const fullFilePath = path.join(context.app.workspacePathOrFolder, fileName); +// const exists = await fs.pathExists(fullFilePath); +// expect(exists).to.equal(true, `File '${fullFilePath}' should exist`); +// }); + +async function expectFile(app: IApplication, fileName: string, timeout = 1000) { + const checkFile = async () => { + const fullFilePath = path.join(app.workspacePathOrFolder, fileName); + const exists = await fs.pathExists(fullFilePath); + assert.ok(exists, `File '${fullFilePath}' should exist`); + }; + await retryWrapper({ timeout }, checkFile); +} + +Then('a file named {string} will be created', async function(fileName: string) { + await expectFile(this.app, fileName); +}); +Then('a file named {string} is created', async function(fileName: string) { + await expectFile(this.app, fileName); +}); +Then('a file named {string} is created within {int} seconds', async function(fileName: string, seconds: number) { + await expectFile(this.app, fileName, seconds * 1000); +}); + +// When(/^I press (.*)$/, async (key: string) => { +// await context.app.code.dispatchKeybinding(key); +// }); + +// When('I press {word} {int} times', async (key: string, counter: number) => { +// for (let i = 0; i <= counter; i += 1) { +// await context.app.code.dispatchKeybinding(key); +// } +// }); + +// Then('code lens {string} is visible in {int} seconds', async (title: string, timeout: number) => { +// const retryInterval = 200; +// const retryCount = timeout * 1000 / 200; +// const eles = await context.app.code.waitForElements('div[id="workbench.editors.files.textFileEditor"] span.codelens-decoration a', true, undefined, retryCount, retryInterval); +// const expectedLenses = eles.filter(item => item.textContent.trim().indexOf(title) === 0); +// expect(expectedLenses).to.be.lengthOf.greaterThan(0); +// }); +// Then('code lens {string} is visible', async (title: string) => { +// const eles = await context.app.code.waitForElements('div[id="workbench.editors.files.textFileEditor"] span.codelens-decoration a', true); +// const expectedLenses = eles.filter(item => item.textContent.trim().indexOf(title) === 0); +// expect(expectedLenses).to.be.lengthOf.greaterThan(0); +// }); + +// Given('the file {string} does not exist', async (file: string) => { +// const filePath = path.join(context.app.workspacePathOrFolder, file); +// if (await fs.pathExists(filePath)) { +// await fs.unlink(filePath); +// } +// }); + +// When('I open the file {string}', async (file: string) => { +// await context.app.workbench.quickopen.openFile(file); +// }); + +// Given('the file is scrolled to the top', async () => { +// await context.app.workbench.quickopen.runCommand('Go to Line...'); +// await context.app.workbench.quickopen.waitForQuickOpenOpened(10); +// await context.app.code.dispatchKeybinding('1'); +// await context.app.code.dispatchKeybinding('Enter'); +// await sleep(100); +// }); + +// Given('the file {string} is updated with the value {string}', async (file: string, value: string) => { +// await fs.writeFile(path.join(context.app.workspacePathOrFolder, file), value); +// }); + +// When('I update file {string} with value {string}', async (file: string, value: string) => { +// await fs.writeFile(path.join(context.app.workspacePathOrFolder, file), value); +// }); + +// When('I select the text {string} in line {int} of file {string}', async (selection: string, line: number, file: string) => { +// await context.app.workbench.editor.clickOnTerm(file, selection, line); +// }); + +// When('I set cursor to line {int} of file {string}', async (line: number, file: string) => { +// await context.app.workbench.editor.waitForEditorFocus(file, line); +// }); + +// When('I press {string}', async (keyStroke: string) => { +// await context.app.code.dispatchKeybinding(keyStroke); +// }); + +// Then('line {int} of file {string} will be highlighted', async (line: number, file: string) => { +// await context.app.workbench.editor.waitForHighlightingLine(file, line); +// }); + +// Then('text {string} will appear in the file {string}', async (text: number, file: string) => { +// await context.app.workbench.editor.waitForEditorContents(file, contents => contents.indexOf(`${text}`) > -1); +// }); + +// When('I type the text {string} into the file {string}', async (text: string, file: string) => { +// await context.app.workbench.editor.waitForTypeInEditor(file, text); +// }); + +// When('I go to definition for {string} in line {int} of file {string}', async (selection: string, line: number, file: string) => { +// await context.app.workbench.quickopen.openFile(file); +// await context.app.workbench.editor.clickOnTerm(file, selection, line); +// await context.app.code.dispatchKeybinding('right'); +// await context.app.code.dispatchKeybinding('F12'); +// }); diff --git a/uitests/src/steps/interpreter.ts b/uitests/src/steps/interpreter.ts new file mode 100644 index 000000000000..3b9dcb5e2f8c --- /dev/null +++ b/uitests/src/steps/interpreter.ts @@ -0,0 +1,112 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +// tslint:disable: no-invalid-this + +import { Given, When } from 'cucumber'; +import { ensurePackageIsInstalled, ensurePackageIsNotInstalled } from '../helpers/python'; + +When('I select the Python Interpreter containing the text {string}', async function(text: string) { + await this.app.interpreters.select({ name: text }); +}); + +// When('I select the default mac Interpreter', async () => { +// await context.app.workbench.interpreters.selectInterpreter({ tooltip: '/usr/bin/python' }); +// }); + +Given('the package {string} is not installed', async function(moduleName: string) { + await ensurePackageIsNotInstalled(this.options.pythonPath, moduleName); +}); + +When('I install the package {string}', async function(moduleName: string) { + await ensurePackageIsInstalled(this.options.pythonPath, moduleName); +}); +When('I uninstall the package {string}', async function(moduleName: string) { + await ensurePackageIsInstalled(this.options.pythonPath, moduleName); +}); +Given('the package {string} is installed', async function(moduleName: string) { + await ensurePackageIsInstalled(this.options.pythonPath, moduleName); +}); + +// Given('there are no pipenv environments', async () => { +// await deletePipEnv(context.app); +// }); + +// Given('there are no virtual environments in the workspace', async () => { +// await deleteVenvs(context.app); +// }); + +// Given('there are no virtual environments in the workspace', async () => { +// await deleteVenvs(context.app); +// }); + +// Given('some random interpreter is selected', async () => { +// await selectGenericInterpreter(context.app); +// }); + +// When('I select some random interpreter', async () => { +// await selectGenericInterpreter(context.app); +// }); + +// When('I create a pipenv environment', async () => { +// await createPipEnv(context.app.activeEnvironment as PipEnvEnviroment, context.app); +// }); + +// When('I create a venv environment with the name {string}', async (venvName: string) => { +// const venvEnv = context.app.activeEnvironment as VenvEnviroment; +// venvEnv.venvArgs = [venvName]; +// await createVenv(venvEnv, context.app); +// }); + +// When('I change the python path in settings.json to {string}', async (pythonPath: string) => { +// await updateSetting('python.pythonPath', pythonPath, context.app.workspacePathOrFolder); +// }); + +// When('I select a python interpreter', async () => { +// await updateSetting('python.pythonPath', context.app.activeEnvironment.pythonPath!, context.app.workspacePathOrFolder); +// await sleep(1000); +// }); + +// Given('there is no python path in settings.json', async () => { +// await removeSetting('python.pythonPath', context.app.workspacePathOrFolder); +// }); + +// Then('settings.json will automatically be updated with pythonPath', { timeout: 60000 }, async () => { +// const currentPythonPath = await getSetting('python.pythonPath', context.app.workspacePathOrFolder); +// assert.notEqual(currentPythonPath, undefined); +// await interpreterInStatusBarDisplaysCorrectPath(currentPythonPath!, context.app); +// }); + +// Then('the selected interpreter contains the name {string}', async (name: string) => { +// const pythonPathInSettings = await getSetting('python.pythonPath', context.app.workspacePathOrFolder); +// const tooltip = getDisplayPath(pythonPathInSettings, context.app.workspacePathOrFolder); + +// const text = await context.app.workbench.statusbar.waitForStatusbarLinkText(tooltip); +// assert.notEqual(text.indexOf(name), -1, `'${name}' not found in display name`); +// }); + +// Then('a message containing the text {string} will be displayed', async (message: string) => { +// await context.app.workbench.quickinput.waitForMessage(message); +// try { +// await sleep(100); +// await context.app.code.waitAndClick('.action-label.icon.clear-notification-action'); +// await sleep(100); +// await context.app.code.waitAndClick('.action-label.icon.clear-notification-action'); +// await sleep(100); +// } catch { +// // Do nothing. +// } +// }); + +// Then('interpreter informantion in status bar has refreshed', async () => { +// const tooltip = getDisplayPath(context.app.activeEnvironment.pythonPath!, context.app.workspacePathOrFolder); +// const text = await context.app.workbench.statusbar.waitForStatusbarLinkText(tooltip); +// context.app.activeEnvironment.displayNameParts.forEach(item => { +// // In the case of pipenv environments, the spaces are replaced with '_'. +// const parsed = item.replace('/ /g', '_'); +// const found = text.indexOf(item) >= 0 || text.indexOf(parsed) >= 0; +// assert.equal(found, true, `'${item}' not found in display name`); +// }); +// }); diff --git a/uitests/src/steps/main.ts b/uitests/src/steps/main.ts new file mode 100644 index 000000000000..bb9d005204e5 --- /dev/null +++ b/uitests/src/steps/main.ts @@ -0,0 +1,185 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { After, Before, HookScenarioResult, setDefaultTimeout, setDefinitionFunctionWrapper, setWorldConstructor, Status } from 'cucumber'; +import * as fs from 'fs-extra'; +import * as path from 'path'; +import * as rimraf from 'rimraf'; +import { extensionActivationTimeout, maxStepTimeout } from '../constants'; +import { noop, RetryOptions, retryWrapper } from '../helpers'; +import { debug, error, warn } from '../helpers/logger'; +import { getTestOptions, restoreDefaultUserSettings, TestOptions, waitForPythonExtensionToActivate } from '../setup'; +import { clearWorkspace, dismissMessages, initializeWorkspace } from '../setup/environment'; +import { IApplication, ITestOptions } from '../types'; +import { Application } from '../vscode'; +import { WorldParameters } from './types'; + +// tslint:disable: no-invalid-this mocha-no-side-effect-code no-any non-literal-require no-function-expression + +// keeps track of the fact that we have dismissed onetime messages displayed in VSC. +// E.g. messages such as 'Tip: You can select an interpreter from statusbar'. +// Such messages will keep showing up until they are dismissed by user - never to be displayed again. +// Dismissing such messages makes it easy for testing (less noise when testing messages and less noice in screenshots). +// This will get reset when user loads VSC for first time. +let oneTimeMessagesDismissed = false; + +/** + * Context object available in every step. + * Step = BDD Step such as `Given`, `When` and `Then`. + * + * @class MyWorld + */ +class MyWorld { + public readonly app: IApplication; + public readonly options: ITestOptions; + constructor({ parameters, attach }: { attach: Function; parameters: WorldParameters }) { + debug('Start MyWorld contructor'); + const testOptions = getTestOptions(parameters.channel, parameters.testDir, parameters.pythonPath, parameters.verboseLogging); + this.app = new Application(testOptions); + this.app.on('start', emulateFirstTimeLoad => (emulateFirstTimeLoad ? (oneTimeMessagesDismissed = false) : undefined)); + this.app.on('screenshotCatured', data => attach(data, 'image/png')); + this.options = testOptions; + debug('End MyWorld contructor'); + } +} +declare module 'cucumber' { + /** + * Context object available in every step. + * Step = BDD Step such as `Given`, `When` and `Then`. + * + * @export + * @interface World + */ + // tslint:disable-next-line: interface-name + export interface World { + app: IApplication; + options: ITestOptions; + } +} + +setWorldConstructor(MyWorld); + +// We might have steps that are slow, hence allow max timeouts of 2 minutes. +// Also easy for debugging. +setDefaultTimeout(maxStepTimeout); + +// const lastSetWorkspaceFolder = ''; +Before(async function(scenario: HookScenarioResult) { + debug('Start Before'); + const options = (this.app as Application).options as TestOptions; + await options.updateForScenario(scenario); + // Initialize the workspace with the required code. + // I.e. if required download the source that's meant to be used for testing (from a git repo). + // Optionally if we're to use a new sub directory in the repo, then update the workspace folder accordingly. + const newWorkspaceFolder = await initializeWorkspace(scenario, this.app.workspacePathOrFolder); + if (newWorkspaceFolder) { + options.udpateWorkspaceFolder(newWorkspaceFolder); + } + + // These must never change (we control the test environment, hence we need to ensure `settings.json` is as expected). + // For every test this will be reset (possible a test had updated the user settings). + await restoreDefaultUserSettings(options); + await this.app.start(); + // Activating extension can be slow on windows. + await waitForPythonExtensionToActivate(extensionActivationTimeout, this.app); + + debug('Waiting for VSC & Python Extension to display its messages, so they can be dimissed'); + // Rather than waiting & then dismissing messages, just keep retrying to dismiss messages for 5 seconds. + const dismiss = async () => dismissMessages(this.app).then(() => Promise.reject()); + const timeout = oneTimeMessagesDismissed ? 5000 : 1000; + await retryWrapper({ timeout, logFailures: false }, dismiss).catch(noop); + // eslint-disable-next-line require-atomic-updates + oneTimeMessagesDismissed = true; + + // Since we activated the python extension, lets close and reload. + // For the tests the extension should not be activated (the tests will do that). + await this.app.reload(); +}); + +After(async function(scenario: HookScenarioResult) { + // Close all editors, etc. + // Finally reset user history. + // performance.mark(`${scenario.pickle.name}-after-start`); + try { + if (this.app.isAlive) { + // Capture a screenshot after every scenario. + // Whether it fails or not (very useful when trying to figure out whether a test actually ran, why it failed, etc). + const name = `After_${new Date().getTime()}`; + // If VS Code has died, then ignore the errors (capturing screenshots will fail). + await this.app.captureScreenshot(name).catch(warn.bind(warn, 'Failed to capture after hook screenshot.')); + await dismissMessages(this.app); + // If VS Code has died, then ignore the errors (clearing workspace using `commands` from the `command palette` will fail). + await clearWorkspace(this.app).catch(warn.bind(warn, 'Failed to clear the workspace.')); + } + } catch (ex) { + // Handle exception as cucumber doesn't handle (log) errors in hooks too well. + // Basically they aren't logged, i.e. get swallowed up. + error('After hook failed', ex); + throw ex; + } finally { + await this.app.exit().catch(warn.bind(warn, 'Failed to exit in After hook')); + const options = (this.app as Application).options; + if (scenario.result.status === Status.PASSED) { + // If the tests have passed, then delete everything related to previous tests. + // Delete screenshots, logs, everything that's transient. + await Promise.all([ + new Promise(resolve => rimraf(options.logsPath, resolve)).catch(noop), + new Promise(resolve => rimraf(options.reportsPath, resolve)).catch(noop), + new Promise(resolve => rimraf(options.screenshotsPath, resolve)).catch(noop), + new Promise(resolve => rimraf(options.tempPath, resolve)).catch(noop), + new Promise(resolve => rimraf(options.workspacePathOrFolder, resolve)).catch(noop) + ]); + } else { + // Ok, test failed, copy everythign we'll need to triage this issue. + // State of the workspace folder, logs, screenshots, everything. + // Rememeber, screenshots are specific to each test (hence they are preserved as long as we don't delete them). + await fs.copy(options.workspacePathOrFolder, path.join(options.reportsPath, 'workspace folder')); + await fs.copyFile(options.userSettingsFilePath, path.join(options.reportsPath, 'user_settings.json')); + await Promise.all([ + new Promise(resolve => rimraf(options.workspacePathOrFolder, resolve)).catch(noop), + new Promise(resolve => rimraf(options.tempPath, resolve)).catch(noop) + ]); + } + } +}); + +/* + * Create a wrapper for all steps to re-try if the step is configured for retry. + * (its possible the UI isn't ready, hence we need to re-try some steps). + * + * Cast to any as type definitions setDefinitionFunctionWrapper is wrong. + */ +type AsyncFunction = (...args: any[]) => Promise; +(setDefinitionFunctionWrapper as any)(function(fn: Function, opts?: { retry?: RetryOptions }) { + return async function(this: {}) { + const args = [].slice.call(arguments); + if (!opts || !opts.retry) { + return fn.apply(this, args); + } + return retryWrapper.bind(this)(opts.retry, fn as AsyncFunction, ...args); + }; +}); + +// /* +// Capture screenshots after every step. +// */ +// (setDefinitionFunctionWrapper as any)(function (fn: Function) { +// async function captureScreenshot() { +// try { +// const name = `After_${new Date().getTime()}`.replace(/[^a-z0-9\-]/gi, '_'); +// // Ignore errors, as its possible app hasn't started. +// await context.app.captureScreenshot(name).catch(noop); +// } catch { noop(); } +// } +// return async function (this: {}) { +// const result = fn.apply(this, [].slice.call(arguments)); +// if (result.then) { +// return (result as Promise).finally(captureScreenshot); +// } else { +// await captureScreenshot(); +// return result; +// } +// }; +// }); diff --git a/uitests/src/steps/notifications.ts b/uitests/src/steps/notifications.ts new file mode 100644 index 000000000000..8a70f16295d3 --- /dev/null +++ b/uitests/src/steps/notifications.ts @@ -0,0 +1,85 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +// tslint:disable: no-invalid-this + +import * as assert from 'assert'; +import { expect } from 'chai'; +import { Then } from 'cucumber'; +import { CucumberRetryMax20Seconds } from '../constants'; +import { retryWrapper, sleep } from '../helpers'; +import { debug } from '../helpers/logger'; +import { IApplication } from '../types'; + +async function notificationDisplayed(app: IApplication, message: string, timeout: number = 10_000) { + async function checkMessages() { + const hasMessages = await app.notifications.hasMessages(); + debug(`Has Messages ${hasMessages}`); + expect(hasMessages).to.be.equal(true, 'No messages displayed'); + const messages = await app.notifications.getMessages(); + if (messages.findIndex(item => item.toLowerCase().indexOf(message.toLowerCase()) >= 0) === -1) { + assert.fail(`Message '${message}' not found in [${messages.join(',')}]`); + } + } + await retryWrapper({ timeout }, checkMessages); +} + +Then('no notifications are displayed', async function() { + const hasMessages = await this.app.notifications.hasMessages(); + assert.ok(!hasMessages); +}); + +Then('no error notifications are displayed', async function() { + const hasMessages = await this.app.notifications.hasMessages('error'); + assert.ok(!hasMessages); +}); + +Then('a message with the text {string} is displayed', async function(message: string) { + await notificationDisplayed(this.app, message); +}); + +Then('a message containing the text {string} is displayed', async function(message: string) { + await notificationDisplayed(this.app, message); +}); + +Then('a message containing the text {string} will be displayed within {int} seconds', async function(message: string, timeoutSeconds: number) { + await notificationDisplayed(this.app, message, timeoutSeconds * 1000); +}); + +/** + * Checks whether a message is not displayed. + * If it is, then an assertion error is thrown. + * + * @param {string} message + * @returns + */ +async function messageIsNotDisplayed(app: IApplication, message: string) { + // Wait for a max of 5 seconds for messages to appear. + // If it doesn't appear within this period, then assume everyting is ok. + await sleep(5000); + + const hasMessages = await app.notifications.hasMessages(); + if (!hasMessages) { + return; + } + const messages = await app.notifications.getMessages(); + if (messages.findIndex(item => item.toLowerCase().indexOf(message.toLowerCase()) >= 0) !== -1) { + assert.fail(`Message '${message}' found in [${messages.join(',')}]`); + } +} +Then('a message containing the text {string} is not displayed', async function(message: string) { + await messageIsNotDisplayed(this.app, message); +}); + +Then('I click the {string} button for the message with the text {string}', CucumberRetryMax20Seconds, async function(button: string, message: string) { + await notificationDisplayed(this.app, message); + await this.app.notifications.dismiss([{ buttonText: button, content: message }], 2); + // We might have to retry closing the message as its possible a new message was displayed in the mean time. + // In which case closing the message won't work. + // Imagine you as a user are about to close a message, then a new message appears! It doesn't work! + await messageIsNotDisplayed(this.app, message); + // Wait for state to get updated (e.g. if we're dismissing one time messages, then this state needs to be persisted). + await sleep(500); +}); diff --git a/uitests/src/steps/outputPanel.ts b/uitests/src/steps/outputPanel.ts new file mode 100644 index 000000000000..4e787422d397 --- /dev/null +++ b/uitests/src/steps/outputPanel.ts @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +// tslint:disable: no-invalid-this + +import { Then } from 'cucumber'; +import '../helpers/extensions'; + +type TextOrWordOrContent = 'text' | 'word' | 'message' | 'content'; +Then('the {word} {string} will be displayed in the output panel', async function(_textOrMessage: TextOrWordOrContent, text: string) { + await this.app.panels.waitUtilContent(text); +}); + +Then('the {word} {string} will be displayed in the output panel within {int} seconds', async function(_textOrMessage: TextOrWordOrContent, text: string, timeoutSeconds: number) { + await this.app.panels.waitUtilContent(text, timeoutSeconds); +}); diff --git a/uitests/src/steps/problems.ts b/uitests/src/steps/problems.ts new file mode 100644 index 000000000000..74712384eee0 --- /dev/null +++ b/uitests/src/steps/problems.ts @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +// tslint:disable: no-invalid-this + +import * as assert from 'assert'; +import { expect } from 'chai'; +import { Then } from 'cucumber'; +import { CucumberRetryMax5Seconds } from '../constants'; + +// Wait for some time as it take take at least 1s to appear. +// Surely problems won't take more than 5 seconds to appear. +// Why 5? Well, needs to be > 1, but most certainly not more than 5. + +Then('there are no problems in the problems panel', CucumberRetryMax5Seconds, async function() { + const count = await this.app.problems.getProblemCount(); + assert.equal(count, 0); +}); + +Then('there is at least one problem in the problems panel', CucumberRetryMax5Seconds, async function() { + const count = await this.app.problems.getProblemCount(); + expect(count).to.greaterThan(0); +}); + +Then('there are at least {int} problems in the problems panel', CucumberRetryMax5Seconds, async function(expectedMinimumCount: number) { + const count = await this.app.problems.getProblemCount(); + expect(count).to.greaterThan(expectedMinimumCount - 1); +}); + +Then('there is a problem with the message {string}', CucumberRetryMax5Seconds, async function(message: string) { + const messages = await this.app.problems.getProblemMessages(); + expect(messages.join(', ').toLowerCase()).to.include(message.toLowerCase()); +}); + +Then('there is a problem with the file named {string}', CucumberRetryMax5Seconds, async function(fileName: string) { + const messages = await this.app.problems.getProblemFiles(); + expect(messages.join(', ').toLowerCase()).to.include(fileName.toLowerCase()); +}); diff --git a/uitests/src/steps/settings.ts b/uitests/src/steps/settings.ts new file mode 100644 index 000000000000..022cb8d02cff --- /dev/null +++ b/uitests/src/steps/settings.ts @@ -0,0 +1,62 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +// tslint:disable: no-invalid-this + +import * as assert from 'assert'; +import { Given, Then, When } from 'cucumber'; +import { ConfigurationTarget } from '../types'; + +const translateType = (type: SettingType) => (type === 'user' ? ConfigurationTarget.Global : ConfigurationTarget.WorkspaceFolder); +type SettingType = 'user' | 'workspaceFolder' | 'workspace'; +type EnabledOrDisabled = 'enabled' | 'disabled'; +type EnabledOrDisabledOrRemove = 'enable' | 'disable' | 'remove'; + +Given('the {word} setting {string} is {word}', async function(type: SettingType, setting: string, enabledOrDisabled: EnabledOrDisabled) { + await this.app.settings.updateSetting(setting, enabledOrDisabled === 'enabled', translateType(type)); +}); +Given('the {word} setting {string} has the value {string}', async function(type: SettingType, setting: string, value: string) { + await this.app.settings.updateSetting(setting, value, translateType(type)); +}); +Given('the {word} setting {string} has the value {int}', async function(type: SettingType, setting: string, value: number) { + await this.app.settings.updateSetting(setting, value, translateType(type)); +}); +Given('the {word} setting {string} does not exist', async function(type: SettingType, setting: string) { + await this.app.settings.updateSetting(setting, void 0, translateType(type)); +}); + +When('I {word} the {word} setting {string}', async function(change: EnabledOrDisabledOrRemove, type: SettingType, setting: string) { + const newValue = change === 'remove' ? void 0 : change === 'enable'; + await this.app.settings.updateSetting(setting, newValue, translateType(type)); +}); + +When('I update the {word} setting {string} with the value {string}', async function(type: SettingType, setting: string, value: string) { + await this.app.settings.updateSetting(setting, value, translateType(type)); +}); +When('I update the {word} setting {string} with the value {int}', async function(type: SettingType, setting: string, value: number) { + await this.app.settings.updateSetting(setting, value, translateType(type)); +}); + +Then('the {word} setting {string} will be {word}', async function(type: SettingType, setting: string, enabledOrDisabled: EnabledOrDisabled) { + const value = await this.app.settings.getSetting(setting, translateType(type)); + assert.equal(value, enabledOrDisabled === 'enabled'); +}); +Then('the workspace setting {string} does not exist', async function(setting: string) { + const value = await this.app.settings.getSetting(setting, ConfigurationTarget.WorkspaceFolder); + assert.equal(value, undefined); +}); +Then('the workspace setting {string} has the value {string}', async function(setting: string, expectedValue: string) { + const value = await this.app.settings.getSetting(setting, ConfigurationTarget.WorkspaceFolder); + assert.equal(value, expectedValue); +}); +Then('the workspace setting {string} has the value {int}', async function(setting: string, expectedValue: number) { + const value = await this.app.settings.getSetting(setting, ConfigurationTarget.WorkspaceFolder); + assert.equal(value, expectedValue); +}); +Then('the workspace setting {string} contains the value {string}', async function(setting: string, expectedValue: string) { + const value = await this.app.settings.getSetting(setting, ConfigurationTarget.WorkspaceFolder); + assert.notEqual(value, undefined); + assert.equal(value!.indexOf(expectedValue) >= 0, expectedValue); +}); diff --git a/uitests/src/steps/statusbar.ts b/uitests/src/steps/statusbar.ts new file mode 100644 index 000000000000..7cf625213bcd --- /dev/null +++ b/uitests/src/steps/statusbar.ts @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +// tslint:disable: no-invalid-this + +import { expect } from 'chai'; +import { Then } from 'cucumber'; +import { CucumberRetryMax5Seconds } from '../constants'; +import '../helpers/extensions'; + +// Add a delay, as this can take around 1s (from the time something was selected). +Then('the python the status bar contains the text {string}', CucumberRetryMax5Seconds, async function(text: string) { + const statubarText = await this.app.statusbar.getPythonStatusBarText(); + expect(statubarText).contains(text); +}); + +// Add a delay, as this can take around 1s (from the time something was selected). +Then('the python the status bar does not contain the text {string}', CucumberRetryMax5Seconds, async function(text: string) { + const statubarText = await this.app.statusbar.getPythonStatusBarText(); + expect(statubarText).not.contains(text); +}); + +// Then('the python the status bar is not visible', async () => { +// await context.app.workbench.statusbar.pythonStatusBarElementIsNotVisible(); +// }); diff --git a/uitests/src/steps/testing.ts b/uitests/src/steps/testing.ts new file mode 100644 index 000000000000..df142f06ebc9 --- /dev/null +++ b/uitests/src/steps/testing.ts @@ -0,0 +1,212 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +// tslint:disable: no-invalid-this + +import { expect } from 'chai'; +import { Then, When } from 'cucumber'; +import { CucumberRetryMax5Seconds } from '../constants'; +import { IApplication, TestExplorerNodeStatus } from '../types'; + +Then('the test explorer icon will be visible', async function() { + await this.app.testExplorer.waitUntilIconVisible(5_000); +}); + +// Surely tests can't take more than 30s to get discovered. +When('I wait for test discovery to complete', async function() { + await this.app.testExplorer.waitUntilTestsStop(30_000); +}); + +// Surely pythonn tests (in our UI Tests) can't take more than 30s to run. +When('I wait for tests to complete running', async function() { + await this.app.testExplorer.waitUntilTestsStop(30_000); +}); + +Then('there are {int} nodes in the test explorer', CucumberRetryMax5Seconds, async function(expectedCount: number) { + const count = await this.app.testExplorer.getNodeCount(); + expect(count).to.equal(expectedCount); +}); +Then('all of the test tree nodes have a progress icon', CucumberRetryMax5Seconds, async function() { + const elements = await this.app.testExplorer.getNodes(); + const progressCount = elements.filter(node => node.status === 'Progress').length; + expect(progressCount).to.equal(elements.length); +}); +async function getNumberOfNodesWithIcon(app: IApplication, status: TestExplorerNodeStatus): Promise { + const elements = await app.testExplorer.getNodes(); + return elements.filter(node => node.status === status).length; +} +Then('{int} nodes in the test explorer have a status of "{word}"', CucumberRetryMax5Seconds, async function(count: number, status: TestExplorerNodeStatus) { + const nodeCount = await getNumberOfNodesWithIcon(this.app, status); + expect(nodeCount).to.equal(count); +}); +Then('1 node in the test explorer has a status of "{word}"', CucumberRetryMax5Seconds, async function(status: TestExplorerNodeStatus) { + const nodeCount = await getNumberOfNodesWithIcon(this.app, status); + expect(nodeCount).to.equal(1); +}); +Then('the node {string} in the test explorer has a status of "{word}"', CucumberRetryMax5Seconds, async function(label: string, status: TestExplorerNodeStatus) { + const node = await this.app.testExplorer.getNode(label); + expect(node.status).to.equal(status); +}); + +Then('the stop icon is visible in the toolbar', async function() { + await this.app.testExplorer.waitUntilToolbarIconVisible('Stop'); +}); +Then('the run failed tests icon is visible in the toolbar', async function() { + await this.app.testExplorer.waitUntilToolbarIconVisible('RunFailedTests'); +}); +Then('I stop discovering tests', async function() { + await this.app.testExplorer.clickToolbarIcon('Stop'); +}); +When('I stop running tests', async function() { + await this.app.testExplorer.clickToolbarIcon('Stop'); +}); +When('I run failed tests', async function() { + await this.app.testExplorer.clickToolbarIcon('RunFailedTests'); +}); + +Then('the stop icon is not visible in the toolbar', async function() { + await this.app.testExplorer.waitUntilToolbarIconVisible('Stop'); +}); +When('I click the test node with the label {string}', async function(label: string) { + await this.app.testExplorer.clickNode(label); +}); +When('I navigate to the code associated with the test node {string}', async function(label: string) { + await this.app.testExplorer.selectActionForNode(label, 'open'); +}); +// tslint:disable: no-invalid-this no-any restrict-plus-operands no-console +When('I debug the node {string} from the test explorer', async function(label: string) { + await this.app.testExplorer.selectActionForNode(label, 'debug'); +}); +When('I run the node {string} from the test explorer', async function(label: string) { + await this.app.testExplorer.selectActionForNode(label, 'run'); +}); +When('I expand all of the nodes in the test explorer', async function() { + await this.app.testExplorer.expandNodes(); +}); + +// Given('the test framework is {word}', async function (testFramework: string) { +// await updateSetting('python.unitTest.nosetestsEnabled', testFramework === 'nose', context.app.workspacePathOrFolder); +// await updateSetting('python.unitTest.pyTestEnabled', testFramework === 'pytest', context.app.workspacePathOrFolder); +// await updateSetting('python.unitTest.unittestEnabled', testFramework === 'unittest', context.app.workspacePathOrFolder); +// }); +// Then('wait for the test icon to appear within {int} seconds', async function (timeout: number) { +// const icon = '.part.activitybar.left .composite-bar li a[title="Test"]'; +// await context.app.code.waitForElement(icon, undefined, timeout * 1000 / 250, 250); +// await sleep(250); +// }); +// Then('wait for the toolbar button with the text {string} to appear within {int} seconds', async function (title: string, timeout: number) { +// const button = `div[id = "workbench.parts.sidebar"] a[title = "${title}"]`; +// await context.app.code.waitForElement(button, undefined, timeout * 1000 / 250, 250); +// await sleep(1000); +// Then('the toolbar button with the text {string} is visible', async function (title: string) { +// }); +// await context.app.code.waitForElement(`div[id = "workbench.parts.sidebar"] a[title = "${title}"]`); +// }); +// Then('the toolbar button with the text {string} is not visible', async function (title: string) { +// const eles = await context.app.code.waitForElements('div[id="workbench.parts.sidebar"] ul[aria-label="PYTHON actions"] li a', true); +// assert.equal(eles.find(ele => ele.attributes['title'] === title), undefined); +// }); +// Then('select first node', async function () { +// // await context.app.code.waitAndClick('div[id="workbench.view.extension.test"] div.has-children:nth-child(1) a.label-name:nth-child(1n)'); +// await context.app.code.waitAndClick('div[id="workbench.view.extension.test"] div.monaco-tree-row:nth-child(1) a.label-name:nth-child(1n)'); +// }); +// Then('select second node', async function () { +// // await context.app.code.waitAndClick('div[id="workbench.view.extension.test"] div.has-children:nth-child(2) a.label-name:nth-child(1n)'); +// await context.app.code.waitAndClick('div[id="workbench.view.extension.test"] div.monaco-tree-row:nth-child(2) a.label-name:nth-child(1n)'); +// }); +// Then('has {int} error test items', async function (count: number) { +// const eles = await context.app.code.waitForElements('div[id="workbench.view.extension.test"] div.custom-view-tree-node-item-icon[style^="background-image:"][style*="status-error.svg"]', true); +// assert.equal(eles.length, count); +// }); +// Then('there are at least {int} error test items', async function (count: number) { +// const eles = await context.app.code.waitForElements('div[id="workbench.view.extension.test"] div.custom-view-tree-node-item-icon[style^="background-image:"][style*="status-error.svg"]', true); +// expect(eles).to.be.lengthOf.greaterThan(count - 1); +// }); +// Then('there are at least {int} error test items', async function (count: number) { +// const eles = await context.app.code.waitForElements('div[id="workbench.view.extension.test"] div.custom-view-tree-node-item-icon[style^="background-image:"][style*="status-error.svg"]', true); +// expect(eles).to.be.lengthOf.greaterThan(count - 1); +// }); +// Then('there are {int} success test items', async function (count: number) { +// const eles = await context.app.code.waitForElements('div[id="workbench.view.extension.test"] div.custom-view-tree-node-item-icon[style^="background-image:"][style*="status-ok.svg"]', true); +// assert.equal(eles.length, count); +// }); +// Then('there are {int} running test items', async function (count: number) { +// const eles = await context.app.code.waitForElements('div[id="workbench.view.extension.test"] div.custom-view-tree-node-item-icon[style^="background-image:"][style*="discovering-tests.svg"]', true); +// assert.equal(eles.length, count); +// }); +// Then('there are at least {int} running test items', async function (count: number) { +// const eles = await context.app.code.waitForElements('div[id="workbench.view.extension.test"] div.custom-view-tree-node-item-icon[style^="background-image:"][style*="discovering-tests.svg"]', true); +// expect(eles).to.be.lengthOf.greaterThan(count - 1); +// }); +// When('I select test tree node number {int} and press run', async function (nodeNumber: number) { +// await highlightNode(nodeNumber); +// const selector = `div.monaco - tree - row: nth - child(${ nodeNumber }) div.monaco - icon - label.custom - view - tree - node - item - resourceLabel > div.actions > div > ul a[title = "Run"]`; +// await context.app.code.waitAndClick(selector); +// }); +// When('I select test tree node number {int} and press open', async function (nodeNumber: number) { +// await highlightNode(nodeNumber); +// const selector = `div.monaco - tree - row: nth - child(${ nodeNumber }) div.monaco - icon - label.custom - view - tree - node - item - resourceLabel a[title = "Open"]`; +// await context.app.code.waitAndClick(selector); +// }); +// When('I select test tree node number {int} and press debug', async function (nodeNumber: number) { +// await highlightNode(nodeNumber); +// const selector = `div.monaco - tree - row: nth - child(${ nodeNumber }) div.monaco - icon - label.custom - view - tree - node - item - resourceLabel a[title = "Debug"]`; +// await context.app.code.waitAndClick(selector); +// }); +// When('I select test tree node number {int}', async function (nodeNumber: number) { +// await highlightNode(nodeNumber); +// await context.app.code.waitAndClick(`div[id = "workbench.view.extension.test"] div.monaco - tree - row: nth - child(${ nodeNumber }) a.label - name: nth - child(1n)`); +// }); +// When('I stop the tests', async function () { +// const selector = 'div[id="workbench.parts.sidebar"] a[title="Stop"]'; +// await context.app.code.waitAndClick(selector); +// }); +// Then('stop the tests', async function () { +// await stopRunningTests(); +// }); +// export async function killRunningTests() { +// try { +// const selector = 'div[id="workbench.parts.sidebar"] a[title="Stop"]'; +// await context.app.code.waitForElement(selector, undefined, 1, 100); +// } catch { +// return; +// } +// try { +// await stopRunningTests(); +// } catch { +// noop(); +// } +// } +// async function stopRunningTests() { +// const selector = 'div[id="workbench.parts.sidebar"] a[title="Stop"]'; +// await context.app.code.waitAndClick(selector); +// } +// When('I click first code lens "Run Test"', async function () { +// const selector = 'div[id="workbench.editors.files.textFileEditor"] span.codelens-decoration:nth-child(2) a:nth-child(1)'; +// const eles = await context.app.code.waitForElements(selector, true); +// expect(eles[0].textContent).to.contain('Run Test'); +// await context.app.code.waitAndClick(selector); +// }); + +// When('I click first code lens "Debug Test"', async function () { +// const selector = 'div[id="workbench.editors.files.textFileEditor"] span.codelens-decoration:nth-child(2) a:nth-child(3)'; +// const eles = await context.app.code.waitForElements(selector, true); +// expect(eles[0].textContent).to.contain('Debug Test'); +// await context.app.code.waitAndClick(selector); +// }); + +// When('I click second code lens "Debug Test"', async function () { +// const selector = 'div[id="workbench.editors.files.textFileEditor"] span.codelens-decoration:nth-child(3) a:nth-child(3)'; +// const eles = await context.app.code.waitForElements(selector, true); +// expect(eles[0].textContent).to.contain('Debug Test'); +// await context.app.code.waitAndClick(selector); +// }); + +// When('I click second code lens "Run Test"', async function () { +// const selector = 'div[id="workbench.editors.files.textFileEditor"] span.codelens-decoration:nth-child(3) a:nth-child(1)'; +// const eles = await context.app.code.waitForElements(selector, true); +// expect(eles[0].textContent).to.contain('Run Test'); +// await context.app.code.waitAndClick(selector); +// }); diff --git a/uitests/src/steps/types.ts b/uitests/src/steps/types.ts new file mode 100644 index 000000000000..9777358dc76b --- /dev/null +++ b/uitests/src/steps/types.ts @@ -0,0 +1,8 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { Channel } from '../types'; + +export type WorldParameters = { channel: Channel; testDir: string; verboseLogging: boolean; pythonPath: string }; diff --git a/uitests/src/testRunner.ts b/uitests/src/testRunner.ts new file mode 100644 index 000000000000..d1d0b8ea6cef --- /dev/null +++ b/uitests/src/testRunner.ts @@ -0,0 +1,245 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { spawn } from 'child_process'; +import * as fs from 'fs-extra'; +import * as path from 'path'; +import { uitestsRootPath } from './constants'; +import { noop } from './helpers'; +import { debug } from './helpers/logger'; +import { addReportMetadata, generateHtmlReport, generateJUnitReport } from './helpers/report'; +import { getTestOptions } from './setup'; +import { WorldParameters } from './steps/types'; +import { Channel, ITestOptions } from './types'; + +// tslint:disable-next-line: no-var-requires no-require-imports +const Cli = require('cucumber/lib/cli'); + +const retryCount = 3; +// If we have a large number of tests, and a large nuber of them failed. +// Then lets not retry, fix the flaky tests. +// Lets allow only 25% failure rate to allow retries. +const failureThresholdToRetry = 0.25; +// If total number of tests is <= 10, then retry, no matter how many failed. +// I.e. even if all 10 failed, try again (we're assuming these are small tests) +const maxTestsToRetryAll = 10; + +export async function initialize(options: ITestOptions) { + // Delete all old test related stuff. + debug('Deleting old test data'); + await Promise.all([ + fs.remove(options.tempPath).catch(noop), + fs.remove(options.reportsPath).catch(noop), + fs.remove(options.logsPath).catch(noop), + fs.remove(options.screenshotsPath).catch(noop), + fs.remove(options.userDataPath).catch(noop) + ]); +} + +type Scenario = { + id: string; + name: string; + tags: [{ name: string; line: number }]; + line: number; + steps: [{ result: { status: 'passed' | 'other' } }]; +}; +type CucumberReport = [{ id: string; elements: [Scenario] }]; +type CucumberReportStats = { total: number; failed: number }; + +type CucumberResults = { + success: boolean; + jsonReportFile: string; + rerunFile: string; + stats?: CucumberReportStats; +}; + +async function parseCucumberJson(jsonFile: string): Promise { + return JSON.parse(await fs.readFile(jsonFile, 'utf8')); +} +function hasScenarioPassed(scenario: Scenario): boolean { + return scenario.steps.every(step => step.result.status === 'passed'); +} +async function findScenario(report: CucumberReport, featureId: string, scenarioId: string, line: number): Promise { + for (const feature of report) { + if (feature.id !== featureId) { + continue; + } + const found = feature.elements.find(scenario => scenario.id === scenarioId && scenario.line === line); + if (found) { + return found; + } + } + throw new Error(`Feature & Scenario not found. FeatureId = ${featureId}, ScenarioId = ${scenarioId}, line = ${line}`); +} + +async function getCucumberResultStats(json: CucumberReport): Promise { + const report = { total: 0, failed: 0 }; + json.forEach(feature => + feature.elements.forEach(scenario => { + report.total += 1; + report.failed += hasScenarioPassed(scenario) ? 0 : 1; + }) + ); + return report; +} +async function shouldRerunTests(results: CucumberResults): Promise { + if (results.success || !results.stats) { + // tslint:disable-next-line: no-console + console.info('Not retrying, as there are no stats'); + return false; + } + + // If we have just <= 10 tests, then always retry. + if (results.stats.total <= maxTestsToRetryAll) { + return true; + } + + if (results.stats.failed / results.stats.total < failureThresholdToRetry) { + return true; + } + // tslint:disable-next-line: no-console + console.info(`Not retrying, as there are too many failures ${results.stats.failed}/${results.stats.total}`); + return false; +} + +export async function start(channel: Channel, testDir: string, verboseLogging: boolean, pythonPath: string, cucumberArgs: string[]) { + const options = getTestOptions(channel, testDir, pythonPath, verboseLogging); + await initialize(options); + await fs.ensureDir(options.reportsPath); + + const worldParameters: WorldParameters = { channel, testDir, verboseLogging, pythonPath }; + const results = await runCucumber(cucumberArgs, worldParameters); + const rerunFilesToDelete = [results.rerunFile]; + let rerunFule = results.rerunFile; + let success = results.success; + + // if failed, then re-run the failed tests. + if (await shouldRerunTests(results)) { + try { + for (let retry = 1; retry <= retryCount; retry += 1) { + // tslint:disable-next-line: no-console + console.info(`Rerunning tests (retry #${retry})`); + const cucumberArgsWithoutTags = cucumberArgs.filter(arg => !arg.startsWith('--tags')); + const rerunResults = await runCucumber(cucumberArgsWithoutTags, worldParameters, rerunFule); + rerunFilesToDelete.push(rerunResults.rerunFile); + // if some tests passed in the re-run, then update those tests in the original report as having succeeded. + const rerurnJson = await parseCucumberJson(rerunResults.jsonReportFile); + const originalJson = await parseCucumberJson(results.jsonReportFile); + let originalJsonUpdated = false; + for (const feature of rerurnJson) { + for (const scenario of feature.elements) { + if (hasScenarioPassed(scenario)) { + const originalScenario = await findScenario(originalJson, feature.id, scenario.id, scenario.line); + Object.keys(scenario).forEach(key => { + // tslint:disable-next-line: no-any + (originalScenario as any)[key] = (scenario as any)[key]; + }); + // Keep track of the fact that this was successful only after a retry. + originalScenario.name += ` (Retry ${retry})`; + originalScenario.tags.push({ name: `retry${retry}`, line: 0 }); + originalJsonUpdated = true; + } + } + } + + if (originalJsonUpdated) { + await fs.writeFile(results.jsonReportFile, JSON.stringify(originalJson)); + } + if (rerunResults.success) { + success = true; + break; + } + // Set rerun file for next retry. + rerunFule = rerunResults.rerunFile; + + // If only 10 failed, we can retry, event if this is the same 10 that we retried. + // Its small enough to retry again. + if (rerunResults.stats && rerunResults.stats.failed <= maxTestsToRetryAll) { + continue; + } + // If all tests that were supposed to be rerun failed, then don't bother trying again. + if (!results.stats || !rerunResults.stats || rerunResults.stats.failed === results.stats.failed) { + break; + } + continue; + } + } catch (ex) { + // tslint:disable-next-line: no-console + console.error('Rerun failed', ex); + } + } + + // Delete all the rerun files that were created in root directory. + await Promise.all(rerunFilesToDelete.map(item => fs.unlink(item).catch(noop))); + + // Generate necessary reports. + const jsonReportFilePath = results.jsonReportFile; + await addReportMetadata(options, jsonReportFilePath); + await Promise.all([generateHtmlReport(options, jsonReportFilePath), generateJUnitReport(options, jsonReportFilePath)]); + + // Bye bye. + if (!success) { + throw new Error('Error in running UI Tests'); + } +} + +async function runCucumber(cucumberArgs: string[], worldParameters: WorldParameters, rerunFile?: string): Promise { + const jsonReportFile = path.join(uitestsRootPath, '.vscode test', 'reports', `cucumber_report_${new Date().getTime()}.json`); + const newRerunFile = `@rerun${new Date().getTime()}.txt`; + const args: string[] = [ + '', // Leave empty (not used by cucmberjs) + '', // Leave empty (not used by cucmberjs) + // If we have a rerun file, use that else run all features in `features` folder. + rerunFile || 'features', + '--require-module', + 'source-map-support/register', + '-r', + 'out/steps/**/*.js', + '--format', + 'node_modules/cucumber-pretty', + '--format', + `rerun:${newRerunFile}`, + '--format', + `json:${jsonReportFile}`, + '--world-parameters', + JSON.stringify(worldParameters), + ...cucumberArgs + ]; + + let success = true; + if (rerunFile) { + // When this is a rerun, then spanw a node process to run cucumber. + // We cannot re-run cucumber in the same process more than once. + // It seems to maintain some global state. + // Cucumberjs isn't designed to be run more than once in same process, its meant to be run via its cli. + // const proc = spawn(process.execPath, ['./node_modules/.bin/cucumber-js', ...args.slice(2)], { + const cucumberjsPath = path.join(uitestsRootPath, 'node_modules', 'cucumber', 'bin', 'cucumber-js'); + const proc = spawn(process.execPath, [cucumberjsPath, ...args.slice(2)], { + cwd: uitestsRootPath + }); + proc.stdout.pipe(process.stdout); + proc.stderr.pipe(process.stderr); + const exitCode = await new Promise(resolve => proc.once('exit', resolve)); + success = exitCode === 0; + } else { + // This is a hack, reunnin cucumberjs like this, but allows us to debug it and have our own code run as part of the test initialization. + const cli = new Cli.default({ argv: args, cwd: uitestsRootPath, stdout: process.stdout }); + // tslint:disable-next-line: no-console + const result = await cli.run().catch(console.error); + success = result.success; + } + + let stats; + if (fs.pathExists(jsonReportFile)) { + const json = await parseCucumberJson(jsonReportFile); + stats = await getCucumberResultStats(json); + } + return { + success, + jsonReportFile, + rerunFile: newRerunFile, + stats + }; +} diff --git a/uitests/src/types.ts b/uitests/src/types.ts new file mode 100644 index 000000000000..0c3432495ca4 --- /dev/null +++ b/uitests/src/types.ts @@ -0,0 +1,699 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { ClickOptions, ElementHandle, UnwrapElementHandle, WrapElementHandle } from 'puppeteer-core'; +import { localizationKeys as localizationKey } from './constants'; +import { Selector } from './selectors'; + +// tslint:disable: no-any + +export type Channel = 'insider' | 'stable'; + +/** + * Similar to ConfigurationTarget found in VS Code API. + * + * @export + * @enum {number} + */ +export enum ConfigurationTarget { + Global = 1, + Workspace = 2, + WorkspaceFolder = 3 +} + +export interface ITestOptions { + /** + * VS Code channel to be used for testing. + * + * @type {Channel} + * @memberof ITestOptions + */ + readonly channel: Channel; + /** + * Root directory for the UI Tests (typically `.vscode test`). + * + * @type {string} + * @memberof ITestOptions + */ + readonly testPath: string; + /** + * Path where VSC extensions are located. + * (typically an `extensions` folder). + * + * @type {string} + * @memberof ITestOptions + */ + readonly extensionsPath: string; + /** + * Path where VS Code stores user data. + * + * @type {string} + * @memberof ITestOptions + */ + readonly userDataPath: string; + readonly userSettingsFilePath: string; + /** + * Directory where screenshots are located. + * This path changes based on the scenario being tested. + * Basically each test scenario has its own screenshots directory. + * + * @type {string} + * @memberof ITestOptions + */ + readonly screenshotsPath: string; + /** + * Path to temporary directory. + * + * @type {string} + * @memberof ITestOptions + */ + readonly tempPath: string; + /** + * Directory where reports are located. + * This path changes based on the scenario being tested. + * Basically each test scenario has its own reports directory. + * + * @type {string} + * @memberof ITestOptions + */ + readonly reportsPath: string; + /** + * Directory where logs are located. + * This path changes based on the scenario being tested. + * Basically each test scenario has its own logs directory. + * + * @type {string} + * @memberof ITestOptions + */ + readonly logsPath: string; + // readonly originalWorkspacePathOrFolder: string; + /** + * Directory for VS Code workspace or the workspace file path (not yet implemented). + * This path changes based on the scenario being tested. + * Basically each test scenario has its own workspace directory. + * + * @type {string} + * @memberof ITestOptions + */ + readonly workspacePathOrFolder: string; + /** + * Whether to use verbose logging or not. + * + * @type {boolean} + * @memberof ITestOptions + */ + readonly verbose: boolean; + /** + * Path to python executable that's used by the extension. + * + * @type {string} + * @memberof ITestOptions + */ + readonly pythonPath: string; +} + +export type Timeoutable = { + /** + * Maximum navigation time in milliseconds, pass 0 to disable timeout. + * @default 30000 + */ + timeout?: number; +}; +export type WaitForSelectorOptions = Timeoutable & { + /** + * Wait for element to be present in DOM and to be visible, + * i.e. to not have display: none or visibility: hidden CSS properties. + * @default false + */ + visible: boolean; +}; +export type WaitForSelectorOptionsHidden = Timeoutable & { + /** + * Wait for element to not be found in the DOM or to be hidden, + * i.e. have display: none or visibility: hidden CSS properties. + * @default false + */ + hidden: boolean; +}; + +export type SelectorRetryOptions = + | { + /** + * Time in milli seconds to keep retrying until an element(s) is found. + * + * @type {number} + */ + retryTimeout: number; + /** + * Error message to be displayed as part of error raied when there's a timeout. + * + * @type {string} + */ + errorMessage?: string; + /** + * If true, then do not log failures. + * Defaults to true. + * + * @type {boolean} + */ + logFailures?: boolean; + } + | { + /** + * Max number of times to retry. + * + * @type {number} + */ + retryCount: number; + /** + * Error message to be displayed as part of error raied when there's a timeout. + * + * @type {string} + */ + errorMessage?: string; + /** + * If true, then do not log failures. + * Defaults to true. + * + * @type {boolean} + */ + logFailures?: boolean; + }; +export type ElementsSelectorPredicate = (elements: ElementHandle[]) => ElementHandle[]; +export interface IDriver { + /** + * Wait for the selector to appear in page. + * If at the moment of calling the method the selector already exists, the method will return immediately. + * If the selector doesn't appear after the timeout milliseconds of waiting, the function will throw. + * + * options.visible wait for element to be present in DOM and to be visible, i.e. to not have display: none or visibility: hidden CSS properties. Defaults to false. + * options.timeout maximum time to wait for in milliseconds. + * Defaults to 30000 (30 seconds). + * Pass 0 to disable timeout. + * The default value can be changed by using the page.setDefaultTimeout(timeout) method. + * + * @param {string} selector A selector of an element to wait for + * @param {WaitForSelectorOptions} [options] Optional waiting parameters + * @returns {Promise} Promise which resolves when element specified by selector string is added to DOM. Resolves to null if waiting for hidden: true and selector is not found in DOM. + * @memberof IDriver + */ + waitForSelector(selector: string, options?: WaitForSelectorOptions): Promise; + /** + * Wait for the selector to appear in page. + * If at the moment of calling the method the selector already exists, the method will return immediately. + * If the selector doesn't appear after the timeout milliseconds of waiting, the function will throw. + * + * options.hidden wait for element to not be found in the DOM or to be hidden, i.e. have display: none or visibility: hidden CSS properties. Defaults to false. + * options.timeout maximum time to wait for in milliseconds. + * Defaults to 30000 (30 seconds). + * Pass 0 to disable timeout. + * The default value can be changed by using the page.setDefaultTimeout(timeout) method. + * + * @param {string} selector A selector of an element to wait for + * @param {WaitForSelectorOptions} [options] Optional waiting parameters + * @returns {Promise} Promise which resolves when element specified by selector string is added to DOM. Resolves to null if waiting for hidden: true and selector is not found in DOM. + * @memberof IDriver + */ + waitForSelector(selector: string, options?: WaitForSelectorOptionsHidden): Promise; + /** + * The method queries frame for the selector. + * If there's no such element within the frame, the method will throw an error. + * + * Use {retryTimeout} to keep retrying until timeout or element is available. + * + * @param {string} selector + * @param {SelectorRetryOptions} [options] + * @returns {(Promise)} + * @memberof IDriver + */ + $(selector: string, options?: SelectorRetryOptions): Promise; + + /** + * The method runs document.querySelectorAll within the frame. + * If no elements match the selector, the return value resolve to []. + * + * Use {retryTimeout} to keep retrying until timeout or at least one element is available. + * (optionally use the predicate to filter out elements). + * + * @param {string} selector + * @returns {Promise} + * @memberof IDriver + */ + $$(selector: string, options?: SelectorRetryOptions & { predicate?: ElementsSelectorPredicate }): Promise; + /** + * This method runs `document.querySelector` within the context and passes it as the first argument to `pageFunction`. + * If there's no element matching `selector`, the method throws an error. + * + * If `pageFunction` returns a Promise, then `$eval` would wait for the promise to resolve and return its value. + * + * @param selector A selector to query for + * @param pageFunction Function to be evaluated in browser context + * @returns Promise which resolves to the return value of pageFunction + */ + $eval(selector: string, pageFunction: (element: Element) => R | Promise): Promise>; + + /** + * This method runs `document.querySelector` within the context and passes it as the first argument to `pageFunction`. + * If there's no element matching `selector`, the method throws an error. + * + * If `pageFunction` returns a Promise, then `$eval` would wait for the promise to resolve and return its value. + * + * @param selector A selector to query for + * @param pageFunction Function to be evaluated in browser context + * @param x1 First argument to pass to pageFunction + * @returns Promise which resolves to the return value of pageFunction + */ + $eval(selector: string, pageFunction: (element: Element, x1: UnwrapElementHandle) => R | Promise, x1: X1): Promise>; + + /** + * This method runs `Array.from(document.querySelectorAll(selector))` within the context and passes it as the + * first argument to `pageFunction`. + * + * If `pageFunction` returns a Promise, then `$$eval` would wait for the promise to resolve and return its value. + * + * @param selector A selector to query for + * @param pageFunction Function to be evaluated in browser context + * @returns Promise which resolves to the return value of pageFunction + */ + $$eval(selector: string, pageFunction: (elements: Element[]) => R | Promise): Promise>; + /** + * This method runs `Array.from(document.querySelectorAll(selector))` within the context and passes it as the + * first argument to `pageFunction`. + * + * If `pageFunction` returns a Promise, then `$$eval` would wait for the promise to resolve and return its value. + * + * @param selector A selector to query for + * @param pageFunction Function to be evaluated in browser context + * @param x1 First argument to pass to pageFunction + * @returns Promise which resolves to the return value of pageFunction + */ + $$eval(selector: string, pageFunction: (elements: Element[], x1: UnwrapElementHandle) => R | Promise, x1: X1): Promise>; + + /** + * This method fetches an element with selector, scrolls it into view if needed, and + * then uses `page.mouse` to click in the center of the element. If there's no element + * matching selector, the method throws an error. + * + * @param {string} selector + * @param {(ClickOptions & SelectorRetryOptions)} [options] + * @returns {Promise} + * @memberof IDriver + */ + click(selector: string, options?: ClickOptions & SelectorRetryOptions): Promise; + + /** + * This method fetches an element with selector and focuses it. + * + * @param {string} selector + * @returns {Promise} + * @memberof IDriver + */ + focus(selector: string): Promise; + + /** + * This method fetches an element with `selector`, scrolls it into view if needed, + * and then uses page.mouse to hover over the center of the element. If there's no + * element matching `selector`, the method throws an error. + * @param selector A selector to search for element to hover. If there are multiple elements satisfying the selector, the first will be hovered. + */ + hover(selector: string): Promise; + + /** + * Sends a `keydown`, `keypress/input`, and `keyup` event for each character in the text. + * @param selector A selector of an element to type into. If there are multiple elements satisfying the selector, the first will be used. + * @param text: A text to type into a focused element. + * @param options: The typing parameters. + */ + type(selector: string, text: string, options?: { delay: number }): Promise; + /** + * Press a combination of keys. + * + * @param {string} keys + * @param {{ delay: number }} [options] + * @returns {Promise} + * @memberof IDriver + */ + press(keys: string, options?: { delay: number }): Promise; +} +export interface IApplication { + /** + * Whether VS Code application is alive. + * + * @type {boolean} + * @memberof IApplication + */ + readonly isAlive: boolean; + /** + * VS Code channel. + * + * @type {Channel} + * @memberof IApplication + */ + readonly channel: Channel; + /** + * UI Driver for VS Code. + * + * @type {IDriver} + * @memberof IApplication + */ + readonly driver: IDriver; + /** + * Test Options. + * + * @type {ITestOptions} + * @memberof IApplication + */ + readonly options: ITestOptions; + readonly workspacePathOrFolder: string; + /** + * Path to where VSC extension are located. + * (typically an `extensions` folder). + * + * @type {string} + * @memberof IApplication + */ + readonly extensionsPath: string; + readonly userDataPath: string; + /** + * Path to the user `settings.json` file. + * + * @type {string} + * @memberof IApplication + */ + readonly userSettingsFilePath: string; + readonly quickopen: IQuickOpen; + readonly quickinput: IQuickInput; + readonly documents: IDocuments; + readonly debugger: IDebugger; + readonly statusbar: IStatusBar; + readonly problems: IProblems; + readonly settings: ISettings; + readonly terminal: ITerminal; + readonly notifications: INotifications; + readonly interpreters: IInterpreters; + readonly testExplorer: ITestExplorer; + readonly panels: IPanels; + readonly localization: ILocalization; + /** + * Starts VS Code. + * + * @param {boolean} [emulateFirstTimeLoad] If true, start VS Code as though it was launched for the first time ever. + * @returns {Promise} + * @memberof IApplication + */ + start(emulateFirstTimeLoad?: boolean): Promise; + /** + * Event raised when VS Code starts. + * + * @param {'start'} event + * @param {(emulateFirstTimeLoad: boolean) => void} listener + * @returns {this} + * @memberof IApplication + */ + on(event: 'start', listener: (emulateFirstTimeLoad: boolean) => void): this; + /** + * Event raised when a screenshot has been captured. + * + * @param {'screenshotCatured'} event + * @param {(data:Buffer) => void} listener + * @returns {this} + * @memberof IApplication + */ + on(event: 'screenshotCatured', listener: (data: Buffer) => void): this; + /** + * Reloads VS Code. + * + * @returns {Promise} + * @memberof IApplication + */ + reload(): Promise; + /** + * Exits VS Code. + * + * @returns {Promise} + * @memberof IApplication + */ + exit(): Promise; + /** + * Captures a screenshot with the given name, storing it in the screenshots directory. + * + * @param {string} name + * @returns {Promise} + * @memberof IApplication + */ + captureScreenshot(name: string): Promise; + /** + * Gets the CSS Selector for various parts of the VS Code UI. + * + * @param {Selector} selector + * @returns {string} + * @memberof IApplication + */ + getCSSSelector(selector: Selector): string; +} + +export interface IDisposable { + dispose(): void; +} +/** + * Quick Input dropdown UI. + * + * @export + * @interface IQuickInput + */ +export interface IQuickInput { + /** + * Select a value in the (currently displayed) quick input dropdown and closes the dropdown. + * + * @param {({ value: string } | { index: number })} options + * @returns {Promise} + * @memberof IQuickInput + */ + select(options: { value: string } | { index: number }): Promise; + // close(): Promise; + // waitUntilOpened(retryCount?: number): Promise; + // waitUntilClosed(): Promise; +} +/** + * Quick Open dropdown UI. + * This is the dropdown that's used to select files, select commands. + * + * @export + * @interface IQuickOpen + * @extends {IDisposable} + */ +export interface IQuickOpen extends IDisposable { + openFile(fileName: string): Promise; + runCommand(value: string): Promise; + /** + * Selects an item from the currently displayed Quick Open UI. + * + * @param {string} value + * @returns {Promise} + * @memberof IQuickOpen + */ + select(value: string): Promise; + /** + * Displays the Quick Open UI. + * + * @returns {Promise} + * @memberof IQuickOpen + */ + open(): Promise; + /** + * Closes the Quick Open UI. + * + * @returns {Promise} + * @memberof IQuickOpen + */ + close(): Promise; + waitUntilOpened(retryCount?: number): Promise; + waitUntilClosed(): Promise; + /** + * Event handler for events raised by the Quick Open UI. + * + * @param {'command'} event + * @param {(command: string) => void} listener + * @returns {this} + * @memberof IQuickOpen + */ + on(event: 'command', listener: (command: string) => void): this; +} +export interface IDocuments { + createNewUntitledFile(): Promise; + createNewFile(fileName: string, contents: string): Promise; + waitUntilFileOpened(fileName: string): Promise; + isExplorerViewOpen(): Promise; + waitUntilExplorerViewOpened(): Promise; + refreshExplorer(): Promise; + gotToPosition(options: { line: number } | { column?: number } | { line: number; column: number }): Promise; + waitForPosition(options: { line: number }): Promise; + getCurrentPosition(): Promise<{ line: number; column: number }>; + getAutoCompletionList(): Promise; + /** + * Waits until a file editor has focus. + * + * @param {string} fileName + * @returns {Promise} + * @memberof IDocuments + */ + waitForEditorFocus(fileName: string): Promise; + /** + * Waits until a file is the active editor. + * + * @param {string} filename + * @returns {Promise} + * @memberof IDocuments + */ + waitForActiveEditor(filename: string): Promise; + /** + * Waits until a file is the active file in editor tabs. + * + * @param {string} fileName + * @param {boolean} [isDirty] + * @returns {Promise} + * @memberof IDocuments + */ + waitForActiveTab(fileName: string, isDirty?: boolean): Promise; +} +export interface IDebugger { + isDebugViewOpened(): Promise; + waitUntilViewOpened(): Promise; + waitUntilConsoleOpened(): Promise; + waitForConfigPicker(): Promise; + selectConfiguration(configItem: string): Promise; + waitUntilStarted(): Promise; + waitUntilStopped(timeout?: number): Promise; + waitUntilPaused(): Promise; + setBreakpointOnLine(lineNumber: number): Promise; +} +export interface IStatusBar { + /** + * Gets the statusbar text from the statusbar entry created by the Python Extension. + * This generally contains the display name of the Python Interpreter selected. + * + * @returns {Promise} + * @memberof IStatusBar + */ + getPythonStatusBarText(): Promise; + /** + * Waits until the statubar item created by the Python extension is visible. + * + * @returns {Promise} + * @memberof IStatusBar + */ + waitUntilPythonItemVisible(): Promise; + /** + * Waits until the statubar item created by the Bootstrap extension is visible. + * + * @returns {Promise} + * @memberof IStatusBar + */ + waitUntilBootstrapItemVisible(): Promise; +} +export type ProblemSeverity = 'error' | 'warning'; +export interface IProblems { + /** + * Gets the number of problems in the problems panel. + * + * @returns {Promise} + * @memberof IProblems + */ + getProblemCount(): Promise; + waitUntilOpened(): Promise; + /** + * Gets the list of file names that have problem entries in the problems panel. + * + * @returns {Promise} + * @memberof IProblems + */ + getProblemFiles(): Promise; + /** + * Gets the list of problem messages in the problems panel. + * + * @returns {Promise} + * @memberof IProblems + */ + getProblemMessages(): Promise; +} +export interface ISettings { + removeSetting(setting: string, scope: ConfigurationTarget): Promise; + updateSetting(setting: string, value: string | boolean | number | void, scope: ConfigurationTarget): Promise; + getSetting(setting: string, scope: ConfigurationTarget): Promise; +} +export interface ITerminal { + waitUntilOpened(): Promise; + runCommand(command: string): Promise; +} +export interface INotifications { + hasMessages(type?: 'error'): Promise; + getMessages(): Promise; + dismiss(messages: { content: string; buttonText?: string }[], timeout: number): Promise; +} +export interface IInterpreters { + select(options: { name: string } | { tooltip: string }): Promise; +} + +export type TestExplorerToolbarIcon = 'Stop' | 'RunFailedTests'; +export type TestingAction = 'run' | 'debug' | 'open'; +export type TestExplorerNodeStatus = 'Unknown' | 'Success' | 'Progress' | 'Skip' | 'Ok' | 'Pass' | 'Fail' | 'Error'; +export interface ITestExplorer { + isOpened(): Promise; + isIconVisible(): Promise; + waitUntilOpened(timeout?: number): Promise; + waitUntilIconVisible(timeout?: number): Promise; + waitUntilTestsStop(timeout: number): Promise; + expandNodes(maxNodes?: number): Promise; + getNodeCount(maxNodes?: number): Promise; + /** + * Selects a node in the test explorer tree view, but doesn't click it. + * I.e. ensure the node has focus. + * + * @param {string} label + * @returns {Promise} + * @memberof ITestExplorer + */ + selectNode(label: string): Promise; + clickNode(label: string): Promise; + waitUntilToolbarIconVisible(icon: TestExplorerToolbarIcon, timeout?: number): Promise; + waitUntilToolbarIconHidden(icon: TestExplorerToolbarIcon, timeout?: number): Promise; + clickToolbarIcon(icon: TestExplorerToolbarIcon): Promise; + getNodes(): Promise<{ label: string; index: number; status: TestExplorerNodeStatus }[]>; + getNode(label: string): Promise<{ label: string; index: number; status: TestExplorerNodeStatus }>; + /** + * Test explorer treeview nodes have icons associated with them. These can be used to perfom some actions. + * Use this method to perform an action against a specific node. + * + * @param {string} label + * @param {TestingAction} action + * @returns {Promise} + * @memberof ITestExplorer + */ + selectActionForNode(label: string, action: TestingAction): Promise; +} +export interface IPanels { + maximize(): Promise; + minimize(): Promise; + /** + * Wait until the content is displayed in the output panel. + * (will ensure the output panel is maximized before checking - in case the content scrolls.) + * + * @param {string} text + * @param {number} timeout Defaults to 10ms. + * @returns {Promise} + * @memberof IPanels + */ + waitUtilContent(text: string, timeout?: number): Promise; +} +export interface ILocalization { + /** + * Gets a localized value given the key (from Python Extension). + * + * @param {localizationKey} key + * @returns {string} + * @memberof ILocalization + */ + get(key: localizationKey): string; +} diff --git a/uitests/src/typings/extensions.d.ts b/uitests/src/typings/extensions.d.ts new file mode 100644 index 000000000000..a1cdf01a43be --- /dev/null +++ b/uitests/src/typings/extensions.d.ts @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +/** + * @typedef {Object} SplitLinesOptions + * @property {boolean} [trim=true] - Whether to trim the lines. + * @property {boolean} [removeEmptyEntries=true] - Whether to remove empty entries. + */ + +// https://stackoverflow.com/questions/39877156/how-to-extend-string-prototype-and-use-it-next-in-typescript +// tslint:disable-next-line:interface-name +declare interface String { + /** + * Replaces characters such as 160 with 32. + * When we get string content of html elements, we get char code 160 instead of 32. + */ + normalize(): string; + /** + * String.format() implementation. + * Tokens such as {0}, {1} will be replaced with corresponding positional arguments. + */ + format(...args: string[]): string; +} diff --git a/uitests/src/vscode/application.ts b/uitests/src/vscode/application.ts new file mode 100644 index 000000000000..b2630ce1b1e6 --- /dev/null +++ b/uitests/src/vscode/application.ts @@ -0,0 +1,168 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { EventEmitter } from 'events'; +import * as path from 'path'; +import * as rimraf from 'rimraf'; +import { getSelector, Selector } from '../selectors'; +import { restoreDefaultUserSettings } from '../setup'; +import { Driver } from '../setup/driver'; +import { + Channel, + IApplication, + IDebugger, + IDocuments, + IDriver, + IInterpreters, + ILocalization, + INotifications, + IPanels, + IProblems, + IQuickInput, + IQuickOpen, + ISettings, + IStatusBar, + ITerminal, + ITestExplorer, + ITestOptions +} from '../types'; +import { Debugger } from './debugger'; +import { Documents } from './documents'; +import { Interpreters } from './interpreters'; +import { Localization } from './localization'; +import { Notifications } from './notifications'; +import { Panels } from './panels'; +import { Problems } from './problems'; +import { QuickInput } from './quickInput'; +import { QuickOpen } from './quickOpen'; +import { Settings } from './settings'; +import { StatusBar } from './statusbar'; +import { TestExplorer } from './testExplorer'; + +export class Application extends EventEmitter implements IApplication { + public readonly quickopen: IQuickOpen; + public readonly quickinput!: IQuickInput; + public readonly documents!: IDocuments; + public readonly debugger!: IDebugger; + public readonly statusbar!: IStatusBar; + public readonly problems!: IProblems; + public readonly settings!: ISettings; + public readonly terminal!: ITerminal; + public readonly notifications!: INotifications; + public readonly interpreters!: IInterpreters; + public readonly testExplorer!: ITestExplorer; + public readonly panels!: IPanels; + public readonly localization!: ILocalization; + get isAlive(): boolean { + return this._driver.isAlive; + } + get channel(): Channel { + return this.options.channel; + } + get driver(): IDriver { + return this._driver; + } + get workspacePathOrFolder(): string { + return this.options.workspacePathOrFolder; + } + get extensionsPath(): string { + return this.options.extensionsPath; + } + get userDataPath(): string { + return this.options.userDataPath; + } + get userSettingsFilePath(): string { + return this.options.userSettingsFilePath; + } + private readonly _driver: Driver; + private readonly screenshotCounter = new Map(); + constructor(public readonly options: ITestOptions) { + super(); + this._driver = new Driver(options); + this.quickopen = new QuickOpen(this); + this.quickinput = new QuickInput(this); + this.interpreters = new Interpreters(this); + this.documents = new Documents(this); + this.notifications = new Notifications(this); + this.debugger = new Debugger(this); + this.testExplorer = new TestExplorer(this); + this.statusbar = new StatusBar(this); + this.settings = new Settings(this); + this.panels = new Panels(this); + this.problems = new Problems(this); + this.localization = new Localization(this); + this.registerPostCommandHandlers(); + } + public async start(emulateFirstTimeLoad: boolean = false): Promise { + if (emulateFirstTimeLoad) { + // Also delete the downloaded language server. + await Promise.all([ + new Promise(resolve => rimraf(this.options.userDataPath, resolve)), + new Promise(resolve => rimraf(path.join(this.options.extensionsPath, '**', 'languageServer.*'), resolve)) + ]); + + // These must never change (we control the test environment). + // For now we hard coded the python interpreter used by the extension as well (by setting in user settings). + await restoreDefaultUserSettings(this.options); + } + await this._driver.start(); + this.emit('start', emulateFirstTimeLoad); + } + public async exit(): Promise { + await this._driver.exit(); + } + public async reload(): Promise { + await this.exit(); + await this.start(); + } + public dispose() { + this.quickopen.dispose(); + } + public getCSSSelector(selector: Selector): string { + return getSelector(selector, this.options.channel); + } + public async captureScreenshot(name: string = 'screenshot'): Promise { + // Ensure we don't have any special characters in the file name. + name = name.replace(/[^a-z0-9\-]/gi, '_'); + if (!this.screenshotCounter.has(this.options.screenshotsPath)) { + this.screenshotCounter.set(this.options.screenshotsPath, 0); + } + const previousCounter = this.screenshotCounter.get(this.options.screenshotsPath)!; + this.screenshotCounter.set(this.options.screenshotsPath, previousCounter + 1); + + name = `${previousCounter.toString().padStart(3, '0')}.${name}_${new Date() + .toISOString() + // Ensure we don't have any special characters in the file name. + .replace(/[^a-z0-9\-]/gi, '_')}.png`; + const buffer = await this._driver.captureScreenshot(path.join(this.options.screenshotsPath, name)); + // If we have a hook registered for screenshots, then let them know abou the screenshot captured. + this.emit('screenshotCatured', buffer); + } + /** + * When selecting commands such as `View Show Test`, the intent is for the test explorer to be displayed. + * However, this can take a few milliseconds, hence we need to ensure the code waits until it is displayed. + * Solution - When a command is selected, lets wait immeidately after the command is selected. + * This is where we'll add such delays (not in the quick open class, as thats not the concern of quick open). + * + * @private + * @memberof Application + */ + private registerPostCommandHandlers() { + const postCommandHandlers: Record Promise> = { + 'View: Show Test': this.testExplorer.waitUntilOpened.bind(this.testExplorer), + 'View: Show Explorer': this.documents.waitUntilExplorerViewOpened.bind(this.documents), + // 'Debug: Start Debugging': this.debugger.waitUntilStarted.bind(this.debugger), + 'Debug: Stop Debugging': this.debugger.waitUntilStopped.bind(this.debugger), + // 'Open New Terminal': this.terminal.waitUntilOpened.bind(this.terminal), + // 'Python: Create Terminal': this.terminal.waitUntilOpened.bind(this.terminal), + 'View: Focus Problems (Errors, Warnings, Infos)': this.problems.waitUntilOpened.bind(this.problems) + }; + this.quickopen.on('command', async command => { + if (postCommandHandlers[command]) { + await postCommandHandlers[command](); + } + }); + } +} diff --git a/uitests/src/vscode/debugger.ts b/uitests/src/vscode/debugger.ts new file mode 100644 index 000000000000..accf115b2aaf --- /dev/null +++ b/uitests/src/vscode/debugger.ts @@ -0,0 +1,64 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { ok } from 'assert'; +import { RetryMax10Seconds, RetryMax5Seconds } from '../constants'; +import { retry } from '../helpers'; +import { Selector } from '../selectors'; +import { IApplication, IDebugger } from '../types'; + +export class Debugger implements IDebugger { + constructor(private readonly app: IApplication) {} + public async isDebugViewOpened(): Promise { + return this.app.driver + .$(this.app.getCSSSelector(Selector.DebugActivityBar)) + .then(ele => !!ele) + .catch(() => false); + } + public async waitUntilViewOpened(): Promise { + await this.app.driver.waitForSelector(this.app.getCSSSelector(Selector.DebugActivityBar)); + } + public waitUntilConsoleOpened(): Promise { + throw new Error('Method not implemented.'); + } + @retry(RetryMax5Seconds) + public async waitForConfigPicker(): Promise { + const selector = this.app.getCSSSelector(Selector.DebugConfigurationPickerDropDownInput); + const text = this.app.localization.get('debug.selectConfigurationTitle'); + const found = await this.app.driver.$$eval( + selector, + (elements, textToSearch) => { + return elements.find(element => (element.textContent || '').includes(textToSearch)) !== undefined; + }, + text + ); + ok(found); + } + public async selectConfiguration(value: string): Promise { + await this.app.quickinput.select({ value }); + } + @retry(RetryMax10Seconds) + public async waitUntilStarted(): Promise { + await this.app.driver.waitForSelector(this.app.getCSSSelector(Selector.DebugToolbar)); + } + public async waitUntilStopped(timeout: number = 10_000): Promise { + await this.app.driver.waitForSelector(this.app.getCSSSelector(Selector.DebugToolbar), { + timeout, + hidden: true + }); + } + + @retry(RetryMax10Seconds) + public async waitUntilPaused(): Promise { + const iconSelector = this.app.getCSSSelector(Selector.DebugToolbarIcon); + const predicateToFindTitleWithContinue = (elements: Element[]) => elements.find(element => (element.getAttribute('title') || '').includes('Continue')) !== undefined; + const found = await this.app.driver.$$eval(iconSelector, predicateToFindTitleWithContinue); + ok(found); + } + public async setBreakpointOnLine(line: number): Promise { + await this.app.documents.gotToPosition({ line }); + await this.app.quickopen.runCommand('Debug: Toggle Breakpoint'); + } +} diff --git a/uitests/src/vscode/documents.ts b/uitests/src/vscode/documents.ts new file mode 100644 index 000000000000..aeaf664e81e6 --- /dev/null +++ b/uitests/src/vscode/documents.ts @@ -0,0 +1,130 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +// tslint:disable: no-var-requires no-require-imports + +import * as assert from 'assert'; +import { RetryMax10Seconds, RetryMax2Seconds, RetryMax5Seconds } from '../constants'; +import { retry, sleep } from '../helpers'; +import '../helpers/extensions'; +import { Selector } from '../selectors'; +import { IApplication, IDocuments } from '../types'; + +const namedRegexp = require('named-js-regexp'); + +// Reg Ex to get line and column number from Bootstrap Statusbar. +// Values are `12,23` +const pyBootstrapLineColumnRegEx = namedRegexp('(?\\d+),(?\\d+)'); + +export class Documents implements IDocuments { + constructor(private readonly app: IApplication) {} + public async createNewUntitledFile(): Promise { + await this.app.quickopen.runCommand('File: New Untitled File'); + await this.waitForEditorFocus('Untitled-1'); + } + public createNewFile(_fileName: string, _contents: string): Promise { + throw new Error('Method not implemented.'); + } + @retry(RetryMax5Seconds) + public async waitUntilFileOpened(fileName: string): Promise { + await this.waitForEditorFocus(fileName); + } + public isExplorerViewOpen(): Promise { + return this.app.driver + .$(this.app.getCSSSelector(Selector.ExplorerActivityBar)) + .then(() => true) + .catch(() => false); + } + @retry(RetryMax5Seconds) + public async waitUntilExplorerViewOpened(): Promise { + await this.app.driver.$(this.app.getCSSSelector(Selector.ExplorerActivityBar)); + } + public async refreshExplorer(): Promise { + // Check what explorer is currently visible + let commandToRunAfterRefreshingExplorer: string | undefined; + if (await this.app.debugger.isDebugViewOpened()) { + commandToRunAfterRefreshingExplorer = 'View: Show Debug'; + } else if (!commandToRunAfterRefreshingExplorer && (await this.app.testExplorer.isOpened())) { + commandToRunAfterRefreshingExplorer = 'View: Show Test'; + } + + // Refresh the explorer, its possible a new file was created, we need to ensure + // VSC is aware of this.Else opening files in vsc fails. + // Note: This will cause explorer to be displayed. + await this.app.quickopen.runCommand('File: Refresh Explorer'); + + // Wait for explorer to get refreshed. + await sleep(500); + await this.app.quickopen.runCommand('File: Focus on Files Explorer'); + await sleep(500); + await this.app.quickopen.runCommand('File: Refresh Explorer'); + await sleep(500); + + if (commandToRunAfterRefreshingExplorer) { + await this.app.quickopen.runCommand(commandToRunAfterRefreshingExplorer); + } + } + public async gotToPosition(options: { line: number } | { column: number } | { line: number; column: number }): Promise { + if ('line' in options) { + await this.goToLine(options.line); + } + if ('column' in options) { + await this.gotToColumn(options.column); + } + } + public async waitForPosition(options: { line: number }): Promise { + if ('line' in options) { + await this.waitForLine(options.line); + } + } + public getCurrentPosition(): Promise<{ line: number; column: number }> { + return this.getCurrentPositionFromPyBootstrapStatusBar(); + } + public getAutoCompletionList(): Promise { + const selector = this.app.getCSSSelector(Selector.AutoCompletionListItem); + return this.app.driver.$$eval(selector, elements => elements.map(element => element.textContent || '')).then(items => items.map(item => item.normalize())); + } + public async waitForEditorFocus(fileName: string): Promise { + await this.waitForActiveTab(fileName); + await this.waitForActiveEditor(fileName); + } + public async waitForActiveEditor(filename: string): Promise { + const selector = `.editor-instance .monaco-editor[data-uri$="${escape(filename)}"] textarea`; + await this.app.driver.waitForSelector(selector, { timeout: 5000, visible: true }); + } + public async waitForActiveTab(fileName: string, isDirty: boolean = false): Promise { + const selector = `.tabs-container div.tab.active${isDirty ? '.dirty' : ''}[aria-selected="true"][aria-label="${fileName}, tab"]`; + await this.app.driver.waitForSelector(selector, { timeout: 5000, visible: true }); + } + private async gotToColumn(columnNumber: number): Promise { + for (let i = 0; i <= columnNumber; i += 1) { + const { column } = await this.getCurrentPosition(); + if (column === columnNumber) { + return; + } + await this.app.driver.press('ArrowRight'); + // Wait for UI to respond to the key press. + await sleep(100); + } + assert.fail(`Failed to set cursor to column ${columnNumber}`); + } + @retry(RetryMax10Seconds) + private async goToLine(line: number): Promise { + await this.app.quickopen.open(); + await this.app.quickopen.select(line.toString()); + await this.waitForLine(line); + } + @retry(RetryMax2Seconds) + private async waitForLine(lineNumber: number): Promise { + const { line } = await this.getCurrentPositionFromPyBootstrapStatusBar(); + assert.equal(line, lineNumber, `Line number ${line} not same as expected ${lineNumber}.`); + } + private async getCurrentPositionFromPyBootstrapStatusBar(): Promise<{ line: number; column: number }> { + const lineColumnSelector = this.app.getCSSSelector(Selector.CurrentEditorLineColumnStatusBar); + const textContent = await this.app.driver.$eval(lineColumnSelector, ele => ele.textContent || ''); + const groups = pyBootstrapLineColumnRegEx.execGroups(textContent.normalize()); + return { line: parseInt(groups.line, 10), column: parseInt(groups.col, 10) }; + } +} diff --git a/uitests/src/vscode/index.ts b/uitests/src/vscode/index.ts new file mode 100644 index 000000000000..1fbb81e4420c --- /dev/null +++ b/uitests/src/vscode/index.ts @@ -0,0 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +export * from './application'; diff --git a/uitests/src/vscode/interpreters.ts b/uitests/src/vscode/interpreters.ts new file mode 100644 index 000000000000..f42142b1afdc --- /dev/null +++ b/uitests/src/vscode/interpreters.ts @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { sleep } from '../helpers'; +import '../helpers/extensions'; +import { IApplication, IInterpreters } from '../types'; + +export class Interpreters implements IInterpreters { + constructor(private readonly app: IApplication) {} + public async select(options: { name: string } | { tooltip: string }): Promise { + await this.app.quickopen.runCommand('Python: Select Interpreter'); + await this.app.quickinput.select({ value: 'name' in options ? options.name : options.tooltip }); + // Wait for 1s for ui to get updated. + await sleep(1000); + } +} diff --git a/uitests/src/vscode/localization.ts b/uitests/src/vscode/localization.ts new file mode 100644 index 000000000000..2c94ea34cf04 --- /dev/null +++ b/uitests/src/vscode/localization.ts @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import * as fs from 'fs'; +import * as path from 'path'; +import { localizationKeys } from '../constants'; +import { IApplication, ILocalization } from '../types'; + +export class Localization implements ILocalization { + private localizedStrings?: Record; + constructor(private readonly app: IApplication) {} + public get(key: localizationKeys): string { + this.initialize(); + return this.localizedStrings![key]; + } + private initialize() { + if (this.localizedStrings) { + return; + } + const localizedJsonFile = path.join(this.app.options.extensionsPath, 'ms-python.python', 'package.nls.json'); + this.localizedStrings = JSON.parse(fs.readFileSync(localizedJsonFile).toString()); + } +} diff --git a/uitests/src/vscode/notifications.ts b/uitests/src/vscode/notifications.ts new file mode 100644 index 000000000000..9dd91a5135e8 --- /dev/null +++ b/uitests/src/vscode/notifications.ts @@ -0,0 +1,91 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { sleep, StopWatch } from '../helpers'; +import '../helpers/extensions'; +import { debug, warn } from '../helpers/logger'; +import { Selector } from '../selectors'; +import { IApplication, INotifications } from '../types'; + +export class Notifications implements INotifications { + constructor(private readonly app: IApplication) {} + public hasMessages(type?: 'error'): Promise { + const selector = type === 'error' ? Selector.NotificationError : Selector.Notification; + return this.app.driver + .$$(this.app.getCSSSelector(selector)) + .then(eles => Array.isArray(eles) && eles.length > 0) + .catch(() => false); + } + public getMessages(): Promise { + return this.app.driver.$$eval(this.app.getCSSSelector(Selector.IndividualNotification), elements => + elements.map(ele => ele.textContent || '').filter(text => text.length > 0) + ); + } + /** + * Possibly a faster way would be to find the first visible message in UI, then look for it in the array. + * + * @param {({ content: string; buttonText?: string | undefined }[])} messages + * @param {number} timeout + * @returns {Promise} + * @memberof Notifications + */ + public async dismiss(messages: { content: string; buttonText?: string | undefined }[], timeout: number): Promise { + const stopwatch = new StopWatch(); + const _closeNotifications = async (): Promise => { + if (messages.length === 0) { + return; + } + const count = await this.getMessages().then(msgs => msgs.length); + if (count === 0) { + return; + } + + // tslint:disable-next-line: prefer-array-literal + for (const i of [...new Array(count).keys()]) { + // Check if we can find a notification with this message. + const selector = this.app.getCSSSelector(Selector.NthNotificationMessage).format((i + 1).toString()); + const textContent = await this.app.driver + .$$eval(selector, elements => elements.reduce((content, element) => element.textContent || content, '')) + .catch(warn.bind(warn, `Failed to get content of notification with selector '${selector}'`)); + + if (!textContent) { + continue; + } + + debug(`Found notification '${textContent}'.`); + // Find a notification that matches this message. + const message = messages.find(msg => textContent.normalize().startsWith(msg.content)); + if (!message) { + // warn(`Unknown notification ${textContent}. Not dismissed!`); + continue; + } + + const closeSelector = message.buttonText + ? this.app.getCSSSelector(Selector.ButtonInNthNotification).format((i + 1).toString(), message.buttonText) + : this.app.getCSSSelector(Selector.CloseButtonInNthNotification).format((i + 1).toString()); + + // If we found a notification with this message, then use the selector to dismiss it. + const failed = await this.app.driver + .click(closeSelector) + // Wait for message to get clicked and dissappear. + .then(() => sleep(200)) + .catch(() => true); + + if (!failed) { + debug(`Dismissed message '${message.content}`); + // Continue dismissing other messages. + return _closeNotifications(); + } + messages.push(message); + } + + if (stopwatch.elapsedTime > timeout) { + return; + } + await _closeNotifications(); + }; + await _closeNotifications(); + } +} diff --git a/uitests/src/vscode/panels.ts b/uitests/src/vscode/panels.ts new file mode 100644 index 000000000000..618a846151ee --- /dev/null +++ b/uitests/src/vscode/panels.ts @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { noop, retryWrapper, sleep } from '../helpers'; +import '../helpers/extensions'; +import { debug } from '../helpers/logger'; +import { Selector } from '../selectors'; +import { IApplication, IPanels } from '../types'; + +export class Panels implements IPanels { + constructor(private readonly app: IApplication) {} + public async maximize(): Promise { + debug('Maximize panels'); + await this.app.driver + .click(this.app.getCSSSelector(Selector.MaximizePanel)) + // Wait for some time for click to take affect. + .then(() => sleep(500)) + // Ignore Errors. + .catch(noop); + } + public async minimize(): Promise { + debug('Minimize panels'); + await this.app.driver + .click(this.app.getCSSSelector(Selector.MinimizePanel)) + // Wait for some time for click to take affect. + .then(() => sleep(500)) + // Ignore Errors. + .catch(noop); + } + public async waitUtilContent(text: string, timeoutSeconds: number = 10) { + await this.maximize(); + const selector = this.app.getCSSSelector(Selector.IndividualLinesInOutputPanel); + debug(`Look for the content '${text} in the panel ${selector}`); + try { + const checkOutput = async () => { + debug(`Looking for the content '${text} in the panel`); + const output = await this.app.driver + // Join without spaces, as its possible we have multiple elements, that may not necessarily break at words. + // I.e. it might break in the middle of a word. + .$$eval(selector, elements => elements.map(element => element.textContent || '').join('')); + debug(`Content in output panel is '${output}'`); + if ( + output + .normalize() + .toLowerCase() + .includes(text.toLowerCase()) + ) { + return Promise.resolve(); + } + + debug('Content not found'); + return Promise.reject(new Error(`Message '${text}' not found in Output Panel: [${output}]`)); + }; + await retryWrapper({ timeout: timeoutSeconds * 1000 }, checkOutput); + } finally { + await this.minimize(); + } + } +} diff --git a/uitests/src/vscode/problems.ts b/uitests/src/vscode/problems.ts new file mode 100644 index 000000000000..adc0aeb30588 --- /dev/null +++ b/uitests/src/vscode/problems.ts @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import '../helpers/extensions'; +import { Selector } from '../selectors'; +import { IApplication, IProblems } from '../types'; + +export class Problems implements IProblems { + constructor(private readonly app: IApplication) {} + public async waitUntilOpened(): Promise { + await this.app.driver.waitForSelector(this.app.getCSSSelector(Selector.ProblemsPanel), { + timeout: 3000, + visible: true + }); + } + public async getProblemCount() { + const selector = this.app.getCSSSelector(Selector.ProblemsBadge); + const content = await this.app.driver.$eval(selector, ele => ele.textContent || '').catch(() => ''); + return content.trim() === '' ? 0 : parseInt(content.trim(), 10); + } + public async getProblemFiles() { + const selector = this.app.getCSSSelector(Selector.FileNameInProblemsPanel); + return this.app.driver + .$$eval(selector, elements => elements.map(element => element.textContent || '')) + .then(items => items.map(item => item.normalize())) + .catch(() => []); + } + public async getProblemMessages(): Promise { + const selector = this.app.getCSSSelector(Selector.ProblemMessageInProblemsPanel); + return this.app.driver + .$$eval(selector, elements => elements.map(element => element.textContent || '')) + .then(items => items.map(item => item.normalize())) + .catch(() => []); + } +} diff --git a/uitests/src/vscode/quickInput.ts b/uitests/src/vscode/quickInput.ts new file mode 100644 index 000000000000..7eca6f0a71e7 --- /dev/null +++ b/uitests/src/vscode/quickInput.ts @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { Selector } from '../selectors'; +import { IApplication, IQuickInput } from '../types'; + +export class QuickInput implements IQuickInput { + constructor(private readonly app: IApplication) {} + public async select(options: { value: string } | { index: number }): Promise { + await this.waitUntilOpened(); + + if ('value' in options) { + const selector = this.app.getCSSSelector(Selector.QuickInputInput); + await this.app.driver.type(selector, options.value); + } else { + throw new Error('Selecting input in QuickInput with index not supported'); + } + + // await this.app.captureScreenshot('Filtered Interpreter List'); + await this.app.driver.press('Enter'); + await this.waitUntilClosed(); + } + public async close(): Promise { + const selector = this.app.getCSSSelector(Selector.QuickInputInput); + const failed = await this.app.driver.focus(selector).catch(() => true); + if (failed) { + return; + } + await this.app.driver.press('Escape'); + await this.waitUntilClosed(); + } + public async waitUntilOpened(retryCount?: number | undefined): Promise { + const selector = this.app.getCSSSelector(Selector.QuickInputInput); + // const retryOptions: SelectorRetryOptions = retryCount ? { retryCount } : { retryTimeout: 5000 }; + // await this.app.driver.$(selector, retryOptions).catch(() => true); + await this.app.driver.waitForSelector(selector, { + visible: true, + timeout: retryCount ? retryCount * 100 : 5000 + }); + } + // @retry(RetryMax5Seconds) + public async waitUntilClosed(): Promise { + const selector = this.app.getCSSSelector(Selector.QuickInput); + await this.app.driver.waitForSelector(selector, { hidden: true, timeout: 5000 }); + } +} diff --git a/uitests/src/vscode/quickOpen.ts b/uitests/src/vscode/quickOpen.ts new file mode 100644 index 000000000000..08baf7361032 --- /dev/null +++ b/uitests/src/vscode/quickOpen.ts @@ -0,0 +1,129 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { EventEmitter } from 'events'; +import { RetryMax10Seconds, RetryMax20Seconds, RetryMax2Seconds, RetryMax30Seconds } from '../constants'; +import { retry, retryWrapper } from '../helpers'; +import { debug, warn } from '../helpers/logger'; +import { Selector } from '../selectors'; +import { IApplication, IQuickOpen } from '../types'; + +export class QuickOpen extends EventEmitter implements IQuickOpen { + constructor(private readonly app: IApplication) { + super(); + } + public async openFile(fileName: string): Promise { + let retryCounter = 0; + const tryOpening = async () => { + retryCounter += 1; + // Possible VSC explorer hasn't refreshed, and it hasn't detected a new file in the file system. + if (retryCounter > 1) { + await this.app.documents.refreshExplorer(); + } + // await this.runCommand('Go to File...'); + await this.open(); + await this._selectValue(fileName, fileName); + await this.app.documents.waitUntilFileOpened(fileName); + }; + + await retryWrapper(RetryMax20Seconds, tryOpening); + } + /** + * Don't know what UI element in VSC handles keyboard events. + * (should be possible to find out, but found it easier to just use the bootstrap extension). + * Solution: + * - Use bootstrap extension to launch the quic open dropdown + * - Send the text `> command name` + * - Send the `Enter` key to the quick open window. + * + * @param {string} value + * @returns {Promise} + * @memberof QuickOpen + */ + public async runCommand(value: string): Promise { + await this._runCommand(value); + this.emit('command', value); + } + public async select(value: string): Promise { + await this._selectValue(`:${value}`); + } + public async open(): Promise { + await this.app.driver.click(this.app.getCSSSelector(Selector.PyBootstrapStatusBar)); + } + public async close(): Promise { + throw new Error('Not implemented'); + } + public dispose() { + this.removeAllListeners(); + } + public async waitUntilOpened(_retryCount?: number): Promise { + await this.app.driver.waitForSelector(this.app.getCSSSelector(Selector.QuickOpenInput)); + } + public async waitUntilClosed(): Promise { + await this.app.driver.waitForSelector(this.app.getCSSSelector(Selector.QuickOpenInput), { hidden: true }).catch(warn.bind(warn, 'Quick Open not hidden')); + } + @retry(RetryMax30Seconds) + private async _runCommand(command: string): Promise { + debug(`Run command ${command}`); + debug(' - display quick open'); + await this.open(); + await this._selectValue(`> ${command}`, command); + } + /** + * Keep retrying (possible things didn't get typed in correctly as something else stole focus). + * + * @private + * @param {string} valueToType + * @param {string} [valueToGetSelected] + * @returns {Promise} + * @memberof QuickOpen + */ + @retry(RetryMax10Seconds) + private async _selectValue(valueToType: string, valueToGetSelected?: string): Promise { + await this.waitUntilOpened(); + debug(' - type into input'); + const selector = this.app.getCSSSelector(Selector.QuickOpenInput); + await this.app.driver.type(selector, valueToType); + if (valueToGetSelected) { + debug(' - wait until item is selected'); + await this.waitUntilSelected(valueToGetSelected).catch(async ex => { + // Close the quick open before we try to re-open (as part of retry operations). + await this.app.driver.press('Escape'); + throw ex; + }); + } + await this.app.driver.press('Enter'); + await this.waitUntilClosed(); + } + /** + * Waits until the provided value has been selected in the quick input dropdown. + * Convert values to lower case to avoid instances where command names are not written in the exact case. + * + * @private + * @param {string} value + * @returns {Promise} + * @memberof QuickOpen + */ + @retry(RetryMax2Seconds) + private async waitUntilSelected(value: string): Promise { + value = value.toLowerCase(); + // If the first highlighted item is exactly what we need, then use that. + // For some reason VSC seems to display the item in the quick open in a funky way. + // If we retry it will work, but why waste cpu cycles, lets accomodate for this funky state. + const [highlightedItems, highlightedItem2s] = await Promise.all([ + this.app.driver.$$eval(this.app.getCSSSelector(Selector.QuickOpenEntryLabelFocused), elements => elements.map(e => (e.textContent || '').toLowerCase())), + this.app.driver.$$eval(this.app.getCSSSelector(Selector.QuickOpenEntryLabelFocused2), elements => elements.map(e => (e.textContent || '').toLowerCase())) + ]); + if (Array.isArray(highlightedItems) && highlightedItems.length > 0 && (highlightedItems[0] || '').normalize() === value) { + debug(' - Command highlighted in quick open'); + return; + } + if (Array.isArray(highlightedItem2s) && highlightedItem2s.length > 0 && (highlightedItem2s[0] || '').normalize() === value) { + debug(' - Command highlighted in quick open'); + return; + } + throw new Error(`Item '${value}' not found in quick open, lets wait for some more time. Items found ${highlightedItems.join(', ')} & ${highlightedItem2s.join(', ')}.`); + } +} diff --git a/uitests/src/vscode/settings.ts b/uitests/src/vscode/settings.ts new file mode 100644 index 000000000000..8fd1b3abf735 --- /dev/null +++ b/uitests/src/vscode/settings.ts @@ -0,0 +1,116 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import * as fs from 'fs-extra'; +import { applyEdits, modify, parse } from 'jsonc-parser'; +import * as path from 'path'; +import { noop } from '../helpers'; +import { sleep } from '../helpers/misc'; +import { ConfigurationTarget, IApplication, ISettings } from '../types'; + +// tslint:disable: max-func-body-length no-invalid-template-strings no-http-string no-invalid-this no-multiline-string member-access no-any radix no-shadowed-variable no-unnecessary-callback-wrapper member-ordering no-constant-condition prefer-const no-increment-decrement no-single-line-block-comment prefer-object-spread no-function-expression no-string-literal + +const modifyOptions = { formattingOptions: { tabSize: 4, insertSpaces: true } }; +type CrudSetting = { + type: 'user' | 'workspaceFolder'; + remove?: string[]; + workspaceFolder?: string; + update?: { [key: string]: string | number | boolean | void }; +}; + +export class Settings implements ISettings { + private readonly app: IApplication; + constructor(app: IApplication) { + this.app = app; + } + public async removeSetting(setting: string, scope: ConfigurationTarget): Promise { + const content = await this.getSettingsContent(scope); + if (!content) { + return; + } + if (this.app.isAlive) { + const type = scope === ConfigurationTarget.Global ? 'user' : 'workspaceFolder'; + const workspaceFolder = scope === ConfigurationTarget.Global ? undefined : this.app.workspacePathOrFolder; + await this.sendCommandToBootstrap({ type, remove: [setting], workspaceFolder }); + } else { + const edits = modify(content, [setting], void 0, modifyOptions); + await this.saveSettingsContent(applyEdits(content, edits), scope); + } + } + public async updateSetting(setting: string, value: string | boolean | number | void, scope: ConfigurationTarget): Promise { + let content = await this.getSettingsContent(scope); + if (!content) { + content = '{}'; + } + if (this.app.isAlive) { + const type = scope === ConfigurationTarget.Global ? 'user' : 'workspaceFolder'; + const workspaceFolder = scope === ConfigurationTarget.Global ? undefined : this.app.workspacePathOrFolder; + await this.sendCommandToBootstrap({ type, update: { [setting]: value }, workspaceFolder }); + } else { + const edits = modify(content, [setting], value, modifyOptions); + await this.saveSettingsContent(applyEdits(content, edits), scope); + } + } + public async getSetting(setting: string, scope: ConfigurationTarget): Promise { + const content = await this.getSettingsContent(scope); + return content ? (this.getJson(content)[setting] as T) : undefined; + } + /** + * Let the bootstrap extension update the settings. This way VSC will be aware of it and extensions + * will get the right values.If we update the file directly then VSC might not get notified immediately. + * We'll let the bootstrap extension update the settings and delete the original file. + * When the file has been deleted we know the settings have been updated and VSC is aware of the updates. + * + * @private + * @param {*} context + * @param {*} crud_settings + * @memberof Settings + */ + private async sendCommandToBootstrap(crudSettings: CrudSetting) { + const instructionsFile = path.join(this.app.extensionsPath, 'settingsToUpdate.txt'); + const errorFile = path.join(this.app.extensionsPath, 'settingsToUpdate_error.txt'); + await fs.remove(errorFile).catch(noop); + await fs.writeFile(instructionsFile, JSON.stringify(crudSettings, undefined, 4)); + + await this.app.quickopen.runCommand('Smoke: Update Settings'); + // uitests.vscode.application.capture_screen(context) + // Wait for 5 seconds for settings to get updated. + // If file has been deleted then yes it has been udpated, else error + for (let _ of [1, 2, 3, 4, 5]) { + if (await fs.pathExists(instructionsFile)) { + await sleep(500); + continue; + } + return; + } + + let errorMessage = ''; + if (await fs.pathExists(errorFile)) { + errorMessage += await fs.readFile(errorFile); + } + if (await fs.pathExists(instructionsFile)) { + errorMessage += await fs.readFile(instructionsFile); + } + throw new Error(`Settings not updated by Bootstrap\n ${errorMessage}`); + } + + private async getSettingsContent(scope: ConfigurationTarget): Promise { + const jsonFile = scope === ConfigurationTarget.Global ? this.app.userSettingsFilePath : path.join(this.app.workspacePathOrFolder, '.vscode', 'settings.json'); + if (!(await fs.pathExists(jsonFile))) { + return; + } + return fs.readFile(jsonFile, 'utf8'); + } + + private async saveSettingsContent(content: string, scope: ConfigurationTarget): Promise { + const jsonFile = scope === ConfigurationTarget.Global ? this.app.userSettingsFilePath : path.join(this.app.workspacePathOrFolder, '.vscode', 'settings.json'); + await fs.mkdirp(path.dirname(jsonFile)).catch(noop); + return fs.writeFile(jsonFile, content, 'utf8'); + } + + private getJson(content: string): any { + return parse(content, undefined, { allowTrailingComma: true, disallowComments: false }); + } +} diff --git a/uitests/src/vscode/statusbar.ts b/uitests/src/vscode/statusbar.ts new file mode 100644 index 000000000000..826786440f32 --- /dev/null +++ b/uitests/src/vscode/statusbar.ts @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import '../helpers/extensions'; +import { Selector } from '../selectors'; +import { IApplication, IStatusBar } from '../types'; + +export class StatusBar implements IStatusBar { + constructor(private readonly app: IApplication) {} + public getPythonStatusBarText(): Promise { + const selector = this.app.getCSSSelector(Selector.PythonExtensionStatusBar); + return this.app.driver.$eval(selector, ele => ele.textContent || '').then(text => text.normalize()); + } + public async waitUntilPythonItemVisible(): Promise { + throw new Error('Method not implemented.'); + } + public async waitUntilBootstrapItemVisible(): Promise { + throw new Error('Method not implemented.'); + } +} diff --git a/uitests/src/vscode/testExplorer.ts b/uitests/src/vscode/testExplorer.ts new file mode 100644 index 000000000000..45206df89cd6 --- /dev/null +++ b/uitests/src/vscode/testExplorer.ts @@ -0,0 +1,311 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { RetryMax10Seconds } from '../constants'; +import { retry, sleep } from '../helpers'; +import '../helpers/extensions'; +import { Selector } from '../selectors'; +import { IApplication, ITestExplorer, TestExplorerNodeStatus, TestExplorerToolbarIcon, TestingAction } from '../types'; + +const statusToIconMapping: Map = new Map([ + ['Unknown', 'status-unknown.svg'], + ['Progress', 'discovering-tests.svg'], + ['Ok', 'status-ok.svg'], + ['Pass', 'status-ok.svg'], + ['Success', 'status-ok.svg'], + ['Fail', 'status-error.svg'], + ['Error', 'status-error.svg'] +]); +const delayForUIToUpdate = 100; +const iconTitleMapping: Record = { + Stop: 'Stop', + RunFailedTests: 'Run Failed Tests' +}; + +const maxNodesToExpand = 50; +type NodeInfo = { + expanded: boolean; + hasChildren: boolean; + focused: boolean; + index: number; + status: TestExplorerNodeStatus; + label: string; +}; + +export class TestExplorer implements ITestExplorer { + constructor(private readonly app: IApplication) {} + public async isOpened(): Promise { + return this.app.driver + .$(this.app.getCSSSelector(Selector.TestActivityBar)) + .then(() => true) + .catch(() => false); + } + public async isIconVisible(): Promise { + return this.app.driver + .$(this.app.getCSSSelector(Selector.TestActivityIcon)) + .then(() => true) + .catch(() => false); + } + public async waitUntilOpened(timeout: number = 3000): Promise { + await this.app.driver.waitForSelector(this.app.getCSSSelector(Selector.TestActivityBar), { + timeout, + visible: true + }); + } + public async waitUntilIconVisible(timeout: number = 3000): Promise { + await this.app.driver.waitForSelector(this.app.getCSSSelector(Selector.TestActivityIcon), { + timeout, + visible: true + }); + } + public async waitUntilTestsStop(timeout: number): Promise { + await this.app.driver.waitForSelector(this.app.getCSSSelector(Selector.TestExplorerToolbarcon).format(iconTitleMapping.Stop), { timeout, hidden: true }); + } + public async expandNodes(maxNodes: number = maxNodesToExpand): Promise { + await this.ensureOpened(); + // We only want to support <= 15 nodes in testing. + const initialNodeCount = await this.getNodeCount(); + if (initialNodeCount === 0) { + return; + } + // wait at least 1s before selecting nodes and expanding. + // Its possible the UI is not yet ready. + await sleep(1500); + await this.selectFirstNode(); + try { + let nodeNumber = 0; + while (nodeNumber < maxNodes) { + nodeNumber += 1; + const visibleNodes = await this.getNodeCount(); + let info: { expanded: boolean; hasChildren: boolean; focused: boolean }; + try { + info = await this.getNodeInfo({ nodeNumber }); + } catch { + return; + } + if (!info.hasChildren && nodeNumber > visibleNodes) { + return; + } + if (nodeNumber === 1 && info.expanded && info.hasChildren) { + await this.app.driver.press('ArrowDown'); + await sleep(delayForUIToUpdate); + continue; + } + if (!info.expanded && info.hasChildren) { + await this.app.driver.press('ArrowRight'); + await sleep(delayForUIToUpdate); + await this.app.driver.press('ArrowDown'); + await sleep(delayForUIToUpdate); + continue; + } + if (!info.hasChildren) { + await this.app.driver.press('ArrowDown'); + await sleep(delayForUIToUpdate); + continue; + } + } + } finally { + const visibleNodes = await this.getNodeCount(); + if (visibleNodes === initialNodeCount) { + // Something is wrong, try again. + // tslint:disable-next-line: no-unsafe-finally + throw new Error('Retry expanding nodes. First iteration did not reveal any new nodes!'); + } + } + } + public async getNodeCount(_maxNodes?: number | undefined): Promise { + await this.ensureOpened(); + const elements = await this.app.driver.$$(this.app.getCSSSelector(Selector.TestExplorerNode)); + return elements.length; + } + public async selectNode(label: string): Promise { + await this.ensureOpened(); + if ((await this.getNodeCount()) === 0) { + throw new Error('No nodes to select'); + } + + await this.selectNodeNumber(await this.getNodeNumber(label)); + } + public async clickNode(label: string): Promise { + await this.ensureOpened(); + const nodeNumber = await this.getNodeNumber(label); + // await this.selectNodeNumber(nodeNumber); + // await this.app.driver.click(`div[id="workbench.view.extension.test"] div.monaco-tree-row:nth-child(${nodeNumber})`); + await this.selectNodeNumber(nodeNumber); + await this.app.driver.press('Enter'); + } + public async waitUntilToolbarIconVisible(icon: TestExplorerToolbarIcon, timeout: number = 30_000): Promise { + await this.ensureOpened(); + const selector = this.app.getCSSSelector(Selector.TestExplorerToolbarcon).format(iconTitleMapping[icon]); + await this.app.driver.waitForSelector(selector, { timeout, visible: true }); + } + public async waitUntilToolbarIconHidden(icon: TestExplorerToolbarIcon, timeout: number = 30_000): Promise { + await this.ensureOpened(); + const selector = this.app.getCSSSelector(Selector.TestExplorerToolbarcon).format(iconTitleMapping[icon]); + await this.app.driver.waitForSelector(selector, { timeout, hidden: true }); + } + public async clickToolbarIcon(icon: TestExplorerToolbarIcon): Promise { + await this.ensureOpened(); + const selector = this.app.getCSSSelector(Selector.TestExplorerToolbarcon).format(iconTitleMapping[icon]); + await this.app.driver.click(selector); + } + public async getNodes(): Promise<{ label: string; index: number; status: TestExplorerNodeStatus }[]> { + const nodeCount = await this.getNodeCount(); + // tslint:disable-next-line: prefer-array-literal + const indices = [...new Array(nodeCount).keys()]; + return Promise.all(indices.map(index => this.getNodeInfo({ nodeNumber: index + 1 }))); + } + public async getNode(label: string): Promise<{ label: string; index: number; status: TestExplorerNodeStatus }> { + return this.getNodeInfo({ label }); + } + public async selectActionForNode(label: string, action: TestingAction): Promise { + await this.ensureOpened(); + // First select the node to highlight the icons. + await this.selectNode(label); + const node = await this.getSelectedNode(); + if (!node) { + throw new Error(`Node with the label '${label}' not selected`); + } + // For some reason this doesn't work on CI. + // Instead just select the icon by tabbing to it and hit the `Enter` key. + // This way, the icon is displayed (similar to when you hover over the icon before clicking it). + // We could probably use `hoever` to ensure the icon is visible, however tabbing works. + // const selector = nodeActionSelector.format(node.number.toString(), actionTitleMapping[action]); + // await context.app.code.waitAndClick(selector, 2, 2); + + const tabCounter = action === 'run' ? 1 : action === 'debug' ? 2 : 3; + for (let counter = 0; counter < tabCounter; counter += 1) { + await this.app.driver.press('tab'); + await sleep(delayForUIToUpdate); + } + await this.app.driver.press('Enter'); + await sleep(delayForUIToUpdate); + } + public async getNodeNumber(label: string): Promise { + await this.ensureOpened(); + if ((await this.getNodeCount()) === 0) { + throw new Error('There are no nodes'); + } + // Walk through each node and check the label. + for (let nodeNumber = 1; nodeNumber < maxNodesToExpand; nodeNumber += 1) { + const nodeLabel = await this.getNodeLabel(nodeNumber); + if ( + nodeLabel + .normalize() + .trim() + .toLowerCase() + .includes(label.toLowerCase()) + ) { + return nodeNumber; + } + } + + throw new Error(`Unable to find node named '${label}'`); + } + @retry(RetryMax10Seconds) + private async selectNodeNumber(nodeNumber: number): Promise { + await this.ensureOpened(); + // We only want to support <= 15 nodes in testing. + if ((await this.getNodeCount()) === 0) { + return; + } + await this.selectFirstNode(); + for (let i = 1; i < maxNodesToExpand; i += 1) { + if (i === nodeNumber) { + return; + } + const visibleNodes = await this.getNodeCount(); + let info: { expanded: boolean; hasChildren: boolean }; + try { + info = await this.getNodeInfo({ nodeNumber: i }); + } catch { + return; + } + if (!info.hasChildren && i > visibleNodes) { + return; + } + if (i === 1 && info.expanded && info.hasChildren) { + await this.app.driver.press('ArrowDown'); + await sleep(delayForUIToUpdate); + continue; + } + if (!info.expanded && info.hasChildren) { + await this.app.driver.press('ArrowRight'); + await sleep(delayForUIToUpdate); + await this.app.driver.press('ArrowDown'); + await sleep(delayForUIToUpdate); + continue; + } + await this.app.driver.press('ArrowDown'); + await sleep(delayForUIToUpdate); + } + } + private async getNodeInfo(options: { label: string } | { nodeNumber: number } | { label: string; nodeNumber: number }): Promise { + let label = ''; + let nodeNumber = -1; + if ('nodeNumber' in options) { + nodeNumber = options.nodeNumber; + } + if ('label' in options) { + label = options.label; + } + if (nodeNumber === -1) { + nodeNumber = await this.getNodeNumber(label); + } + + const iconSelector = this.app.getCSSSelector(Selector.NthTestExplorerNodeIcon).format(nodeNumber.toString()); + const selector = this.app.getCSSSelector(Selector.NthTestExplorerNode).format(nodeNumber.toString()); + + const [bgIcon, nodeLabel, className] = await Promise.all([ + this.app.driver.$eval(iconSelector, element => getComputedStyle(element).backgroundImage || ''), + label ? Promise.resolve(label) : this.getNodeLabel(nodeNumber), + this.app.driver.$eval(selector, element => element.className) + ]); + + const status = Array.from(statusToIconMapping.entries()).reduce((currentStatus, item) => { + if (bgIcon.includes(item[1])) { + return item[0]; + } + return currentStatus; + }, 'Unknown'); + + return { + expanded: className.indexOf('expanded') >= 0, + focused: className.indexOf('focused') >= 0, + hasChildren: className.indexOf('has-children') >= 0, + status, + index: nodeNumber - 1, + label: nodeLabel + }; + } + private async getNodeLabel(nodeNumber: number): Promise { + const selector = this.app.getCSSSelector(Selector.NthTestExplorerNodeLabel).format(nodeNumber.toString()); + return this.app.driver.$eval(selector, element => element.textContent || '').then(text => text.normalize()); + } + private async getSelectedNode(): Promise { + if ((await this.getNodeCount()) === 0) { + return; + } + for (let nodeNumber = 1; nodeNumber < maxNodesToExpand; nodeNumber += 1) { + const info = await this.getNodeInfo({ nodeNumber }); + if (info.focused) { + return info; + } + } + } + private async selectFirstNode() { + await this.app.driver.click(this.app.getCSSSelector(Selector.TestExplorerTreeViewContainer)); + await sleep(delayForUIToUpdate); + await this.app.driver.press('ArrowDown'); + await sleep(delayForUIToUpdate); + } + private async ensureOpened(): Promise { + if (await this.isOpened()) { + return; + } + await this.app.quickopen.runCommand('View: Show Test'); + await this.waitUntilOpened(); + } +} diff --git a/uitests/tsconfig.json b/uitests/tsconfig.json new file mode 100644 index 000000000000..acd031b88dfe --- /dev/null +++ b/uitests/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es2018", + "rootDir": "src", + "lib": ["es6", "es2018", "dom"], + "jsx": "react", + "sourceMap": true, + "outDir": "./out", + "experimentalDecorators": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "noImplicitAny": true, + "noImplicitThis": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "exclude": ["node_modules", "bootstrap"] +} diff --git a/uitests/tslint.json b/uitests/tslint.json new file mode 100644 index 000000000000..5af66a3f6f69 --- /dev/null +++ b/uitests/tslint.json @@ -0,0 +1,51 @@ +{ + "rulesDirectory": [], + "extends": ["tslint-eslint-rules", "tslint-microsoft-contrib", "tslint-plugin-prettier", "tslint-config-prettier"], + "rules": { + "no-unused-expression": true, + "no-duplicate-variable": true, + "curly": true, + "non-literal-fs-path": false, + "newline-per-chained-call": false, + "class-name": true, + "semicolon": [true], + "triple-equals": true, + "no-relative-imports": false, + "max-line-length": false, + "typedef": false, + "no-string-throw": true, + "missing-jsdoc": false, + "one-line": [true, "check-catch", "check-finally", "check-else"], + "no-parameter-properties": false, + "no-parameter-reassignment": false, + "no-reserved-keywords": false, + "newline-before-return": false, + "export-name": false, + "align": false, + "linebreak-style": false, + "strict-boolean-expressions": false, + "await-promise": [true, "Thenable", "PromiseLike"], + "completed-docs": false, + "no-unsafe-any": false, + "no-backbone-get-set-outside-model": false, + "underscore-consistent-invocation": false, + "no-void-expression": false, + "no-non-null-assertion": false, + "prefer-type-cast": false, + "promise-function-async": false, + "function-name": false, + "variable-name": false, + "no-import-side-effect": false, + "no-string-based-set-timeout": false, + "no-floating-promises": true, + "no-empty-interface": false, + "no-bitwise": false, + "eofline": true, + "switch-final-break": false, + "no-unnecessary-type-assertion": false, + "no-submodule-imports": false, + "no-redundant-jsdoc": false, + "binary-expression-operand-order": false, + "prettier": [true, ".prettierrc.js"] + } +}