From 239b59ccdfe8af4d9cf0ee7633d4a9bd9c684684 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Jel=C3=ADnek?= Date: Thu, 16 May 2024 21:10:41 +0200 Subject: [PATCH] chore: Use Prettier for code formatting (#1312) Signed-off-by: Dominik Jelinek --- .eslintrc.json | 117 +- .github/ISSUE_TEMPLATE/bug-reports.yml | 2 +- .github/ISSUE_TEMPLATE/feature-requests.yml | 2 +- .github/dependabot.yml | 6 +- .github/release.yml | 4 +- .github/workflows/coverage.yml | 4 +- .github/workflows/insiders.yml | 2 +- .github/workflows/main.yml | 8 +- .github/workflows/nodejs.yml | 2 +- .github/workflows/publish-wiki.yml | 2 +- .github/workflows/stalebot.yml | 18 +- .github/workflows/template-main.yaml | 9 +- .prettierignore | 6 + .prettierrc | 23 + CODE_OF_CONDUCT.md | 30 +- CONTRIBUTING.md | 14 +- README.md | 6 +- SECURITY.md | 7 +- docs/ActivityBar.md | 4 +- docs/CodeCoverage.md | 80 +- docs/ContentAssist.md | 6 +- docs/ContextMenu.md | 10 +- docs/CustomTreeSection.md | 2 +- docs/DebugConsoleView.md | 4 +- docs/DebugView.md | 4 +- docs/Debugging-Tests.md | 40 +- docs/DiffEditor.md | 2 +- docs/EditorView.md | 17 +- docs/ExtensionsViewSection.md | 6 +- docs/FindWidget.md | 8 +- docs/Home.md | 16 +- docs/Input.md | 8 +- docs/Mocha-Configuration.md | 10 +- docs/ModalDialog.md | 4 +- docs/Notification.md | 4 +- docs/NotificationsCenter.md | 2 +- docs/OutputView.md | 2 +- docs/Page-Object-APIs.md | 2 +- docs/ProblemsView.md | 2 +- docs/ScmView.md | 10 +- docs/Setting.md | 2 +- docs/SettingsEditor.md | 6 +- docs/SideBarView.md | 4 +- docs/StatusBar.md | 2 +- docs/Taking Screenshots.md | 4 +- docs/TerminalView.md | 4 +- docs/Test-Setup.md | 172 +- docs/TextEditor.md | 14 +- docs/TitleBar.md | 4 +- docs/TitleBarItem.md | 4 +- docs/ViewContent.md | 2 +- docs/{ViewContol.md => ViewControl.md} | 0 docs/ViewItem.md | 4 +- docs/ViewSection.md | 13 +- docs/ViewTitlePart.md | 2 +- docs/WebView.md | 2 +- docs/Workbench.md | 6 +- docs/Writing-Simple-Tests.md | 16 +- examples/helloworld-extester/.eslintrc.json | 64 +- examples/helloworld-extester/.mocharc.js | 4 +- .../helloworld-extester/.vscode/launch.json | 64 +- .../helloworld-extester/.vscode/tasks.json | 34 +- .../helloworld-extester/package-lock.json | 10348 ++++++++-------- examples/helloworld-extester/package.json | 112 +- examples/helloworld-extester/settings.json | 10 +- examples/helloworld-extester/src/extension.ts | 12 +- .../src/ui-test/.mocharc-debug.js | 4 +- .../src/ui-test/activityBar.test.ts | 80 +- .../src/ui-test/bottomBar.test.ts | 322 +- .../src/ui-test/commands.test.ts | 31 +- .../src/ui-test/extensionsView.test.ts | 50 +- .../src/ui-test/input.test.ts | 132 +- .../src/ui-test/menus.test.ts | 112 +- .../src/ui-test/modalDialog.test.ts | 78 +- .../src/ui-test/notifications.test.ts | 115 +- .../src/ui-test/settingsEditor.test.ts | 42 +- .../src/ui-test/statusBar.test.ts | 74 +- .../src/ui-test/textEditor.test.ts | 108 +- .../src/ui-test/treeView.test.ts | 172 +- .../src/ui-test/webView.test.ts | 61 +- examples/helloworld-extester/tsconfig.json | 33 +- package-lock.json | 29 + package.json | 2 + packages/extester/src/browser.ts | 368 +- packages/extester/src/cli.ts | 283 +- packages/extester/src/extester.ts | 318 +- packages/extester/src/suite/mochaHooks.ts | 170 +- packages/extester/src/suite/runner.ts | 324 +- packages/extester/src/util/codeUtil.ts | 841 +- packages/extester/src/util/coverage.ts | 155 +- packages/extester/src/util/download.ts | 75 +- packages/extester/src/util/driverUtil.ts | 339 +- packages/extester/src/util/unpack.ts | 84 +- packages/extester/tsconfig.json | 10 +- packages/locators/index.ts | 4 +- packages/locators/lib/1.37.0.ts | 927 +- packages/locators/lib/1.38.0.ts | 18 +- packages/locators/lib/1.39.0.ts | 36 +- packages/locators/lib/1.40.0.ts | 18 +- packages/locators/lib/1.41.0.ts | 22 +- packages/locators/lib/1.43.0.ts | 24 +- packages/locators/lib/1.44.0.ts | 18 +- packages/locators/lib/1.45.0.ts | 36 +- packages/locators/lib/1.46.0.ts | 16 +- packages/locators/lib/1.47.0.ts | 28 +- packages/locators/lib/1.49.0.ts | 16 +- packages/locators/lib/1.50.0.ts | 16 +- packages/locators/lib/1.52.0.ts | 16 +- packages/locators/lib/1.54.0.ts | 18 +- packages/locators/lib/1.56.0.ts | 22 +- packages/locators/lib/1.57.0.ts | 22 +- packages/locators/lib/1.59.0.ts | 14 +- packages/locators/lib/1.60.0.ts | 14 +- packages/locators/lib/1.61.0.ts | 20 +- packages/locators/lib/1.66.0.ts | 22 +- packages/locators/lib/1.70.0.ts | 42 +- packages/locators/lib/1.71.0.ts | 26 +- packages/locators/lib/1.73.0.ts | 14 +- packages/locators/lib/1.74.0.ts | 16 +- packages/locators/lib/1.83.0.ts | 14 +- packages/locators/lib/1.84.0.ts | 14 +- packages/locators/lib/1.85.0.ts | 20 +- packages/locators/lib/1.87.0.ts | 74 +- packages/locators/lib/1.88.0.ts | 16 +- packages/locators/tsconfig.json | 7 +- .../src/components/AbstractElement.ts | 119 +- .../src/components/ElementWithContextMenu.ts | 54 +- .../src/components/WebviewMixin.ts | 135 +- .../components/activityBar/ActionsControl.ts | 40 +- .../src/components/activityBar/ActivityBar.ts | 128 +- .../src/components/activityBar/ViewControl.ts | 96 +- .../src/components/bottomBar/AbstractViews.ts | 151 +- .../components/bottomBar/BottomBarPanel.ts | 219 +- .../src/components/bottomBar/ProblemsView.ts | 245 +- .../src/components/bottomBar/Views.ts | 376 +- .../src/components/bottomBar/WebviewView.ts | 24 +- .../src/components/dialog/ModalDialog.ts | 97 +- .../src/components/editor/Breakpoint.ts | 59 +- .../src/components/editor/ContentAssist.ts | 157 +- .../src/components/editor/CustomEditor.ts | 73 +- .../src/components/editor/DiffEditor.ts | 39 +- .../src/components/editor/Editor.ts | 41 +- .../src/components/editor/EditorAction.ts | 24 +- .../src/components/editor/EditorView.ts | 716 +- .../src/components/editor/SettingsEditor.ts | 907 +- .../src/components/editor/TextEditor.ts | 1474 +-- .../src/components/editor/WebView.ts | 63 +- .../src/components/menu/ContextMenu.ts | 205 +- .../src/components/menu/MacTitleBar.ts | 63 +- .../page-objects/src/components/menu/Menu.ts | 109 +- .../src/components/menu/MenuItem.ts | 57 +- .../src/components/menu/TitleBar.ts | 124 +- .../src/components/menu/WindowControls.ts | 98 +- .../src/components/sidebar/SideBarView.ts | 40 +- .../src/components/sidebar/ViewContent.ts | 208 +- .../src/components/sidebar/ViewItem.ts | 434 +- .../src/components/sidebar/ViewSection.ts | 393 +- .../src/components/sidebar/ViewTitlePart.ts | 87 +- .../src/components/sidebar/WelcomeContent.ts | 98 +- .../src/components/sidebar/debug/DebugView.ts | 127 +- .../sidebar/extensions/ExtensionsViewItem.ts | 173 +- .../extensions/ExtensionsViewSection.ts | 203 +- .../src/components/sidebar/scm/NewScmView.ts | 305 +- .../src/components/sidebar/scm/ScmView.ts | 494 +- .../components/sidebar/tree/TreeSection.ts | 90 +- .../sidebar/tree/custom/CustomTreeItem.ts | 70 +- .../sidebar/tree/custom/CustomTreeSection.ts | 108 +- .../tree/debug/BreakpointSectionItem.ts | 119 +- .../tree/debug/DebugBreakpointSection.ts | 16 +- .../tree/debug/DebugVariablesSection.ts | 14 +- .../sidebar/tree/debug/SectionBreakpoint.ts | 6 +- .../sidebar/tree/debug/VariableSectionItem.ts | 124 +- .../sidebar/tree/default/DefaultTreeItem.ts | 64 +- .../tree/default/DefaultTreeSection.ts | 70 +- .../src/components/statusBar/StatusBar.ts | 328 +- .../src/components/workbench/DebugToolbar.ts | 164 +- .../src/components/workbench/Notification.ts | 225 +- .../workbench/NotificationsCenter.ts | 94 +- .../src/components/workbench/Workbench.ts | 252 +- .../src/components/workbench/input/Input.ts | 474 +- .../components/workbench/input/InputBox.ts | 133 +- .../workbench/input/QuickOpenBox.ts | 61 +- .../src/conditions/WaitForAttribute.ts | 12 +- .../src/errors/NullAttributeError.ts | 8 +- packages/page-objects/src/index.ts | 6 +- packages/page-objects/src/locators/loader.ts | 130 +- .../page-objects/src/locators/locators.ts | 998 +- packages/page-objects/tsconfig.json | 6 +- tests/test-project/.mocharc-debug.js | 4 +- tests/test-project/.mocharc.js | 4 +- tests/test-project/.vscode/launch.json | 64 +- tests/test-project/.vscode/settings.json | 4 +- tests/test-project/.vscode/tasks.json | 32 +- tests/test-project/media/catScratch.js | 198 +- tests/test-project/media/pawDraw.css | 9 +- tests/test-project/media/pawDraw.js | 81 +- tests/test-project/package.json | 410 +- tests/test-project/src/catScratchEditor.ts | 50 +- tests/test-project/src/codelensProvider.ts | 94 +- tests/test-project/src/extension.ts | 92 +- .../src/test/01_general/resource.test.ts | 71 +- .../test/activityBar/actionsControl.test.ts | 30 +- .../src/test/activityBar/activityBar.test.ts | 82 +- .../src/test/activityBar/viewControl.test.ts | 74 +- .../src/test/bottomBar/bottomBarPanel.test.ts | 98 +- .../src/test/bottomBar/problemsView.test.ts | 184 +- .../src/test/bottomBar/views.test.ts | 170 +- .../test-project/src/test/cli/order-1.test.ts | 12 +- .../test-project/src/test/cli/order-2.test.ts | 12 +- .../test-project/src/test/cli/order-3.test.ts | 12 +- .../test-project/src/test/debug/debug.test.ts | 683 +- .../src/test/dialog/dialog.test.ts | 76 +- .../src/test/editor/customEditor.test.ts | 118 +- .../src/test/editor/diffEditor.test.ts | 62 +- .../src/test/editor/editorView.test.ts | 422 +- .../src/test/editor/settingsEditor.test.ts | 471 +- .../src/test/editor/textEditor.test.ts | 669 +- .../src/test/menu/contextMenu.test.ts | 46 +- .../src/test/menu/macTitleBar.test.ts | 62 +- .../src/test/menu/titleBar.test.ts | 181 +- .../src/test/statusBar/statusBar.test.ts | 192 +- .../src/test/system/clipboard.test.ts | 12 +- .../src/test/webview/webView.test.ts | 237 +- .../src/test/webview/webviewView.test.ts | 102 +- .../src/test/workbench/input.test.ts | 367 +- .../src/test/workbench/notifications.test.ts | 246 +- .../src/test/workbench/open.test.ts | 27 +- .../src/test/workbench/workbench.test.ts | 114 +- .../src/test/xsideBar/customView.test.ts | 491 +- .../src/test/xsideBar/scmView.test.ts | 230 +- .../src/test/xsideBar/sideBarView.test.ts | 449 +- .../src/test/xsideBar/xtensionsView.test.ts | 169 +- tests/test-project/src/treeView.ts | 70 +- tests/test-project/tsconfig.json | 20 +- tsconfig.json | 10 +- 235 files changed, 18456 insertions(+), 18280 deletions(-) create mode 100644 .prettierignore create mode 100644 .prettierrc rename docs/{ViewContol.md => ViewControl.md} (100%) diff --git a/.eslintrc.json b/.eslintrc.json index cf7eb854a..d5afd1e29 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,67 +1,54 @@ { - "root": true, // this is root configuration for project - "parser": "@typescript-eslint/parser", - "parserOptions": { - "ecmaVersion": 2022, - "sourceType": "module", - "project": "./tsconfig.json", - "ecmaFeatures": { - "impliedStrict": true - } - }, - "env": { - "browser": true, - "es2022": true, - "mocha": true - }, - "extends": [ - "eslint:recommended", - "plugin:@typescript-eslint/recommended" - ], - "plugins": [ - "@typescript-eslint", - "@stylistic" - ], - "rules": { - "@typescript-eslint/no-var-requires": "off", // allows require statements outside of imports - "@typescript-eslint/no-floating-promises": "warn", - "no-unused-expressions": "off", - "@typescript-eslint/no-unused-expressions": "off", - "@typescript-eslint/no-unused-vars": "warn", - "@typescript-eslint/no-namespace": "off", - "@typescript-eslint/no-explicit-any": "off", - "@typescript-eslint/no-this-alias": "off", - "@typescript-eslint/member-delimiter-style": [ - "warn", - { - "multiline": { - "delimiter": "semi", - "requireLast": true - }, - "singleline": { - "delimiter": "semi", - "requireLast": false - } - } - ], - "@typescript-eslint/naming-convention": [ - "error", - { - "selector": "variable", - "format": [ - "camelCase", - "PascalCase", - "UPPER_CASE" - ] - } - ], - "@stylistic/semi": "warn", - "curly": "warn", - "eqeqeq": [ - "warn", - "always" - ], - "no-redeclare": "warn", - "no-throw-literal": "warn" + "root": true, // this is root configuration for project + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaVersion": 2022, + "sourceType": "module", + "project": "./tsconfig.json", + "ecmaFeatures": { + "impliedStrict": true } -} \ No newline at end of file + }, + "env": { + "browser": true, + "es2022": true, + "mocha": true + }, + "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"], + "plugins": ["@typescript-eslint", "@stylistic"], + "rules": { + "@typescript-eslint/no-var-requires": "off", // allows require statements outside of imports + "@typescript-eslint/no-floating-promises": "warn", + "no-unused-expressions": "off", + "@typescript-eslint/no-unused-expressions": "off", + "@typescript-eslint/no-unused-vars": "warn", + "@typescript-eslint/no-namespace": "off", + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-this-alias": "off", + "@typescript-eslint/member-delimiter-style": [ + "warn", + { + "multiline": { + "delimiter": "semi", + "requireLast": true + }, + "singleline": { + "delimiter": "semi", + "requireLast": false + } + } + ], + "@typescript-eslint/naming-convention": [ + "error", + { + "selector": "variable", + "format": ["camelCase", "PascalCase", "UPPER_CASE"] + } + ], + "@stylistic/semi": "warn", + "curly": "warn", + "eqeqeq": ["warn", "always"], + "no-redeclare": "warn", + "no-throw-literal": "warn" + } +} diff --git a/.github/ISSUE_TEMPLATE/bug-reports.yml b/.github/ISSUE_TEMPLATE/bug-reports.yml index 24a05782c..a826e2393 100644 --- a/.github/ISSUE_TEMPLATE/bug-reports.yml +++ b/.github/ISSUE_TEMPLATE/bug-reports.yml @@ -1,7 +1,7 @@ name: đŸšĢ Bug Report description: If something is not working properly title: "[đŸšĢ Bug] " -labels: [ bug, new-issue ] +labels: [bug, new-issue] body: - type: markdown attributes: diff --git a/.github/ISSUE_TEMPLATE/feature-requests.yml b/.github/ISSUE_TEMPLATE/feature-requests.yml index 196e102a7..aab46cbe3 100644 --- a/.github/ISSUE_TEMPLATE/feature-requests.yml +++ b/.github/ISSUE_TEMPLATE/feature-requests.yml @@ -1,7 +1,7 @@ name: 🚀 Feature Request description: If something is missing or could be improved title: "[🚀 Request] " -labels: [ enhancement, new-issue ] +labels: [enhancement, new-issue] body: - type: markdown attributes: diff --git a/.github/dependabot.yml b/.github/dependabot.yml index eb9f04ae5..fb7bc70db 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -10,10 +10,10 @@ updates: - "skip-changelog" reviewers: - redhat-developer/extester-maintainers - - package-ecosystem: 'npm' - directory: '/' + - package-ecosystem: "npm" + directory: "/" schedule: - interval: 'daily' + interval: "daily" open-pull-requests-limit: 20 versioning-strategy: increase reviewers: diff --git a/.github/release.yml b/.github/release.yml index 2be793b39..89c8ca2d9 100644 --- a/.github/release.yml +++ b/.github/release.yml @@ -5,13 +5,13 @@ changelog: categories: - title: đŸšĢ Bugs labels: - - 'bug' + - "bug" exclude: labels: - dependencies - title: 🚀 Features labels: - - 'enhancement' + - "enhancement" exclude: labels: - dependencies diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 847688ff5..691da4eae 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -2,9 +2,9 @@ name: 📊 Code Coverage on: push: - branches: [ main ] + branches: [main] pull_request: - branches: [ main ] + branches: [main] workflow_dispatch: concurrency: diff --git a/.github/workflows/insiders.yml b/.github/workflows/insiders.yml index 358d7c381..4ccbbff67 100644 --- a/.github/workflows/insiders.yml +++ b/.github/workflows/insiders.yml @@ -4,7 +4,7 @@ on: schedule: - cron: "0 0 * * *" push: - branches: [ insider ] + branches: [insider] workflow_dispatch: concurrency: diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index fcbd2f1a0..524b96e4a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -2,9 +2,9 @@ name: 🏗ī¸ Main CI on: push: - branches: [ main ] + branches: [main] pull_request: - branches: [ main ] + branches: [main] workflow_dispatch: concurrency: @@ -17,7 +17,7 @@ jobs: strategy: fail-fast: false matrix: - version: [ min, 1.88.1, max ] + version: [min, 1.88.1, max] uses: ./.github/workflows/template-main.yaml with: version: ${{ matrix.version }} @@ -26,7 +26,7 @@ jobs: if: always() runs-on: ubuntu-latest name: đŸšĻ Status Check - needs: [ test ] + needs: [test] steps: - name: ℹī¸ Test Matrix Result run: | diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 5ecd822d7..8abb352a6 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -15,7 +15,7 @@ jobs: strategy: fail-fast: false matrix: - node: [ lts/*, latest ] + node: [lts/*, latest] uses: ./.github/workflows/template-main.yaml with: version: max diff --git a/.github/workflows/publish-wiki.yml b/.github/workflows/publish-wiki.yml index 515167082..6aba755da 100644 --- a/.github/workflows/publish-wiki.yml +++ b/.github/workflows/publish-wiki.yml @@ -2,7 +2,7 @@ name: 📖 Publish wiki on: push: - branches: [ main ] + branches: [main] paths: - docs/** - .github/workflows/publish-wiki.yml diff --git a/.github/workflows/stalebot.yml b/.github/workflows/stalebot.yml index b44007346..aa1eef9f2 100644 --- a/.github/workflows/stalebot.yml +++ b/.github/workflows/stalebot.yml @@ -3,7 +3,7 @@ name: 🗄ī¸ Close Stale Issues and PRs on: schedule: - - cron: '30 1 * * *' + - cron: "30 1 * * *" jobs: stale: @@ -12,13 +12,13 @@ jobs: - name: 🗄ī¸ Close Inactive uses: actions/stale@v9 with: - stale-issue-message: 'This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 7 days.' - stale-pr-message: 'This PR is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 7 days.' - close-issue-message: 'This issue was closed because it has been stalled for 5 days with no activity.' - exempt-issue-milestones: 'BACKLOG,NEXT' + stale-issue-message: "This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 7 days." + stale-pr-message: "This PR is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 7 days." + close-issue-message: "This issue was closed because it has been stalled for 5 days with no activity." + exempt-issue-milestones: "BACKLOG,NEXT" exempt-all-assignees: true - stale-issue-label: 'no-issue-activity' - stale-pr-label: 'no-pr-activity' - exempt-issue-labels: 'enhancement,question,help-wanted,do-not-stale' - exempt-pr-labels: 'help-wanted,prio,do-not-stale' + stale-issue-label: "no-issue-activity" + stale-pr-label: "no-pr-activity" + exempt-issue-labels: "enhancement,question,help-wanted,do-not-stale" + exempt-pr-labels: "help-wanted,prio,do-not-stale" exempt-draft-pr: true diff --git a/.github/workflows/template-main.yaml b/.github/workflows/template-main.yaml index f418237cb..ffa40a11b 100644 --- a/.github/workflows/template-main.yaml +++ b/.github/workflows/template-main.yaml @@ -8,11 +8,11 @@ on: type: string nodejs: required: false - default: '18.x' + default: "18.x" type: string code_type: required: false - default: 'stable' + default: "stable" type: string jobs: @@ -21,7 +21,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ ubuntu-latest, macos-latest, windows-latest ] + os: [ubuntu-latest, macos-latest, windows-latest] fail-fast: false env: @@ -45,6 +45,9 @@ jobs: - name: 🔧 Build run: npm run build + - name: ✨ Code Formatter - Prettier + run: npx prettier . --check + - name: 🔧 Install - Test Project run: npm install --workspace=extester-test diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 000000000..07eea753e --- /dev/null +++ b/.prettierignore @@ -0,0 +1,6 @@ +coverage/ +out/ +resources/ +test-resources/ +test-extensions/ +.test-extensions/ diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 000000000..d9f922ca7 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,23 @@ +{ + "arrowParens": "always", + "bracketSameLine": false, + "bracketSpacing": true, + "endOfLine": "auto", + "printWidth": 160, + "semi": true, + "singleQuote": true, + "trailingComma": "all", + "useTabs": true, + "tabWidth": 4, + "quoteProps": "as-needed", + "overrides": [ + { + "files": ["*.yaml", "*.yml", "*.json", "*.md"], + "options": { + "useTabs": false, + "tabWidth": 2, + "singleQuote": false + } + } + ] +} diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index e5fa5955a..48bbe3de9 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -17,23 +17,23 @@ diverse, inclusive, and healthy community. Examples of behavior that contributes to a positive environment for our community include: -* Demonstrating empathy and kindness toward other people -* Being respectful of differing opinions, viewpoints, and experiences -* Giving and gracefully accepting constructive feedback -* Accepting responsibility and apologizing to those affected by our mistakes, +- Demonstrating empathy and kindness toward other people +- Being respectful of differing opinions, viewpoints, and experiences +- Giving and gracefully accepting constructive feedback +- Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience -* Focusing on what is best not just for us as individuals, but for the +- Focusing on what is best not just for us as individuals, but for the overall community Examples of unacceptable behavior include: -* The use of sexualized language or imagery, and sexual attention or +- The use of sexualized language or imagery, and sexual attention or advances of any kind -* Trolling, insulting or derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or email +- Trolling, insulting or derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or email address, without their explicit permission -* Other conduct which could reasonably be considered inappropriate in a +- Other conduct which could reasonably be considered inappropriate in a professional setting ## Enforcement Responsibilities @@ -60,7 +60,7 @@ representative at an online or offline event. Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at -developers@redhat.com. +. All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the @@ -106,7 +106,7 @@ Violating these terms may lead to a permanent ban. ### 4. Permanent Ban **Community Impact**: Demonstrating a pattern of violation of community -standards, including sustained inappropriate behavior, harassment of an +standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. **Consequence**: A permanent ban from any sort of public interaction within @@ -116,7 +116,7 @@ the community. This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, available at -https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. +. Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity). @@ -124,5 +124,5 @@ enforcement ladder](https://github.com/mozilla/diversity). [homepage]: https://www.contributor-covenant.org For answers to common questions about this code of conduct, see the FAQ at -https://www.contributor-covenant.org/faq. Translations are available at -https://www.contributor-covenant.org/translations. +. Translations are available at +. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 47845c91a..1a9de4696 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -36,7 +36,7 @@ npm install ### Test It -**When you make a change, make sure the tests are passing**. In the ```tests/test-project``` directory there is a dummy VS Code extension we use to test the framework itself. +**When you make a change, make sure the tests are passing**. In the `tests/test-project` directory there is a dummy VS Code extension we use to test the framework itself. For that, you can use the following script and launch the tests: @@ -44,7 +44,7 @@ For that, you can use the following script and launch the tests: npm run test:build ``` -If you are adding a new feature, be sure to **write new tests** for it. If you navigate to the ```tests/test-project/src/test``` folder, you will find a test file structure that mirrors the source files. Put your new test into the appropriate existing file, or create a new one that follows the same structure. +If you are adding a new feature, be sure to **write new tests** for it. If you navigate to the `tests/test-project/src/test` folder, you will find a test file structure that mirrors the source files. Put your new test into the appropriate existing file, or create a new one that follows the same structure. ### Pull Requests @@ -66,12 +66,12 @@ Lastly, a pull request check on [Github Actions](../../actions) is going to kick - [ ] Check all related PR's were merged and the `Main CI` is green 1. Execute `npm run version` - - [ ] commit changes and open new PR - - [ ] wait for PR is approved and merged - - [ ] after merge, wait until `Main CI` is green + - [ ] commit changes and open new PR + - [ ] wait for PR is approved and merged + - [ ] after merge, wait until `Main CI` is green 2. Execute `npm run publish` - - [ ] create and push new `vX.X.X` tag - - [ ] create a new GitHub [release](../../releases) from a new `vX.X.X` tag (with generated release notes) + - [ ] create and push new `vX.X.X` tag + - [ ] create a new GitHub [release](../../releases) from a new `vX.X.X` tag (with generated release notes) ### Post publish tasks diff --git a/README.md b/README.md index b88199f2e..1bfc9385a 100644 --- a/README.md +++ b/README.md @@ -37,9 +37,9 @@ ### Requirements -|NodeJS|Visual Studio Code|Operating System| -|--|--|--| -|
18.x.xLTSLatest
✅❓❓
❓ Best-effort
|
min-max
1.87.x1.88.x1.89.x
|
LinuxWindowsmacOS
✅✅⚠ī¸
⚠ī¸ [Known Issues](KNOWN_ISSUES.md#macos-known-limitations-of-native-objects)
| +| NodeJS | Visual Studio Code | Operating System | +| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +|
18.x.xLTSLatest
✅❓❓
❓ Best-effort
|
min-max
1.87.x1.88.x1.89.x
|
LinuxWindowsmacOS
✅✅⚠ī¸
⚠ī¸ [Known Issues](KNOWN_ISSUES.md#macos-known-limitations-of-native-objects)
| #### NodeJS Support Policy diff --git a/SECURITY.md b/SECURITY.md index 258d33317..97a2f4410 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -6,14 +6,15 @@ We are supporting only the latest version of ExTester framework package. | Version | Supported | | ------- | ------------------ | -| latest | :white_check_mark: | +| latest | :white_check_mark: | ## Reporting a Vulnerability -If you discover a vulnerability in the ExTester project, we encourage you to report it to us so that we can address it promptly. +If you discover a vulnerability in the ExTester project, we encourage you to report it to us so that we can address it promptly. Here's how you can report a vulnerability: -1. Email the details of the vulnerability to djelinek@redhat.com. + +1. Email the details of the vulnerability to . 2. Include as much information as possible about the vulnerability, including steps to reproduce if applicable. 3. Please do not disclose the vulnerability publicly until we have had a chance to investigate and respond. diff --git a/docs/ActivityBar.md b/docs/ActivityBar.md index 21a90a49e..27767d89f 100644 --- a/docs/ActivityBar.md +++ b/docs/ActivityBar.md @@ -24,7 +24,7 @@ Find a view control/button in the activity bar by its title ```typescript // get Explorer view control -const controls = await activityBar.getViewControl('Explorer'); +const controls = await activityBar.getViewControl("Explorer"); ``` #### Get global actions @@ -40,7 +40,7 @@ const actions = await activityBar.getGlobalActions(); Find global actions button by title ```typescript -const actions = await activityBar.getGlobalAction('Manage'); +const actions = await activityBar.getGlobalAction("Manage"); ``` #### Open context menu diff --git a/docs/CodeCoverage.md b/docs/CodeCoverage.md index d722f823a..67672a6e1 100644 --- a/docs/CodeCoverage.md +++ b/docs/CodeCoverage.md @@ -1,61 +1,55 @@ -To generate a code coverage report using the [c8](https://github.com/bcoe/c8) tool, include the `-C` (or `--coverage`) -option when running tests via a CLI command -[`extest run-tests`](Test-Setup#set-up-and-run-tests) or +To generate a code coverage report using the [c8](https://github.com/bcoe/c8) tool, include the `-C` (or `--coverage`) +option when running tests via a CLI command +[`extest run-tests`](Test-Setup#set-up-and-run-tests) or [`extest setup-and-run`](Test-Setup#run-tests). ### Configuration Options -A configuration file can change the default behaviors of the c8 tool. The framework searches for c8 JSON configuration files named `.c8rc`, `.c8rc.json`, `.nycrc`, or `.nycrc.json`, starting from the root of your project and walking up the filesystem tree. You can check out what options are supported in the [c8 documentation](https://github.com/bcoe/c8?tab=readme-ov-file#cli-options--configuration). +A configuration file can change the default behaviors of the c8 tool. The framework searches for c8 JSON configuration files named `.c8rc`, `.c8rc.json`, `.nycrc`, or `.nycrc.json`, starting from the root of your project and walking up the filesystem tree. You can check out what options are supported in the [c8 documentation](https://github.com/bcoe/c8?tab=readme-ov-file#cli-options--configuration). If no c8 JSON configuration file is provided, the following default values will be used: ```typescript - const reportOptions: any = { - "reporter": ["text", "html"], - "all": false, - "excludeNodeModules": true, - "include": [], - "exclude": [ - "coverage/**", - "packages/*/test{,s}/**", - "**/*.d.ts", - "test{,s}/**", - "test{,-*}.{js,cjs,mjs,ts,tsx,jsx}", - "**/*{.,-}test.{js,cjs,mjs,ts,tsx,jsx}", - "**/__tests__/**", - "**/{ava,babel,nyc}.config.{js,cjs,mjs}", - "**/jest.config.{js,cjs,mjs,ts}", - "**/{karma,rollup,webpack}.config.js", - "**/.{eslint,mocha}rc.{js,cjs}" - ], - "extension": [ - ".js", - ".cjs", - ".mjs", - ".ts", - ".tsx", - ".jsx" - ], - "excludeAfterRemap": false, - "skipFull": false, - "tempDirectory": this.targetDir, - "resolve": "", - "omitRelative": true, - "allowExternal": false, - }; +const reportOptions: any = { + reporter: ["text", "html"], + all: false, + excludeNodeModules: true, + include: [], + exclude: [ + "coverage/**", + "packages/*/test{,s}/**", + "**/*.d.ts", + "test{,s}/**", + "test{,-*}.{js,cjs,mjs,ts,tsx,jsx}", + "**/*{.,-}test.{js,cjs,mjs,ts,tsx,jsx}", + "**/__tests__/**", + "**/{ava,babel,nyc}.config.{js,cjs,mjs}", + "**/jest.config.{js,cjs,mjs,ts}", + "**/{karma,rollup,webpack}.config.js", + "**/.{eslint,mocha}rc.{js,cjs}", + ], + extension: [".js", ".cjs", ".mjs", ".ts", ".tsx", ".jsx"], + excludeAfterRemap: false, + skipFull: false, + tempDirectory: this.targetDir, + resolve: "", + omitRelative: true, + allowExternal: false, +}; ``` + where `this.targetDir` is a directory created under the temporary directory specified with the `TMP` enviornment variable and coverage reports are saved under `./coverage`, where the `c8` tool stores outputs by default. ### Notes on loading source files when code coverage is enabled -With code coverage enabled, there's no need to build a vsix -file as sources will be loaded directly from your -project directory during testing. By default the framework does not build or install a vsix file when code coverage enabled by the CLI `-C` (or `--coverage`) option. +With code coverage enabled, there's no need to build a vsix +file as sources will be loaded directly from your +project directory during testing. By default the framework does not build or install a vsix file when code coverage enabled by the CLI `-C` (or `--coverage`) option. -However, in special situations where you need to load a vsix file in your test, for example, when you need to test the Extensions SideBar for installed extensions, add the `-u` (or `--uninstall_extension`) option. +However, in special situations where you need to load a vsix file in your test, for example, when you need to test the Extensions SideBar for installed extensions, add the `-u` (or `--uninstall_extension`) option. -When `-u` option is specified, the framework will build and install a vsix file prior to executing tests. After completing the test run, the framework uninstall the vsix file. +When `-u` option is specified, the framework will build and install a vsix file prior to executing tests. After completing the test run, the framework uninstall the vsix file. Keep in mind that even when using the `-u` option, source codes will still be sourced directly from your -project directory with code coverage enabled. \ No newline at end of file +project directory with code coverage enabled. diff --git a/docs/ContentAssist.md b/docs/ContentAssist.md index 592b938d6..7ca50de7d 100644 --- a/docs/ContentAssist.md +++ b/docs/ContentAssist.md @@ -12,9 +12,9 @@ const contentAssist = await new TextEditor().toggleContentAssist(true); ```typescript // find if an item with given label is present -const hasItem = await contentAssist.hasItem('Get'); +const hasItem = await contentAssist.hasItem("Get"); // get an item by label -const item = await contentAssist.getItem('Get'); +const item = await contentAssist.getItem("Get"); // get all visible items const items = await contentAssist.getItems(); ``` @@ -22,5 +22,5 @@ const items = await contentAssist.getItems(); #### Select an Item ```typescript -await contentAssist.getItem('Get').click(); +await contentAssist.getItem("Get").click(); ``` diff --git a/docs/ContextMenu.md b/docs/ContextMenu.md index 6106ce36c..e17746821 100644 --- a/docs/ContextMenu.md +++ b/docs/ContextMenu.md @@ -3,7 +3,7 @@ Page object for any context menu opened by left-clicking an element that has a c #### Open/Lookup -Typically, a context menu is opened by calling ```openContextMenu``` on elements that support it. For example: +Typically, a context menu is opened by calling `openContextMenu` on elements that support it. For example: ```typescript import { ActivityBar, ContextMenu } from 'vscode-extension-tester'; @@ -15,9 +15,9 @@ const menu = await new ActivityBar().openContextMenu(); ```typescript // find if an item with title exists -const exists = await menu.hasItem('Copy'); +const exists = await menu.hasItem("Copy"); // get a handle for an item -const item = await menu.getItem('Copy'); +const item = await menu.getItem("Copy"); // get all displayed items const items = await menu.getItems(); ``` @@ -26,7 +26,7 @@ const items = await menu.getItems(); ```typescript // recursively select an item in nested submenus -await menu.select('File', 'Preferences', 'Settings'); +await menu.select("File", "Preferences", "Settings"); // select an item that has a child submenu -const submenu = await menu.select('File', 'Preferences'); +const submenu = await menu.select("File", "Preferences"); ``` diff --git a/docs/CustomTreeSection.md b/docs/CustomTreeSection.md index 20c15e0e5..0b9ce614a 100644 --- a/docs/CustomTreeSection.md +++ b/docs/CustomTreeSection.md @@ -22,7 +22,7 @@ const welcome: WelcomeContentSection = await section.findWelcomeContent(); // get all the possible buttons and paragraphs in a list const contents = await welcome.getContents(); -// get all buttons +// get all buttons const btns = await welcome.getButtons(); // get paragraphs as strings in a list diff --git a/docs/DebugConsoleView.md b/docs/DebugConsoleView.md index 80cb5a01c..d2106540d 100644 --- a/docs/DebugConsoleView.md +++ b/docs/DebugConsoleView.md @@ -21,11 +21,11 @@ await debugView.clearText(); ```typescript // type an expression -await debugView.setExpression('expression'); +await debugView.setExpression("expression"); // evaluate an existing expression await debugView.evaluateExpression(); // type and evaluate an expression -await debugView.evaluateExpression('expression'); +await debugView.evaluateExpression("expression"); // get a handle for content assist const assist = await debugView.getContentAssist(); ``` diff --git a/docs/DebugView.md b/docs/DebugView.md index bca6be79b..8ed6fae24 100644 --- a/docs/DebugView.md +++ b/docs/DebugView.md @@ -4,7 +4,7 @@ ```typescript // open the view using the icon in the view container -const btn = await new ActivityBar().getViewControl('Run'); +const btn = await new ActivityBar().getViewControl("Run"); const debugView = (await btn.openView()) as DebugView; ``` @@ -16,7 +16,7 @@ const config = await debugView.getLaunchConfiguration(); // get titles of all available laynch configurations const configs = await debugView.getLaunchConfigurations(); // select launch configuration by title -await debugConfiguration.selectLaunchConfiguration('Test Launch'); +await debugConfiguration.selectLaunchConfiguration("Test Launch"); ``` #### Launch diff --git a/docs/Debugging-Tests.md b/docs/Debugging-Tests.md index df25f4df5..bc9f32704 100644 --- a/docs/Debugging-Tests.md +++ b/docs/Debugging-Tests.md @@ -2,18 +2,13 @@ Attaching a debugger from VS Code can be achieved with a launch configuration su ```json { - "name": "Debug UI Tests", - "type": "node", - "request": "launch", - "program": "${workspaceFolder}/node_modules/.bin/extest", - "args": [ - "setup-and-run", - "${workspaceFolder}/out/ui-test/*.js", - "--mocha_config", - "${workspaceFolder}/src/ui-test/.mocharc-debug.js" - ], - "console": "integratedTerminal", - "internalConsoleOptions": "neverOpen" + "name": "Debug UI Tests", + "type": "node", + "request": "launch", + "program": "${workspaceFolder}/node_modules/.bin/extest", + "args": ["setup-and-run", "${workspaceFolder}/out/ui-test/*.js", "--mocha_config", "${workspaceFolder}/src/ui-test/.mocharc-debug.js"], + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen" } ``` @@ -21,19 +16,14 @@ Sometimes Windows terminal has trouble interpreting the executable. If that happ ```json { - "name": "Debug UI Tests", - "type": "node", - "request": "launch", - "program": "${workspaceFolder}/node_modules/vscode-extension-tester/out/cli.js", - "args": [ - "setup-and-run", - "${workspaceFolder}/out/ui-test/*.js", - "--mocha_config", - "${workspaceFolder}/src/ui-test/.mocharc-debug.js" - ], - "console": "integratedTerminal", - "runtimeExecutable": "node", - "internalConsoleOptions": "neverOpen" + "name": "Debug UI Tests", + "type": "node", + "request": "launch", + "program": "${workspaceFolder}/node_modules/vscode-extension-tester/out/cli.js", + "args": ["setup-and-run", "${workspaceFolder}/out/ui-test/*.js", "--mocha_config", "${workspaceFolder}/src/ui-test/.mocharc-debug.js"], + "console": "integratedTerminal", + "runtimeExecutable": "node", + "internalConsoleOptions": "neverOpen" } ``` diff --git a/docs/DiffEditor.md b/docs/DiffEditor.md index 5c6fa8f83..87f5d80d3 100644 --- a/docs/DiffEditor.md +++ b/docs/DiffEditor.md @@ -4,7 +4,7 @@ ```typescript // through editors view -const diffEditor1 = await new EditorView().openEditor('editorTitle'); +const diffEditor1 = await new EditorView().openEditor("editorTitle"); // directly const diffEditor2 = new DiffEditor(); diff --git a/docs/EditorView.md b/docs/EditorView.md index be9395baa..f3e819a83 100644 --- a/docs/EditorView.md +++ b/docs/EditorView.md @@ -12,14 +12,14 @@ const editorView = new EditorView(); ```typescript // open editor tab by title -const editor = await editorView.openEditor('package.json'); +const editor = await editorView.openEditor("package.json"); ``` #### Closing Editors ```typescript // close an editor tab by title -await editorView.closeEditor('package.json'); +await editorView.closeEditor("package.json"); // close all open tabs await editorView.closeAllEditors(); ``` @@ -34,9 +34,9 @@ const titles = await editorView.getOpenEditorTitles(); ```typescript // find an editor action button by title -const button = await editorView.getAction('Open Changes'); +const button = await editorView.getAction("Open Changes"); // also works for multiple editor groups, select the group by index, starting with 0 from the left -const buttonFromSecondGroup = await editorView.getAction('More Actions...', 1); +const buttonFromSecondGroup = await editorView.getAction("More Actions...", 1); // get all visible action buttons, again you may specify a group index, default is 0 const buttons = await editorView.getActions(); ``` @@ -49,11 +49,10 @@ By default, all EditorView methods work with the first (left-most) editor group, ```typescript // open editor in the second group (from the left, using a zero-based index) -const editor = await editorView.openEditor('package.json', 1); - +const editor = await editorView.openEditor("package.json", 1); // close editor in the second group -await editorView.closeEditor('package.json', 1); +await editorView.closeEditor("package.json", 1); // close all editors in the second group (and the whole group) await editorView.closeAllEditors(1); @@ -85,7 +84,7 @@ There are two basic ways to get `EditorTab` objects, through `EditorView`/`Edito ```typescript // using EditorView, the same principle applies to EditorGroup // get tab by title from the first group -const tab = await editorView.getTabByTitle('Index.d.ts'); +const tab = await editorView.getTabByTitle("Index.d.ts"); // get all open tabs in a list const tabs = await editorView.getOpenTabs(); // get the active tab (or undefined if none is active) @@ -95,7 +94,7 @@ const active = await editorView.getActiveTab(); From an `Editor` instance: ```typescript -const editor = await editorView.openEditor('Index.d.ts'); +const editor = await editorView.openEditor("Index.d.ts"); const etab = await editorView.getTab(); ``` diff --git a/docs/ExtensionsViewSection.md b/docs/ExtensionsViewSection.md index d1d0ca581..e592c3cd1 100644 --- a/docs/ExtensionsViewSection.md +++ b/docs/ExtensionsViewSection.md @@ -22,17 +22,17 @@ Item lookup behaves in a completely different way to the tree sections. In this const items = await section.getVisibleItems(); // find an extension anywhere (including the marketplace) -const item = await section.findItem('npm'); +const item = await section.findItem("npm"); // clear the search bar so the original section reappears await section.clearSearch(); // find an extension in the installed section -const item2 = await section.findItem('@installed java'); +const item2 = await section.findItem("@installed java"); // clear the search bar so the original section reappears await section.clearSearch(); // open an item in the editor view -await section.openItem('@installed java'); +await section.openItem("@installed java"); ``` #### ExtensionsViewItem diff --git a/docs/FindWidget.md b/docs/FindWidget.md index 21e8b2438..e18338b87 100644 --- a/docs/FindWidget.md +++ b/docs/FindWidget.md @@ -44,13 +44,13 @@ await widget.replaceAll(); ```typescript // switch 'Match Case' on/off -await widget.toggleMatchCase(true/false); +await widget.toggleMatchCase(true / false); // switch 'Match Whole Word' on/off -await widget.toggleMatchWholeWord(true/false); +await widget.toggleMatchWholeWord(true / false); // switch 'Use Regular Expression' on/off -await widget.toggleUseRegularExpression(true/false); +await widget.toggleUseRegularExpression(true / false); // switch 'Preserve Case' on/off -await widget.togglePreserveCase(true/false); +await widget.togglePreserveCase(true / false); ``` #### Close diff --git a/docs/Home.md b/docs/Home.md index f934ef1d5..918fb3eed 100644 --- a/docs/Home.md +++ b/docs/Home.md @@ -3,7 +3,7 @@ ExTester is a package designed to help you run UI tests for your VS Code extensions using selenium-webdriver. Simply install it into your extension devDependencies to get started: -```npm install --save-dev vscode-extension-tester``` +`npm install --save-dev vscode-extension-tester` As such there are two major parts to the project: @@ -19,14 +19,14 @@ The first part automates all the steps necessary to launch UI tests of your VSCo Find more about the test setup: [[Test-Setup]] -See how to change the Mocha test runner configuration: [[Mocha-Configuration]] and +See how to change the Mocha test runner configuration: [[Mocha-Configuration]] and also how to enable code coverage using the c8 tool [[CodeCoverage]] Once the setup is complete, check out the sample test file: [[Writing-Simple-Tests]]. Debugging UI tests from VSCode: [[Debugging-Tests]] -*** +--- ## Page Object APIs @@ -34,7 +34,7 @@ Once your tests are set up and running, you may use the convenient page object A Find documentation about the Page Object APIs: [[Page-Object-APIs]]. -*** +--- ## Opening Files and Folders @@ -56,14 +56,14 @@ If you are running ExTester 4.2.0 or newer, the Simple Dialog is enabled by defa ```plain Files > Simple Dialog > Enable: True Window > Dialog Style: Custom -``` +``` You can also pass in custom options JSON file to the command that runs the tests using the `-o` flag. The property names are as follows: ```json { - "files.simpleDialog.enable": true, - "window.dialogStyle": "custom" + "files.simpleDialog.enable": true, + "window.dialogStyle": "custom" } ``` @@ -71,7 +71,7 @@ Invoking the open dialog (e.g. via `File > Open Folder...`) then opens an input ```typescript const input = await InputBox.create(); -await input.setText('/path/to/your/folder/'); +await input.setText("/path/to/your/folder/"); await input.confirm(); ``` diff --git a/docs/Input.md b/docs/Input.md index 8b095543e..b5727542d 100644 --- a/docs/Input.md +++ b/docs/Input.md @@ -56,7 +56,7 @@ const input = new QuickOpenBox(); // get text in the input box const text = await input.getText(); // replace text in the input box with a string -await input.setText('amazing text'); +await input.setText("amazing text"); // get the placeholder text const placeholder = await input.getPlaceHolder(); ``` @@ -72,10 +72,10 @@ await input.cancel(); const picks = await input.getQuickPicks(); // search for a quick pick item by text or index and click it (includes scrolling if item is not visible) await input.selectQuickPick(1); -await input.selectQuickPick('Input.d.ts'); +await input.selectQuickPick("Input.d.ts"); // search for a quick pick item and get a handle for it (includes scrolling if item is not visible) const pick = await input.findQuickPick(1); -const pick2 = await input.findQuickPick('Input.d.ts'); +const pick2 = await input.findQuickPick("Input.d.ts"); ``` #### Progress @@ -98,7 +98,7 @@ await input.back(); ### QuickPickItem -Page objects retrieved when calling ```getQuickPick``` +Page objects retrieved when calling `getQuickPick` ```typescript const picks = await input.getQuickPicks(); diff --git a/docs/Mocha-Configuration.md b/docs/Mocha-Configuration.md index 2cfc847db..38d7ac2de 100644 --- a/docs/Mocha-Configuration.md +++ b/docs/Mocha-Configuration.md @@ -32,11 +32,11 @@ An example config.ts file might look like this: import { MochaOptions } from "vscode-extension-tester"; const options: MochaOptions = { - reporter: 'spec', - slow: 75, - timeout: 2000, - ui: 'bdd' -} + reporter: "spec", + slow: 75, + timeout: 2000, + ui: "bdd", +}; export default options; ``` diff --git a/docs/ModalDialog.md b/docs/ModalDialog.md index dc38faf75..cea2996e8 100644 --- a/docs/ModalDialog.md +++ b/docs/ModalDialog.md @@ -18,12 +18,12 @@ const message = await dialog.getMessage(); const details = await dialog.getDetails(); // get the button web elements -const buttons = await dialog.getButtons() +const buttons = await dialog.getButtons(); ``` #### Push a button ```typescript // push button with a given title -await dialog.pushButton('Save All'); +await dialog.pushButton("Save All"); ``` diff --git a/docs/Notification.md b/docs/Notification.md index fb0423927..8efc8d49b 100644 --- a/docs/Notification.md +++ b/docs/Notification.md @@ -2,7 +2,7 @@ #### Lookup -To get notifications outside the notifications center, one should use a ```Workbench``` object: +To get notifications outside the notifications center, one should use a `Workbench` object: ```typescript import { Workbench } from 'vscode-extension-tester'; @@ -34,5 +34,5 @@ const actions = await notification.getActions(); // get an action's title (text) const title = actions[0].getTitle(); // take action (i.e. click a button with title) -await notification.takeAction('Install All'); +await notification.takeAction("Install All"); ``` diff --git a/docs/NotificationsCenter.md b/docs/NotificationsCenter.md index 6476af998..2484c66b1 100644 --- a/docs/NotificationsCenter.md +++ b/docs/NotificationsCenter.md @@ -2,7 +2,7 @@ #### Lookup -To open the notifications center, use either ```Workbench``` or ```StatusBar``` object: +To open the notifications center, use either `Workbench` or `StatusBar` object: ```typescript import { Workbench, StatusBar, NotificationType } from 'vscode-extension-tester'; diff --git a/docs/OutputView.md b/docs/OutputView.md index 0a6f5e08e..1e200c7cc 100644 --- a/docs/OutputView.md +++ b/docs/OutputView.md @@ -23,5 +23,5 @@ await outputView.clearText(); // get names of all available channels const names = await outputView.getChannelNames(); // select a channel from the drop box by name -await outputView.selectChannel('Git'); +await outputView.selectChannel("Git"); ``` diff --git a/docs/Page-Object-APIs.md b/docs/Page-Object-APIs.md index e7f479b1c..3cc0c8290 100644 --- a/docs/Page-Object-APIs.md +++ b/docs/Page-Object-APIs.md @@ -6,7 +6,7 @@ See the individual page object pages below for quick usage guide. - [[ActionsControl]] - [[ActivityBar]] -- [[ViewContol]] +- [[ViewControl]] ##### Bottom Bar diff --git a/docs/ProblemsView.md b/docs/ProblemsView.md index acb5bd8de..c230d85c0 100644 --- a/docs/ProblemsView.md +++ b/docs/ProblemsView.md @@ -13,7 +13,7 @@ const problemsView = await new BottomBarPanel().openProblemsView(); Fill in a string into the filter box. ```typescript -await problemsView.setFilter('**/filter/glob*'); +await problemsView.setFilter("**/filter/glob*"); ``` #### Collapse All Markers diff --git a/docs/ScmView.md b/docs/ScmView.md index f03fb19e6..7250da9f8 100644 --- a/docs/ScmView.md +++ b/docs/ScmView.md @@ -5,7 +5,7 @@ The best way to open the view is to use the activity bar on the left. It might take a second for the view to initialize, so make sure to account for it. ```typescript -view = await new ActivityBar().getViewControl('Source Control').openView() as ScmView; +view = (await new ActivityBar().getViewControl("Source Control").openView()) as ScmView; ``` #### Actions @@ -14,7 +14,7 @@ In an SCM view we can do two things: initialize a repository if none exists, or ```typescript // get a provider (repository) by title -const provider = await view.getProvider('vscode-extension-tester'); +const provider = await view.getProvider("vscode-extension-tester"); // get all providers (useful if you have multiple repositories open) const providers = await view.getProviders(); // initialize repository if none is present in current workspace @@ -42,13 +42,13 @@ There are several buttons to push and an input field to fill, which you can do a ```typescript // click an action button (the buttons are either on the title part on the top for a single repo, or next to the provider title for multiple repos). For instance, refresh: -await provider.takeAction('Refresh'); +await provider.takeAction("Refresh"); // click the `More Actions` button to open a context menu with all the available commands const contextmenu = await provider.openMoreActions(); // Fill in the commit message and make a commit -await provider.commitChanges('Commit message'); +await provider.commitChanges("Commit message"); ``` #### Handling Changes @@ -83,5 +83,5 @@ const expanded = await change.isExpanded(); await change.toggleExpand(true); // or false to collapse it :) // use on of the action buttons for the item, e.g. stage -await change.takeAction('Stage Changes'); +await change.takeAction("Stage Changes"); ``` diff --git a/docs/Setting.md b/docs/Setting.md index f7253012c..640328fec 100644 --- a/docs/Setting.md +++ b/docs/Setting.md @@ -36,7 +36,7 @@ All setting types share the same functions to manipulate their values, however t const value = await setting.getValue(); // generic setting of a value -await setting.setValue('off'); +await setting.setValue("off"); ``` ##### Setting Value Types diff --git a/docs/SettingsEditor.md b/docs/SettingsEditor.md index 647645232..8afbec0d0 100644 --- a/docs/SettingsEditor.md +++ b/docs/SettingsEditor.md @@ -16,10 +16,10 @@ Search for a setting with a given name and category, see more about the [[Settin ```typescript // look for a setting named 'Auto Save' under 'Editor' category -const setting = await settingsEditor.findSetting('Auto Save', 'Editor'); +const setting = await settingsEditor.findSetting("Auto Save", "Editor"); // find a setting in nested categories, e.g. 'Enable' in 'Files' > 'Simple Dialog' -const setting1 = await settingsEditor.findSetting('Enable', 'Files', 'Simple Dialog'); +const setting1 = await settingsEditor.findSetting("Enable", "Files", "Simple Dialog"); ``` #### Switch Settings Perspectives @@ -28,5 +28,5 @@ VSCode has two perspectives for its settings: 'User' and 'Workspace'. If your VS ```typescript // switch to Workspace perspective -await settingsEditor.switchToPerspective('Workspace'); +await settingsEditor.switchToPerspective("Workspace"); ``` diff --git a/docs/SideBarView.md b/docs/SideBarView.md index 531359eac..de2cc0b2a 100644 --- a/docs/SideBarView.md +++ b/docs/SideBarView.md @@ -3,11 +3,11 @@ #### Lookup ```typescript -import { ActivityBar, SideBarView } from 'vscode-extension-tester'; +import { ActivityBar, SideBarView } from "vscode-extension-tester"; // to look up the currently open view (if any is open) const view = new SideBarView(); // to open a specific view and look it up -const control = await new ActivityBar().getViewControl('Explorer'); +const control = await new ActivityBar().getViewControl("Explorer"); const view1 = await control.openView(); ``` diff --git a/docs/StatusBar.md b/docs/StatusBar.md index 6b0a7d25c..e5edfb1ac 100644 --- a/docs/StatusBar.md +++ b/docs/StatusBar.md @@ -46,7 +46,7 @@ const posString = await statusbar.getCurrentPosition(); ```typescript // find a status item by title -const item = await statusbar.getItem('Select Encoding'); +const item = await statusbar.getItem("Select Encoding"); // get all status items as web elements const items = await statusbar.getItems(); ``` diff --git a/docs/Taking Screenshots.md b/docs/Taking Screenshots.md index 6558d5857..5bdbde5e0 100644 --- a/docs/Taking Screenshots.md +++ b/docs/Taking Screenshots.md @@ -5,7 +5,7 @@ Screenshot can be captured by calling: VSBrowser.instance.takeScreenshot(basename: string) ``` -Captured screenshots will be saved in *screenshots* folder which can be found in a test storage folder (`$TMPDIR/test-resources` by default). +Captured screenshots will be saved in _screenshots_ folder which can be found in a test storage folder (`$TMPDIR/test-resources` by default). File name will be generated from given basename in the following format: `${basename}.png`. #### Mocha integration @@ -17,5 +17,5 @@ must import vscode-extension-tester hook. ```typescript // Supported hooks: before, beforeEach, after and afterEach -import { before } from 'vscode-extension-tester' +import { before } from "vscode-extension-tester"; ``` diff --git a/docs/TerminalView.md b/docs/TerminalView.md index a3204c1ca..02becb6ee 100644 --- a/docs/TerminalView.md +++ b/docs/TerminalView.md @@ -14,13 +14,13 @@ const terminalView = await new BottomBarPanel().openTerminalView(); // get names of all available terminals const names = await terminalView.getChannelNames(); // select a terminal from the drop box by name -await terminalView.selectChannel('Git'); +await terminalView.selectChannel("Git"); ``` #### Execute Commands ```typescript -await terminalView.executeCommand('git status'); +await terminalView.executeCommand("git status"); ``` #### Get Text diff --git a/docs/Test-Setup.md b/docs/Test-Setup.md index 905fc2551..ef4f36516 100644 --- a/docs/Test-Setup.md +++ b/docs/Test-Setup.md @@ -4,19 +4,19 @@ The ExTester offers both CLI and API to perform all the setup actions. That way - `CODE_VERSION` - can be used to set version of VS Code you want to run with the appropriate ChromeDriver version - ```shell - export CODE_VERSION="1.84.2" - ``` + ```shell + export CODE_VERSION="1.84.2" + ``` - `TEST_RESOURCES` - can be used to set folder used for all test resources, by default `$TMPDIR/test-resources` (value of `$TMPDIR` differs based on operating system) - ```shell - export TEST_RESOURCES="./test-folder" - ``` + ```shell + export TEST_RESOURCES="./test-folder" + ``` ## Using the CLI -All the CLI actions are available with the command ```extest``` which is available to your npm scripts once the package is installed. The default storage folder for all test resources is a ```$TMPDIR/test-resources```. +All the CLI actions are available with the command `extest` which is available to your npm scripts once the package is installed. The default storage folder for all test resources is a `$TMPDIR/test-resources`. ### Download VS Code @@ -154,100 +154,102 @@ Options: ## Using the API -The same actions are available in the ```ExTester``` class as API: +The same actions are available in the `ExTester` class as API: ```typescript export interface SetupOptions { - /** version of VS Code to test against, defaults to latest */ - vscodeVersion?: string; - /** when true run `vsce package` with the `--yarn` flag */ - useYarn?: boolean; - /** install the extension's dependencies from the marketplace. Defaults to `false`. */ - installDependencies?: boolean; + /** version of VS Code to test against, defaults to latest */ + vscodeVersion?: string; + /** when true run `vsce package` with the `--yarn` flag */ + useYarn?: boolean; + /** install the extension's dependencies from the marketplace. Defaults to `false`. */ + installDependencies?: boolean; } export declare const DEFAULT_SETUP_OPTIONS: { - vscodeVersion: string; - installDependencies: boolean; + vscodeVersion: string; + installDependencies: boolean; }; export interface RunOptions { - /** version of VS Code to test against, defaults to latest */ - vscodeVersion?: string; - /** path to custom settings json file */ - settings?: string; - /** remove the extension's directory as well (if present) */ - cleanup?: boolean; - /** path to a custom mocha configuration file */ - config?: string; - /** logging level of the webdriver */ - logLevel?: VSBrowserLogLevel; - /** try to perform all setup without internet connection, needs all requirements pre-downloaded manually */ - offline?: boolean; + /** version of VS Code to test against, defaults to latest */ + vscodeVersion?: string; + /** path to custom settings json file */ + settings?: string; + /** remove the extension's directory as well (if present) */ + cleanup?: boolean; + /** path to a custom mocha configuration file */ + config?: string; + /** logging level of the webdriver */ + logLevel?: VSBrowserLogLevel; + /** try to perform all setup without internet connection, needs all requirements pre-downloaded manually */ + offline?: boolean; } /** defaults for the [[RunOptions]] */ export declare const DEFAULT_RUN_OPTIONS: { - vscodeVersion: 'latest', - settings: '', - logLevel: logging.Level.INFO, - offline: false + vscodeVersion: "latest"; + settings: ""; + logLevel: logging.Level.INFO; + offline: false; }; /** * ExTester */ export declare class ExTester { - private code; - private chrome; - constructor(storageFolder?: string, releaseType?: ReleaseQuality, extensionsDir?: string); - /** - * Download VS Code of given version and release quality stream - * @param version version to download, default latest - */ - downloadCode(version?: string): Promise; - /** - * Install the extension into the test instance of VS Code - * @param vsixFile path to extension .vsix file. If not set, default vsce path will be used - * @param useYarn when true run `vsce package` with the `--yarn` flag - */ - installVsix({ vsixFile, useYarn }?: { - vsixFile?: string; - useYarn?: boolean; - }): Promise; - /** - * Install an extension from VS Code marketplace into the test instance - * @param id id of the extension to install - */ - installFromMarketplace(id: string): Promise; - /** - * Download the matching chromedriver for a given VS Code version - * @param vscodeVersion selected versio nof VS Code, default latest - */ - downloadChromeDriver(vscodeVersion?: string): Promise; - /** - * Performs all necessary setup: getting VS Code + ChromeDriver - * and packaging/installing extension into the test instance - * - * @param options Additional options for setting up the tests - */ - setupRequirements(options?: SetupOptions): Promise; - /** - * Performs requirements setup and runs extension tests - * - * @param testFilesPattern glob pattern for test files to run - * @param vscodeVersion version of VS Code to test against, defaults to latest - * @param setupOptions Additional options for setting up the tests - * @param runOptions Additional options for running the tests - * - * @returns Promise resolving to the mocha process exit code - 0 for no failures, 1 otherwise - */ - setupAndRunTests(testFilesPattern: string, vscodeVersion?: string, setupOptions?: Omit, runOptions?: Omit): Promise; - /** - * Runs the selected test files in VS Code using mocha and webdriver - * @param testFilesPattern glob pattern for selected test files - * @param runOptions Additional options for running the tests - * - * @returns Promise resolving to the mocha process exit code - 0 for no failures, 1 otherwise - */ - runTests(testFilesPattern: string, runOptions?: RunOptions): Promise; + private code; + private chrome; + constructor(storageFolder?: string, releaseType?: ReleaseQuality, extensionsDir?: string); + /** + * Download VS Code of given version and release quality stream + * @param version version to download, default latest + */ + downloadCode(version?: string): Promise; + /** + * Install the extension into the test instance of VS Code + * @param vsixFile path to extension .vsix file. If not set, default vsce path will be used + * @param useYarn when true run `vsce package` with the `--yarn` flag + */ + installVsix({ vsixFile, useYarn }?: { vsixFile?: string; useYarn?: boolean }): Promise; + /** + * Install an extension from VS Code marketplace into the test instance + * @param id id of the extension to install + */ + installFromMarketplace(id: string): Promise; + /** + * Download the matching chromedriver for a given VS Code version + * @param vscodeVersion selected versio nof VS Code, default latest + */ + downloadChromeDriver(vscodeVersion?: string): Promise; + /** + * Performs all necessary setup: getting VS Code + ChromeDriver + * and packaging/installing extension into the test instance + * + * @param options Additional options for setting up the tests + */ + setupRequirements(options?: SetupOptions): Promise; + /** + * Performs requirements setup and runs extension tests + * + * @param testFilesPattern glob pattern for test files to run + * @param vscodeVersion version of VS Code to test against, defaults to latest + * @param setupOptions Additional options for setting up the tests + * @param runOptions Additional options for running the tests + * + * @returns Promise resolving to the mocha process exit code - 0 for no failures, 1 otherwise + */ + setupAndRunTests( + testFilesPattern: string, + vscodeVersion?: string, + setupOptions?: Omit, + runOptions?: Omit, + ): Promise; + /** + * Runs the selected test files in VS Code using mocha and webdriver + * @param testFilesPattern glob pattern for selected test files + * @param runOptions Additional options for running the tests + * + * @returns Promise resolving to the mocha process exit code - 0 for no failures, 1 otherwise + */ + runTests(testFilesPattern: string, runOptions?: RunOptions): Promise; } ``` diff --git a/docs/TextEditor.md b/docs/TextEditor.md index 61ef677a7..8eb107f5b 100644 --- a/docs/TextEditor.md +++ b/docs/TextEditor.md @@ -28,13 +28,13 @@ const numberOfLines = await editor.getNumberOfLines(); ```typescript // replace all text with a string -await editor.setText('my fabulous text'); +await editor.setText("my fabulous text"); // replace text at a given line number -await editor.setTextAtLine(1, 'my fabulous line'); +await editor.setTextAtLine(1, "my fabulous line"); // type text at the current coordinates -await editor.typeText('I have the best text'); +await editor.typeText("I have the best text"); // type text starting at given coordinates (line, column) -await editor.typeTextAt(1, 3, ' absolutely'); +await editor.typeTextAt(1, 3, " absolutely"); // format the whole document with built-in tools await editor.formatDocument(); // get the current cursor coordinates as number array [x,y] @@ -71,9 +71,9 @@ await editor.toggleContentAssist(false); ```typescript // get line number that contains some text -const lineNum = await editor.getLineOfText('some text'); +const lineNum = await editor.getLineOfText("some text"); // find and select text -await editor.selectText('some text'); +await editor.selectText("some text"); // get selected text as string const text = await editor.getSelectedText(); // get selection block as a page object @@ -93,7 +93,7 @@ await editor.toggleBreakpoint(1); ```typescript // get a code lens by (partial) text -const lens = await editor.getCodeLens('my code lens text'); +const lens = await editor.getCodeLens("my code lens text"); // get code lens by index (zero-based from the top of the editor) const firstLens = await editor.getCodeLens(0); // get all code lenses diff --git a/docs/TitleBar.md b/docs/TitleBar.md index e6304f0aa..ac23a32e6 100644 --- a/docs/TitleBar.md +++ b/docs/TitleBar.md @@ -14,9 +14,9 @@ const titleBar = new TitleBar(); ```typescript // find if an item with title exists -const exists = await titleBar.hasItem('File'); +const exists = await titleBar.hasItem("File"); // get a handle for an item -const item = await titleBar.getItem('File'); +const item = await titleBar.getItem("File"); // get all displayed items const items = await titleBar.getItems(); ``` diff --git a/docs/TitleBarItem.md b/docs/TitleBarItem.md index 8e124e751..4090bec41 100644 --- a/docs/TitleBarItem.md +++ b/docs/TitleBarItem.md @@ -3,10 +3,10 @@ #### Lookup ```typescript -import { TitleBar } from 'vscode-extension-tester'; +import { TitleBar } from "vscode-extension-tester"; // get an item from the title bar -const item = await new TitleBar().getItem('File'); +const item = await new TitleBar().getItem("File"); ``` #### Select the Item diff --git a/docs/ViewContent.md b/docs/ViewContent.md index 9221e02ec..9a1758fb0 100644 --- a/docs/ViewContent.md +++ b/docs/ViewContent.md @@ -12,7 +12,7 @@ const contentPart = new SideBarView().getContent(); ```typescript // get a section by title, case insensitive -const section = await contentPart.getSection('Open Editors'); +const section = await contentPart.getSection("Open Editors"); // get all sections const sections = await contentPart.getSections(); ``` diff --git a/docs/ViewContol.md b/docs/ViewControl.md similarity index 100% rename from docs/ViewContol.md rename to docs/ViewControl.md diff --git a/docs/ViewItem.md b/docs/ViewItem.md index 962ddf4af..ef8730ea6 100644 --- a/docs/ViewItem.md +++ b/docs/ViewItem.md @@ -2,7 +2,7 @@ #### Lookup -The best way to get an item reference is to use the ```findItem``` method from ```ViewSection```. +The best way to get an item reference is to use the `findItem` method from `ViewSection`. ```typescript const viewSection = ...; @@ -13,7 +13,7 @@ const item = await viewSection.findItem('package.json'); ```typescript // get item's label -const label = item.getLabel() +const label = item.getLabel(); // find if the item can be expanded const isExpandable = await item.isExpandable(); // try to expand the item and find if it has children diff --git a/docs/ViewSection.md b/docs/ViewSection.md index 78e0737d9..6655cb991 100644 --- a/docs/ViewSection.md +++ b/docs/ViewSection.md @@ -31,7 +31,7 @@ Section header may also contain some action buttons. ```typescript // get an action button by label -const action = await section.getAction('New File'); +const action = await section.getAction("New File"); // get all action buttons for the section const actions = await section.getActions(); // click an action button @@ -44,11 +44,10 @@ await action.click(); // get all visible items, note that currently not shown on screen will not be retrieved const visibleItems = await section.getVisibleItems(); // find an item with a given label, involves scrolling to items currently not showing -const item = await section.findItem('package.json'); +const item = await section.findItem("package.json"); // recursively navigate to an item and click it - // if the item has children (./src/webdriver/components folder) - const children = await section.openItem('src', 'webdriver', 'components'); - // if the item is a leaf - await section.openItem('src', 'webdriver', 'components', 'AbstractElement.ts'); - +// if the item has children (./src/webdriver/components folder) +const children = await section.openItem("src", "webdriver", "components"); +// if the item is a leaf +await section.openItem("src", "webdriver", "components", "AbstractElement.ts"); ``` diff --git a/docs/ViewTitlePart.md b/docs/ViewTitlePart.md index e4a229350..4b509d90e 100644 --- a/docs/ViewTitlePart.md +++ b/docs/ViewTitlePart.md @@ -20,7 +20,7 @@ Some views have action buttons in their title part. ```typescript // get action button by title -const button = await titlePart.getActionButton('Clear'); +const button = await titlePart.getActionButton("Clear"); // get all action buttons const buttons = await titlePart.getActionButtons(); // click a button diff --git a/docs/WebView.md b/docs/WebView.md index 0589d13d0..48cfd9b7c 100644 --- a/docs/WebView.md +++ b/docs/WebView.md @@ -26,7 +26,7 @@ await webview.switchBack(); #### Searching for Elements Inside a Web View -Make sure when searching for and manipulating with elements inside (or outside) the web view that you have switched webdriver to the appropriate context. Also, be aware that referencing an element from the default window while switched to the web view (and vice versa) will throw a `StaleElementReference` error. +Make sure when searching for and manipulating with elements inside (or outside) the web view that you have switched webdriver to the appropriate context. Also, be aware that referencing an element from the default window while switched to the web view (and vice versa) will throw a `StaleElementReference` error. ```typescript // first, switch inside the web view diff --git a/docs/Workbench.md b/docs/Workbench.md index b474cbd4c..c97fad418 100644 --- a/docs/Workbench.md +++ b/docs/Workbench.md @@ -3,7 +3,7 @@ Workbench is the container for all the other elements. As such it mainly offers #### Lookup ```typescript -import { Workbench } from 'vscode-extension-tester'; +import { Workbench } from 'vscode-extension-tester'; ... const workbench = new Workbench(); ``` @@ -35,7 +35,7 @@ const center = workbench.openNotificationsCenter(); #### Command Prompt -You can also use ```Workbench``` to open the command prompt and execute commands. +You can also use `Workbench` to open the command prompt and execute commands. ```typescript // open command prompt, can then be handled as a QuickOpenBox @@ -43,7 +43,7 @@ const commandInput = await workbench.openCommandPrompt(); /* open command prompt and execute a command in it, the text does not need to be a perfect match uses VS Code's fuzzy search to find the best match */ -await workbench.executeCommand('close workspace'); +await workbench.executeCommand("close workspace"); ``` #### Settings diff --git a/docs/Writing-Simple-Tests.md b/docs/Writing-Simple-Tests.md index 7853e8726..405cff69c 100644 --- a/docs/Writing-Simple-Tests.md +++ b/docs/Writing-Simple-Tests.md @@ -3,25 +3,25 @@ ExTester is integrated with Mocha framework (as such requires Mocha 5.2+ to be p This is what a really simple test case might look like. Note that here we are only using pure webdriver. To use the provided page objects, see the [[Page-Object-APIs]]. ```typescript -import { assert } from 'chai'; +import { assert } from "chai"; // import the webdriver and the high level browser wrapper -import { VSBrowser, WebDriver } from 'vscode-extension-tester'; +import { VSBrowser, WebDriver } from "vscode-extension-tester"; // Create a Mocha suite -describe('My Test Suite', () => { +describe("My Test Suite", () => { let browser: VSBrowser; - let driver: WebDriver - + let driver: WebDriver; + // initialize the browser and webdriver before(async () => { browser = VSBrowser.instance; driver = browser.driver; }); - + // test whatever we want using webdriver, here we are just checking the page title - it('My Test Case', async () => { + it("My Test Case", async () => { const title = await driver.getTitle(); - assert.equal(title, 'whatever'); + assert.equal(title, "whatever"); }); }); ``` diff --git a/examples/helloworld-extester/.eslintrc.json b/examples/helloworld-extester/.eslintrc.json index c4f4ccb9c..f23e269aa 100644 --- a/examples/helloworld-extester/.eslintrc.json +++ b/examples/helloworld-extester/.eslintrc.json @@ -1,40 +1,30 @@ { - "parser": "@typescript-eslint/parser", - "plugins": [ - "@typescript-eslint" + "parser": "@typescript-eslint/parser", + "plugins": ["@typescript-eslint"], + "root": true, + "rules": { + "@typescript-eslint/member-delimiter-style": [ + "warn", + { + "multiline": { + "delimiter": "semi", + "requireLast": true + }, + "singleline": { + "delimiter": "semi", + "requireLast": false + } + } ], - "root": true, - "rules": { - "@typescript-eslint/member-delimiter-style": [ - "warn", - { - "multiline": { - "delimiter": "semi", - "requireLast": true - }, - "singleline": { - "delimiter": "semi", - "requireLast": false - } - } - ], - "@typescript-eslint/naming-convention": "warn", - "@typescript-eslint/no-unused-expressions": "off", - "@typescript-eslint/semi": [ - "warn", - "always" - ], - "curly": "warn", - "eqeqeq": [ - "warn", - "always" - ], - "no-redeclare": "warn", - "no-throw-literal": "warn", - "no-unused-expressions": "off", - "semi": "off" - }, - "ignorePatterns": [ - "src/ui-test/resources/*" - ] + "@typescript-eslint/naming-convention": "warn", + "@typescript-eslint/no-unused-expressions": "off", + "@typescript-eslint/semi": ["warn", "always"], + "curly": "warn", + "eqeqeq": ["warn", "always"], + "no-redeclare": "warn", + "no-throw-literal": "warn", + "no-unused-expressions": "off", + "semi": "off" + }, + "ignorePatterns": ["src/ui-test/resources/*"] } diff --git a/examples/helloworld-extester/.mocharc.js b/examples/helloworld-extester/.mocharc.js index 5438d5c0d..8db6d5b1c 100644 --- a/examples/helloworld-extester/.mocharc.js +++ b/examples/helloworld-extester/.mocharc.js @@ -1,4 +1,4 @@ // increase default test case timeout to 5 seconds module.exports = { - timeout: 5000 -} \ No newline at end of file + timeout: 5000, +}; diff --git a/examples/helloworld-extester/.vscode/launch.json b/examples/helloworld-extester/.vscode/launch.json index 984dd64c1..0f680305a 100644 --- a/examples/helloworld-extester/.vscode/launch.json +++ b/examples/helloworld-extester/.vscode/launch.json @@ -3,38 +3,34 @@ // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 { - "version": "0.2.0", - "configurations": [ - { - "name": "Debug UI Tests", - "type": "node", - "request": "launch", - "program": "${workspaceFolder}/node_modules/.bin/extest", - "args": [ - "setup-and-run", - "${workspaceFolder}/out/ui-test/*-test.js", - "--code_settings", - "settings.json", - "--extensions_dir", - ".test-extensions", - "--mocha_config", - "${workspaceFolder}/src/ui-test/.mocharc-debug.js" - ], - "console": "integratedTerminal", - "internalConsoleOptions": "neverOpen" - }, - { - "name": "Run Extension", - "type": "extensionHost", - "request": "launch", - "runtimeExecutable": "${execPath}", - "args": [ - "--extensionDevelopmentPath=${workspaceFolder}" - ], - "outFiles": [ - "${workspaceFolder}/out/**/*.js" - ], - "preLaunchTask": "npm: watch" - } - ] + "version": "0.2.0", + "configurations": [ + { + "name": "Debug UI Tests", + "type": "node", + "request": "launch", + "program": "${workspaceFolder}/node_modules/.bin/extest", + "args": [ + "setup-and-run", + "${workspaceFolder}/out/ui-test/*-test.js", + "--code_settings", + "settings.json", + "--extensions_dir", + ".test-extensions", + "--mocha_config", + "${workspaceFolder}/src/ui-test/.mocharc-debug.js" + ], + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen" + }, + { + "name": "Run Extension", + "type": "extensionHost", + "request": "launch", + "runtimeExecutable": "${execPath}", + "args": ["--extensionDevelopmentPath=${workspaceFolder}"], + "outFiles": ["${workspaceFolder}/out/**/*.js"], + "preLaunchTask": "npm: watch" + } + ] } diff --git a/examples/helloworld-extester/.vscode/tasks.json b/examples/helloworld-extester/.vscode/tasks.json index 241aa6d99..078ff7e01 100644 --- a/examples/helloworld-extester/.vscode/tasks.json +++ b/examples/helloworld-extester/.vscode/tasks.json @@ -1,20 +1,20 @@ // See https://go.microsoft.com/fwlink/?LinkId=733558 // for the documentation about the tasks.json format { - "version": "2.0.0", - "tasks": [ - { - "type": "npm", - "script": "watch", - "problemMatcher": "$tsc-watch", - "isBackground": true, - "presentation": { - "reveal": "never" - }, - "group": { - "kind": "build", - "isDefault": true - } - } - ] -} \ No newline at end of file + "version": "2.0.0", + "tasks": [ + { + "type": "npm", + "script": "watch", + "problemMatcher": "$tsc-watch", + "isBackground": true, + "presentation": { + "reveal": "never" + }, + "group": { + "kind": "build", + "isDefault": true + } + } + ] +} diff --git a/examples/helloworld-extester/package-lock.json b/examples/helloworld-extester/package-lock.json index 06aeec029..2e0a7ac6f 100644 --- a/examples/helloworld-extester/package-lock.json +++ b/examples/helloworld-extester/package-lock.json @@ -1,5176 +1,5176 @@ { - "name": "helloworld-extester", - "version": "0.1.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "helloworld-extester", - "version": "0.1.0", - "license": "Apache-2.0", - "devDependencies": { - "@types/chai": "^4.3.14", - "@types/mocha": "^10.0.6", - "@types/node": "^20.12.7", - "@types/vscode": "^1.85.0", - "@typescript-eslint/eslint-plugin": "^7.7.1", - "chai": "^4.4.1", - "eslint": "^8.57.0", - "mocha": "^10.4.0", - "typescript": "^5.4.5", - "vscode-extension-tester": "^8.1.0" - }, - "engines": { - "vscode": "^1.85.0" - } - }, - "node_modules/@aashutoshrathi/word-wrap": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", - "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@azure/abort-controller": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-1.1.0.tgz", - "integrity": "sha512-TrRLIoSQVzfAJX9H1JeFjzAoDGcoK1IYX1UImfceTZpsyYfWr09Ss1aHW1y5TrrR3iq6RZLBwJ3E24uwPhwahw==", - "dev": true, - "dependencies": { - "tslib": "^2.2.0" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/@azure/core-auth": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.7.2.tgz", - "integrity": "sha512-Igm/S3fDYmnMq1uKS38Ae1/m37B3zigdlZw+kocwEhh5GjyKjPrXKO2J6rzpC1wAxrNil/jX9BJRqBshyjnF3g==", - "dev": true, - "dependencies": { - "@azure/abort-controller": "^2.0.0", - "@azure/core-util": "^1.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@azure/core-auth/node_modules/@azure/abort-controller": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", - "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==", - "dev": true, - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@azure/core-client": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@azure/core-client/-/core-client-1.9.2.tgz", - "integrity": "sha512-kRdry/rav3fUKHl/aDLd/pDLcB+4pOFwPPTVEExuMyaI5r+JBbMWqRbCY1pn5BniDaU3lRxO9eaQ1AmSMehl/w==", - "dev": true, - "dependencies": { - "@azure/abort-controller": "^2.0.0", - "@azure/core-auth": "^1.4.0", - "@azure/core-rest-pipeline": "^1.9.1", - "@azure/core-tracing": "^1.0.0", - "@azure/core-util": "^1.6.1", - "@azure/logger": "^1.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@azure/core-client/node_modules/@azure/abort-controller": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", - "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==", - "dev": true, - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@azure/core-rest-pipeline": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.15.2.tgz", - "integrity": "sha512-BmWfpjc/QXc2ipHOh6LbUzp3ONCaa6xzIssTU0DwH9bbYNXJlGUL6tujx5TrbVd/QQknmS+vlQJGrCq2oL1gZA==", - "dev": true, - "dependencies": { - "@azure/abort-controller": "^2.0.0", - "@azure/core-auth": "^1.4.0", - "@azure/core-tracing": "^1.0.1", - "@azure/core-util": "^1.3.0", - "@azure/logger": "^1.0.0", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@azure/core-rest-pipeline/node_modules/@azure/abort-controller": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", - "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==", - "dev": true, - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@azure/core-tracing": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.1.2.tgz", - "integrity": "sha512-dawW9ifvWAWmUm9/h+/UQ2jrdvjCJ7VJEuCJ6XVNudzcOwm53BFZH4Q845vjfgoUAM8ZxokvVNxNxAITc502YA==", - "dev": true, - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@azure/core-util": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.9.0.tgz", - "integrity": "sha512-AfalUQ1ZppaKuxPPMsFEUdX6GZPB3d9paR9d/TTL7Ow2De8cJaC7ibi7kWVlFAVPCYo31OcnGymc0R89DX8Oaw==", - "dev": true, - "dependencies": { - "@azure/abort-controller": "^2.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@azure/core-util/node_modules/@azure/abort-controller": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", - "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==", - "dev": true, - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@azure/identity": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@azure/identity/-/identity-4.1.0.tgz", - "integrity": "sha512-BhYkF8Xr2gXjyDxocm0pc9RI5J5a1jw8iW0dw6Bx95OGdYbuMyFZrrwNw4eYSqQ2BB6FZOqpJP3vjsAqRcvDhw==", - "dev": true, - "dependencies": { - "@azure/abort-controller": "^1.0.0", - "@azure/core-auth": "^1.5.0", - "@azure/core-client": "^1.4.0", - "@azure/core-rest-pipeline": "^1.1.0", - "@azure/core-tracing": "^1.0.0", - "@azure/core-util": "^1.3.0", - "@azure/logger": "^1.0.0", - "@azure/msal-browser": "^3.11.1", - "@azure/msal-node": "^2.6.6", - "events": "^3.0.0", - "jws": "^4.0.0", - "open": "^8.0.0", - "stoppable": "^1.1.0", - "tslib": "^2.2.0" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@azure/logger": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@azure/logger/-/logger-1.1.2.tgz", - "integrity": "sha512-l170uE7bsKpIU6B/giRc9i4NI0Mj+tANMMMxf7Zi/5cKzEqPayP7+X1WPrG7e+91JgY8N+7K7nF2WOi7iVhXvg==", - "dev": true, - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@azure/msal-browser": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-3.13.0.tgz", - "integrity": "sha512-fD906nmJei3yE7la6DZTdUtXKvpwzJURkfsiz9747Icv4pit77cegSm6prJTKLQ1fw4iiZzrrWwxnhMLrTf5gQ==", - "dev": true, - "dependencies": { - "@azure/msal-common": "14.9.0" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/@azure/msal-common": { - "version": "14.9.0", - "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-14.9.0.tgz", - "integrity": "sha512-yzBPRlWPnTBeixxLNI3BBIgF5/bHpbhoRVuuDBnYjCyWRavaPUsKAHUDYLqpGkBLDciA6TCc6GOxN4/S3WiSxg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/@azure/msal-node": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-2.7.0.tgz", - "integrity": "sha512-wXD8LkUvHICeSWZydqg6o8Yvv+grlBEcmLGu+QEI4FcwFendbTEZrlSygnAXXSOCVaGAirWLchca35qrgpO6Jw==", - "dev": true, - "dependencies": { - "@azure/msal-common": "14.9.0", - "jsonwebtoken": "^9.0.0", - "uuid": "^8.3.0" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", - "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", - "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", - "dev": true, - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", - "dev": true, - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/@eslint/js": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", - "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.11.14", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", - "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", - "dev": true, - "dependencies": { - "@humanwhocodes/object-schema": "^2.0.2", - "debug": "^4.3.1", - "minimatch": "^3.0.5" - }, - "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", - "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", - "dev": true - }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true - }, - "node_modules/@isaacs/cliui/node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, - "optional": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/@redhat-developer/locators": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@redhat-developer/locators/-/locators-1.1.0.tgz", - "integrity": "sha512-sSSyA1Le5ju9kvD2uY8AUKZk3iJ/ocL6KpJmBMnhecYcH96cATw9mow/5OkDEnwWihefcieerlsVRGHIQlhh0Q==", - "dev": true, - "peerDependencies": { - "@redhat-developer/page-objects": ">=1.0.0", - "selenium-webdriver": ">=4.6.1" - } - }, - "node_modules/@redhat-developer/page-objects": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@redhat-developer/page-objects/-/page-objects-1.1.0.tgz", - "integrity": "sha512-h+WDFrTFcvJJq33vhakzjlFgE2tM7K0nCwonDgDuGUoW9ukerb+M/dM7m3qEGwW5T+ugz240fGElAn4jsEHBZg==", - "dev": true, - "dependencies": { - "clipboardy": "^4.0.0", - "clone-deep": "^4.0.1", - "compare-versions": "^6.1.0", - "fs-extra": "^11.2.0" - }, - "peerDependencies": { - "selenium-webdriver": ">=4.6.1", - "typescript": ">=4.6.2" - } - }, - "node_modules/@sindresorhus/is": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.6.0.tgz", - "integrity": "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==", - "dev": true, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sindresorhus/is?sponsor=1" - } - }, - "node_modules/@szmarczak/http-timer": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", - "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==", - "dev": true, - "dependencies": { - "defer-to-connect": "^2.0.1" - }, - "engines": { - "node": ">=14.16" - } - }, - "node_modules/@types/chai": { - "version": "4.3.14", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.14.tgz", - "integrity": "sha512-Wj71sXE4Q4AkGdG9Tvq1u/fquNz9EdG4LIJMwVVII7ashjD/8cf8fyIfJAjRr6YcsXnSE8cOGQPq1gqeR8z+3w==", - "dev": true - }, - "node_modules/@types/http-cache-semantics": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", - "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", - "dev": true - }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true - }, - "node_modules/@types/mocha": { - "version": "10.0.6", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.6.tgz", - "integrity": "sha512-dJvrYWxP/UcXm36Qn36fxhUKu8A/xMRXVT2cliFF1Z7UA9liG5Psj3ezNSZw+5puH2czDXRLcXQxf8JbJt0ejg==", - "dev": true - }, - "node_modules/@types/node": { - "version": "20.12.7", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.7.tgz", - "integrity": "sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg==", - "dev": true, - "dependencies": { - "undici-types": "~5.26.4" - } - }, - "node_modules/@types/selenium-webdriver": { - "version": "4.1.22", - "resolved": "https://registry.npmjs.org/@types/selenium-webdriver/-/selenium-webdriver-4.1.22.tgz", - "integrity": "sha512-MCL4l7q8dwxejr2Q2NXLyNwHWMPdlWE0Kpn6fFwJtvkJF7PTkG5jkvbH/X1IAAQxgt/L1dA8u2GtDeekvSKvOA==", - "dev": true, - "dependencies": { - "@types/ws": "*" - } - }, - "node_modules/@types/semver": { - "version": "7.5.8", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", - "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", - "dev": true - }, - "node_modules/@types/vscode": { - "version": "1.88.0", - "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.88.0.tgz", - "integrity": "sha512-rWY+Bs6j/f1lvr8jqZTyp5arRMfovdxolcqGi+//+cPDOh8SBvzXH90e7BiSXct5HJ9HGW6jATchbRTpTJpEkw==", - "dev": true - }, - "node_modules/@types/ws": { - "version": "8.5.10", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", - "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.7.1.tgz", - "integrity": "sha512-KwfdWXJBOviaBVhxO3p5TJiLpNuh2iyXyjmWN0f1nU87pwyvfS0EmjC6ukQVYVFJd/K1+0NWGPDXiyEyQorn0Q==", - "dev": true, - "dependencies": { - "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "7.7.1", - "@typescript-eslint/type-utils": "7.7.1", - "@typescript-eslint/utils": "7.7.1", - "@typescript-eslint/visitor-keys": "7.7.1", - "debug": "^4.3.4", - "graphemer": "^1.4.0", - "ignore": "^5.3.1", - "natural-compare": "^1.4.0", - "semver": "^7.6.0", - "ts-api-utils": "^1.3.0" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^7.0.0", - "eslint": "^8.56.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.7.1.tgz", - "integrity": "sha512-vmPzBOOtz48F6JAGVS/kZYk4EkXao6iGrD838sp1w3NQQC0W8ry/q641KU4PrG7AKNAf56NOcR8GOpH8l9FPCw==", - "dev": true, - "peer": true, - "dependencies": { - "@typescript-eslint/scope-manager": "7.7.1", - "@typescript-eslint/types": "7.7.1", - "@typescript-eslint/typescript-estree": "7.7.1", - "@typescript-eslint/visitor-keys": "7.7.1", - "debug": "^4.3.4" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.56.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.7.1.tgz", - "integrity": "sha512-PytBif2SF+9SpEUKynYn5g1RHFddJUcyynGpztX3l/ik7KmZEv19WCMhUBkHXPU9es/VWGD3/zg3wg90+Dh2rA==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "7.7.1", - "@typescript-eslint/visitor-keys": "7.7.1" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.7.1.tgz", - "integrity": "sha512-ZksJLW3WF7o75zaBPScdW1Gbkwhd/lyeXGf1kQCxJaOeITscoSl0MjynVvCzuV5boUz/3fOI06Lz8La55mu29Q==", - "dev": true, - "dependencies": { - "@typescript-eslint/typescript-estree": "7.7.1", - "@typescript-eslint/utils": "7.7.1", - "debug": "^4.3.4", - "ts-api-utils": "^1.3.0" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.56.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/types": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.7.1.tgz", - "integrity": "sha512-AmPmnGW1ZLTpWa+/2omPrPfR7BcbUU4oha5VIbSbS1a1Tv966bklvLNXxp3mrbc+P2j4MNOTfDffNsk4o0c6/w==", - "dev": true, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.7.1.tgz", - "integrity": "sha512-CXe0JHCXru8Fa36dteXqmH2YxngKJjkQLjxzoj6LYwzZ7qZvgsLSc+eqItCrqIop8Vl2UKoAi0StVWu97FQZIQ==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "7.7.1", - "@typescript-eslint/visitor-keys": "7.7.1", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^1.3.0" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.7.1.tgz", - "integrity": "sha512-QUvBxPEaBXf41ZBbaidKICgVL8Hin0p6prQDu6bbetWo39BKbWJxRsErOzMNT1rXvTll+J7ChrbmMCXM9rsvOQ==", - "dev": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@types/json-schema": "^7.0.15", - "@types/semver": "^7.5.8", - "@typescript-eslint/scope-manager": "7.7.1", - "@typescript-eslint/types": "7.7.1", - "@typescript-eslint/typescript-estree": "7.7.1", - "semver": "^7.6.0" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.56.0" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.7.1.tgz", - "integrity": "sha512-gBL3Eq25uADw1LQ9kVpf3hRM+DWzs0uZknHYK3hq4jcTPqVCClHGDnB6UUUV2SFeBeA4KWHWbbLqmbGcZ4FYbw==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "7.7.1", - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@ungap/structured-clone": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", - "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", - "dev": true - }, - "node_modules/@vscode/vsce": { - "version": "2.26.0", - "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-2.26.0.tgz", - "integrity": "sha512-v54ltgMzUG8lGY0kAgaOlry57xse1RlWzes9FotfGEx+Fr05KeR8rZicQzEMDmi9QnOgVWHuiEq+xA2HWkAz+Q==", - "dev": true, - "dependencies": { - "@azure/identity": "^4.1.0", - "azure-devops-node-api": "^12.5.0", - "chalk": "^2.4.2", - "cheerio": "^1.0.0-rc.9", - "cockatiel": "^3.1.2", - "commander": "^6.2.1", - "form-data": "^4.0.0", - "glob": "^7.0.6", - "hosted-git-info": "^4.0.2", - "jsonc-parser": "^3.2.0", - "leven": "^3.1.0", - "markdown-it": "^12.3.2", - "mime": "^1.3.4", - "minimatch": "^3.0.3", - "parse-semver": "^1.1.1", - "read": "^1.0.7", - "semver": "^7.5.2", - "tmp": "^0.2.1", - "typed-rest-client": "^1.8.4", - "url-join": "^4.0.1", - "xml2js": "^0.5.0", - "yauzl": "^2.3.1", - "yazl": "^2.2.2" - }, - "bin": { - "vsce": "vsce" - }, - "engines": { - "node": ">= 16" - }, - "optionalDependencies": { - "keytar": "^7.7.0" - } - }, - "node_modules/@vscode/vsce/node_modules/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==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@vscode/vsce/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@vscode/vsce/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@vscode/vsce/node_modules/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==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@vscode/vsce/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "node_modules/@vscode/vsce/node_modules/commander": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", - "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/@vscode/vsce/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/@vscode/vsce/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@vscode/vsce/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/@vscode/vsce/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/@vscode/vsce/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/agent-base": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", - "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", - "dev": true, - "dependencies": { - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/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==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true - }, - "node_modules/azure-devops-node-api": { - "version": "12.5.0", - "resolved": "https://registry.npmjs.org/azure-devops-node-api/-/azure-devops-node-api-12.5.0.tgz", - "integrity": "sha512-R5eFskGvOm3U/GzeAuxRkUsAl0hrAwGgWn6zAd2KrZmrEhWZVqLew4OOupbQlXUuojUzpGtq62SmdhJ06N88og==", - "dev": true, - "dependencies": { - "tunnel": "0.0.6", - "typed-rest-client": "^1.8.4" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "optional": true - }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "dev": true, - "optional": true, - "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "node_modules/bl/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "optional": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", - "dev": true - }, - "node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true - }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "optional": true, - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "node_modules/buffer-alloc": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", - "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", - "dev": true, - "dependencies": { - "buffer-alloc-unsafe": "^1.1.0", - "buffer-fill": "^1.0.0" - } - }, - "node_modules/buffer-alloc-unsafe": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", - "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==", - "dev": true - }, - "node_modules/buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/buffer-equal-constant-time": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", - "dev": true - }, - "node_modules/buffer-fill": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", - "integrity": "sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ==", - "dev": true - }, - "node_modules/cacheable-lookup": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", - "integrity": "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==", - "dev": true, - "engines": { - "node": ">=14.16" - } - }, - "node_modules/cacheable-request": { - "version": "10.2.14", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.14.tgz", - "integrity": "sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==", - "dev": true, - "dependencies": { - "@types/http-cache-semantics": "^4.0.2", - "get-stream": "^6.0.1", - "http-cache-semantics": "^4.1.1", - "keyv": "^4.5.3", - "mimic-response": "^4.0.0", - "normalize-url": "^8.0.0", - "responselike": "^3.0.0" - }, - "engines": { - "node": ">=14.16" - } - }, - "node_modules/cacheable-request/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/call-bind": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", - "dev": true, - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/chai": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.4.1.tgz", - "integrity": "sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==", - "dev": true, - "dependencies": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.3", - "deep-eql": "^4.1.3", - "get-func-name": "^2.0.2", - "loupe": "^2.3.6", - "pathval": "^1.1.1", - "type-detect": "^4.0.8" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/check-error": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", - "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", - "dev": true, - "dependencies": { - "get-func-name": "^2.0.2" - }, - "engines": { - "node": "*" - } - }, - "node_modules/cheerio": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz", - "integrity": "sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==", - "dev": true, - "dependencies": { - "cheerio-select": "^2.1.0", - "dom-serializer": "^2.0.0", - "domhandler": "^5.0.3", - "domutils": "^3.0.1", - "htmlparser2": "^8.0.1", - "parse5": "^7.0.0", - "parse5-htmlparser2-tree-adapter": "^7.0.0" - }, - "engines": { - "node": ">= 6" - }, - "funding": { - "url": "https://github.com/cheeriojs/cheerio?sponsor=1" - } - }, - "node_modules/cheerio-select": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", - "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", - "dev": true, - "dependencies": { - "boolbase": "^1.0.0", - "css-select": "^5.1.0", - "css-what": "^6.1.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3", - "domutils": "^3.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/chokidar/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "dev": true - }, - "node_modules/clipboardy": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-4.0.0.tgz", - "integrity": "sha512-5mOlNS0mhX0707P2I0aZ2V/cmHUEO/fL7VFLqszkhUsxt7RwnmrInf/eEQKlf5GzvYeHIjT+Ov1HRfNmymlG0w==", - "dev": true, - "dependencies": { - "execa": "^8.0.1", - "is-wsl": "^3.1.0", - "is64bit": "^2.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/clone-deep": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", - "dev": true, - "dependencies": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/cockatiel": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/cockatiel/-/cockatiel-3.1.2.tgz", - "integrity": "sha512-5yARKww0dWyWg2/3xZeXgoxjHLwpVqFptj9Zy7qioJ6+/L0ARM184sgMUrQDjxw7ePJWlGhV998mKhzrxT0/Kg==", - "dev": true, - "engines": { - "node": ">=16" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/commander": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-12.0.0.tgz", - "integrity": "sha512-MwVNWlYjDTtOjX5PiD7o5pK0UrFU/OYgcJfjjK4RaHZETNtjJqrZa9Y9ds88+A+f+d5lv+561eZ+yCKoS3gbAA==", - "dev": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/compare-versions": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.1.0.tgz", - "integrity": "sha512-LNZQXhqUvqUTotpZ00qLSaify3b4VFD588aRr8MKFw4CMUr98ytzCW5wDH5qx/DEY5kCDXcbcRuCqL0szEf2tg==", - "dev": true - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true - }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/css-select": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", - "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", - "dev": true, - "dependencies": { - "boolbase": "^1.0.0", - "css-what": "^6.1.0", - "domhandler": "^5.0.2", - "domutils": "^3.0.1", - "nth-check": "^2.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/css-what": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", - "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", - "dev": true, - "engines": { - "node": ">= 6" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "dev": true, - "dependencies": { - "mimic-response": "^3.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/decompress-response/node_modules/mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/deep-eql": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", - "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", - "dev": true, - "dependencies": { - "type-detect": "^4.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "dev": true, - "optional": true, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "node_modules/defer-to-connect": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", - "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dev": true, - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/define-lazy-prop": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", - "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/detect-libc": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", - "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", - "dev": true, - "optional": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/diff": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", - "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/dom-serializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", - "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", - "dev": true, - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.2", - "entities": "^4.2.0" - }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" - } - }, - "node_modules/domelementtype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ] - }, - "node_modules/domhandler": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", - "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", - "dev": true, - "dependencies": { - "domelementtype": "^2.3.0" - }, - "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" - } - }, - "node_modules/domutils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", - "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", - "dev": true, - "dependencies": { - "dom-serializer": "^2.0.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3" - }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" - } - }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true - }, - "node_modules/ecdsa-sig-formatter": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", - "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", - "dev": true, - "dependencies": { - "safe-buffer": "^5.0.1" - } - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "dev": true, - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", - "dev": true, - "dependencies": { - "get-intrinsic": "^1.2.4" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/escalade": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", - "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", - "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", - "dev": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.0", - "@humanwhocodes/config-array": "^0.11.14", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/eslint/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", - "dev": true, - "dependencies": { - "acorn": "^8.9.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esquery": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", - "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", - "dev": true, - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true, - "engines": { - "node": ">=0.8.x" - } - }, - "node_modules/execa": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", - "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^8.0.1", - "human-signals": "^5.0.0", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", - "signal-exit": "^4.1.0", - "strip-final-newline": "^3.0.0" - }, - "engines": { - "node": ">=16.17" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/expand-template": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", - "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", - "dev": true, - "optional": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "node_modules/fast-glob": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", - "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true - }, - "node_modules/fastq": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", - "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", - "dev": true, - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/fd-slicer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", - "dev": true, - "dependencies": { - "pend": "~1.2.0" - } - }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "dependencies": { - "flat-cache": "^3.0.4" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "dev": true, - "bin": { - "flat": "cli.js" - } - }, - "node_modules/flat-cache": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", - "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", - "dev": true, - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/flatted": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", - "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", - "dev": true - }, - "node_modules/foreground-child": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", - "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.0", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dev": true, - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/form-data-encoder": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-2.1.4.tgz", - "integrity": "sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==", - "dev": true, - "engines": { - "node": ">= 14.17" - } - }, - "node_modules/fs-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "dev": true - }, - "node_modules/fs-extra": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", - "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=14.14" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/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==", - "dev": true, - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-func-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", - "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", - "dev": true, - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-stream": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", - "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", - "dev": true, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/github-from-package": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", - "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", - "dev": true, - "optional": true - }, - "node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/glob/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dev": true, - "dependencies": { - "get-intrinsic": "^1.1.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/got": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/got/-/got-13.0.0.tgz", - "integrity": "sha512-XfBk1CxOOScDcMr9O1yKkNaQyy865NbYs+F7dr4H0LZMVgCj2Le59k6PqbNHoL5ToeaEQUYh6c6yMfVcc6SJxA==", - "dev": true, - "dependencies": { - "@sindresorhus/is": "^5.2.0", - "@szmarczak/http-timer": "^5.0.1", - "cacheable-lookup": "^7.0.0", - "cacheable-request": "^10.2.8", - "decompress-response": "^6.0.0", - "form-data-encoder": "^2.1.2", - "get-stream": "^6.0.1", - "http2-wrapper": "^2.1.10", - "lowercase-keys": "^3.0.0", - "p-cancelable": "^3.0.0", - "responselike": "^3.0.0" - }, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sindresorhus/got?sponsor=1" - } - }, - "node_modules/got/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true - }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dev": true, - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-proto": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", - "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true, - "bin": { - "he": "bin/he" - } - }, - "node_modules/hosted-git-info": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", - "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/hpagent": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/hpagent/-/hpagent-1.2.0.tgz", - "integrity": "sha512-A91dYTeIB6NoXG+PxTQpCCDDnfHsW9kc06Lvpu1TEe9gnd6ZFeiBoRO9JvzEv6xK7EX97/dUE8g/vBMTqTS3CA==", - "dev": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/htmlparser2": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", - "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", - "dev": true, - "funding": [ - "https://github.com/fb55/htmlparser2?sponsor=1", - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3", - "domutils": "^3.0.1", - "entities": "^4.4.0" - } - }, - "node_modules/http-cache-semantics": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", - "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", - "dev": true - }, - "node_modules/http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "dev": true, - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/http2-wrapper": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.1.tgz", - "integrity": "sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==", - "dev": true, - "dependencies": { - "quick-lru": "^5.1.1", - "resolve-alpn": "^1.2.0" - }, - "engines": { - "node": ">=10.19.0" - } - }, - "node_modules/https-proxy-agent": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", - "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", - "dev": true, - "dependencies": { - "agent-base": "^7.0.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/human-signals": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", - "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", - "dev": true, - "engines": { - "node": ">=16.17.0" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "optional": true - }, - "node_modules/ignore": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", - "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/immediate": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", - "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", - "dev": true - }, - "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true, - "optional": true - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-docker": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", - "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", - "dev": true, - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-inside-container": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", - "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", - "dev": true, - "dependencies": { - "is-docker": "^3.0.0" - }, - "bin": { - "is-inside-container": "cli.js" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/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==", - "dev": true, - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", - "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", - "dev": true, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-wsl": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", - "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", - "dev": true, - "dependencies": { - "is-inside-container": "^1.0.0" - }, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is64bit": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is64bit/-/is64bit-2.0.0.tgz", - "integrity": "sha512-jv+8jaWCl0g2lSBkNSVXdzfBA0npK1HGC2KtWM9FumFRoGS94g3NbCCLVnCYHLjp4GrW2KZeeSTMo5ddtznmGw==", - "dev": true, - "dependencies": { - "system-architecture": "^0.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/jackspeak": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", - "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", - "dev": true, - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true - }, - "node_modules/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==", - "dev": true - }, - "node_modules/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": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true - }, - "node_modules/jsonc-parser": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz", - "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==", - "dev": true - }, - "node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/jsonwebtoken": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", - "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", - "dev": true, - "dependencies": { - "jws": "^3.2.2", - "lodash.includes": "^4.3.0", - "lodash.isboolean": "^3.0.3", - "lodash.isinteger": "^4.0.4", - "lodash.isnumber": "^3.0.3", - "lodash.isplainobject": "^4.0.6", - "lodash.isstring": "^4.0.1", - "lodash.once": "^4.0.0", - "ms": "^2.1.1", - "semver": "^7.5.4" - }, - "engines": { - "node": ">=12", - "npm": ">=6" - } - }, - "node_modules/jsonwebtoken/node_modules/jwa": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", - "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", - "dev": true, - "dependencies": { - "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/jsonwebtoken/node_modules/jws": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", - "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", - "dev": true, - "dependencies": { - "jwa": "^1.4.1", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/jszip": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", - "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", - "dev": true, - "dependencies": { - "lie": "~3.3.0", - "pako": "~1.0.2", - "readable-stream": "~2.3.6", - "setimmediate": "^1.0.5" - } - }, - "node_modules/jwa": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", - "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", - "dev": true, - "dependencies": { - "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/jws": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", - "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", - "dev": true, - "dependencies": { - "jwa": "^2.0.0", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/keytar": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/keytar/-/keytar-7.9.0.tgz", - "integrity": "sha512-VPD8mtVtm5JNtA2AErl6Chp06JBfy7diFQ7TQQhdpWOl6MrCRB+eRbvAZUsbGQS9kiMq0coJsy0W0vHpDCkWsQ==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "dependencies": { - "node-addon-api": "^4.3.0", - "prebuild-install": "^7.0.1" - } - }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/lie": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", - "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", - "dev": true, - "dependencies": { - "immediate": "~3.0.5" - } - }, - "node_modules/linkify-it": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", - "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", - "dev": true, - "dependencies": { - "uc.micro": "^1.0.1" - } - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash.includes": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", - "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", - "dev": true - }, - "node_modules/lodash.isboolean": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", - "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", - "dev": true - }, - "node_modules/lodash.isinteger": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", - "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", - "dev": true - }, - "node_modules/lodash.isnumber": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", - "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", - "dev": true - }, - "node_modules/lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", - "dev": true - }, - "node_modules/lodash.isstring": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", - "dev": true - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "node_modules/lodash.once": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", - "dev": true - }, - "node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/loupe": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", - "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", - "dev": true, - "dependencies": { - "get-func-name": "^2.0.1" - } - }, - "node_modules/lowercase-keys": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", - "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==", - "dev": true, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/markdown-it": { - "version": "12.3.2", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", - "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1", - "entities": "~2.1.0", - "linkify-it": "^3.0.1", - "mdurl": "^1.0.1", - "uc.micro": "^1.0.5" - }, - "bin": { - "markdown-it": "bin/markdown-it.js" - } - }, - "node_modules/markdown-it/node_modules/entities": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", - "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", - "dev": true, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/mdurl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", - "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==", - "dev": true - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, - "dependencies": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "dev": true, - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mimic-fn": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", - "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mimic-response": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", - "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==", - "dev": true, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/minimatch": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", - "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/minipass": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", - "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", - "dev": true, - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dev": true, - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/mkdirp-classic": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", - "dev": true, - "optional": true - }, - "node_modules/mocha": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.4.0.tgz", - "integrity": "sha512-eqhGB8JKapEYcC4ytX/xrzKforgEc3j1pGlAXVy3eRwrtAy5/nIfT1SvgGzfN0XZZxeLq0aQWkOUAmqIJiv+bA==", - "dev": true, - "dependencies": { - "ansi-colors": "4.1.1", - "browser-stdout": "1.3.1", - "chokidar": "3.5.3", - "debug": "4.3.4", - "diff": "5.0.0", - "escape-string-regexp": "4.0.0", - "find-up": "5.0.0", - "glob": "8.1.0", - "he": "1.2.0", - "js-yaml": "4.1.0", - "log-symbols": "4.1.0", - "minimatch": "5.0.1", - "ms": "2.1.3", - "serialize-javascript": "6.0.0", - "strip-json-comments": "3.1.1", - "supports-color": "8.1.1", - "workerpool": "6.2.1", - "yargs": "16.2.0", - "yargs-parser": "20.2.4", - "yargs-unparser": "2.0.0" - }, - "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha.js" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/mocha/node_modules/minimatch": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", - "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/mocha/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, - "node_modules/mocha/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", - "dev": true - }, - "node_modules/napi-build-utils": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", - "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==", - "dev": true, - "optional": true - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true - }, - "node_modules/node-abi": { - "version": "3.62.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.62.0.tgz", - "integrity": "sha512-CPMcGa+y33xuL1E0TcNIu4YyaZCxnnvkVaEXrsosR3FxN+fV8xvb7Mzpb7IgKler10qeMkE6+Dp8qJhpzdq35g==", - "dev": true, - "optional": true, - "dependencies": { - "semver": "^7.3.5" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/node-addon-api": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz", - "integrity": "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==", - "dev": true, - "optional": true - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/normalize-url": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.1.tgz", - "integrity": "sha512-IO9QvjUMWxPQQhs60oOu10CRkWCiZzSUkzbXGGV9pviYl1fXYcvkzQ5jV9z8Y6un8ARoVRl4EtC6v6jNqbaJ/w==", - "dev": true, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/npm-run-path": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", - "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", - "dev": true, - "dependencies": { - "path-key": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/npm-run-path/node_modules/path-key": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", - "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/nth-check": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", - "dev": true, - "dependencies": { - "boolbase": "^1.0.0" - }, - "funding": { - "url": "https://github.com/fb55/nth-check?sponsor=1" - } - }, - "node_modules/object-inspect": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", - "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", - "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", - "dev": true, - "dependencies": { - "mimic-fn": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/open": { - "version": "8.4.2", - "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", - "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", - "dev": true, - "dependencies": { - "define-lazy-prop": "^2.0.0", - "is-docker": "^2.1.1", - "is-wsl": "^2.2.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/open/node_modules/is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", - "dev": true, - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/open/node_modules/is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "dev": true, - "dependencies": { - "is-docker": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/optionator": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", - "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", - "dev": true, - "dependencies": { - "@aashutoshrathi/word-wrap": "^1.2.3", - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/p-cancelable": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz", - "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==", - "dev": true, - "engines": { - "node": ">=12.20" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pako": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", - "dev": true - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-semver": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/parse-semver/-/parse-semver-1.1.1.tgz", - "integrity": "sha512-Eg1OuNntBMH0ojvEKSrvDSnwLmvVuUOSdylH/pSCPNMIspLlweJyIWXCE+k/5hm3cj/EBUYwmWkjhBALNP4LXQ==", - "dev": true, - "dependencies": { - "semver": "^5.1.0" - } - }, - "node_modules/parse-semver/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/parse5": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", - "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", - "dev": true, - "dependencies": { - "entities": "^4.4.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/parse5-htmlparser2-tree-adapter": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz", - "integrity": "sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==", - "dev": true, - "dependencies": { - "domhandler": "^5.0.2", - "parse5": "^7.0.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-scurry": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.2.tgz", - "integrity": "sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==", - "dev": true, - "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.1.tgz", - "integrity": "sha512-tS24spDe/zXhWbNPErCHs/AGOzbKGHT+ybSBqmdLm8WZ1xXLWvH8Qn71QPAlqVhd0qUTWjy+Kl9JmISgDdEjsA==", - "dev": true, - "engines": { - "node": "14 || >=16.14" - } - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/pathval": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", - "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/pend": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", - "dev": true - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/prebuild-install": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.2.tgz", - "integrity": "sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ==", - "dev": true, - "optional": true, - "dependencies": { - "detect-libc": "^2.0.0", - "expand-template": "^2.0.3", - "github-from-package": "0.0.0", - "minimist": "^1.2.3", - "mkdirp-classic": "^0.5.3", - "napi-build-utils": "^1.0.1", - "node-abi": "^3.3.0", - "pump": "^3.0.0", - "rc": "^1.2.7", - "simple-get": "^4.0.0", - "tar-fs": "^2.0.0", - "tunnel-agent": "^0.6.0" - }, - "bin": { - "prebuild-install": "bin.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/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==", - "dev": true - }, - "node_modules/pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "optional": true, - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/qs": { - "version": "6.12.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.12.1.tgz", - "integrity": "sha512-zWmv4RSuB9r2mYQw3zxQuHWeU+42aKi1wWig/j4ele4ygELZ7PEO6MM7rim9oAQH2A5MWfsAVf/jPvTPgCbvUQ==", - "dev": true, - "dependencies": { - "side-channel": "^1.0.6" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/quick-lru": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", - "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "dev": true, - "optional": true, - "dependencies": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "bin": { - "rc": "cli.js" - } - }, - "node_modules/rc/node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/read": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", - "integrity": "sha512-rSOKNYUmaxy0om1BNjMN4ezNT6VKK+2xF4GBhc81mkH7L60i6dp8qPYrkndNLT3QPphoII3maL9PVC9XmhHwVQ==", - "dev": true, - "dependencies": { - "mute-stream": "~0.0.4" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "dev": true, - "dependencies": { - "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" - } - }, - "node_modules/readable-stream/node_modules/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==", - "dev": true - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve-alpn": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", - "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", - "dev": true - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/responselike": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz", - "integrity": "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==", - "dev": true, - "dependencies": { - "lowercase-keys": "^3.0.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/rimraf/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/sanitize-filename": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.3.tgz", - "integrity": "sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg==", - "dev": true, - "dependencies": { - "truncate-utf8-bytes": "^1.0.0" - } - }, - "node_modules/sax": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz", - "integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==", - "dev": true - }, - "node_modules/selenium-webdriver": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.20.0.tgz", - "integrity": "sha512-s/G44lGQ1xB3tmtX6NNPomlkpL6CxLdmAvp/AGWWwi4qv5Te1+qji7tPSyr6gyuoPpdYiof1rKnWe3luy0MrYA==", - "dev": true, - "dependencies": { - "jszip": "^3.10.1", - "tmp": "^0.2.3", - "ws": ">=8.16.0" - }, - "engines": { - "node": ">= 14.20.0" - } - }, - "node_modules/semver": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", - "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", - "dev": true, - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "dev": true, - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", - "dev": true - }, - "node_modules/shallow-clone": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/side-channel": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", - "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "object-inspect": "^1.13.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/simple-concat": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", - "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "optional": true - }, - "node_modules/simple-get": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", - "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "optional": true, - "dependencies": { - "decompress-response": "^6.0.0", - "once": "^1.3.1", - "simple-concat": "^1.0.0" - } - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/stoppable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/stoppable/-/stoppable-1.1.0.tgz", - "integrity": "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==", - "dev": true, - "engines": { - "node": ">=4", - "npm": ">=6" - } - }, - "node_modules/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==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/string_decoder/node_modules/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==", - "dev": true - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-final-newline": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", - "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/system-architecture": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/system-architecture/-/system-architecture-0.1.0.tgz", - "integrity": "sha512-ulAk51I9UVUyJgxlv9M6lFot2WP3e7t8Kz9+IS6D4rVba1tR9kON+Ey69f+1R4Q8cd45Lod6a4IcJIxnzGc/zA==", - "dev": true, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/tar-fs": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", - "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", - "dev": true, - "optional": true, - "dependencies": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" - } - }, - "node_modules/tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "dev": true, - "optional": true, - "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/tar-stream/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "optional": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/targz": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/targz/-/targz-1.0.1.tgz", - "integrity": "sha512-6q4tP9U55mZnRuMTBqnqc3nwYQY3kv+QthCFZuMk+Tn1qYUnMPmL/JZ/mzgXINzFpSqfU+242IFmFU9VPvqaQw==", - "dev": true, - "dependencies": { - "tar-fs": "^1.8.1" - } - }, - "node_modules/targz/node_modules/bl": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.3.tgz", - "integrity": "sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==", - "dev": true, - "dependencies": { - "readable-stream": "^2.3.5", - "safe-buffer": "^5.1.1" - } - }, - "node_modules/targz/node_modules/pump": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/pump/-/pump-1.0.3.tgz", - "integrity": "sha512-8k0JupWme55+9tCVE+FS5ULT3K6AbgqrGa58lTT49RpyfwwcGedHqaC5LlQNdEAumn/wFsu6aPwkuPMioy8kqw==", - "dev": true, - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/targz/node_modules/tar-fs": { - "version": "1.16.3", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-1.16.3.tgz", - "integrity": "sha512-NvCeXpYx7OsmOh8zIOP/ebG55zZmxLE0etfWRbWok+q2Qo8x/vOR/IJT1taADXPe+jsiu9axDb3X4B+iIgNlKw==", - "dev": true, - "dependencies": { - "chownr": "^1.0.1", - "mkdirp": "^0.5.1", - "pump": "^1.0.0", - "tar-stream": "^1.1.2" - } - }, - "node_modules/targz/node_modules/tar-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz", - "integrity": "sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==", - "dev": true, - "dependencies": { - "bl": "^1.0.0", - "buffer-alloc": "^1.2.0", - "end-of-stream": "^1.0.0", - "fs-constants": "^1.0.0", - "readable-stream": "^2.3.0", - "to-buffer": "^1.1.1", - "xtend": "^4.0.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true - }, - "node_modules/tmp": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", - "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", - "dev": true, - "engines": { - "node": ">=14.14" - } - }, - "node_modules/to-buffer": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz", - "integrity": "sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==", - "dev": true - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/truncate-utf8-bytes": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", - "integrity": "sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==", - "dev": true, - "dependencies": { - "utf8-byte-length": "^1.0.1" - } - }, - "node_modules/ts-api-utils": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", - "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", - "dev": true, - "engines": { - "node": ">=16" - }, - "peerDependencies": { - "typescript": ">=4.2.0" - } - }, - "node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "dev": true - }, - "node_modules/tunnel": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", - "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", - "dev": true, - "engines": { - "node": ">=0.6.11 <=0.7.0 || >=0.7.3" - } - }, - "node_modules/tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", - "dev": true, - "optional": true, - "dependencies": { - "safe-buffer": "^5.0.1" - }, - "engines": { - "node": "*" - } - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/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==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typed-rest-client": { - "version": "1.8.11", - "resolved": "https://registry.npmjs.org/typed-rest-client/-/typed-rest-client-1.8.11.tgz", - "integrity": "sha512-5UvfMpd1oelmUPRbbaVnq+rHP7ng2cE4qoQkQeAqxRL6PklkxsM0g32/HL0yfvruK6ojQ5x8EE+HF4YV6DtuCA==", - "dev": true, - "dependencies": { - "qs": "^6.9.1", - "tunnel": "0.0.6", - "underscore": "^1.12.1" - } - }, - "node_modules/typescript": { - "version": "5.4.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", - "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", - "dev": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/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==", - "dev": true - }, - "node_modules/underscore": { - "version": "1.13.6", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", - "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==", - "dev": true - }, - "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true - }, - "node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/url-join": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", - "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==", - "dev": true - }, - "node_modules/utf8-byte-length": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz", - "integrity": "sha512-4+wkEYLBbWxqTahEsWrhxepcoVOJ+1z5PGIjPZxRkytcdSUaNjIjBM7Xn8E+pdSuV7SzvWovBFA54FO0JSoqhA==", - "dev": true - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true - }, - "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/vscode-extension-tester": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/vscode-extension-tester/-/vscode-extension-tester-8.1.0.tgz", - "integrity": "sha512-gXyGi4D+ZqemAvt7IegFUBxVLQH1ftTRiMKqlwH1BBi+x1yhPXXuIVo6TSWUlyGMznbpFV5DUK3GEAfc13nSsg==", - "dev": true, - "dependencies": { - "@redhat-developer/locators": "^1.1.0", - "@redhat-developer/page-objects": "^1.1.0", - "@types/selenium-webdriver": "^4.1.22", - "@vscode/vsce": "^2.26.0", - "commander": "^12.0.0", - "compare-versions": "^6.1.0", - "fs-extra": "^11.2.0", - "glob": "^10.3.12", - "got": "^13.0.0", - "hpagent": "^1.2.0", - "js-yaml": "^4.1.0", - "sanitize-filename": "^1.6.3", - "selenium-webdriver": "^4.19.0", - "targz": "^1.0.1" - }, - "bin": { - "extest": "out/cli.js" - }, - "peerDependencies": { - "mocha": ">=5.2.0", - "typescript": ">=4.6.2" - } - }, - "node_modules/vscode-extension-tester/node_modules/glob": { - "version": "10.3.12", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz", - "integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==", - "dev": true, - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^2.3.6", - "minimatch": "^9.0.1", - "minipass": "^7.0.4", - "path-scurry": "^1.10.2" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/workerpool": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", - "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", - "dev": true - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true - }, - "node_modules/ws": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", - "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", - "dev": true, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/xml2js": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz", - "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==", - "dev": true, - "dependencies": { - "sax": ">=0.6.0", - "xmlbuilder": "~11.0.0" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/xmlbuilder": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", - "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true, - "engines": { - "node": ">=0.4" - } - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-parser": { - "version": "20.2.4", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", - "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-unparser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", - "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", - "dev": true, - "dependencies": { - "camelcase": "^6.0.0", - "decamelize": "^4.0.0", - "flat": "^5.0.2", - "is-plain-obj": "^2.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", - "dev": true, - "dependencies": { - "buffer-crc32": "~0.2.3", - "fd-slicer": "~1.1.0" - } - }, - "node_modules/yazl": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/yazl/-/yazl-2.5.1.tgz", - "integrity": "sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw==", - "dev": true, - "dependencies": { - "buffer-crc32": "~0.2.3" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - } + "name": "helloworld-extester", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "helloworld-extester", + "version": "0.1.0", + "license": "Apache-2.0", + "devDependencies": { + "@types/chai": "^4.3.14", + "@types/mocha": "^10.0.6", + "@types/node": "^20.12.7", + "@types/vscode": "^1.85.0", + "@typescript-eslint/eslint-plugin": "^7.7.1", + "chai": "^4.4.1", + "eslint": "^8.57.0", + "mocha": "^10.4.0", + "typescript": "^5.4.5", + "vscode-extension-tester": "^8.1.0" + }, + "engines": { + "vscode": "^1.85.0" + } + }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@azure/abort-controller": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-1.1.0.tgz", + "integrity": "sha512-TrRLIoSQVzfAJX9H1JeFjzAoDGcoK1IYX1UImfceTZpsyYfWr09Ss1aHW1y5TrrR3iq6RZLBwJ3E24uwPhwahw==", + "dev": true, + "dependencies": { + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@azure/core-auth": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.7.2.tgz", + "integrity": "sha512-Igm/S3fDYmnMq1uKS38Ae1/m37B3zigdlZw+kocwEhh5GjyKjPrXKO2J6rzpC1wAxrNil/jX9BJRqBshyjnF3g==", + "dev": true, + "dependencies": { + "@azure/abort-controller": "^2.0.0", + "@azure/core-util": "^1.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/core-auth/node_modules/@azure/abort-controller": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", + "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==", + "dev": true, + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/core-client": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@azure/core-client/-/core-client-1.9.2.tgz", + "integrity": "sha512-kRdry/rav3fUKHl/aDLd/pDLcB+4pOFwPPTVEExuMyaI5r+JBbMWqRbCY1pn5BniDaU3lRxO9eaQ1AmSMehl/w==", + "dev": true, + "dependencies": { + "@azure/abort-controller": "^2.0.0", + "@azure/core-auth": "^1.4.0", + "@azure/core-rest-pipeline": "^1.9.1", + "@azure/core-tracing": "^1.0.0", + "@azure/core-util": "^1.6.1", + "@azure/logger": "^1.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/core-client/node_modules/@azure/abort-controller": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", + "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==", + "dev": true, + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/core-rest-pipeline": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.15.2.tgz", + "integrity": "sha512-BmWfpjc/QXc2ipHOh6LbUzp3ONCaa6xzIssTU0DwH9bbYNXJlGUL6tujx5TrbVd/QQknmS+vlQJGrCq2oL1gZA==", + "dev": true, + "dependencies": { + "@azure/abort-controller": "^2.0.0", + "@azure/core-auth": "^1.4.0", + "@azure/core-tracing": "^1.0.1", + "@azure/core-util": "^1.3.0", + "@azure/logger": "^1.0.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/core-rest-pipeline/node_modules/@azure/abort-controller": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", + "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==", + "dev": true, + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/core-tracing": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.1.2.tgz", + "integrity": "sha512-dawW9ifvWAWmUm9/h+/UQ2jrdvjCJ7VJEuCJ6XVNudzcOwm53BFZH4Q845vjfgoUAM8ZxokvVNxNxAITc502YA==", + "dev": true, + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/core-util": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.9.0.tgz", + "integrity": "sha512-AfalUQ1ZppaKuxPPMsFEUdX6GZPB3d9paR9d/TTL7Ow2De8cJaC7ibi7kWVlFAVPCYo31OcnGymc0R89DX8Oaw==", + "dev": true, + "dependencies": { + "@azure/abort-controller": "^2.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/core-util/node_modules/@azure/abort-controller": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", + "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==", + "dev": true, + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/identity": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@azure/identity/-/identity-4.1.0.tgz", + "integrity": "sha512-BhYkF8Xr2gXjyDxocm0pc9RI5J5a1jw8iW0dw6Bx95OGdYbuMyFZrrwNw4eYSqQ2BB6FZOqpJP3vjsAqRcvDhw==", + "dev": true, + "dependencies": { + "@azure/abort-controller": "^1.0.0", + "@azure/core-auth": "^1.5.0", + "@azure/core-client": "^1.4.0", + "@azure/core-rest-pipeline": "^1.1.0", + "@azure/core-tracing": "^1.0.0", + "@azure/core-util": "^1.3.0", + "@azure/logger": "^1.0.0", + "@azure/msal-browser": "^3.11.1", + "@azure/msal-node": "^2.6.6", + "events": "^3.0.0", + "jws": "^4.0.0", + "open": "^8.0.0", + "stoppable": "^1.1.0", + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/logger": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@azure/logger/-/logger-1.1.2.tgz", + "integrity": "sha512-l170uE7bsKpIU6B/giRc9i4NI0Mj+tANMMMxf7Zi/5cKzEqPayP7+X1WPrG7e+91JgY8N+7K7nF2WOi7iVhXvg==", + "dev": true, + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/msal-browser": { + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-3.13.0.tgz", + "integrity": "sha512-fD906nmJei3yE7la6DZTdUtXKvpwzJURkfsiz9747Icv4pit77cegSm6prJTKLQ1fw4iiZzrrWwxnhMLrTf5gQ==", + "dev": true, + "dependencies": { + "@azure/msal-common": "14.9.0" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@azure/msal-common": { + "version": "14.9.0", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-14.9.0.tgz", + "integrity": "sha512-yzBPRlWPnTBeixxLNI3BBIgF5/bHpbhoRVuuDBnYjCyWRavaPUsKAHUDYLqpGkBLDciA6TCc6GOxN4/S3WiSxg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@azure/msal-node": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-2.7.0.tgz", + "integrity": "sha512-wXD8LkUvHICeSWZydqg6o8Yvv+grlBEcmLGu+QEI4FcwFendbTEZrlSygnAXXSOCVaGAirWLchca35qrgpO6Jw==", + "dev": true, + "dependencies": { + "@azure/msal-common": "14.9.0", + "jsonwebtoken": "^9.0.0", + "uuid": "^8.3.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", + "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", + "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "dev": true + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@redhat-developer/locators": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@redhat-developer/locators/-/locators-1.1.0.tgz", + "integrity": "sha512-sSSyA1Le5ju9kvD2uY8AUKZk3iJ/ocL6KpJmBMnhecYcH96cATw9mow/5OkDEnwWihefcieerlsVRGHIQlhh0Q==", + "dev": true, + "peerDependencies": { + "@redhat-developer/page-objects": ">=1.0.0", + "selenium-webdriver": ">=4.6.1" + } + }, + "node_modules/@redhat-developer/page-objects": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@redhat-developer/page-objects/-/page-objects-1.1.0.tgz", + "integrity": "sha512-h+WDFrTFcvJJq33vhakzjlFgE2tM7K0nCwonDgDuGUoW9ukerb+M/dM7m3qEGwW5T+ugz240fGElAn4jsEHBZg==", + "dev": true, + "dependencies": { + "clipboardy": "^4.0.0", + "clone-deep": "^4.0.1", + "compare-versions": "^6.1.0", + "fs-extra": "^11.2.0" + }, + "peerDependencies": { + "selenium-webdriver": ">=4.6.1", + "typescript": ">=4.6.2" + } + }, + "node_modules/@sindresorhus/is": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.6.0.tgz", + "integrity": "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==", + "dev": true, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@szmarczak/http-timer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", + "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==", + "dev": true, + "dependencies": { + "defer-to-connect": "^2.0.1" + }, + "engines": { + "node": ">=14.16" + } + }, + "node_modules/@types/chai": { + "version": "4.3.14", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.14.tgz", + "integrity": "sha512-Wj71sXE4Q4AkGdG9Tvq1u/fquNz9EdG4LIJMwVVII7ashjD/8cf8fyIfJAjRr6YcsXnSE8cOGQPq1gqeR8z+3w==", + "dev": true + }, + "node_modules/@types/http-cache-semantics": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", + "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", + "dev": true + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "node_modules/@types/mocha": { + "version": "10.0.6", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.6.tgz", + "integrity": "sha512-dJvrYWxP/UcXm36Qn36fxhUKu8A/xMRXVT2cliFF1Z7UA9liG5Psj3ezNSZw+5puH2czDXRLcXQxf8JbJt0ejg==", + "dev": true + }, + "node_modules/@types/node": { + "version": "20.12.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.7.tgz", + "integrity": "sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/selenium-webdriver": { + "version": "4.1.22", + "resolved": "https://registry.npmjs.org/@types/selenium-webdriver/-/selenium-webdriver-4.1.22.tgz", + "integrity": "sha512-MCL4l7q8dwxejr2Q2NXLyNwHWMPdlWE0Kpn6fFwJtvkJF7PTkG5jkvbH/X1IAAQxgt/L1dA8u2GtDeekvSKvOA==", + "dev": true, + "dependencies": { + "@types/ws": "*" + } + }, + "node_modules/@types/semver": { + "version": "7.5.8", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", + "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", + "dev": true + }, + "node_modules/@types/vscode": { + "version": "1.88.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.88.0.tgz", + "integrity": "sha512-rWY+Bs6j/f1lvr8jqZTyp5arRMfovdxolcqGi+//+cPDOh8SBvzXH90e7BiSXct5HJ9HGW6jATchbRTpTJpEkw==", + "dev": true + }, + "node_modules/@types/ws": { + "version": "8.5.10", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", + "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.7.1.tgz", + "integrity": "sha512-KwfdWXJBOviaBVhxO3p5TJiLpNuh2iyXyjmWN0f1nU87pwyvfS0EmjC6ukQVYVFJd/K1+0NWGPDXiyEyQorn0Q==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "7.7.1", + "@typescript-eslint/type-utils": "7.7.1", + "@typescript-eslint/utils": "7.7.1", + "@typescript-eslint/visitor-keys": "7.7.1", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^7.0.0", + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.7.1.tgz", + "integrity": "sha512-vmPzBOOtz48F6JAGVS/kZYk4EkXao6iGrD838sp1w3NQQC0W8ry/q641KU4PrG7AKNAf56NOcR8GOpH8l9FPCw==", + "dev": true, + "peer": true, + "dependencies": { + "@typescript-eslint/scope-manager": "7.7.1", + "@typescript-eslint/types": "7.7.1", + "@typescript-eslint/typescript-estree": "7.7.1", + "@typescript-eslint/visitor-keys": "7.7.1", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.7.1.tgz", + "integrity": "sha512-PytBif2SF+9SpEUKynYn5g1RHFddJUcyynGpztX3l/ik7KmZEv19WCMhUBkHXPU9es/VWGD3/zg3wg90+Dh2rA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.7.1", + "@typescript-eslint/visitor-keys": "7.7.1" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.7.1.tgz", + "integrity": "sha512-ZksJLW3WF7o75zaBPScdW1Gbkwhd/lyeXGf1kQCxJaOeITscoSl0MjynVvCzuV5boUz/3fOI06Lz8La55mu29Q==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "7.7.1", + "@typescript-eslint/utils": "7.7.1", + "debug": "^4.3.4", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.7.1.tgz", + "integrity": "sha512-AmPmnGW1ZLTpWa+/2omPrPfR7BcbUU4oha5VIbSbS1a1Tv966bklvLNXxp3mrbc+P2j4MNOTfDffNsk4o0c6/w==", + "dev": true, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.7.1.tgz", + "integrity": "sha512-CXe0JHCXru8Fa36dteXqmH2YxngKJjkQLjxzoj6LYwzZ7qZvgsLSc+eqItCrqIop8Vl2UKoAi0StVWu97FQZIQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.7.1", + "@typescript-eslint/visitor-keys": "7.7.1", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.7.1.tgz", + "integrity": "sha512-QUvBxPEaBXf41ZBbaidKICgVL8Hin0p6prQDu6bbetWo39BKbWJxRsErOzMNT1rXvTll+J7ChrbmMCXM9rsvOQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.15", + "@types/semver": "^7.5.8", + "@typescript-eslint/scope-manager": "7.7.1", + "@typescript-eslint/types": "7.7.1", + "@typescript-eslint/typescript-estree": "7.7.1", + "semver": "^7.6.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.7.1.tgz", + "integrity": "sha512-gBL3Eq25uADw1LQ9kVpf3hRM+DWzs0uZknHYK3hq4jcTPqVCClHGDnB6UUUV2SFeBeA4KWHWbbLqmbGcZ4FYbw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.7.1", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, + "node_modules/@vscode/vsce": { + "version": "2.26.0", + "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-2.26.0.tgz", + "integrity": "sha512-v54ltgMzUG8lGY0kAgaOlry57xse1RlWzes9FotfGEx+Fr05KeR8rZicQzEMDmi9QnOgVWHuiEq+xA2HWkAz+Q==", + "dev": true, + "dependencies": { + "@azure/identity": "^4.1.0", + "azure-devops-node-api": "^12.5.0", + "chalk": "^2.4.2", + "cheerio": "^1.0.0-rc.9", + "cockatiel": "^3.1.2", + "commander": "^6.2.1", + "form-data": "^4.0.0", + "glob": "^7.0.6", + "hosted-git-info": "^4.0.2", + "jsonc-parser": "^3.2.0", + "leven": "^3.1.0", + "markdown-it": "^12.3.2", + "mime": "^1.3.4", + "minimatch": "^3.0.3", + "parse-semver": "^1.1.1", + "read": "^1.0.7", + "semver": "^7.5.2", + "tmp": "^0.2.1", + "typed-rest-client": "^1.8.4", + "url-join": "^4.0.1", + "xml2js": "^0.5.0", + "yauzl": "^2.3.1", + "yazl": "^2.2.2" + }, + "bin": { + "vsce": "vsce" + }, + "engines": { + "node": ">= 16" + }, + "optionalDependencies": { + "keytar": "^7.7.0" + } + }, + "node_modules/@vscode/vsce/node_modules/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==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@vscode/vsce/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@vscode/vsce/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@vscode/vsce/node_modules/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==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@vscode/vsce/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@vscode/vsce/node_modules/commander": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", + "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@vscode/vsce/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@vscode/vsce/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@vscode/vsce/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@vscode/vsce/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@vscode/vsce/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/acorn": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "dev": true, + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/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==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true + }, + "node_modules/azure-devops-node-api": { + "version": "12.5.0", + "resolved": "https://registry.npmjs.org/azure-devops-node-api/-/azure-devops-node-api-12.5.0.tgz", + "integrity": "sha512-R5eFskGvOm3U/GzeAuxRkUsAl0hrAwGgWn6zAd2KrZmrEhWZVqLew4OOupbQlXUuojUzpGtq62SmdhJ06N88og==", + "dev": true, + "dependencies": { + "tunnel": "0.0.6", + "typed-rest-client": "^1.8.4" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "optional": true + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "optional": true, + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bl/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "optional": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "optional": true, + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-alloc": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", + "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", + "dev": true, + "dependencies": { + "buffer-alloc-unsafe": "^1.1.0", + "buffer-fill": "^1.0.0" + } + }, + "node_modules/buffer-alloc-unsafe": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", + "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==", + "dev": true + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "dev": true + }, + "node_modules/buffer-fill": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", + "integrity": "sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ==", + "dev": true + }, + "node_modules/cacheable-lookup": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", + "integrity": "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==", + "dev": true, + "engines": { + "node": ">=14.16" + } + }, + "node_modules/cacheable-request": { + "version": "10.2.14", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.14.tgz", + "integrity": "sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==", + "dev": true, + "dependencies": { + "@types/http-cache-semantics": "^4.0.2", + "get-stream": "^6.0.1", + "http-cache-semantics": "^4.1.1", + "keyv": "^4.5.3", + "mimic-response": "^4.0.0", + "normalize-url": "^8.0.0", + "responselike": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + } + }, + "node_modules/cacheable-request/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/chai": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.4.1.tgz", + "integrity": "sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==", + "dev": true, + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.3", + "deep-eql": "^4.1.3", + "get-func-name": "^2.0.2", + "loupe": "^2.3.6", + "pathval": "^1.1.1", + "type-detect": "^4.0.8" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/check-error": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", + "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.2" + }, + "engines": { + "node": "*" + } + }, + "node_modules/cheerio": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz", + "integrity": "sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==", + "dev": true, + "dependencies": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "htmlparser2": "^8.0.1", + "parse5": "^7.0.0", + "parse5-htmlparser2-tree-adapter": "^7.0.0" + }, + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" + } + }, + "node_modules/cheerio-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true + }, + "node_modules/clipboardy": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-4.0.0.tgz", + "integrity": "sha512-5mOlNS0mhX0707P2I0aZ2V/cmHUEO/fL7VFLqszkhUsxt7RwnmrInf/eEQKlf5GzvYeHIjT+Ov1HRfNmymlG0w==", + "dev": true, + "dependencies": { + "execa": "^8.0.1", + "is-wsl": "^3.1.0", + "is64bit": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/cockatiel": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/cockatiel/-/cockatiel-3.1.2.tgz", + "integrity": "sha512-5yARKww0dWyWg2/3xZeXgoxjHLwpVqFptj9Zy7qioJ6+/L0ARM184sgMUrQDjxw7ePJWlGhV998mKhzrxT0/Kg==", + "dev": true, + "engines": { + "node": ">=16" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.0.0.tgz", + "integrity": "sha512-MwVNWlYjDTtOjX5PiD7o5pK0UrFU/OYgcJfjjK4RaHZETNtjJqrZa9Y9ds88+A+f+d5lv+561eZ+yCKoS3gbAA==", + "dev": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/compare-versions": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.1.0.tgz", + "integrity": "sha512-LNZQXhqUvqUTotpZ00qLSaify3b4VFD588aRr8MKFw4CMUr98ytzCW5wDH5qx/DEY5kCDXcbcRuCqL0szEf2tg==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "dev": true, + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-eql": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", + "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", + "dev": true, + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, + "optional": true, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-libc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "dev": true, + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dev": true, + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "dev": true, + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escalade": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", + "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.0", + "@humanwhocodes/config-array": "^0.11.14", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dev": true, + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "bin": { + "flat": "cli.js" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "dev": true + }, + "node_modules/foreground-child": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", + "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/form-data-encoder": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-2.1.4.tgz", + "integrity": "sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==", + "dev": true, + "engines": { + "node": ">= 14.17" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true + }, + "node_modules/fs-extra": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", + "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/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==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-func-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "dev": true, + "optional": true + }, + "node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/got": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/got/-/got-13.0.0.tgz", + "integrity": "sha512-XfBk1CxOOScDcMr9O1yKkNaQyy865NbYs+F7dr4H0LZMVgCj2Le59k6PqbNHoL5ToeaEQUYh6c6yMfVcc6SJxA==", + "dev": true, + "dependencies": { + "@sindresorhus/is": "^5.2.0", + "@szmarczak/http-timer": "^5.0.1", + "cacheable-lookup": "^7.0.0", + "cacheable-request": "^10.2.8", + "decompress-response": "^6.0.0", + "form-data-encoder": "^2.1.2", + "get-stream": "^6.0.1", + "http2-wrapper": "^2.1.10", + "lowercase-keys": "^3.0.0", + "p-cancelable": "^3.0.0", + "responselike": "^3.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/got/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, + "node_modules/hosted-git-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/hpagent": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/hpagent/-/hpagent-1.2.0.tgz", + "integrity": "sha512-A91dYTeIB6NoXG+PxTQpCCDDnfHsW9kc06Lvpu1TEe9gnd6ZFeiBoRO9JvzEv6xK7EX97/dUE8g/vBMTqTS3CA==", + "dev": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/htmlparser2": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", + "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "entities": "^4.4.0" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", + "dev": true + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/http2-wrapper": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.1.tgz", + "integrity": "sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==", + "dev": true, + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.2.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", + "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", + "dev": true, + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true, + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "optional": true + }, + "node_modules/ignore": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "dev": true + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "optional": true + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/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==", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-wsl": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", + "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", + "dev": true, + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is64bit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is64bit/-/is64bit-2.0.0.tgz", + "integrity": "sha512-jv+8jaWCl0g2lSBkNSVXdzfBA0npK1HGC2KtWM9FumFRoGS94g3NbCCLVnCYHLjp4GrW2KZeeSTMo5ddtznmGw==", + "dev": true, + "dependencies": { + "system-architecture": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jackspeak": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", + "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", + "dev": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/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==", + "dev": true + }, + "node_modules/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": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/jsonc-parser": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz", + "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==", + "dev": true + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "dev": true, + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jsonwebtoken/node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "dev": true, + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jsonwebtoken/node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "dev": true, + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "dev": true, + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, + "node_modules/jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "dev": true, + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "dev": true, + "dependencies": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/keytar": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/keytar/-/keytar-7.9.0.tgz", + "integrity": "sha512-VPD8mtVtm5JNtA2AErl6Chp06JBfy7diFQ7TQQhdpWOl6MrCRB+eRbvAZUsbGQS9kiMq0coJsy0W0vHpDCkWsQ==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "dependencies": { + "node-addon-api": "^4.3.0", + "prebuild-install": "^7.0.1" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "dev": true, + "dependencies": { + "immediate": "~3.0.5" + } + }, + "node_modules/linkify-it": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", + "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", + "dev": true, + "dependencies": { + "uc.micro": "^1.0.1" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "dev": true + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "dev": true + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "dev": true + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "dev": true + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "dev": true + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "dev": true + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "dev": true + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/loupe": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", + "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.1" + } + }, + "node_modules/lowercase-keys": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", + "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/markdown-it": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", + "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1", + "entities": "~2.1.0", + "linkify-it": "^3.0.1", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + }, + "bin": { + "markdown-it": "bin/markdown-it.js" + } + }, + "node_modules/markdown-it/node_modules/entities": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", + "dev": true, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==", + "dev": true + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mimic-response": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", + "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", + "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "dev": true, + "optional": true + }, + "node_modules/mocha": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.4.0.tgz", + "integrity": "sha512-eqhGB8JKapEYcC4ytX/xrzKforgEc3j1pGlAXVy3eRwrtAy5/nIfT1SvgGzfN0XZZxeLq0aQWkOUAmqIJiv+bA==", + "dev": true, + "dependencies": { + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.3", + "debug": "4.3.4", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "8.1.0", + "he": "1.2.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "5.0.1", + "ms": "2.1.3", + "serialize-javascript": "6.0.0", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "workerpool": "6.2.1", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/mocha/node_modules/minimatch": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", + "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mocha/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/mocha/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true + }, + "node_modules/napi-build-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", + "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==", + "dev": true, + "optional": true + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/node-abi": { + "version": "3.62.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.62.0.tgz", + "integrity": "sha512-CPMcGa+y33xuL1E0TcNIu4YyaZCxnnvkVaEXrsosR3FxN+fV8xvb7Mzpb7IgKler10qeMkE6+Dp8qJhpzdq35g==", + "dev": true, + "optional": true, + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-addon-api": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz", + "integrity": "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==", + "dev": true, + "optional": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-url": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.1.tgz", + "integrity": "sha512-IO9QvjUMWxPQQhs60oOu10CRkWCiZzSUkzbXGGV9pviYl1fXYcvkzQ5jV9z8Y6un8ARoVRl4EtC6v6jNqbaJ/w==", + "dev": true, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "dev": true, + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/object-inspect": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "dev": true, + "dependencies": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open/node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open/node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/optionator": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, + "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-cancelable": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz", + "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==", + "dev": true, + "engines": { + "node": ">=12.20" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "dev": true + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-semver": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/parse-semver/-/parse-semver-1.1.1.tgz", + "integrity": "sha512-Eg1OuNntBMH0ojvEKSrvDSnwLmvVuUOSdylH/pSCPNMIspLlweJyIWXCE+k/5hm3cj/EBUYwmWkjhBALNP4LXQ==", + "dev": true, + "dependencies": { + "semver": "^5.1.0" + } + }, + "node_modules/parse-semver/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dev": true, + "dependencies": { + "entities": "^4.4.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz", + "integrity": "sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==", + "dev": true, + "dependencies": { + "domhandler": "^5.0.2", + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.2.tgz", + "integrity": "sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==", + "dev": true, + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.1.tgz", + "integrity": "sha512-tS24spDe/zXhWbNPErCHs/AGOzbKGHT+ybSBqmdLm8WZ1xXLWvH8Qn71QPAlqVhd0qUTWjy+Kl9JmISgDdEjsA==", + "dev": true, + "engines": { + "node": "14 || >=16.14" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/prebuild-install": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.2.tgz", + "integrity": "sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ==", + "dev": true, + "optional": true, + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^1.0.1", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/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==", + "dev": true + }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "optional": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.12.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.12.1.tgz", + "integrity": "sha512-zWmv4RSuB9r2mYQw3zxQuHWeU+42aKi1wWig/j4ele4ygELZ7PEO6MM7rim9oAQH2A5MWfsAVf/jPvTPgCbvUQ==", + "dev": true, + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "optional": true, + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", + "integrity": "sha512-rSOKNYUmaxy0om1BNjMN4ezNT6VKK+2xF4GBhc81mkH7L60i6dp8qPYrkndNLT3QPphoII3maL9PVC9XmhHwVQ==", + "dev": true, + "dependencies": { + "mute-stream": "~0.0.4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "dependencies": { + "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" + } + }, + "node_modules/readable-stream/node_modules/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==", + "dev": true + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", + "dev": true + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/responselike": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz", + "integrity": "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==", + "dev": true, + "dependencies": { + "lowercase-keys": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/sanitize-filename": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.3.tgz", + "integrity": "sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg==", + "dev": true, + "dependencies": { + "truncate-utf8-bytes": "^1.0.0" + } + }, + "node_modules/sax": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz", + "integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==", + "dev": true + }, + "node_modules/selenium-webdriver": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.20.0.tgz", + "integrity": "sha512-s/G44lGQ1xB3tmtX6NNPomlkpL6CxLdmAvp/AGWWwi4qv5Te1+qji7tPSyr6gyuoPpdYiof1rKnWe3luy0MrYA==", + "dev": true, + "dependencies": { + "jszip": "^3.10.1", + "tmp": "^0.2.3", + "ws": ">=8.16.0" + }, + "engines": { + "node": ">= 14.20.0" + } + }, + "node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "dev": true + }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "optional": true + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "optional": true, + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/stoppable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stoppable/-/stoppable-1.1.0.tgz", + "integrity": "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==", + "dev": true, + "engines": { + "node": ">=4", + "npm": ">=6" + } + }, + "node_modules/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==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string_decoder/node_modules/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==", + "dev": true + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/system-architecture": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/system-architecture/-/system-architecture-0.1.0.tgz", + "integrity": "sha512-ulAk51I9UVUyJgxlv9M6lFot2WP3e7t8Kz9+IS6D4rVba1tR9kON+Ey69f+1R4Q8cd45Lod6a4IcJIxnzGc/zA==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "dev": true, + "optional": true, + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "optional": true, + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tar-stream/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "optional": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/targz": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/targz/-/targz-1.0.1.tgz", + "integrity": "sha512-6q4tP9U55mZnRuMTBqnqc3nwYQY3kv+QthCFZuMk+Tn1qYUnMPmL/JZ/mzgXINzFpSqfU+242IFmFU9VPvqaQw==", + "dev": true, + "dependencies": { + "tar-fs": "^1.8.1" + } + }, + "node_modules/targz/node_modules/bl": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.3.tgz", + "integrity": "sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==", + "dev": true, + "dependencies": { + "readable-stream": "^2.3.5", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/targz/node_modules/pump": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-1.0.3.tgz", + "integrity": "sha512-8k0JupWme55+9tCVE+FS5ULT3K6AbgqrGa58lTT49RpyfwwcGedHqaC5LlQNdEAumn/wFsu6aPwkuPMioy8kqw==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/targz/node_modules/tar-fs": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-1.16.3.tgz", + "integrity": "sha512-NvCeXpYx7OsmOh8zIOP/ebG55zZmxLE0etfWRbWok+q2Qo8x/vOR/IJT1taADXPe+jsiu9axDb3X4B+iIgNlKw==", + "dev": true, + "dependencies": { + "chownr": "^1.0.1", + "mkdirp": "^0.5.1", + "pump": "^1.0.0", + "tar-stream": "^1.1.2" + } + }, + "node_modules/targz/node_modules/tar-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz", + "integrity": "sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==", + "dev": true, + "dependencies": { + "bl": "^1.0.0", + "buffer-alloc": "^1.2.0", + "end-of-stream": "^1.0.0", + "fs-constants": "^1.0.0", + "readable-stream": "^2.3.0", + "to-buffer": "^1.1.1", + "xtend": "^4.0.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/tmp": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", + "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", + "dev": true, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/to-buffer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz", + "integrity": "sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==", + "dev": true + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/truncate-utf8-bytes": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", + "integrity": "sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==", + "dev": true, + "dependencies": { + "utf8-byte-length": "^1.0.1" + } + }, + "node_modules/ts-api-utils": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", + "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", + "dev": true, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "dev": true + }, + "node_modules/tunnel": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", + "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", + "dev": true, + "engines": { + "node": ">=0.6.11 <=0.7.0 || >=0.7.3" + } + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "dev": true, + "optional": true, + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/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==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typed-rest-client": { + "version": "1.8.11", + "resolved": "https://registry.npmjs.org/typed-rest-client/-/typed-rest-client-1.8.11.tgz", + "integrity": "sha512-5UvfMpd1oelmUPRbbaVnq+rHP7ng2cE4qoQkQeAqxRL6PklkxsM0g32/HL0yfvruK6ojQ5x8EE+HF4YV6DtuCA==", + "dev": true, + "dependencies": { + "qs": "^6.9.1", + "tunnel": "0.0.6", + "underscore": "^1.12.1" + } + }, + "node_modules/typescript": { + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/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==", + "dev": true + }, + "node_modules/underscore": { + "version": "1.13.6", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", + "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==", + "dev": true + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/url-join": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", + "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==", + "dev": true + }, + "node_modules/utf8-byte-length": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz", + "integrity": "sha512-4+wkEYLBbWxqTahEsWrhxepcoVOJ+1z5PGIjPZxRkytcdSUaNjIjBM7Xn8E+pdSuV7SzvWovBFA54FO0JSoqhA==", + "dev": true + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/vscode-extension-tester": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/vscode-extension-tester/-/vscode-extension-tester-8.1.0.tgz", + "integrity": "sha512-gXyGi4D+ZqemAvt7IegFUBxVLQH1ftTRiMKqlwH1BBi+x1yhPXXuIVo6TSWUlyGMznbpFV5DUK3GEAfc13nSsg==", + "dev": true, + "dependencies": { + "@redhat-developer/locators": "^1.1.0", + "@redhat-developer/page-objects": "^1.1.0", + "@types/selenium-webdriver": "^4.1.22", + "@vscode/vsce": "^2.26.0", + "commander": "^12.0.0", + "compare-versions": "^6.1.0", + "fs-extra": "^11.2.0", + "glob": "^10.3.12", + "got": "^13.0.0", + "hpagent": "^1.2.0", + "js-yaml": "^4.1.0", + "sanitize-filename": "^1.6.3", + "selenium-webdriver": "^4.19.0", + "targz": "^1.0.1" + }, + "bin": { + "extest": "out/cli.js" + }, + "peerDependencies": { + "mocha": ">=5.2.0", + "typescript": ">=4.6.2" + } + }, + "node_modules/vscode-extension-tester/node_modules/glob": { + "version": "10.3.12", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz", + "integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^2.3.6", + "minimatch": "^9.0.1", + "minipass": "^7.0.4", + "path-scurry": "^1.10.2" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/workerpool": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", + "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", + "dev": true + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/ws": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", + "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", + "dev": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml2js": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz", + "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==", + "dev": true, + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dev": true, + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/yazl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/yazl/-/yazl-2.5.1.tgz", + "integrity": "sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw==", + "dev": true, + "dependencies": { + "buffer-crc32": "~0.2.3" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } } diff --git a/examples/helloworld-extester/package.json b/examples/helloworld-extester/package.json index 40bd5216e..ff1851ac0 100644 --- a/examples/helloworld-extester/package.json +++ b/examples/helloworld-extester/package.json @@ -1,58 +1,58 @@ { - "name": "helloworld-extester", - "displayName": "HelloWorld ExTester", - "description": "HelloWorld example extension for ExTester", - "icon": "icons/logo.png", - "preview": true, - "private": true, - "version": "0.1.0", - "main": "./out/extension.js", - "author": { - "name": "ExTester" - }, - "publisher": "ExTester", - "license": "Apache-2.0", - "engines": { - "vscode": "^1.85.0" - }, - "repository": { - "type": "git", - "url": "https://github.com/redhat-developer/vscode-extension-tester.git", - "directory": "examples/helloworld-extester" - }, - "categories": [ - "Other", - "Testing" - ], - "contributes": { - "commands": [ - { - "command": "extension.helloWorld", - "title": "Hello World" - }, - { - "command": "extension.webview", - "title": "Webview Test" - } - ] - }, - "scripts": { - "vscode:prepublish": "npm run compile", - "compile": "tsc -p ./ && npm run lint", - "lint": "eslint src --ext .ts", - "watch": "tsc -watch -p ./", - "ui-test": "extest setup-and-run './out/ui-test/*.test.js' --code_version max --code_settings settings.json --extensions_dir .test-extensions" - }, - "devDependencies": { - "@types/chai": "^4.3.14", - "@types/mocha": "^10.0.6", - "@types/node": "^20.12.7", - "@types/vscode": "^1.85.0", - "@typescript-eslint/eslint-plugin": "^7.7.1", - "chai": "^4.4.1", - "eslint": "^8.57.0", - "mocha": "^10.4.0", - "typescript": "^5.4.5", - "vscode-extension-tester": "^8.1.0" - } + "name": "helloworld-extester", + "displayName": "HelloWorld ExTester", + "description": "HelloWorld example extension for ExTester", + "icon": "icons/logo.png", + "preview": true, + "private": true, + "version": "0.1.0", + "main": "./out/extension.js", + "author": { + "name": "ExTester" + }, + "publisher": "ExTester", + "license": "Apache-2.0", + "engines": { + "vscode": "^1.85.0" + }, + "repository": { + "type": "git", + "url": "https://github.com/redhat-developer/vscode-extension-tester.git", + "directory": "examples/helloworld-extester" + }, + "categories": [ + "Other", + "Testing" + ], + "contributes": { + "commands": [ + { + "command": "extension.helloWorld", + "title": "Hello World" + }, + { + "command": "extension.webview", + "title": "Webview Test" + } + ] + }, + "scripts": { + "vscode:prepublish": "npm run compile", + "compile": "tsc -p ./ && npm run lint", + "lint": "eslint src --ext .ts", + "watch": "tsc -watch -p ./", + "ui-test": "extest setup-and-run './out/ui-test/*.test.js' --code_version max --code_settings settings.json --extensions_dir .test-extensions" + }, + "devDependencies": { + "@types/chai": "^4.3.14", + "@types/mocha": "^10.0.6", + "@types/node": "^20.12.7", + "@types/vscode": "^1.85.0", + "@typescript-eslint/eslint-plugin": "^7.7.1", + "chai": "^4.4.1", + "eslint": "^8.57.0", + "mocha": "^10.4.0", + "typescript": "^5.4.5", + "vscode-extension-tester": "^8.1.0" + } } diff --git a/examples/helloworld-extester/settings.json b/examples/helloworld-extester/settings.json index f26e662b6..429af9819 100644 --- a/examples/helloworld-extester/settings.json +++ b/examples/helloworld-extester/settings.json @@ -1,6 +1,6 @@ { - "typescript.updateImportsOnFileMove.enabled": "always", - "workbench.editor.enablePreview": true, - "git.autoRepositoryDetection": false, - "terminal.integrated.sendKeybindingsToShell": true -} \ No newline at end of file + "typescript.updateImportsOnFileMove.enabled": "always", + "workbench.editor.enablePreview": true, + "git.autoRepositoryDetection": false, + "terminal.integrated.sendKeybindingsToShell": true +} diff --git a/examples/helloworld-extester/src/extension.ts b/examples/helloworld-extester/src/extension.ts index 65722e63d..714230c77 100644 --- a/examples/helloworld-extester/src/extension.ts +++ b/examples/helloworld-extester/src/extension.ts @@ -19,7 +19,7 @@ export function activate(context: vscode.ExtensionContext) { vscode.window.showInformationMessage('Hello World!'); }); - let webViewCommand = vscode.commands.registerCommand('extension.webview', async() => { + let webViewCommand = vscode.commands.registerCommand('extension.webview', async () => { TestView.createOrShow(); }); @@ -38,9 +38,7 @@ class TestView { private _disposables: vscode.Disposable[] = []; public static createOrShow() { - const column = vscode.window.activeTextEditor - ? vscode.window.activeTextEditor.viewColumn - : undefined; + const column = vscode.window.activeTextEditor ? vscode.window.activeTextEditor.viewColumn : undefined; if (TestView.instance) { TestView.instance._panel.reveal(column); @@ -58,13 +56,13 @@ class TestView { this._panel.onDidDispose(() => this.dispose(), null, this._disposables); this._panel.onDidChangeViewState( - e => { + (e) => { if (this._panel.visible) { this.update(); } }, null, - this._disposables + this._disposables, ); } @@ -94,4 +92,4 @@ class TestView { `; } -} \ No newline at end of file +} diff --git a/examples/helloworld-extester/src/ui-test/.mocharc-debug.js b/examples/helloworld-extester/src/ui-test/.mocharc-debug.js index 3677bd57a..448a416cb 100644 --- a/examples/helloworld-extester/src/ui-test/.mocharc-debug.js +++ b/examples/helloworld-extester/src/ui-test/.mocharc-debug.js @@ -1,3 +1,3 @@ module.exports = { - timeout: 99999999 -} \ No newline at end of file + timeout: 99999999, +}; diff --git a/examples/helloworld-extester/src/ui-test/activityBar.test.ts b/examples/helloworld-extester/src/ui-test/activityBar.test.ts index ba66c49b1..14dd251bd 100644 --- a/examples/helloworld-extester/src/ui-test/activityBar.test.ts +++ b/examples/helloworld-extester/src/ui-test/activityBar.test.ts @@ -20,53 +20,55 @@ import { ActivityBar } from 'vscode-extension-tester'; // sample tests using the Activity Bar (the left toolbar) describe('Activity Bar Example Tests', () => { - let activityBar: ActivityBar; + let activityBar: ActivityBar; - before(async () => { - // init the activity bar page object - activityBar = new ActivityBar(); - }); + before(async () => { + // init the activity bar page object + activityBar = new ActivityBar(); + }); - // Test what view controls are available - it('Shows explorer view control (container)', async () => { - // get all the view controls - const controls = await activityBar.getViewControls(); - expect(controls).not.empty; + // Test what view controls are available + it('Shows explorer view control (container)', async () => { + // get all the view controls + const controls = await activityBar.getViewControls(); + expect(controls).not.empty; - // get titles from the controls - const titles = await Promise.all(controls.map(async (control) => { - return control.getTitle(); - })); + // get titles from the controls + const titles = await Promise.all( + controls.map(async (control) => { + return control.getTitle(); + }), + ); - // assert a view control named 'Explorer' is present - // the keyboard shortcut is part of the title, so we do a little transformation - expect(titles.some(title => title.startsWith('Explorer'))).is.true; - }); + // assert a view control named 'Explorer' is present + // the keyboard shortcut is part of the title, so we do a little transformation + expect(titles.some((title) => title.startsWith('Explorer'))).is.true; + }); - // Opening a view by title - it('Get a view control and open its associated view', async () => { - // retrieving a view control by title does not require the keyboard shortcut to be part of the argument - // if the given control exists, it will be returned, otherwise it is undefined - const ctrl = await activityBar.getViewControl('Explorer'); + // Opening a view by title + it('Get a view control and open its associated view', async () => { + // retrieving a view control by title does not require the keyboard shortcut to be part of the argument + // if the given control exists, it will be returned, otherwise it is undefined + const ctrl = await activityBar.getViewControl('Explorer'); - // click the given control to open its view (using optional notation since it can be undefined) - const view = await ctrl?.openView(); + // click the given control to open its view (using optional notation since it can be undefined) + const view = await ctrl?.openView(); - // assert the view is open - expect(view).is.not.undefined; - expect(await view?.isDisplayed()).is.true; - }); + // assert the view is open + expect(view).is.not.undefined; + expect(await view?.isDisplayed()).is.true; + }); - // Using the global actions controls (the ones on the bottom of the activity bar) - // This test uses context menus, which are not available on mac, so we skip it there - (process.platform === 'darwin' ? it.skip : it)('Manipulate the Global Actions', async () => { - // get a global action control analogically to view controls - const manage = await activityBar.getGlobalAction('Manage'); + // Using the global actions controls (the ones on the bottom of the activity bar) + // This test uses context menus, which are not available on mac, so we skip it there + (process.platform === 'darwin' ? it.skip : it)('Manipulate the Global Actions', async () => { + // get a global action control analogically to view controls + const manage = await activityBar.getGlobalAction('Manage'); - // actions open a context menu on click - const menu = await manage?.openActionMenu(); + // actions open a context menu on click + const menu = await manage?.openActionMenu(); - // lets just close the menu for now - await menu?.close(); - }); + // lets just close the menu for now + await menu?.close(); + }); }); diff --git a/examples/helloworld-extester/src/ui-test/bottomBar.test.ts b/examples/helloworld-extester/src/ui-test/bottomBar.test.ts index 0f4613a3b..74124d135 100644 --- a/examples/helloworld-extester/src/ui-test/bottomBar.test.ts +++ b/examples/helloworld-extester/src/ui-test/bottomBar.test.ts @@ -15,167 +15,169 @@ * limitations under the License. */ -import { BottomBarPanel, MarkerType, OutputView, ProblemsView, TerminalView, VSBrowser } from "vscode-extension-tester"; +import { BottomBarPanel, MarkerType, OutputView, ProblemsView, TerminalView, VSBrowser } from 'vscode-extension-tester'; import * as path from 'path'; -import { expect } from "chai"; +import { expect } from 'chai'; // Sample tests using the Bottom Bar, the panel that houses the terminal, output, problems, etc. describe('Bottom Bar Example Tests', function () { - let bottomBar: BottomBarPanel; - - before(async function () { - // init the bottom bar page object - bottomBar = new BottomBarPanel(); - - // make sure the panel is open - await bottomBar.toggle(true); - }); - - after(async function () { - // make sure the panel is closed - await bottomBar.toggle(false); - }); - - // The panel houses potentially several different views, lets test those - // starting with the problems view - describe('Problems View', function () { - let view: ProblemsView; - - // wait condition for problem markers to exist within problems view - async function problemsExist(view: ProblemsView) { - // search for markers regardless of type until some are found - const markers = await view.getAllVisibleMarkers(MarkerType.Any); - return markers.length > 0; - } - - before(async function () { - // this operation will likely take more than 2 seconds (default mocha timeout) - // we need to increase the timeout, unless we're using a global config file for that - this.timeout(30000); - - // firstly, open the problems view - view = await bottomBar.openProblemsView(); - - // now we need some problems, lets open a file that contains some - await VSBrowser.instance.openResources(path.join('src', 'ui-test', 'resources', 'problems.ts')); - - // wait for the editor to parse the file and display the problem markers - await view.getDriver().wait(async function () { return await problemsExist(view); }, 15000); - }); - - // These tests use getAllVisibleMarkers() and are unreliable and should not be included. - // - // now we can look at the error markers - it('Error markers are displayed', async function () { - // generally, there are 3 marker types (warning, error, and file - file just contains other markers though) - // we want to see the errors - const errors = await view.getAllVisibleMarkers(MarkerType.Error); - - // assert that there are errors (there should be about 8 in the file) - expect(errors.length).is.greaterThan(5); - }); - - // we can make sure no warnings are present at the same time - it('There are no warnings', async function () { - const warnings = await view.getAllVisibleMarkers(MarkerType.Warning); - expect(warnings).is.empty; - }); - - // there is also a file marker (out problematic file that contains the errors) - it('There is a file marker', async function () { - const files = await view.getAllVisibleMarkers(MarkerType.File); - const file = files[0]; - - // we can get the text of the marker - expect(await file.getText()).contains('problems.ts'); - // and the type - expect(await file.getType()).equals(MarkerType.File); - // and we can collapse & expand the file marker - await file.toggleExpand(false); - await file.toggleExpand(true); - }); - - it('Markers are displayed', async function () { - // Need to throttle this test in order for VS Code to load/display all of the errors - // and warnings. - await new Promise(res => setTimeout((res), 3000)); - - const markers = await view.getAllVisibleMarkers(MarkerType.Any); - const badgeElement = await view.getCountBadge(); - const badgeText = await badgeElement.getText(); - - // getAllVisibleMarkers() only returns the **visible** markers, so we can't rely on the count, - // but we should be able to rely on at least one appearing. - expect(markers.length).is.greaterThan(0); - - // Regardless of how many are visible, the first row contains the summary, and the badge - // contains the count. - expect(badgeText).equals('7'); - }); - - // we can also define filtering for problems - it('Filtering works', async function () { - // set filter to something more specific - // Workaround: calling twice to bypass some weird behaviour in some local cases - await view.setFilter('aa'); - await view.getDriver().sleep(500); - await view.setFilter('aa'); - - // wait a bit for the filter to apply - await view.getDriver().sleep(500); - const errors = await view.getAllVisibleMarkers(MarkerType.Error); - - // now there should be just a single error - expect(errors.length).equals(1); - - // clearing the filter is just as simple - await view.clearFilter(); - }); - }); - - // lets test the output view now - describe('Output View', function () { - let view: OutputView; - - before(async function () { - this.timeout(30_000); - // open the output view first - view = await bottomBar.openOutputView(); - - // select a channel that actually has some text in it - await view.selectChannel('Main'); - }); - - // check if there is text in the output - it('Get the text', async function () { - const text = await view.getText(); - expect(text).is.not.empty; - }); - - it('Clear the output channel', async function () { - await view.clearText(); - const text = await view.getText(); - - // now the log is technically empty, it just contains a newline character - expect(text).equals('\n'); - }); - }); - - describe('Terminal View', function () { - let view: TerminalView; - - before(async function () { - view = await bottomBar.openTerminalView(); - }); - - it('Execute a command', async function () { - await view.executeCommand('echo "hello world\\!"', 2000); - - // now there should be a line saying 'hello world!' in the terminal - const text = await view.getText(); - const textFound = text.split('\n').some(line => line === 'hello world!'); - - expect(textFound).is.true; - }); - }); -}); \ No newline at end of file + let bottomBar: BottomBarPanel; + + before(async function () { + // init the bottom bar page object + bottomBar = new BottomBarPanel(); + + // make sure the panel is open + await bottomBar.toggle(true); + }); + + after(async function () { + // make sure the panel is closed + await bottomBar.toggle(false); + }); + + // The panel houses potentially several different views, lets test those + // starting with the problems view + describe('Problems View', function () { + let view: ProblemsView; + + // wait condition for problem markers to exist within problems view + async function problemsExist(view: ProblemsView) { + // search for markers regardless of type until some are found + const markers = await view.getAllVisibleMarkers(MarkerType.Any); + return markers.length > 0; + } + + before(async function () { + // this operation will likely take more than 2 seconds (default mocha timeout) + // we need to increase the timeout, unless we're using a global config file for that + this.timeout(30000); + + // firstly, open the problems view + view = await bottomBar.openProblemsView(); + + // now we need some problems, lets open a file that contains some + await VSBrowser.instance.openResources(path.join('src', 'ui-test', 'resources', 'problems.ts')); + + // wait for the editor to parse the file and display the problem markers + await view.getDriver().wait(async function () { + return await problemsExist(view); + }, 15000); + }); + + // These tests use getAllVisibleMarkers() and are unreliable and should not be included. + // + // now we can look at the error markers + it('Error markers are displayed', async function () { + // generally, there are 3 marker types (warning, error, and file - file just contains other markers though) + // we want to see the errors + const errors = await view.getAllVisibleMarkers(MarkerType.Error); + + // assert that there are errors (there should be about 8 in the file) + expect(errors.length).is.greaterThan(5); + }); + + // we can make sure no warnings are present at the same time + it('There are no warnings', async function () { + const warnings = await view.getAllVisibleMarkers(MarkerType.Warning); + expect(warnings).is.empty; + }); + + // there is also a file marker (out problematic file that contains the errors) + it('There is a file marker', async function () { + const files = await view.getAllVisibleMarkers(MarkerType.File); + const file = files[0]; + + // we can get the text of the marker + expect(await file.getText()).contains('problems.ts'); + // and the type + expect(await file.getType()).equals(MarkerType.File); + // and we can collapse & expand the file marker + await file.toggleExpand(false); + await file.toggleExpand(true); + }); + + it('Markers are displayed', async function () { + // Need to throttle this test in order for VS Code to load/display all of the errors + // and warnings. + await new Promise((res) => setTimeout(res, 3000)); + + const markers = await view.getAllVisibleMarkers(MarkerType.Any); + const badgeElement = await view.getCountBadge(); + const badgeText = await badgeElement.getText(); + + // getAllVisibleMarkers() only returns the **visible** markers, so we can't rely on the count, + // but we should be able to rely on at least one appearing. + expect(markers.length).is.greaterThan(0); + + // Regardless of how many are visible, the first row contains the summary, and the badge + // contains the count. + expect(badgeText).equals('7'); + }); + + // we can also define filtering for problems + it('Filtering works', async function () { + // set filter to something more specific + // Workaround: calling twice to bypass some weird behaviour in some local cases + await view.setFilter('aa'); + await view.getDriver().sleep(500); + await view.setFilter('aa'); + + // wait a bit for the filter to apply + await view.getDriver().sleep(500); + const errors = await view.getAllVisibleMarkers(MarkerType.Error); + + // now there should be just a single error + expect(errors.length).equals(1); + + // clearing the filter is just as simple + await view.clearFilter(); + }); + }); + + // lets test the output view now + describe('Output View', function () { + let view: OutputView; + + before(async function () { + this.timeout(30_000); + // open the output view first + view = await bottomBar.openOutputView(); + + // select a channel that actually has some text in it + await view.selectChannel('Main'); + }); + + // check if there is text in the output + it('Get the text', async function () { + const text = await view.getText(); + expect(text).is.not.empty; + }); + + it('Clear the output channel', async function () { + await view.clearText(); + const text = await view.getText(); + + // now the log is technically empty, it just contains a newline character + expect(text).equals('\n'); + }); + }); + + describe('Terminal View', function () { + let view: TerminalView; + + before(async function () { + view = await bottomBar.openTerminalView(); + }); + + it('Execute a command', async function () { + await view.executeCommand('echo "hello world\\!"', 2000); + + // now there should be a line saying 'hello world!' in the terminal + const text = await view.getText(); + const textFound = text.split('\n').some((line) => line === 'hello world!'); + + expect(textFound).is.true; + }); + }); +}); diff --git a/examples/helloworld-extester/src/ui-test/commands.test.ts b/examples/helloworld-extester/src/ui-test/commands.test.ts index 4b16e7b83..a6425083b 100644 --- a/examples/helloworld-extester/src/ui-test/commands.test.ts +++ b/examples/helloworld-extester/src/ui-test/commands.test.ts @@ -15,23 +15,22 @@ * limitations under the License. */ -import { Workbench } from "vscode-extension-tester"; +import { Workbench } from 'vscode-extension-tester'; describe('Sample Command palette tests', () => { + it('using executeCommand', async () => { + // the simplest way to execute a command + // this opens the command palette, puts in the command, and confirms + await new Workbench().executeCommand('hello world'); + }); - it('using executeCommand', async () => { - // the simplest way to execute a command - // this opens the command palette, puts in the command, and confirms - await new Workbench().executeCommand('hello world'); - }); + it('using the command prompt', async () => { + // or you can open the command prompt/palette and work with it as with an input box + const prompt = await new Workbench().openCommandPrompt(); - it('using the command prompt', async () => { - // or you can open the command prompt/palette and work with it as with an input box - const prompt = await new Workbench().openCommandPrompt(); - - // make sure that when executing a command this way you prepend it with a '>' symbol - // otherwise it is going to try and find a file with the given name - await prompt.setText('>hello world'); - await prompt.confirm(); - }); -}); \ No newline at end of file + // make sure that when executing a command this way you prepend it with a '>' symbol + // otherwise it is going to try and find a file with the given name + await prompt.setText('>hello world'); + await prompt.confirm(); + }); +}); diff --git a/examples/helloworld-extester/src/ui-test/extensionsView.test.ts b/examples/helloworld-extester/src/ui-test/extensionsView.test.ts index 096f4e528..213757d81 100644 --- a/examples/helloworld-extester/src/ui-test/extensionsView.test.ts +++ b/examples/helloworld-extester/src/ui-test/extensionsView.test.ts @@ -21,32 +21,32 @@ import pjson from '../../package.json'; // sample test code on how to look for an extension describe('Example extension view tests', () => { - let helloExtension: ExtensionsViewItem; - - before(async function () { - this.timeout(15000); - // open the extensions view - const view = await (await new ActivityBar().getViewControl('Extensions'))?.openView(); + let helloExtension: ExtensionsViewItem; - // we want to find the hello-world extension (this project) - // first we need a view section, best place to get started is the 'Installed' section - const extensions = await view?.getContent().getSection('Installed') as ExtensionsViewSection; + before(async function () { + this.timeout(15000); + // open the extensions view + const view = await (await new ActivityBar().getViewControl('Extensions'))?.openView(); - // search for the extension, you can use any syntax vscode supports for the search field - // it is best to prepend @installed to the extension name if you don't want to see the results from marketplace - // also, getting the name directly from package.json seem like a good idea - helloExtension = await extensions.findItem(`@installed ${pjson.displayName}`) as ExtensionsViewItem; - }); + // we want to find the hello-world extension (this project) + // first we need a view section, best place to get started is the 'Installed' section + const extensions = (await view?.getContent().getSection('Installed')) as ExtensionsViewSection; - it('Check the extension info', async () => { - // now we have the extension item, we can check it shows all the fields we want - const author = await helloExtension.getAuthor(); - const desc = await helloExtension.getDescription(); - const version = await helloExtension.getVersion(); + // search for the extension, you can use any syntax vscode supports for the search field + // it is best to prepend @installed to the extension name if you don't want to see the results from marketplace + // also, getting the name directly from package.json seem like a good idea + helloExtension = (await extensions.findItem(`@installed ${pjson.displayName}`)) as ExtensionsViewItem; + }); - // in this case we are comparing the results against the values in package.json - expect(author).equals(pjson.publisher); - expect(desc).equals(pjson.description); - expect(version).equals(pjson.version); - }); -}); \ No newline at end of file + it('Check the extension info', async () => { + // now we have the extension item, we can check it shows all the fields we want + const author = await helloExtension.getAuthor(); + const desc = await helloExtension.getDescription(); + const version = await helloExtension.getVersion(); + + // in this case we are comparing the results against the values in package.json + expect(author).equals(pjson.publisher); + expect(desc).equals(pjson.description); + expect(version).equals(pjson.version); + }); +}); diff --git a/examples/helloworld-extester/src/ui-test/input.test.ts b/examples/helloworld-extester/src/ui-test/input.test.ts index 526416869..a8298c2cc 100644 --- a/examples/helloworld-extester/src/ui-test/input.test.ts +++ b/examples/helloworld-extester/src/ui-test/input.test.ts @@ -15,72 +15,72 @@ * limitations under the License. */ -import { expect } from "chai"; -import { InputBox, Workbench } from "vscode-extension-tester"; +import { expect } from 'chai'; +import { InputBox, Workbench } from 'vscode-extension-tester'; // Example tests on handling input boxes describe('Sample Inputs tests', () => { - let input: InputBox; - - before(async () => { - // we need an input box to open - // extensions usually open inputs as part of their commands - // the built-in input box we can use is the command prompt/palette - await new Workbench().openCommandPrompt(); - - // openCommandPrompt returns an InputBox, but if you need to wait for an arbitrary input to appear - // note this does not open the input, it simply waits for it to open and constructs the page object - input = await InputBox.create(); - }); - - it('Text manipulation', async () => { - // set some text in the input - await input.setText('hello'); - - // get the current text - const text = await input.getText(); - expect(text).equals('hello'); - - // clear the text - await input.clear(); - }); - - it('Quick picks', async () => { - await input.setText('> hello world'); - - // get all the visible quick picks - // note that quick picks outside the scroll window are ignored - const picks = await input.getQuickPicks(); - expect(picks).not.empty; - - // if you want to find a quick pick that might not be currently shown, use - const pick = await input.findQuickPick('Hello World'); - - // or if you want to find and select a quick pick - // await input.selectQuickPick('Hello World'); - - // if checkboxes are available for your quick picks, you can also check/uncheck them all in one call - // await input.toggleAllQuickPicks(true/false); - }); - - // it('Other properties', async () => { - // // input boxes can have multiple other properties, all of these are self explanatory - - // await input.getMessage(); - // await input.getPlaceHolder(); - // await input.hasError(); - // await input.hasProgress(); - // }); - - it('Flow actions', async () => { - // there are a few actions to perform with an input box - // to confirm - // await input.confirm(); - - // if you are using a workflow, there can also be a back button - // await input.back() - - // or you can simply cancel (close) the input - await input.cancel(); - }); -}); \ No newline at end of file + let input: InputBox; + + before(async () => { + // we need an input box to open + // extensions usually open inputs as part of their commands + // the built-in input box we can use is the command prompt/palette + await new Workbench().openCommandPrompt(); + + // openCommandPrompt returns an InputBox, but if you need to wait for an arbitrary input to appear + // note this does not open the input, it simply waits for it to open and constructs the page object + input = await InputBox.create(); + }); + + it('Text manipulation', async () => { + // set some text in the input + await input.setText('hello'); + + // get the current text + const text = await input.getText(); + expect(text).equals('hello'); + + // clear the text + await input.clear(); + }); + + it('Quick picks', async () => { + await input.setText('> hello world'); + + // get all the visible quick picks + // note that quick picks outside the scroll window are ignored + const picks = await input.getQuickPicks(); + expect(picks).not.empty; + + // if you want to find a quick pick that might not be currently shown, use + const pick = await input.findQuickPick('Hello World'); + + // or if you want to find and select a quick pick + // await input.selectQuickPick('Hello World'); + + // if checkboxes are available for your quick picks, you can also check/uncheck them all in one call + // await input.toggleAllQuickPicks(true/false); + }); + + // it('Other properties', async () => { + // // input boxes can have multiple other properties, all of these are self explanatory + + // await input.getMessage(); + // await input.getPlaceHolder(); + // await input.hasError(); + // await input.hasProgress(); + // }); + + it('Flow actions', async () => { + // there are a few actions to perform with an input box + // to confirm + // await input.confirm(); + + // if you are using a workflow, there can also be a back button + // await input.back() + + // or you can simply cancel (close) the input + await input.cancel(); + }); +}); diff --git a/examples/helloworld-extester/src/ui-test/menus.test.ts b/examples/helloworld-extester/src/ui-test/menus.test.ts index 0fd52a3db..ac8e753cb 100644 --- a/examples/helloworld-extester/src/ui-test/menus.test.ts +++ b/examples/helloworld-extester/src/ui-test/menus.test.ts @@ -23,70 +23,70 @@ import { ActivityBar, ContextMenu, EditorView, TitleBar } from 'vscode-extension // the title bar items also open context menus, all context menus were created equal // neither menu will work on mac, since they are native there (process.platform === 'darwin' ? describe.skip : describe)('Example menu manipulation test', () => { - let titleBar: TitleBar; + let titleBar: TitleBar; - before(() => { - titleBar = new TitleBar(); - }); + before(() => { + titleBar = new TitleBar(); + }); - after(async () => { - // we will be opening editors during the tests, close them afterwards - await new EditorView().closeAllEditors(); - }); + after(async () => { + // we will be opening editors during the tests, close them afterwards + await new EditorView().closeAllEditors(); + }); - describe('Title Bar', () => { - // the most useful method of titlebar is 'select' - // which selects an entire path of (possibly) nested menu items - it('Select a top level item', async () => { - // selecting a top level item opens a context menu - // select then returns a ContextMenu page object - const menu = await titleBar.select('File'); + describe('Title Bar', () => { + // the most useful method of titlebar is 'select' + // which selects an entire path of (possibly) nested menu items + it('Select a top level item', async () => { + // selecting a top level item opens a context menu + // select then returns a ContextMenu page object + const menu = await titleBar.select('File'); - expect(menu).not.undefined; - // lets just close the context menu for now - await (menu as ContextMenu).close(); - }); + expect(menu).not.undefined; + // lets just close the context menu for now + await (menu as ContextMenu).close(); + }); - // Selecting an item with no submenu just clicks it - it('Select a leaf item', async () => { - // Open settings using File > Preferences > Settings from the title bar - await titleBar.select('File', 'Preferences', 'Settings'); + // Selecting an item with no submenu just clicks it + it('Select a leaf item', async () => { + // Open settings using File > Preferences > Settings from the title bar + await titleBar.select('File', 'Preferences', 'Settings'); - // now we can use EditorView to look at the open editors - // and assert that Settings did indeed open - const titles = await new EditorView().getOpenEditorTitles(); - expect(titles).contains('Settings'); - }); + // now we can use EditorView to look at the open editors + // and assert that Settings did indeed open + const titles = await new EditorView().getOpenEditorTitles(); + expect(titles).contains('Settings'); + }); - // Apart from selection, all menus have methods for retrieving items - it('Menu items', async () => { - // get items gives you all the menu items as MenuItem objects - const items = await titleBar.getItems(); - // if you want labels/titles instead, you need to use the getLabel method - const titles = await Promise.all(items.map(async item => await item.getLabel())); - expect(titles).contains('File'); + // Apart from selection, all menus have methods for retrieving items + it('Menu items', async () => { + // get items gives you all the menu items as MenuItem objects + const items = await titleBar.getItems(); + // if you want labels/titles instead, you need to use the getLabel method + const titles = await Promise.all(items.map(async (item) => await item.getLabel())); + expect(titles).contains('File'); - // analogically, for a single item - const item = await titleBar.getItem('File'); - expect(item).not.undefined; + // analogically, for a single item + const item = await titleBar.getItem('File'); + expect(item).not.undefined; - // if an item with given label does not exist, undefined is returned - const nonExistent = await titleBar.getItem('this doesnt exist'); - expect(nonExistent).undefined; + // if an item with given label does not exist, undefined is returned + const nonExistent = await titleBar.getItem('this doesnt exist'); + expect(nonExistent).undefined; - // to check if a menu item with given label exists - expect(await titleBar.hasItem('File')).is.true; - }); - }); + // to check if a menu item with given label exists + expect(await titleBar.hasItem('File')).is.true; + }); + }); - describe('Elements with context menu', async () => { - it ('open context menu on an element', async () => { - // lets take an element that has a context menu and open it - // all page objects that support context menus will have the 'openContextMenu' method - const menu = await new ActivityBar().openContextMenu(); - - // click on one of the items - await menu.select('Extensions'); - }); - }); -}); \ No newline at end of file + describe('Elements with context menu', async () => { + it('open context menu on an element', async () => { + // lets take an element that has a context menu and open it + // all page objects that support context menus will have the 'openContextMenu' method + const menu = await new ActivityBar().openContextMenu(); + + // click on one of the items + await menu.select('Extensions'); + }); + }); +}); diff --git a/examples/helloworld-extester/src/ui-test/modalDialog.test.ts b/examples/helloworld-extester/src/ui-test/modalDialog.test.ts index 2595a60f9..6ed6403e1 100644 --- a/examples/helloworld-extester/src/ui-test/modalDialog.test.ts +++ b/examples/helloworld-extester/src/ui-test/modalDialog.test.ts @@ -20,42 +20,42 @@ import { EditorView, ModalDialog, TextEditor, Workbench } from 'vscode-extension // Example of handling a modal dialog describe('Sample Modal Dialog Tests', () => { - let dialog: ModalDialog; - - before(async () => { - // we need to open some modal dialog first, so lets try to close an unsaved file - // create a new file - await new Workbench().executeCommand('create new file'); - // make some changes - const editor = new TextEditor(); - await editor.typeTextAt(1, 1, 'text'); - // try to close the editor unsaved, which opens a modal dialog - await new EditorView().closeEditor(await editor.getTitle()); - dialog = new ModalDialog(); - }); - - // now we can check what the dialog says - it('Get the message', async () => { - const message = await dialog.getMessage(); - - expect(message).contains('Do you want to save the changes you made'); - }); - - // and the additional details - it('Get the details', async () => { - const details = await dialog.getDetails(); - - expect(details).equals(`Your changes will be lost if you don't save them.`); - }); - - // we can also find and use the buttons on the dialog - it('Use the buttons', async () => { - const buttons = await dialog.getButtons(); - - // there should be 3 of them - expect(buttons.length).equals(3); - - // or we can directly push a button by title - await dialog.pushButton(`Don't Save`); - }); -}); \ No newline at end of file + let dialog: ModalDialog; + + before(async () => { + // we need to open some modal dialog first, so lets try to close an unsaved file + // create a new file + await new Workbench().executeCommand('create new file'); + // make some changes + const editor = new TextEditor(); + await editor.typeTextAt(1, 1, 'text'); + // try to close the editor unsaved, which opens a modal dialog + await new EditorView().closeEditor(await editor.getTitle()); + dialog = new ModalDialog(); + }); + + // now we can check what the dialog says + it('Get the message', async () => { + const message = await dialog.getMessage(); + + expect(message).contains('Do you want to save the changes you made'); + }); + + // and the additional details + it('Get the details', async () => { + const details = await dialog.getDetails(); + + expect(details).equals(`Your changes will be lost if you don't save them.`); + }); + + // we can also find and use the buttons on the dialog + it('Use the buttons', async () => { + const buttons = await dialog.getButtons(); + + // there should be 3 of them + expect(buttons.length).equals(3); + + // or we can directly push a button by title + await dialog.pushButton(`Don't Save`); + }); +}); diff --git a/examples/helloworld-extester/src/ui-test/notifications.test.ts b/examples/helloworld-extester/src/ui-test/notifications.test.ts index d947da899..04dcc576d 100644 --- a/examples/helloworld-extester/src/ui-test/notifications.test.ts +++ b/examples/helloworld-extester/src/ui-test/notifications.test.ts @@ -18,65 +18,66 @@ import { expect } from 'chai'; import { Notification, NotificationType, VSBrowser, Workbench } from 'vscode-extension-tester'; - describe('Sample notifications tests', () => { - beforeEach(async () => { - // Execute the Hello World command from the command palette - // this command is going to display a notification - await new Workbench().executeCommand('hello world'); + beforeEach(async () => { + // Execute the Hello World command from the command palette + // this command is going to display a notification + await new Workbench().executeCommand('hello world'); + + // Wait for a notification to appear with a timeout of 2 seconds + // The result is cast to Notification because our wait condition may return undefined + (await VSBrowser.instance.driver.wait(() => { + return notificationExists('Hello'); + }, 2000)) as Notification; + }); - // Wait for a notification to appear with a timeout of 2 seconds - // The result is cast to Notification because our wait condition may return undefined - await VSBrowser.instance.driver.wait(() => { return notificationExists('Hello'); }, 2000) as Notification; - }); + // The first type of notifications is the standalone one + // Standalone notifications are the ones that pop up and usually disappear after a while + it('Standalone notifications', async () => { + // retrieve all currently shown notifications + const notifications = await new Workbench().getNotifications(); - // The first type of notifications is the standalone one - // Standalone notifications are the ones that pop up and usually disappear after a while - it('Standalone notifications', async () => { - // retrieve all currently shown notifications - const notifications = await new Workbench().getNotifications(); + // find the one we are interested in + let notification!: Notification; + for (const not of notifications) { + const message = await not.getMessage(); + if (message.includes('Hello')) { + notification = not; + } + } - // find the one we are interested in - let notification!: Notification; - for (const not of notifications) { - const message = await not.getMessage(); - if (message.includes('Hello')) { - notification = not; - } - } - - expect(await notification.getText()).equals('Hello World!'); - expect(await notification.getType()).equals(NotificationType.Info); + expect(await notification.getText()).equals('Hello World!'); + expect(await notification.getType()).equals(NotificationType.Info); - // and we can manually dismiss the notification - await notification.dismiss(); - }); + // and we can manually dismiss the notification + await notification.dismiss(); + }); - // Another way to look at notifications is to open the notifications center - // Notifications there usually stay until dismissed - it('Notifications Center', async () => { - const center = await new Workbench().openNotificationsCenter(); + // Another way to look at notifications is to open the notifications center + // Notifications there usually stay until dismissed + it('Notifications Center', async () => { + const center = await new Workbench().openNotificationsCenter(); - // get notifications from the notifications center - // this time they can be filtered by type - // lets get info notifications only - const notifications = await center.getNotifications(NotificationType.Info); + // get notifications from the notifications center + // this time they can be filtered by type + // lets get info notifications only + const notifications = await center.getNotifications(NotificationType.Info); - // once again we can look for the hello notification - let notification!: Notification; - for (const not of notifications) { - const message = await not.getMessage(); - if (message.includes('Hello')) { - notification = not; - } - } + // once again we can look for the hello notification + let notification!: Notification; + for (const not of notifications) { + const message = await not.getMessage(); + if (message.includes('Hello')) { + notification = not; + } + } - expect(await notification.getText()).equals('Hello World!'); - expect(await notification.getType()).equals(NotificationType.Info); + expect(await notification.getText()).equals('Hello World!'); + expect(await notification.getType()).equals(NotificationType.Info); - // this time we can clear all notifications - await center.clearAllNotifications(); - }); + // this time we can clear all notifications + await center.clearAllNotifications(); + }); }); /** @@ -86,11 +87,11 @@ describe('Sample notifications tests', () => { * or undefined if no such notification is found. */ async function notificationExists(text: string): Promise { - const notifications = await new Workbench().getNotifications(); - for (const notification of notifications) { - const message = await notification.getMessage(); - if (message.indexOf(text) >= 0) { - return notification; - } - } -} \ No newline at end of file + const notifications = await new Workbench().getNotifications(); + for (const notification of notifications) { + const message = await notification.getMessage(); + if (message.indexOf(text) >= 0) { + return notification; + } + } +} diff --git a/examples/helloworld-extester/src/ui-test/settingsEditor.test.ts b/examples/helloworld-extester/src/ui-test/settingsEditor.test.ts index 38eb7e20c..886819945 100644 --- a/examples/helloworld-extester/src/ui-test/settingsEditor.test.ts +++ b/examples/helloworld-extester/src/ui-test/settingsEditor.test.ts @@ -19,29 +19,29 @@ import { expect } from 'chai'; import { CheckboxSetting, SettingsEditor, Workbench } from 'vscode-extension-tester'; describe('Settings Editor sample tests', () => { - let settings: SettingsEditor; + let settings: SettingsEditor; - before(async () => { - // open the settings (UI) - settings = await new Workbench().openSettings(); - }); + before(async () => { + // open the settings (UI) + settings = await new Workbench().openSettings(); + }); - it('Find and Manipulate a setting', async () => { - // to search for a setting using title, category, subcategories (arguments in this particular order) - // this particular setting is 'Files > Simple Dialog > Enable' (Main category > Subcategory > Title) - const setting = await settings.findSetting('Enable', 'Files', 'Simple Dialog'); + it('Find and Manipulate a setting', async () => { + // to search for a setting using title, category, subcategories (arguments in this particular order) + // this particular setting is 'Files > Simple Dialog > Enable' (Main category > Subcategory > Title) + const setting = await settings.findSetting('Enable', 'Files', 'Simple Dialog'); - // there are different types of settings (e.g. checkbox, combo, text, link) - // to get a more specific interface, we can cast the object to the appropriate type - const simpleDialogSetting = setting as CheckboxSetting; - // now getValue will return a boolean since it is a checkbox - expect(await simpleDialogSetting.getValue()).is.true; + // there are different types of settings (e.g. checkbox, combo, text, link) + // to get a more specific interface, we can cast the object to the appropriate type + const simpleDialogSetting = setting as CheckboxSetting; + // now getValue will return a boolean since it is a checkbox + expect(await simpleDialogSetting.getValue()).is.true; - // we can also get the description - const desc = await simpleDialogSetting.getDescription(); - expect(desc).contains('Enables the simple file dialog'); + // we can also get the description + const desc = await simpleDialogSetting.getDescription(); + expect(desc).contains('Enables the simple file dialog'); - // use 'setValue' to change the setting, the actual value depends on the type of the setting - await simpleDialogSetting.setValue(true); - }); -}); \ No newline at end of file + // use 'setValue' to change the setting, the actual value depends on the type of the setting + await simpleDialogSetting.setValue(true); + }); +}); diff --git a/examples/helloworld-extester/src/ui-test/statusBar.test.ts b/examples/helloworld-extester/src/ui-test/statusBar.test.ts index e5adb0632..61512b079 100644 --- a/examples/helloworld-extester/src/ui-test/statusBar.test.ts +++ b/examples/helloworld-extester/src/ui-test/statusBar.test.ts @@ -20,46 +20,46 @@ import * as path from 'path'; import { InputBox, StatusBar, VSBrowser } from 'vscode-extension-tester'; describe('Example status bar tests', () => { - let statusBar: StatusBar; + let statusBar: StatusBar; - before(async () => { - statusBar = new StatusBar(); - // most basic functions of status bar are only available when a file is opened - await VSBrowser.instance.openResources(path.join('src', 'ui-test', 'resources', 'problems.ts')); - }); + before(async () => { + statusBar = new StatusBar(); + // most basic functions of status bar are only available when a file is opened + await VSBrowser.instance.openResources(path.join('src', 'ui-test', 'resources', 'problems.ts')); + }); - it('Generic items', async () => { - // retrieve an item from the status bar by label (the text visible on the bar) - // we are looking at a ts file, so we can get the language selection item like so - const item = await statusBar.getItem('TypeScript'); - expect(item).not.undefined; + it('Generic items', async () => { + // retrieve an item from the status bar by label (the text visible on the bar) + // we are looking at a ts file, so we can get the language selection item like so + const item = await statusBar.getItem('TypeScript'); + expect(item).not.undefined; - // or get all the available items - const items = await statusBar.getItems(); - expect(items.length).greaterThan(2); - }); + // or get all the available items + const items = await statusBar.getItems(); + expect(items.length).greaterThan(2); + }); - // some items are statically bound when a file is open in the editor - // you can click them in a single step - // they open input boxes, in this example we will only be closing them - it('Items bound to open file', async () => { - // open encoding selection - await statusBar.openEncodingSelection(); - // cancel the input - await (await InputBox.create()).cancel(); + // some items are statically bound when a file is open in the editor + // you can click them in a single step + // they open input boxes, in this example we will only be closing them + it('Items bound to open file', async () => { + // open encoding selection + await statusBar.openEncodingSelection(); + // cancel the input + await (await InputBox.create()).cancel(); - // all the following methods are available and work very much the same - // await statusBar.openIndentationSelection(); - // await statusBar.openLanguageSelection(); - // await statusBar.openLineEndingSelection(); - // await statusBar.openLineSelection(); - }); + // all the following methods are available and work very much the same + // await statusBar.openIndentationSelection(); + // await statusBar.openLanguageSelection(); + // await statusBar.openLineEndingSelection(); + // await statusBar.openLineSelection(); + }); - // the one item that is always present is the notification center (the bell button) - it('Notifications', async () => { - // open - await statusBar.openNotificationsCenter(); - // close - await statusBar.closeNotificationsCenter(); - }); -}); \ No newline at end of file + // the one item that is always present is the notification center (the bell button) + it('Notifications', async () => { + // open + await statusBar.openNotificationsCenter(); + // close + await statusBar.closeNotificationsCenter(); + }); +}); diff --git a/examples/helloworld-extester/src/ui-test/textEditor.test.ts b/examples/helloworld-extester/src/ui-test/textEditor.test.ts index 749d490c6..b2efdb1fc 100644 --- a/examples/helloworld-extester/src/ui-test/textEditor.test.ts +++ b/examples/helloworld-extester/src/ui-test/textEditor.test.ts @@ -19,68 +19,70 @@ import { expect } from 'chai'; import { EditorView, InputBox, TextEditor, Workbench } from 'vscode-extension-tester'; describe('Text Editor sample tests', () => { - let editor: TextEditor; + let editor: TextEditor; - before(async function () { - this.timeout(10_000); - // create a file to open in an editor - await new Workbench().executeCommand('Create: New File...'); - await (await InputBox.create()).selectQuickPick('Text File'); - await new Promise((res) => { setTimeout(res, 1000); }); - editor = await new EditorView().openEditor('Untitled-1') as TextEditor; - // or if the file we want is currently opened we can simply do - // editor = new TextEditor(); - }); + before(async function () { + this.timeout(10_000); + // create a file to open in an editor + await new Workbench().executeCommand('Create: New File...'); + await (await InputBox.create()).selectQuickPick('Text File'); + await new Promise((res) => { + setTimeout(res, 1000); + }); + editor = (await new EditorView().openEditor('Untitled-1')) as TextEditor; + // or if the file we want is currently opened we can simply do + // editor = new TextEditor(); + }); - after(async function () { - this.timeout(10_000); - // cleanup, delete the file contents and close the editor - await editor.clearText(); - await new EditorView().closeAllEditors(); - }); + after(async function () { + this.timeout(10_000); + // cleanup, delete the file contents and close the editor + await editor.clearText(); + await new EditorView().closeAllEditors(); + }); - it('Text manipulation', async () => { - // the file is currently empty, lets write something in it - // note the coordinates are (1, 1) for the beginning of the file - await editor.typeTextAt(1, 1, 'hello'); + it('Text manipulation', async () => { + // the file is currently empty, lets write something in it + // note the coordinates are (1, 1) for the beginning of the file + await editor.typeTextAt(1, 1, 'hello'); - // now we can check if the text is correct - const text = await editor.getText(); - expect(text).equals('hello'); + // now we can check if the text is correct + const text = await editor.getText(); + expect(text).equals('hello'); - // we can also replace all the text with whatever we want - await editor.setText(`line1\nline2\nline3`); - // assert how many lines there are now - expect(await editor.getNumberOfLines()).equals(3); + // we can also replace all the text with whatever we want + await editor.setText(`line1\nline2\nline3`); + // assert how many lines there are now + expect(await editor.getNumberOfLines()).equals(3); - // get text at the line with given number - const line = await editor.getTextAtLine(2); - expect(line).equals('line2'); + // get text at the line with given number + const line = await editor.getTextAtLine(2); + expect(line).equals('line2'); - // get the line number of a search string - const lineNum = await editor.getLineOfText('3'); - expect(lineNum).equals(3); + // get the line number of a search string + const lineNum = await editor.getLineOfText('3'); + expect(lineNum).equals(3); - // the editor should be dirty since we haven't saved yet - expect(await editor.isDirty()).is.true; + // the editor should be dirty since we haven't saved yet + expect(await editor.isDirty()).is.true; - // another way to set test case timeout, this one also works with arrow functions - }).timeout(15000); + // another way to set test case timeout, this one also works with arrow functions + }).timeout(15000); - it('Content Assist', async () => { - // we have content assist at our disposal, open it - const assist = await editor.toggleContentAssist(true); - // toggle can return void, so we need to make sure the object is present - if (assist) { - // get the items visible in the content assist - const items = await assist.getItems(); - expect(items).not.empty; + it('Content Assist', async () => { + // we have content assist at our disposal, open it + const assist = await editor.toggleContentAssist(true); + // toggle can return void, so we need to make sure the object is present + if (assist) { + // get the items visible in the content assist + const items = await assist.getItems(); + expect(items).not.empty; - // to select an item use - // await assist.select('whatever is available') - } + // to select an item use + // await assist.select('whatever is available') + } - // close the assistant again - await editor.toggleContentAssist(false); - }); -}); \ No newline at end of file + // close the assistant again + await editor.toggleContentAssist(false); + }); +}); diff --git a/examples/helloworld-extester/src/ui-test/treeView.test.ts b/examples/helloworld-extester/src/ui-test/treeView.test.ts index 6117b2244..d56fcf08d 100644 --- a/examples/helloworld-extester/src/ui-test/treeView.test.ts +++ b/examples/helloworld-extester/src/ui-test/treeView.test.ts @@ -21,89 +21,89 @@ import { expect } from 'chai'; // in this test we will look at tree views in the left side bar describe('Example tree view tests', () => { - let titlePart: ViewTitlePart; - let content: ViewContent; - - before(async () => { - // we will be looking at the explorer view - // first we need to open a folder to get some items into the view - await VSBrowser.instance.openResources(path.join('src', 'ui-test', 'resources', 'test')); - // make sure the view is open - (await new ActivityBar().getViewControl('Explorer'))?.openView(); - - // now to initialize the view - // this object is basically just a container for two parts: title & content - const view = new SideBarView(); - titlePart = view.getTitlePart(); - content = view.getContent(); - }); - - it('Title part', async () => { - // title part usually only contains the title of the view - // but it can also have action buttons - const title = await titlePart.getTitle(); - expect(title.toLowerCase()).equals('explorer'); - - // explorer doesn't really have action buttons, but you can search for them anyway - const actions = await titlePart.getActions(); - }); - - describe('Content', () => { - // the content part is split into an arbitrary number of sections - // each section may have a different layout - // tree sections not contributed by extensions are covered by 'DefaultTreeSection' page object - // tree sections contributed by extensions are slightly different and covered by 'CustomTreeSection' - let tree: DefaultTreeSection; - - before(async () => { - // in this case we are searching for a section that houses the folder we opened earlier - // that one is usually named the same as the folder - // this is a section provided by vscode, so we are using DefaultTreeSection page object - tree = await content.getSection('test') as DefaultTreeSection; - - // if your view only has one section and you cant see the title, then - // (await content.getSections())[0] - // is the easiest way to access it - }); - - it('Look at the items', async () => { - // get all the items visible in the view - const items = await tree.getVisibleItems(); - - // by default we should only see the contents of the root folder - const labels = await Promise.all(items.map(item => item.getLabel())); - expect(labels).contains('test-folder'); - expect(labels).contains('test-file'); - - // we can also open folders - // using 'openItem' method on a folder will return its children - const children = await tree.openItem('test-folder'); - // test-folder has one file in it - expect(children.length).equals(1); - - // or we can open files the same way - // note that files have no children, so an empty array is returned - // make sure to put in the whole path to the item if it is not directly in the root folder - const notReallyChildren = await tree.openItem('test-folder', 'more-test-file'); - expect(notReallyChildren).is.empty; - - // opening a file will open it in an editor - const editors = await new EditorView().getOpenEditorTitles(); - expect(editors).contains('more-test-file'); - }); - - it('A little bit of searching', async () => { - // note how the previous methods only worked with items that are currently displayed - // in order to find an item that needs to be scrolled to, we can use findItem - // this method will not search collapsed folders, but it will scroll through the whole tree - const item = await tree.findItem('more-test-file'); - expect(item).not.undefined; - - // it can also be limited to only work within a certain depth - const item1 = await tree.findItem('more-test-files', 1); - // the item exists, but we limited the search to only look at depth 1, direct descendants of the root folder - // therefore item1 should be undefined (not found) - expect(item1).undefined; - }); - }); -}); \ No newline at end of file + let titlePart: ViewTitlePart; + let content: ViewContent; + + before(async () => { + // we will be looking at the explorer view + // first we need to open a folder to get some items into the view + await VSBrowser.instance.openResources(path.join('src', 'ui-test', 'resources', 'test')); + // make sure the view is open + (await new ActivityBar().getViewControl('Explorer'))?.openView(); + + // now to initialize the view + // this object is basically just a container for two parts: title & content + const view = new SideBarView(); + titlePart = view.getTitlePart(); + content = view.getContent(); + }); + + it('Title part', async () => { + // title part usually only contains the title of the view + // but it can also have action buttons + const title = await titlePart.getTitle(); + expect(title.toLowerCase()).equals('explorer'); + + // explorer doesn't really have action buttons, but you can search for them anyway + const actions = await titlePart.getActions(); + }); + + describe('Content', () => { + // the content part is split into an arbitrary number of sections + // each section may have a different layout + // tree sections not contributed by extensions are covered by 'DefaultTreeSection' page object + // tree sections contributed by extensions are slightly different and covered by 'CustomTreeSection' + let tree: DefaultTreeSection; + + before(async () => { + // in this case we are searching for a section that houses the folder we opened earlier + // that one is usually named the same as the folder + // this is a section provided by vscode, so we are using DefaultTreeSection page object + tree = (await content.getSection('test')) as DefaultTreeSection; + + // if your view only has one section and you cant see the title, then + // (await content.getSections())[0] + // is the easiest way to access it + }); + + it('Look at the items', async () => { + // get all the items visible in the view + const items = await tree.getVisibleItems(); + + // by default we should only see the contents of the root folder + const labels = await Promise.all(items.map((item) => item.getLabel())); + expect(labels).contains('test-folder'); + expect(labels).contains('test-file'); + + // we can also open folders + // using 'openItem' method on a folder will return its children + const children = await tree.openItem('test-folder'); + // test-folder has one file in it + expect(children.length).equals(1); + + // or we can open files the same way + // note that files have no children, so an empty array is returned + // make sure to put in the whole path to the item if it is not directly in the root folder + const notReallyChildren = await tree.openItem('test-folder', 'more-test-file'); + expect(notReallyChildren).is.empty; + + // opening a file will open it in an editor + const editors = await new EditorView().getOpenEditorTitles(); + expect(editors).contains('more-test-file'); + }); + + it('A little bit of searching', async () => { + // note how the previous methods only worked with items that are currently displayed + // in order to find an item that needs to be scrolled to, we can use findItem + // this method will not search collapsed folders, but it will scroll through the whole tree + const item = await tree.findItem('more-test-file'); + expect(item).not.undefined; + + // it can also be limited to only work within a certain depth + const item1 = await tree.findItem('more-test-files', 1); + // the item exists, but we limited the search to only look at depth 1, direct descendants of the root folder + // therefore item1 should be undefined (not found) + expect(item1).undefined; + }); + }); +}); diff --git a/examples/helloworld-extester/src/ui-test/webView.test.ts b/examples/helloworld-extester/src/ui-test/webView.test.ts index 3a5974a41..76cebc942 100644 --- a/examples/helloworld-extester/src/ui-test/webView.test.ts +++ b/examples/helloworld-extester/src/ui-test/webView.test.ts @@ -20,37 +20,38 @@ import { expect } from 'chai'; // An example how to handle a simple web view describe('Sample WebView Test', () => { + let view: WebView; - let view: WebView; + before(async function () { + this.timeout(8000); + // open a sample web view + await new Workbench().executeCommand('Webview Test'); + await new Promise((res) => { + setTimeout(res, 500); + }); + // init the WebView page object + view = new WebView(); + // switch webdriver into the webview iframe, now all webdriver commands are + // relative to the webview document's root + // make sure not to try accessing elements outside the web view while switched inside and vice versa + await view.switchToFrame(); + }); - before(async function() { - this.timeout(8000); - // open a sample web view - await new Workbench().executeCommand('Webview Test'); - await new Promise((res) => { setTimeout(res, 500); }); - // init the WebView page object - view = new WebView(); - // switch webdriver into the webview iframe, now all webdriver commands are - // relative to the webview document's root - // make sure not to try accessing elements outside the web view while switched inside and vice versa - await view.switchToFrame(); - }); + after(async () => { + // after we are done with the webview, switch webdriver back to the vscode window + await view.switchBack(); + await new EditorView().closeAllEditors(); + }); - after(async () => { - // after we are done with the webview, switch webdriver back to the vscode window - await view.switchBack(); - await new EditorView().closeAllEditors(); - }); + it('Look for a web element', async () => { + // now we can use findWebElement to look for elements inside the webview + const element = await view.findWebElement(By.css('h1')); + expect(await element.getText()).has.string('This is a web view'); + }); - it('Look for a web element', async () => { - // now we can use findWebElement to look for elements inside the webview - const element = await view.findWebElement(By.css('h1')); - expect(await element.getText()).has.string('This is a web view'); - }); - - it('Look for all elements with given locator', async () => { - // analogically, findWebElements to search for all occurences - const elements = await view.findWebElements(By.css('h1')); - expect(elements.length).equals(1); - }); -}); \ No newline at end of file + it('Look for all elements with given locator', async () => { + // analogically, findWebElements to search for all occurences + const elements = await view.findWebElements(By.css('h1')); + expect(elements.length).equals(1); + }); +}); diff --git a/examples/helloworld-extester/tsconfig.json b/examples/helloworld-extester/tsconfig.json index e53d3fc1c..d69d84f52 100644 --- a/examples/helloworld-extester/tsconfig.json +++ b/examples/helloworld-extester/tsconfig.json @@ -1,23 +1,14 @@ { - "compilerOptions": { - "module": "Node16", - "target": "ES2022", - "outDir": "out", - "sourceMap": true, - "strict": true, - "rootDir": "src", - "resolveJsonModule": true, - "skipLibCheck": true - }, - "exclude": [ - "node_modules", - ".vscode-test", - "test-resources", - "src/ui-test/resources/**", - ".test-extensions" - ], - "include": [ - "src", - ".eslintrc.json" - ] + "compilerOptions": { + "module": "Node16", + "target": "ES2022", + "outDir": "out", + "sourceMap": true, + "strict": true, + "rootDir": "src", + "resolveJsonModule": true, + "skipLibCheck": true + }, + "exclude": ["node_modules", ".vscode-test", "test-resources", "src/ui-test/resources/**", ".test-extensions"], + "include": ["src", ".eslintrc.json"] } diff --git a/package-lock.json b/package-lock.json index 5b471d7f7..ab84b60f9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,7 +22,9 @@ "@typescript-eslint/eslint-plugin": "^7.9.0", "@typescript-eslint/parser": "^7.9.0", "eslint": "^8.57.0", + "eslint-config-prettier": "^9.1.0", "lerna": "^8.1.3", + "prettier": "3.2.5", "rimraf": "^5.0.7", "type-fest": "^4.18.2", "typescript": "^5.4.5" @@ -4369,6 +4371,18 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint-config-prettier": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", + "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", + "dev": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, "node_modules/eslint-scope": { "version": "7.2.2", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", @@ -9230,6 +9244,21 @@ "node": ">= 0.8.0" } }, + "node_modules/prettier": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", + "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/pretty-format": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", diff --git a/package.json b/package.json index 67dc05a16..140f791a2 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,9 @@ "@typescript-eslint/eslint-plugin": "^7.9.0", "@typescript-eslint/parser": "^7.9.0", "eslint": "^8.57.0", + "eslint-config-prettier": "^9.1.0", "lerna": "^8.1.3", + "prettier": "3.2.5", "rimraf": "^5.0.7", "type-fest": "^4.18.2", "typescript": "^5.4.5" diff --git a/packages/extester/src/browser.ts b/packages/extester/src/browser.ts index 9aa6f07eb..8700827cb 100644 --- a/packages/extester/src/browser.ts +++ b/packages/extester/src/browser.ts @@ -26,187 +26,187 @@ import { DEFAULT_STORAGE_FOLDER } from './extester'; import { DriverUtil } from './util/driverUtil'; export class VSBrowser { - static readonly baseVersion = '1.37.0'; - static readonly browserName = 'vscode'; - private storagePath: string; - private extensionsFolder: string | undefined; - private customSettings: object; - private _driver!: WebDriver; - private codeVersion: string; - private releaseType: ReleaseQuality; - private logLevel: logging.Level; - private static _instance: VSBrowser; - - constructor(codeVersion: string, releaseType: ReleaseQuality, customSettings: object = {}, logLevel: logging.Level = logging.Level.INFO) { - this.storagePath = process.env.TEST_RESOURCES ? process.env.TEST_RESOURCES : path.resolve(DEFAULT_STORAGE_FOLDER); - this.extensionsFolder = process.env.EXTENSIONS_FOLDER ? process.env.EXTENSIONS_FOLDER : undefined; - this.customSettings = customSettings; - this.codeVersion = codeVersion; - this.releaseType = releaseType; - - this.logLevel = logLevel; - - VSBrowser._instance = this; - } - - /** - * Starts the vscode browser from a given path - * @param codePath path to code binary - */ - async start(codePath: string): Promise { - const userSettings = path.join(this.storagePath, 'settings', 'User'); - if (fs.existsSync(userSettings)) { - fs.removeSync(path.join(this.storagePath, 'settings')); - } - let defaultSettings = { - "workbench.editor.enablePreview": false, - "workbench.startupEditor": "none", - "window.titleBarStyle": "custom", - "window.commandCenter": false, - "window.dialogStyle": "custom", - "window.restoreFullscreen": true, - "window.newWindowDimensions": "maximized", - "security.workspace.trust.enabled": false, - "files.simpleDialog.enable": true, - "terminal.integrated.copyOnSelection": true - }; - if (Object.keys(this.customSettings).length > 0) { - console.log('Detected user defined code settings'); - defaultSettings = { ...defaultSettings, ...this.customSettings }; - } - - fs.mkdirpSync(path.join(userSettings, 'globalStorage')); - await fs.remove(path.join(this.storagePath, 'screenshots')); - fs.writeJSONSync(path.join(userSettings, 'settings.json'), defaultSettings); - console.log(`Writing code settings to ${path.join(userSettings, 'settings.json')}`); - - const args = ['--no-sandbox', '--disable-dev-shm-usage', `--user-data-dir=${path.join(this.storagePath, 'settings')}`]; - - if (this.extensionsFolder) { - args.push(`--extensions-dir=${this.extensionsFolder}`); - } - - if (compareVersions(this.codeVersion, '1.39.0') < 0) { - if (process.platform === 'win32') { - fs.copyFileSync(path.resolve(__dirname, '..', '..', 'resources', 'state.vscdb'), path.join(userSettings, 'globalStorage', 'state.vscdb')); - } - args.push(`--extensionDevelopmentPath=${process.cwd()}`); - } else if(process.env.EXTENSION_DEV_PATH) { - args.push(`--extensionDevelopmentPath=${process.env.EXTENSION_DEV_PATH}`); - } - - let options = new Options().setChromeBinaryPath(codePath).addArguments(...args) as any; - options['options_'].windowTypes = ['webview']; - options = options as Options; - - const prefs = new logging.Preferences(); - prefs.setLevel(logging.Type.DRIVER, this.logLevel); - options.setLoggingPrefs(prefs); - - const driverBinary = process.platform === 'win32' ? 'chromedriver.exe' : 'chromedriver'; - let chromeDriverBinaryPath = path.join(this.storagePath, driverBinary); - if(this.codeVersion >= '1.86.0') { - chromeDriverBinaryPath = path.join(this.storagePath, `chromedriver-${DriverUtil.getChromeDriverPlatform()}`, driverBinary); - } - - console.log('Launching browser...'); - this._driver = await new Builder() - .setChromeService(new ServiceBuilder(chromeDriverBinaryPath)) - .forBrowser(Browser.CHROME) - .setChromeOptions(options) - .build(); - VSBrowser._instance = this; - - initPageObjects(this.codeVersion, VSBrowser.baseVersion, getLocatorsPath(), this._driver, VSBrowser.browserName); - return this; - } - - /** - * Returns a reference to the underlying instance of Webdriver - */ - get driver(): WebDriver { - return this._driver; - } - - /** - * Returns the vscode version as string - */ - get version(): string { - return this.codeVersion; - } - - /** - * Returns an instance of VSBrowser - */ - static get instance(): VSBrowser { - return VSBrowser._instance; - } - - /** - * Waits until parts of the workbench are loaded - */ - async waitForWorkbench(timeout = 30000): Promise { - // Workaround/patch for https://github.com/redhat-developer/vscode-extension-tester/issues/466 - try { - await this._driver.wait(until.elementLocated(By.className('monaco-workbench')), timeout, `Workbench was not loaded properly after ${timeout} ms.`); - } catch (err) { - if((err as Error).name === 'WebDriverError') { - await new Promise(res => setTimeout(res, 3000)); - } else { - throw err; - } - } - } - - /** - * Terminates the webdriver/browser - */ - async quit(): Promise { - const entries = await this._driver.manage().logs().get(logging.Type.DRIVER); - const logFile = path.join(this.storagePath, 'test.log'); - const stream = fs.createWriteStream(logFile, { flags: 'w' }); - entries.forEach(entry => { - stream.write(`[${new Date(entry.timestamp).toLocaleTimeString()}][${entry.level.name}] ${entry.message}`); - }); - stream.end(); - - console.log('Shutting down the browser'); - await this._driver.quit(); - } - - /** - * Take a screenshot of the browser - * @param name file name of the screenshot without extension - */ - async takeScreenshot(name: string): Promise { - const data = await this._driver.takeScreenshot(); - const dir = path.join(this.storagePath, 'screenshots'); - fs.mkdirpSync(dir); - fs.writeFileSync(path.join(dir, `${name}.png`), data, 'base64'); - } - - /** - * Get a screenshots folder path - * @returns string path to the screenshots folder - */ - getScreenshotsDir(): string { - return path.join(this.storagePath, 'screenshots'); - } - - /** - * Open folder(s) or file(s) in the current instance of vscode. - * - * @param paths path(s) of folder(s)/files(s) to open as varargs - * @returns Promise resolving when all selected resources are opened and the workbench reloads - */ - async openResources(...paths: string[]): Promise { - if (paths.length === 0) { - return; - } - - const code = new CodeUtil(this.storagePath, this.releaseType, this.extensionsFolder); - code.open(...paths); - await new Promise(res => setTimeout(res, 3000)); - await this.waitForWorkbench(); - } -} \ No newline at end of file + static readonly baseVersion = '1.37.0'; + static readonly browserName = 'vscode'; + private storagePath: string; + private extensionsFolder: string | undefined; + private customSettings: object; + private _driver!: WebDriver; + private codeVersion: string; + private releaseType: ReleaseQuality; + private logLevel: logging.Level; + private static _instance: VSBrowser; + + constructor(codeVersion: string, releaseType: ReleaseQuality, customSettings: object = {}, logLevel: logging.Level = logging.Level.INFO) { + this.storagePath = process.env.TEST_RESOURCES ? process.env.TEST_RESOURCES : path.resolve(DEFAULT_STORAGE_FOLDER); + this.extensionsFolder = process.env.EXTENSIONS_FOLDER ? process.env.EXTENSIONS_FOLDER : undefined; + this.customSettings = customSettings; + this.codeVersion = codeVersion; + this.releaseType = releaseType; + + this.logLevel = logLevel; + + VSBrowser._instance = this; + } + + /** + * Starts the vscode browser from a given path + * @param codePath path to code binary + */ + async start(codePath: string): Promise { + const userSettings = path.join(this.storagePath, 'settings', 'User'); + if (fs.existsSync(userSettings)) { + fs.removeSync(path.join(this.storagePath, 'settings')); + } + let defaultSettings = { + 'workbench.editor.enablePreview': false, + 'workbench.startupEditor': 'none', + 'window.titleBarStyle': 'custom', + 'window.commandCenter': false, + 'window.dialogStyle': 'custom', + 'window.restoreFullscreen': true, + 'window.newWindowDimensions': 'maximized', + 'security.workspace.trust.enabled': false, + 'files.simpleDialog.enable': true, + 'terminal.integrated.copyOnSelection': true, + }; + if (Object.keys(this.customSettings).length > 0) { + console.log('Detected user defined code settings'); + defaultSettings = { ...defaultSettings, ...this.customSettings }; + } + + fs.mkdirpSync(path.join(userSettings, 'globalStorage')); + await fs.remove(path.join(this.storagePath, 'screenshots')); + fs.writeJSONSync(path.join(userSettings, 'settings.json'), defaultSettings); + console.log(`Writing code settings to ${path.join(userSettings, 'settings.json')}`); + + const args = ['--no-sandbox', '--disable-dev-shm-usage', `--user-data-dir=${path.join(this.storagePath, 'settings')}`]; + + if (this.extensionsFolder) { + args.push(`--extensions-dir=${this.extensionsFolder}`); + } + + if (compareVersions(this.codeVersion, '1.39.0') < 0) { + if (process.platform === 'win32') { + fs.copyFileSync(path.resolve(__dirname, '..', '..', 'resources', 'state.vscdb'), path.join(userSettings, 'globalStorage', 'state.vscdb')); + } + args.push(`--extensionDevelopmentPath=${process.cwd()}`); + } else if (process.env.EXTENSION_DEV_PATH) { + args.push(`--extensionDevelopmentPath=${process.env.EXTENSION_DEV_PATH}`); + } + + let options = new Options().setChromeBinaryPath(codePath).addArguments(...args) as any; + options['options_'].windowTypes = ['webview']; + options = options as Options; + + const prefs = new logging.Preferences(); + prefs.setLevel(logging.Type.DRIVER, this.logLevel); + options.setLoggingPrefs(prefs); + + const driverBinary = process.platform === 'win32' ? 'chromedriver.exe' : 'chromedriver'; + let chromeDriverBinaryPath = path.join(this.storagePath, driverBinary); + if (this.codeVersion >= '1.86.0') { + chromeDriverBinaryPath = path.join(this.storagePath, `chromedriver-${DriverUtil.getChromeDriverPlatform()}`, driverBinary); + } + + console.log('Launching browser...'); + this._driver = await new Builder() + .setChromeService(new ServiceBuilder(chromeDriverBinaryPath)) + .forBrowser(Browser.CHROME) + .setChromeOptions(options) + .build(); + VSBrowser._instance = this; + + initPageObjects(this.codeVersion, VSBrowser.baseVersion, getLocatorsPath(), this._driver, VSBrowser.browserName); + return this; + } + + /** + * Returns a reference to the underlying instance of Webdriver + */ + get driver(): WebDriver { + return this._driver; + } + + /** + * Returns the vscode version as string + */ + get version(): string { + return this.codeVersion; + } + + /** + * Returns an instance of VSBrowser + */ + static get instance(): VSBrowser { + return VSBrowser._instance; + } + + /** + * Waits until parts of the workbench are loaded + */ + async waitForWorkbench(timeout = 30000): Promise { + // Workaround/patch for https://github.com/redhat-developer/vscode-extension-tester/issues/466 + try { + await this._driver.wait(until.elementLocated(By.className('monaco-workbench')), timeout, `Workbench was not loaded properly after ${timeout} ms.`); + } catch (err) { + if ((err as Error).name === 'WebDriverError') { + await new Promise((res) => setTimeout(res, 3000)); + } else { + throw err; + } + } + } + + /** + * Terminates the webdriver/browser + */ + async quit(): Promise { + const entries = await this._driver.manage().logs().get(logging.Type.DRIVER); + const logFile = path.join(this.storagePath, 'test.log'); + const stream = fs.createWriteStream(logFile, { flags: 'w' }); + entries.forEach((entry) => { + stream.write(`[${new Date(entry.timestamp).toLocaleTimeString()}][${entry.level.name}] ${entry.message}`); + }); + stream.end(); + + console.log('Shutting down the browser'); + await this._driver.quit(); + } + + /** + * Take a screenshot of the browser + * @param name file name of the screenshot without extension + */ + async takeScreenshot(name: string): Promise { + const data = await this._driver.takeScreenshot(); + const dir = path.join(this.storagePath, 'screenshots'); + fs.mkdirpSync(dir); + fs.writeFileSync(path.join(dir, `${name}.png`), data, 'base64'); + } + + /** + * Get a screenshots folder path + * @returns string path to the screenshots folder + */ + getScreenshotsDir(): string { + return path.join(this.storagePath, 'screenshots'); + } + + /** + * Open folder(s) or file(s) in the current instance of vscode. + * + * @param paths path(s) of folder(s)/files(s) to open as varargs + * @returns Promise resolving when all selected resources are opened and the workbench reloads + */ + async openResources(...paths: string[]): Promise { + if (paths.length === 0) { + return; + } + + const code = new CodeUtil(this.storagePath, this.releaseType, this.extensionsFolder); + code.open(...paths); + await new Promise((res) => setTimeout(res, 3000)); + await this.waitForWorkbench(); + } +} diff --git a/packages/extester/src/cli.ts b/packages/extester/src/cli.ts index e16dbbadc..eed22285d 100644 --- a/packages/extester/src/cli.ts +++ b/packages/extester/src/cli.ts @@ -21,134 +21,187 @@ import { ExTester } from './extester'; import { ReleaseQuality } from './util/codeUtil'; import pjson from '../package.json'; -program.version(pjson.version) - .description('UI Test Runner for VS Code Extension'); +program.version(pjson.version).description('UI Test Runner for VS Code Extension'); -program.command('get-vscode') - .description('Download VS Code for testing') - .option('-s, --storage ', 'Use this folder for all test resources') - .option('-c, --code_version ', 'Version of VS Code to download, use `min`/`max` to download the oldest/latest VS Code supported by ExTester') - .option('-t, --type ', 'Type of VS Code release (stable/insider)') - .action(withErrors(async (cmd) => { - const extest = new ExTester(cmd.storage, codeStream(cmd.type)); - await extest.downloadCode(cmd.code_version); - })); +program + .command('get-vscode') + .description('Download VS Code for testing') + .option('-s, --storage ', 'Use this folder for all test resources') + .option('-c, --code_version ', 'Version of VS Code to download, use `min`/`max` to download the oldest/latest VS Code supported by ExTester') + .option('-t, --type ', 'Type of VS Code release (stable/insider)') + .action( + withErrors(async (cmd) => { + const extest = new ExTester(cmd.storage, codeStream(cmd.type)); + await extest.downloadCode(cmd.code_version); + }), + ); -program.command('get-chromedriver') - .description('Download ChromeDriver binary') - .option('-s, --storage ', 'Use this folder for all test resources') - .option('-c, --code_version ', 'Version of VS Code you want to run with the ChromeDriver, use `min`/`max` to download the oldest/latest VS Code supported by ExTester') - .option('-t, --type ', 'Type of VS Code release (stable/insider)') - .action(withErrors(async (cmd) => { - const extest = new ExTester(cmd.storage, codeStream(cmd.type)); - await extest.downloadChromeDriver(cmd.code_version); - })); +program + .command('get-chromedriver') + .description('Download ChromeDriver binary') + .option('-s, --storage ', 'Use this folder for all test resources') + .option( + '-c, --code_version ', + 'Version of VS Code you want to run with the ChromeDriver, use `min`/`max` to download the oldest/latest VS Code supported by ExTester', + ) + .option('-t, --type ', 'Type of VS Code release (stable/insider)') + .action( + withErrors(async (cmd) => { + const extest = new ExTester(cmd.storage, codeStream(cmd.type)); + await extest.downloadChromeDriver(cmd.code_version); + }), + ); -program.command('install-vsix') - .description('Install extension from vsix file into test instance of VS Code') - .option('-s, --storage ', 'Use this folder for all test resources') - .option('-e, --extensions_dir ', 'VS Code will use this directory for managing extensions') - .option('-f, --vsix_file ', 'path/URL to vsix file containing the extension') - .option('-y, --yarn', 'Use yarn to build the extension via vsce instead of npm', false) - .option('-t, --type ', 'Type of VS Code release (stable/insider)') - .option('-i, --install_dependencies', 'Automatically install extensions your extension depends on', false) - .action(withErrors(async (cmd) => { - const extest = new ExTester(cmd.storage, codeStream(cmd.type), cmd.extensions_dir); - await extest.installVsix({vsixFile: cmd.vsix_file, useYarn: cmd.yarn, installDependencies: cmd.install_dependencies}); - })); +program + .command('install-vsix') + .description('Install extension from vsix file into test instance of VS Code') + .option('-s, --storage ', 'Use this folder for all test resources') + .option('-e, --extensions_dir ', 'VS Code will use this directory for managing extensions') + .option('-f, --vsix_file ', 'path/URL to vsix file containing the extension') + .option('-y, --yarn', 'Use yarn to build the extension via vsce instead of npm', false) + .option('-t, --type ', 'Type of VS Code release (stable/insider)') + .option('-i, --install_dependencies', 'Automatically install extensions your extension depends on', false) + .action( + withErrors(async (cmd) => { + const extest = new ExTester(cmd.storage, codeStream(cmd.type), cmd.extensions_dir); + await extest.installVsix({ + vsixFile: cmd.vsix_file, + useYarn: cmd.yarn, + installDependencies: cmd.install_dependencies, + }); + }), + ); -program.command('install-from-marketplace [ids...]') - .description('Install extension from marketplace with given into test instance of VS Code') - .option('-s, --storage ', 'Use this folder for all test resources') - .option('-e, --extensions_dir ', 'VS Code will use this directory for managing extensions') - .option('-t, --type ', 'Type of VS Code release (stable/insider)') - .action(withErrors(async (id, ids, cmd) => { - const extest = new ExTester(cmd.storage, codeStream(cmd.type), cmd.extensions_dir); - await extest.installFromMarketplace(id); - if (ids && ids.length > 0) { - for (const idx of ids) { - await extest.installFromMarketplace(idx); - } - } - })); +program + .command('install-from-marketplace [ids...]') + .description('Install extension from marketplace with given into test instance of VS Code') + .option('-s, --storage ', 'Use this folder for all test resources') + .option('-e, --extensions_dir ', 'VS Code will use this directory for managing extensions') + .option('-t, --type ', 'Type of VS Code release (stable/insider)') + .action( + withErrors(async (id, ids, cmd) => { + const extest = new ExTester(cmd.storage, codeStream(cmd.type), cmd.extensions_dir); + await extest.installFromMarketplace(id); + if (ids && ids.length > 0) { + for (const idx of ids) { + await extest.installFromMarketplace(idx); + } + } + }), + ); -program.command('setup-tests') - .description('Set up all necessary requirements for tests to run') - .option('-s, --storage ', 'Use this folder for all test resources') - .option('-e, --extensions_dir ', 'VS Code will use this directory for managing extensions') - .option('-c, --code_version ', 'Version of VS Code to download, use `min`/`max` to download the oldest/latest VS Code supported by ExTester') - .option('-t, --type ', 'Type of VS Code release (stable/insider)') - .option('-y, --yarn', 'Use yarn to build the extension via vsce instead of npm', false) - .option('-i, --install_dependencies', 'Automatically install extensions your extension depends on', false) - .action(withErrors(async (cmd) => { - const extest = new ExTester(cmd.storage, codeStream(cmd.type), cmd.extensions_dir); - await extest.setupRequirements({vscodeVersion: cmd.code_version, useYarn: cmd.yarn, installDependencies: cmd.install_dependencies}); - })); +program + .command('setup-tests') + .description('Set up all necessary requirements for tests to run') + .option('-s, --storage ', 'Use this folder for all test resources') + .option('-e, --extensions_dir ', 'VS Code will use this directory for managing extensions') + .option('-c, --code_version ', 'Version of VS Code to download, use `min`/`max` to download the oldest/latest VS Code supported by ExTester') + .option('-t, --type ', 'Type of VS Code release (stable/insider)') + .option('-y, --yarn', 'Use yarn to build the extension via vsce instead of npm', false) + .option('-i, --install_dependencies', 'Automatically install extensions your extension depends on', false) + .action( + withErrors(async (cmd) => { + const extest = new ExTester(cmd.storage, codeStream(cmd.type), cmd.extensions_dir); + await extest.setupRequirements({ + vscodeVersion: cmd.code_version, + useYarn: cmd.yarn, + installDependencies: cmd.install_dependencies, + }); + }), + ); -program.command('run-tests ') - .description('Run the test files specified by glob pattern(s)') - .option('-s, --storage ', 'Use this folder for all test resources') - .option('-e, --extensions_dir ', 'VS Code will use this directory for managing extensions') - .option('-c, --code_version ', 'Version of VS Code to be used, use `min`/`max` to download the oldest/latest VS Code supported by ExTester') - .option('-t, --type ', 'Type of VS Code release (stable/insider)') - .option('-o, --code_settings ', 'Path to custom settings for VS Code json file') - .option('-u, --uninstall_extension', 'Uninstall the extension after the test run', false) - .option('-m, --mocha_config ', 'Path to Mocha configuration file') - .option('-l, --log_level ', 'Log messages from webdriver with a given level', 'Info') - .option('-f, --offline', 'Attempt to run without internet connection, make sure to have all requirements downloaded', false) - .option('-C, --coverage', 'Enable code coverage using c8') - .option('-r, --open_resource ', 'Open resources in VS Code. Multiple files and folders can be specified.') - .action(withErrors(async (testFiles, cmd) => { - const extest = new ExTester(cmd.storage, codeStream(cmd.type), cmd.extensions_dir, cmd.coverage); - await extest.runTests(testFiles, {vscodeVersion: cmd.code_version, settings: cmd.code_settings, cleanup: cmd.uninstall_extension, config: cmd.mocha_config, logLevel: cmd.log_level, offline: cmd.offline, resources: cmd.open_resource ?? []}); - })); +program + .command('run-tests ') + .description('Run the test files specified by glob pattern(s)') + .option('-s, --storage ', 'Use this folder for all test resources') + .option('-e, --extensions_dir ', 'VS Code will use this directory for managing extensions') + .option('-c, --code_version ', 'Version of VS Code to be used, use `min`/`max` to download the oldest/latest VS Code supported by ExTester') + .option('-t, --type ', 'Type of VS Code release (stable/insider)') + .option('-o, --code_settings ', 'Path to custom settings for VS Code json file') + .option('-u, --uninstall_extension', 'Uninstall the extension after the test run', false) + .option('-m, --mocha_config ', 'Path to Mocha configuration file') + .option('-l, --log_level ', 'Log messages from webdriver with a given level', 'Info') + .option('-f, --offline', 'Attempt to run without internet connection, make sure to have all requirements downloaded', false) + .option('-C, --coverage', 'Enable code coverage using c8') + .option('-r, --open_resource ', 'Open resources in VS Code. Multiple files and folders can be specified.') + .action( + withErrors(async (testFiles, cmd) => { + const extest = new ExTester(cmd.storage, codeStream(cmd.type), cmd.extensions_dir, cmd.coverage); + await extest.runTests(testFiles, { + vscodeVersion: cmd.code_version, + settings: cmd.code_settings, + cleanup: cmd.uninstall_extension, + config: cmd.mocha_config, + logLevel: cmd.log_level, + offline: cmd.offline, + resources: cmd.open_resource ?? [], + }); + }), + ); -program.command('setup-and-run ') - .description('Perform all setup and run tests specified by glob pattern(s)') - .option('-s, --storage ', 'Use this folder for all test resources') - .option('-e, --extensions_dir ', 'VS Code will use this directory for managing extensions') - .option('-c, --code_version ', 'Version of VS Code to download, use `min`/`max` to download the oldest/latest VS Code supported by ExTester') - .option('-t, --type ', 'Type of VS Code release (stable/insider)') - .option('-o, --code_settings ', 'Path to custom settings for VS Code json file') - .option('-y, --yarn', 'Use yarn to build the extension via vsce instead of npm', false) - .option('-u, --uninstall_extension', 'Uninstall the extension after the test run', false) - .option('-m, --mocha_config ', 'Path to Mocha configuration file') - .option('-i, --install_dependencies', 'Automatically install extensions your extension depends on', false) - .option('-l, --log_level ', 'Log messages from webdriver with a given level', 'Info') - .option('-f, --offline', 'Attempt to run without internet connection, make sure to have all requirements downloaded', false) - .option('-C, --coverage', 'Enable code coverage using c8') - .option('-r, --open_resource ', 'Open resources in VS Code. Multiple files and folders can be specified.') - .action(withErrors(async (testFiles, cmd) => { - const extest = new ExTester(cmd.storage, codeStream(cmd.type), cmd.extensions_dir, cmd.coverage); - await extest.setupAndRunTests(testFiles, cmd.code_version, {useYarn: cmd.yarn, installDependencies: cmd.install_dependencies}, {settings: cmd.code_settings, cleanup: cmd.uninstall_extension, config: cmd.mocha_config, logLevel: cmd.log_level, resources: cmd.open_resource ?? []}); - })); +program + .command('setup-and-run ') + .description('Perform all setup and run tests specified by glob pattern(s)') + .option('-s, --storage ', 'Use this folder for all test resources') + .option('-e, --extensions_dir ', 'VS Code will use this directory for managing extensions') + .option('-c, --code_version ', 'Version of VS Code to download, use `min`/`max` to download the oldest/latest VS Code supported by ExTester') + .option('-t, --type ', 'Type of VS Code release (stable/insider)') + .option('-o, --code_settings ', 'Path to custom settings for VS Code json file') + .option('-y, --yarn', 'Use yarn to build the extension via vsce instead of npm', false) + .option('-u, --uninstall_extension', 'Uninstall the extension after the test run', false) + .option('-m, --mocha_config ', 'Path to Mocha configuration file') + .option('-i, --install_dependencies', 'Automatically install extensions your extension depends on', false) + .option('-l, --log_level ', 'Log messages from webdriver with a given level', 'Info') + .option('-f, --offline', 'Attempt to run without internet connection, make sure to have all requirements downloaded', false) + .option('-C, --coverage', 'Enable code coverage using c8') + .option('-r, --open_resource ', 'Open resources in VS Code. Multiple files and folders can be specified.') + .action( + withErrors(async (testFiles, cmd) => { + const extest = new ExTester(cmd.storage, codeStream(cmd.type), cmd.extensions_dir, cmd.coverage); + await extest.setupAndRunTests( + testFiles, + cmd.code_version, + { + useYarn: cmd.yarn, + installDependencies: cmd.install_dependencies, + }, + { + settings: cmd.code_settings, + cleanup: cmd.uninstall_extension, + config: cmd.mocha_config, + logLevel: cmd.log_level, + resources: cmd.open_resource ?? [], + }, + ); + }), + ); program.parse(process.argv); function withErrors(command: (...args: any[]) => Promise) { - return async (...args: any[]) => { - try { - await command(...args); - } catch (err) { - if (err instanceof Error) { - console.log(err.stack); - } else { - console.log(err); - } - process.exitCode = 1; - } - }; + return async (...args: any[]) => { + try { + await command(...args); + } catch (err) { + if (err instanceof Error) { + console.log(err.stack); + } else { + console.log(err); + } + process.exitCode = 1; + } + }; } function codeStream(stream: string) { - const envType = process.env.CODE_TYPE; - let type = stream; + const envType = process.env.CODE_TYPE; + let type = stream; - if (!type && envType) { - type = envType; - } - if (type && type.toLowerCase() === 'insider') { - return ReleaseQuality.Insider; - } - return ReleaseQuality.Stable; + if (!type && envType) { + type = envType; + } + if (type && type.toLowerCase() === 'insider') { + return ReleaseQuality.Insider; + } + return ReleaseQuality.Stable; } diff --git a/packages/extester/src/extester.ts b/packages/extester/src/extester.ts index 72a343453..db2023371 100644 --- a/packages/extester/src/extester.ts +++ b/packages/extester/src/extester.ts @@ -30,17 +30,17 @@ export * from './suite/mochaHooks'; export * from '@redhat-developer/page-objects'; export interface SetupOptions { - /** version of VS Code to test against, defaults to latest */ - vscodeVersion?: string; - /** when true run `vsce package` with the `--yarn` flag */ - useYarn?: boolean; - /** install the extension's dependencies from the marketplace. Defaults to `false`. */ - installDependencies?: boolean; + /** version of VS Code to test against, defaults to latest */ + vscodeVersion?: string; + /** when true run `vsce package` with the `--yarn` flag */ + useYarn?: boolean; + /** install the extension's dependencies from the marketplace. Defaults to `false`. */ + installDependencies?: boolean; } export const DEFAULT_SETUP_OPTIONS = { - vscodeVersion: 'latest', - installDependencies: false + vscodeVersion: 'latest', + installDependencies: false, }; export const DEFAULT_STORAGE_FOLDER = process.env.TEST_RESOURCES ? process.env.TEST_RESOURCES : path.join(os.tmpdir(), 'test-resources'); @@ -57,146 +57,170 @@ export const NODEJS_VERSION_MAX = pjson.supportedVersions.nodejs; * ExTester */ export class ExTester { - private code: CodeUtil; - private chrome: DriverUtil; - - constructor(storageFolder: string = DEFAULT_STORAGE_FOLDER, releaseType: ReleaseQuality = ReleaseQuality.Stable, extensionsDir?: string, coverage?: boolean) { - this.code = new CodeUtil(storageFolder, releaseType, extensionsDir, coverage); - this.chrome = new DriverUtil(storageFolder); - - if (process.versions.node.slice(0, 2) > NODEJS_VERSION_MAX) { - console.log( - '\x1b[33m%s\x1b[0m', - `\nWarning: You are using the untested NodeJS version '${process.versions.node}'. The latest supported version is '${NODEJS_VERSION_MAX}.x.x'.\n\t We recommend to use tested version to have ExTester working properly.\n\n` - ); - } - } - - /** - * Download VS Code of given version and release quality stream - * @param version version to download, default latest - */ - async downloadCode(version: string = 'latest'): Promise { - return await this.code.downloadVSCode(loadCodeVersion(version)); - } - - /** - * Install the extension into the test instance of VS Code - * @param vsixFile path to extension .vsix file. If not set, default vsce path will be used - * @param useYarn when true run `vsce package` with the `--yarn` flag - */ - async installVsix({ vsixFile, useYarn, installDependencies }: { vsixFile?: string; useYarn?: boolean; installDependencies?: boolean } = {}): Promise { - let target = vsixFile; - if (vsixFile) { - try { - const uri = new URL(vsixFile); - if (!(process.platform === 'win32' && /^\w:/.test(uri.protocol))) { - target = path.basename(vsixFile); - } - } catch (err) { - if (!fs.existsSync(vsixFile)) { - throw new Error(`File ${vsixFile} does not exist`); - } - } - if (target !== vsixFile) { - target = await this.code.downloadExtension(vsixFile); - } - } else { - await this.code.packageExtension(useYarn); - } - this.code.installExtension(target); - if (installDependencies) { - this.code.installDependencies(); - } - } - - /** - * Install an extension from VS Code marketplace into the test instance - * @param id id of the extension to install - */ - async installFromMarketplace(id: string): Promise { - return this.code.installExtension(undefined, id); - } - - /** - * Download the matching chromedriver for a given VS Code version - * @param vscodeVersion selected version of VS Code, default latest - */ - async downloadChromeDriver(vscodeVersion: string = 'latest'): Promise { - const chromiumVersion = await this.code.getChromiumVersion(loadCodeVersion(vscodeVersion)); - return await this.chrome.downloadChromeDriverForChromiumVersion(chromiumVersion); - } - - /** - * Performs all necessary setup: getting VS Code + ChromeDriver - * and packaging/installing extension into the test instance - * - * @param options Additional options for setting up the tests - */ - async setupRequirements(options: SetupOptions = DEFAULT_SETUP_OPTIONS, offline = false, cleanup = false): Promise { - const { useYarn, vscodeVersion, installDependencies } = options; - - const vscodeParsedVersion = loadCodeVersion(vscodeVersion); - if (!offline) { - await this.downloadCode(vscodeParsedVersion); - await this.downloadChromeDriver(vscodeParsedVersion); - } else { - console.log('Attempting Setup in offline mode'); - const expectedChromeVersion = (this.code.checkOfflineRequirements()).split('.')[0]; - const actualChromeVersion = (await this.chrome.checkDriverVersionOffline(vscodeParsedVersion)).split('.')[0]; - if (expectedChromeVersion !== actualChromeVersion) { - console.log('\x1b[33m%s\x1b[0m', `WARNING: Local copy of VS Code runs Chromium version ${expectedChromeVersion}, the installed ChromeDriver is version ${actualChromeVersion}.`); - console.log(`Attempting with ChromeDriver ${actualChromeVersion} anyway. Tests may experience issues due to version mismatch.`); - } - } - if (!this.code.coverageEnabled || cleanup) { - await this.installVsix({useYarn}); - } - if (installDependencies && !offline) { - this.code.installDependencies(); - } - } - - /** - * Performs requirements setup and runs extension tests - * - * @param testFilesPattern glob pattern(s) for test files to run - * @param vscodeVersion version of VS Code to test against, defaults to latest - * @param setupOptions Additional options for setting up the tests - * @param runOptions Additional options for running the tests - * - * @returns Promise resolving to the mocha process exit code - 0 for no failures, 1 otherwise - */ - async setupAndRunTests(testFilesPattern: string | string[], vscodeVersion: string = 'latest', setupOptions: Omit = DEFAULT_SETUP_OPTIONS, runOptions: Omit = DEFAULT_RUN_OPTIONS): Promise { - await this.setupRequirements({...setupOptions, vscodeVersion}, runOptions.offline, runOptions.cleanup); - return await this.runTests(testFilesPattern, {...runOptions, vscodeVersion}); - } - - /** - * Runs the selected test files in VS Code using mocha and webdriver - * @param testFilesPattern glob pattern(s) for selected test files - * @param runOptions Additional options for running the tests - * - * @returns Promise resolving to the mocha process exit code - 0 for no failures, 1 otherwise - */ - async runTests(testFilesPattern: string | string[], runOptions: RunOptions = DEFAULT_RUN_OPTIONS): Promise { - runOptions.vscodeVersion = loadCodeVersion(runOptions.vscodeVersion); - const patterns = (typeof testFilesPattern === 'string') ? ([testFilesPattern]) : (testFilesPattern); - return await this.code.runTests(patterns, runOptions); - } + private code: CodeUtil; + private chrome: DriverUtil; + + constructor( + storageFolder: string = DEFAULT_STORAGE_FOLDER, + releaseType: ReleaseQuality = ReleaseQuality.Stable, + extensionsDir?: string, + coverage?: boolean, + ) { + this.code = new CodeUtil(storageFolder, releaseType, extensionsDir, coverage); + this.chrome = new DriverUtil(storageFolder); + + if (process.versions.node.slice(0, 2) > NODEJS_VERSION_MAX) { + console.log( + '\x1b[33m%s\x1b[0m', + `\nWarning: You are using the untested NodeJS version '${process.versions.node}'. The latest supported version is '${NODEJS_VERSION_MAX}.x.x'.\n\t We recommend to use tested version to have ExTester working properly.\n\n`, + ); + } + } + + /** + * Download VS Code of given version and release quality stream + * @param version version to download, default latest + */ + async downloadCode(version: string = 'latest'): Promise { + return await this.code.downloadVSCode(loadCodeVersion(version)); + } + + /** + * Install the extension into the test instance of VS Code + * @param vsixFile path to extension .vsix file. If not set, default vsce path will be used + * @param useYarn when true run `vsce package` with the `--yarn` flag + */ + async installVsix({ + vsixFile, + useYarn, + installDependencies, + }: { + vsixFile?: string; + useYarn?: boolean; + installDependencies?: boolean; + } = {}): Promise { + let target = vsixFile; + if (vsixFile) { + try { + const uri = new URL(vsixFile); + if (!(process.platform === 'win32' && /^\w:/.test(uri.protocol))) { + target = path.basename(vsixFile); + } + } catch (err) { + if (!fs.existsSync(vsixFile)) { + throw new Error(`File ${vsixFile} does not exist`); + } + } + if (target !== vsixFile) { + target = await this.code.downloadExtension(vsixFile); + } + } else { + await this.code.packageExtension(useYarn); + } + this.code.installExtension(target); + if (installDependencies) { + this.code.installDependencies(); + } + } + + /** + * Install an extension from VS Code marketplace into the test instance + * @param id id of the extension to install + */ + async installFromMarketplace(id: string): Promise { + return this.code.installExtension(undefined, id); + } + + /** + * Download the matching chromedriver for a given VS Code version + * @param vscodeVersion selected version of VS Code, default latest + */ + async downloadChromeDriver(vscodeVersion: string = 'latest'): Promise { + const chromiumVersion = await this.code.getChromiumVersion(loadCodeVersion(vscodeVersion)); + return await this.chrome.downloadChromeDriverForChromiumVersion(chromiumVersion); + } + + /** + * Performs all necessary setup: getting VS Code + ChromeDriver + * and packaging/installing extension into the test instance + * + * @param options Additional options for setting up the tests + */ + async setupRequirements(options: SetupOptions = DEFAULT_SETUP_OPTIONS, offline = false, cleanup = false): Promise { + const { useYarn, vscodeVersion, installDependencies } = options; + + const vscodeParsedVersion = loadCodeVersion(vscodeVersion); + if (!offline) { + await this.downloadCode(vscodeParsedVersion); + await this.downloadChromeDriver(vscodeParsedVersion); + } else { + console.log('Attempting Setup in offline mode'); + const expectedChromeVersion = this.code.checkOfflineRequirements().split('.')[0]; + const actualChromeVersion = (await this.chrome.checkDriverVersionOffline(vscodeParsedVersion)).split('.')[0]; + if (expectedChromeVersion !== actualChromeVersion) { + console.log( + '\x1b[33m%s\x1b[0m', + `WARNING: Local copy of VS Code runs Chromium version ${expectedChromeVersion}, the installed ChromeDriver is version ${actualChromeVersion}.`, + ); + console.log(`Attempting with ChromeDriver ${actualChromeVersion} anyway. Tests may experience issues due to version mismatch.`); + } + } + if (!this.code.coverageEnabled || cleanup) { + await this.installVsix({ useYarn }); + } + if (installDependencies && !offline) { + this.code.installDependencies(); + } + } + + /** + * Performs requirements setup and runs extension tests + * + * @param testFilesPattern glob pattern(s) for test files to run + * @param vscodeVersion version of VS Code to test against, defaults to latest + * @param setupOptions Additional options for setting up the tests + * @param runOptions Additional options for running the tests + * + * @returns Promise resolving to the mocha process exit code - 0 for no failures, 1 otherwise + */ + async setupAndRunTests( + testFilesPattern: string | string[], + vscodeVersion: string = 'latest', + setupOptions: Omit = DEFAULT_SETUP_OPTIONS, + runOptions: Omit = DEFAULT_RUN_OPTIONS, + ): Promise { + await this.setupRequirements({ ...setupOptions, vscodeVersion }, runOptions.offline, runOptions.cleanup); + return await this.runTests(testFilesPattern, { + ...runOptions, + vscodeVersion, + }); + } + + /** + * Runs the selected test files in VS Code using mocha and webdriver + * @param testFilesPattern glob pattern(s) for selected test files + * @param runOptions Additional options for running the tests + * + * @returns Promise resolving to the mocha process exit code - 0 for no failures, 1 otherwise + */ + async runTests(testFilesPattern: string | string[], runOptions: RunOptions = DEFAULT_RUN_OPTIONS): Promise { + runOptions.vscodeVersion = loadCodeVersion(runOptions.vscodeVersion); + const patterns = typeof testFilesPattern === 'string' ? [testFilesPattern] : testFilesPattern; + return await this.code.runTests(patterns, runOptions); + } } export function loadCodeVersion(version: string | undefined): string { - const codeVersion = process.env.CODE_VERSION ? process.env.CODE_VERSION : version; - - if (codeVersion !== undefined) { - if (codeVersion.toLowerCase() === 'max') { - return VSCODE_VERSION_MAX; - } - if (codeVersion.toLowerCase() === 'min') { - return VSCODE_VERSION_MIN; - } - return codeVersion; - } - return 'latest'; + const codeVersion = process.env.CODE_VERSION ? process.env.CODE_VERSION : version; + + if (codeVersion !== undefined) { + if (codeVersion.toLowerCase() === 'max') { + return VSCODE_VERSION_MAX; + } + if (codeVersion.toLowerCase() === 'min') { + return VSCODE_VERSION_MIN; + } + return codeVersion; + } + return 'latest'; } diff --git a/packages/extester/src/suite/mochaHooks.ts b/packages/extester/src/suite/mochaHooks.ts index d08a1585e..d4308edd5 100644 --- a/packages/extester/src/suite/mochaHooks.ts +++ b/packages/extester/src/suite/mochaHooks.ts @@ -26,26 +26,25 @@ type HookType = 'before' | 'beforeEach' | 'after' | 'afterEach'; function vscodeBefore(fn: Function): void; function vscodeBefore(name: string, fn: Function): void; function vscodeBefore(name?: any, fn?: any): void { - callHook('before', name, fn); + callHook('before', name, fn); } function vscodeBeforeEach(fn: Function): void; function vscodeBeforeEach(name: string, fn: Function): void; function vscodeBeforeEach(name?: any, fn?: any): void { - callHook('beforeEach', name, fn); + callHook('beforeEach', name, fn); } function vscodeAfter(fn: Function): void; function vscodeAfter(name: string, fn: Function): void; function vscodeAfter(name?: any, fn?: any): void { - callHook('after', name, fn); + callHook('after', name, fn); } - function vscodeAfterEach(fn: Function): void; function vscodeAfterEach(name: string, fn: Function): void; function vscodeAfterEach(name?: any, fn?: any): void { - callHook('afterEach', name, fn); + callHook('afterEach', name, fn); } /** @@ -56,36 +55,33 @@ function vscodeAfterEach(name?: any, fn?: any): void { * @returns wrapped function capable of doing screenshots on callback failure */ function createScreenshotCallbackFunction(name: string | undefined, hookType: HookType, fn: Function): Func { - const alternativeFileName: string = createAlternativeFileName(hookType); - - return async function (this: Mocha.Context) { - try { - await fn.call(this); - } - catch (e) { - if (this === undefined) { - throw e; - } - if (this.test === undefined) { - try { - await VSBrowser.instance.takeScreenshot(sanitize(alternativeFileName)); - } - catch (screenshotError) { - console.error(`Could not take screenshot. this.test is undefined. Reason:\n${screenshotError}\n\n`); - } - throw e; - } - - try { - const titlePath = this.test.titlePath(); - await VSBrowser.instance.takeScreenshot(sanitize(titlePath.join('.'))); - } - catch (screenshotError) { - console.error(`Could not take screenshot. Reason:\n${screenshotError}\n\n`); - } - throw e; - } - }; + const alternativeFileName: string = createAlternativeFileName(hookType); + + return async function (this: Mocha.Context) { + try { + await fn.call(this); + } catch (e) { + if (this === undefined) { + throw e; + } + if (this.test === undefined) { + try { + await VSBrowser.instance.takeScreenshot(sanitize(alternativeFileName)); + } catch (screenshotError) { + console.error(`Could not take screenshot. this.test is undefined. Reason:\n${screenshotError}\n\n`); + } + throw e; + } + + try { + const titlePath = this.test.titlePath(); + await VSBrowser.instance.takeScreenshot(sanitize(titlePath.join('.'))); + } catch (screenshotError) { + console.error(`Could not take screenshot. Reason:\n${screenshotError}\n\n`); + } + throw e; + } + }; } /** @@ -95,29 +91,28 @@ function createScreenshotCallbackFunction(name: string | undefined, hookType: Ho * @param secondArgument callback to be called or undefined */ function callHook(hookType: HookType, firstArgument: string | Function | undefined, secondArgument: Function | undefined): void { - /* Disallowed combinations */ - if (typeof firstArgument === 'function' && secondArgument !== undefined) { - throw new Error(`${hookType}(func1, func2) If the first argument is a function, then the second argument must be undefined.`); - } - if (typeof firstArgument === 'string' && secondArgument === undefined) { - throw new Error(`${hookType}(${firstArgument}) required callback function as seconds argument.`); - } - if (firstArgument === undefined && secondArgument === undefined) { - throw new Error(`${hookType}() requires at least callback function.`); - } - /* Remaining 2 combinations are valid. eg. before(callback) and before(name, callback). */ - const name = (typeof firstArgument === 'string') ? (firstArgument) : (undefined); - const fn = (typeof firstArgument === 'function') ? (firstArgument) : (secondArgument); - - const hook = getHookFunction(hookType); - const callback = fn ? fn : (() => { }); - - if (name !== undefined) { - hook(name, createScreenshotCallbackFunction(name, hookType, callback)); - } - else { - hook(createScreenshotCallbackFunction(name, hookType, callback)); - } + /* Disallowed combinations */ + if (typeof firstArgument === 'function' && secondArgument !== undefined) { + throw new Error(`${hookType}(func1, func2) If the first argument is a function, then the second argument must be undefined.`); + } + if (typeof firstArgument === 'string' && secondArgument === undefined) { + throw new Error(`${hookType}(${firstArgument}) required callback function as seconds argument.`); + } + if (firstArgument === undefined && secondArgument === undefined) { + throw new Error(`${hookType}() requires at least callback function.`); + } + /* Remaining 2 combinations are valid. eg. before(callback) and before(name, callback). */ + const name = typeof firstArgument === 'string' ? firstArgument : undefined; + const fn = typeof firstArgument === 'function' ? firstArgument : secondArgument; + + const hook = getHookFunction(hookType); + const callback = fn ? fn : () => {}; + + if (name !== undefined) { + hook(name, createScreenshotCallbackFunction(name, hookType, callback)); + } else { + hook(createScreenshotCallbackFunction(name, hookType, callback)); + } } /** @@ -126,26 +121,30 @@ function callHook(hookType: HookType, firstArgument: string | Function | undefin * @returns hook function */ function getHookFunction(hookType: HookType): Mocha.HookFunction { - switch (hookType) { - case 'before': return before; - case 'beforeEach': return beforeEach; - case 'after': return after; - case 'afterEach': return afterEach; - default: - throw new Error(`Unknown hook type "${hookType}".`); - } + switch (hookType) { + case 'before': + return before; + case 'beforeEach': + return beforeEach; + case 'after': + return after; + case 'afterEach': + return afterEach; + default: + throw new Error(`Unknown hook type "${hookType}".`); + } } /** * Create number generator [1..]. */ function* createScreenshotNameGenerator(): Generator { - let counter = 1; + let counter = 1; - while (true) { - yield counter; - counter++; - } + while (true) { + yield counter; + counter++; + } } /** @@ -154,24 +153,25 @@ function* createScreenshotNameGenerator(): Generator { * @returns alternative file name without extension */ function createAlternativeFileName(hookType: HookType): string { - const generator = screenshotNameGenerators.get(hookType); - - if (generator) { - return `${hookType} ${generator.next().value}`; - } - else { - throw new Error(`Unknown mocha hook type "${hookType}".`); - } + const generator = screenshotNameGenerators.get(hookType); + + if (generator) { + return `${hookType} ${generator.next().value}`; + } else { + throw new Error(`Unknown mocha hook type "${hookType}".`); + } } /** * Create number generator for each callback. */ -const screenshotNameGenerators = new Map(Object.entries({ - "before": createScreenshotNameGenerator(), - "beforeEach": createScreenshotNameGenerator(), - "after": createScreenshotNameGenerator(), - "afterEach": createScreenshotNameGenerator() -})); +const screenshotNameGenerators = new Map( + Object.entries({ + before: createScreenshotNameGenerator(), + beforeEach: createScreenshotNameGenerator(), + after: createScreenshotNameGenerator(), + afterEach: createScreenshotNameGenerator(), + }), +); export { vscodeBefore as before, vscodeBeforeEach as beforeEach, vscodeAfter as after, vscodeAfterEach as afterEach }; diff --git a/packages/extester/src/suite/runner.ts b/packages/extester/src/suite/runner.ts index a29124da7..7e5a9921b 100644 --- a/packages/extester/src/suite/runner.ts +++ b/packages/extester/src/suite/runner.ts @@ -31,166 +31,166 @@ import { Coverage } from '../util/coverage'; * Mocha runner wrapper */ export class VSRunner { - private mocha: Mocha; - private chromeBin: string; - private customSettings: object; - private codeVersion: string; - private cleanup: boolean; - private tmpLink = path.join(os.tmpdir(), 'extest-code'); - private releaseType: ReleaseQuality; - - constructor(bin: string, codeVersion: string, customSettings: object = {}, cleanup: boolean = false, releaseType: ReleaseQuality, config?: string) { - const conf = this.loadConfig(config); - this.mocha = new Mocha(conf); - this.chromeBin = bin; - this.customSettings = customSettings; - this.codeVersion = codeVersion; - this.cleanup = cleanup; - this.releaseType = releaseType; - } - - /** - * Set up mocha suite, add vscode instance handling, run tests - * @param testFilesPattern glob pattern of test files to run - * @param logLevel The logging level for the Webdriver - * @return The exit code of the mocha process - */ - runTests(testFilesPattern: string[], code: CodeUtil, logLevel: logging.Level = logging.Level.INFO, resources: string[]): Promise { - return new Promise(resolve => { - const self = this; - const browser: VSBrowser = new VSBrowser(this.codeVersion, this.releaseType, this.customSettings, logLevel); - let coverage: Coverage | undefined; - - const testFiles = new Set(); - for (const pattern of testFilesPattern) { - const universalPattern = pattern.replace(/'/g, ''); - globSync(universalPattern).reverse() - .forEach((val) => testFiles.add(val)); - } - - testFiles.forEach((file) => this.mocha.addFile(file)); - this.mocha.suite.afterEach(async function () { - if (this.currentTest && this.currentTest.state !== 'passed') { - try { - const filename = sanitize(this.currentTest.fullTitle()); - await browser.takeScreenshot(filename); - } catch (err) { - console.log('Screenshot capture failed.', err); - } - } - }); - - this.mocha.suite.beforeAll(async function () { - this.timeout(180000); - if (code.coverageEnabled) { - coverage = new Coverage(); - await coverage.loadConfig(); - process.env.NODE_V8_COVERAGE = coverage?.targetDir; - } - - const start = Date.now(); - const binPath = process.platform === 'darwin' ? await self.createShortcut(code.getCodeFolder(), self.tmpLink) : self.chromeBin; - await browser.start(binPath); - await browser.openResources(...resources); - await browser.waitForWorkbench(); - await new Promise((res) => { setTimeout(res, 3000); }); - console.log(`Browser ready in ${Date.now() - start} ms`); - console.log('Launching tests...'); - }); - - this.mocha.suite.afterAll(async function () { - this.timeout(180000); - await browser.quit(); - if (process.platform === 'darwin') { - if (await fs.pathExists(self.tmpLink)) { - try { - fs.unlinkSync(self.tmpLink); - } catch (err) { - console.log(err); - } - } - } - if (code.coverageEnabled) { - await coverage?.write(); - } - code.uninstallExtension(self.cleanup); - }); - - this.mocha.run((failures) => { - process.exitCode = failures ? 1 : 0; - if (process.exitCode) { - console.log( - '\x1b[33m%s\x1b[0m', - `INFO: Screenshots of failures can be found in: ${browser.getScreenshotsDir()}\n` - ); - } - resolve(process.exitCode); - }); - }); - } - - private async createShortcut(src: string, dest: string): Promise { - try { - await fs.ensureSymlink(src, dest, 'dir'); - } catch (err) { - return this.chromeBin; - } - - const dir = path.parse(src); - const segments = this.chromeBin.split(path.sep); - const newSegments = dest.split(path.sep); - - let found = false; - for (const segment of segments) { - if (!found) { - found = segment === dir.base; - } else { - newSegments.push(segment); - } - } - return path.join(dir.root, ...newSegments); - } - - private loadConfig(config?: string): Mocha.MochaOptions { - const defaultFiles = ['.mocharc.js', '.mocharc.json', '.mocharc.yml', '.mocharc.yaml']; - let conf: Mocha.MochaOptions = {}; - let file = config; - if (!config) { - file = path.resolve('.'); - for (const defFile of defaultFiles) { - if (fs.existsSync(path.join(file, defFile))) { - file = path.join(file, defFile); - break; - } - } - } - - if (file && fs.existsSync(file) && fs.statSync(file).isFile()) { - console.log(`Loading mocha configuration from ${file}`); - if (/\.(yml|yaml)$/.test(file)) { - try { - conf = yaml.load(fs.readFileSync(file, 'utf-8')) as Mocha.MochaOptions; - } catch (err) { - console.log('Invalid mocha configuration file, will be ignored'); - } - } else if (/\.(js|json)$/.test(file)) { - try { - conf = require(path.resolve(file)); - } catch (err) { - console.log('Invalid mocha configuration file, will be ignored'); - } - } else { - console.log('Unsupported mocha configuration file extension, make sure to use .js, .json, .yml or .yaml file'); - } - } - - if (process.env.MOCHA_GREP) { - conf.grep = process.env.MOCHA_GREP; - } - if (process.env.MOCHA_INVERT) { - conf.invert = process.env.MOCHA_INVERT === 'true'; - } - - return conf; - } + private mocha: Mocha; + private chromeBin: string; + private customSettings: object; + private codeVersion: string; + private cleanup: boolean; + private tmpLink = path.join(os.tmpdir(), 'extest-code'); + private releaseType: ReleaseQuality; + + constructor(bin: string, codeVersion: string, customSettings: object = {}, cleanup: boolean = false, releaseType: ReleaseQuality, config?: string) { + const conf = this.loadConfig(config); + this.mocha = new Mocha(conf); + this.chromeBin = bin; + this.customSettings = customSettings; + this.codeVersion = codeVersion; + this.cleanup = cleanup; + this.releaseType = releaseType; + } + + /** + * Set up mocha suite, add vscode instance handling, run tests + * @param testFilesPattern glob pattern of test files to run + * @param logLevel The logging level for the Webdriver + * @return The exit code of the mocha process + */ + runTests(testFilesPattern: string[], code: CodeUtil, logLevel: logging.Level = logging.Level.INFO, resources: string[]): Promise { + return new Promise((resolve) => { + const self = this; + const browser: VSBrowser = new VSBrowser(this.codeVersion, this.releaseType, this.customSettings, logLevel); + let coverage: Coverage | undefined; + + const testFiles = new Set(); + for (const pattern of testFilesPattern) { + const universalPattern = pattern.replace(/'/g, ''); + globSync(universalPattern) + .reverse() + .forEach((val) => testFiles.add(val)); + } + + testFiles.forEach((file) => this.mocha.addFile(file)); + this.mocha.suite.afterEach(async function () { + if (this.currentTest && this.currentTest.state !== 'passed') { + try { + const filename = sanitize(this.currentTest.fullTitle()); + await browser.takeScreenshot(filename); + } catch (err) { + console.log('Screenshot capture failed.', err); + } + } + }); + + this.mocha.suite.beforeAll(async function () { + this.timeout(180000); + if (code.coverageEnabled) { + coverage = new Coverage(); + await coverage.loadConfig(); + process.env.NODE_V8_COVERAGE = coverage?.targetDir; + } + + const start = Date.now(); + const binPath = process.platform === 'darwin' ? await self.createShortcut(code.getCodeFolder(), self.tmpLink) : self.chromeBin; + await browser.start(binPath); + await browser.openResources(...resources); + await browser.waitForWorkbench(); + await new Promise((res) => { + setTimeout(res, 3000); + }); + console.log(`Browser ready in ${Date.now() - start} ms`); + console.log('Launching tests...'); + }); + + this.mocha.suite.afterAll(async function () { + this.timeout(180000); + await browser.quit(); + if (process.platform === 'darwin') { + if (await fs.pathExists(self.tmpLink)) { + try { + fs.unlinkSync(self.tmpLink); + } catch (err) { + console.log(err); + } + } + } + if (code.coverageEnabled) { + await coverage?.write(); + } + code.uninstallExtension(self.cleanup); + }); + + this.mocha.run((failures) => { + process.exitCode = failures ? 1 : 0; + if (process.exitCode) { + console.log('\x1b[33m%s\x1b[0m', `INFO: Screenshots of failures can be found in: ${browser.getScreenshotsDir()}\n`); + } + resolve(process.exitCode); + }); + }); + } + + private async createShortcut(src: string, dest: string): Promise { + try { + await fs.ensureSymlink(src, dest, 'dir'); + } catch (err) { + return this.chromeBin; + } + + const dir = path.parse(src); + const segments = this.chromeBin.split(path.sep); + const newSegments = dest.split(path.sep); + + let found = false; + for (const segment of segments) { + if (!found) { + found = segment === dir.base; + } else { + newSegments.push(segment); + } + } + return path.join(dir.root, ...newSegments); + } + + private loadConfig(config?: string): Mocha.MochaOptions { + const defaultFiles = ['.mocharc.js', '.mocharc.json', '.mocharc.yml', '.mocharc.yaml']; + let conf: Mocha.MochaOptions = {}; + let file = config; + if (!config) { + file = path.resolve('.'); + for (const defFile of defaultFiles) { + if (fs.existsSync(path.join(file, defFile))) { + file = path.join(file, defFile); + break; + } + } + } + + if (file && fs.existsSync(file) && fs.statSync(file).isFile()) { + console.log(`Loading mocha configuration from ${file}`); + if (/\.(yml|yaml)$/.test(file)) { + try { + conf = yaml.load(fs.readFileSync(file, 'utf-8')) as Mocha.MochaOptions; + } catch (err) { + console.log('Invalid mocha configuration file, will be ignored'); + } + } else if (/\.(js|json)$/.test(file)) { + try { + conf = require(path.resolve(file)); + } catch (err) { + console.log('Invalid mocha configuration file, will be ignored'); + } + } else { + console.log('Unsupported mocha configuration file extension, make sure to use .js, .json, .yml or .yaml file'); + } + } + + if (process.env.MOCHA_GREP) { + conf.grep = process.env.MOCHA_GREP; + } + if (process.env.MOCHA_INVERT) { + conf.invert = process.env.MOCHA_INVERT === 'true'; + } + + return conf; + } } diff --git a/packages/extester/src/util/codeUtil.ts b/packages/extester/src/util/codeUtil.ts index 04ac4b5e9..4c1060cfb 100644 --- a/packages/extester/src/util/codeUtil.ts +++ b/packages/extester/src/util/codeUtil.ts @@ -19,41 +19,41 @@ import * as childProcess from 'child_process'; import * as fs from 'fs-extra'; import * as path from 'path'; import * as vsce from '@vscode/vsce'; -import { VSRunner } from "../suite/runner"; -import { Unpack } from "./unpack"; -import { logging } from "selenium-webdriver"; +import { VSRunner } from '../suite/runner'; +import { Unpack } from './unpack'; +import { logging } from 'selenium-webdriver'; import { Download } from './download'; import { DEFAULT_STORAGE_FOLDER } from '../extester'; export enum ReleaseQuality { - Stable = 'stable', - Insider = 'insider' + Stable = 'stable', + Insider = 'insider', } export interface RunOptions { - /** version of VS Code to test against, defaults to latest */ - vscodeVersion?: string; - /** path to custom settings json file */ - settings?: string; - /** remove the extension's directory as well (if present) */ - cleanup?: boolean; - /** path to a custom mocha configuration file */ - config?: string; - /** logging level of the Webdriver */ - logLevel?: logging.Level; - /** try to perform all setup without internet connection, needs all requirements pre-downloaded manually */ - offline?: boolean; - /** list of resources to be opened by VS Code */ - resources: string[]; + /** version of VS Code to test against, defaults to latest */ + vscodeVersion?: string; + /** path to custom settings json file */ + settings?: string; + /** remove the extension's directory as well (if present) */ + cleanup?: boolean; + /** path to a custom mocha configuration file */ + config?: string; + /** logging level of the Webdriver */ + logLevel?: logging.Level; + /** try to perform all setup without internet connection, needs all requirements pre-downloaded manually */ + offline?: boolean; + /** list of resources to be opened by VS Code */ + resources: string[]; } /** defaults for the [[RunOptions]] */ export const DEFAULT_RUN_OPTIONS = { - vscodeVersion: 'latest', - settings: '', - logLevel: logging.Level.INFO, - offline: false, - resources: [] + vscodeVersion: 'latest', + settings: '', + logLevel: logging.Level.INFO, + offline: false, + resources: [], }; /** @@ -61,396 +61,405 @@ export const DEFAULT_RUN_OPTIONS = { * Includes downloading, unpacking, launching, and version checks. */ export class CodeUtil { - private codeFolder: string; - private downloadPlatform: string; - private downloadFolder: string; - private releaseType: ReleaseQuality; - private executablePath!: string; - private cliPath!: string; - private cliEnv!: string; - private availableVersions: string[]; - private extensionsFolder: string | undefined; - private coverage: boolean | undefined; - - /** - * Create an instance of code handler - * @param folder Path to folder where all the artifacts will be stored. - * @param extensionsFolder Path to use as extensions directory by VS Code - */ - constructor(folder: string = DEFAULT_STORAGE_FOLDER, type: ReleaseQuality = ReleaseQuality.Stable, extensionsFolder?: string, coverage?: boolean) { - this.availableVersions = []; - this.downloadPlatform = this.getPlatform(); - this.downloadFolder = path.resolve(folder); - this.extensionsFolder = extensionsFolder ? path.resolve(extensionsFolder) : undefined; - this.coverage = coverage; - this.releaseType = type; - - if (type === ReleaseQuality.Stable) { - this.codeFolder = path.join(this.downloadFolder, (process.platform === 'darwin') - ? 'Visual Studio Code.app' : `VSCode-${this.downloadPlatform}`); - } else { - this.codeFolder = path.join(this.downloadFolder, (process.platform === 'darwin') - ? 'Visual Studio Code - Insiders.app' : `VSCode-${this.downloadPlatform}-insider`); - } - this.findExecutables(); - } - - /** - * Get all versions for the given release stream - */ - async getVSCodeVersions(): Promise { - const apiUrl = `https://update.code.visualstudio.com/api/releases/${this.releaseType}`; - const json = await Download.getText(apiUrl); - return json as unknown as string[]; - } - - /** - * Download and unpack VS Code for testing purposes - * - * @param version VS Code version to get, default latest - */ - async downloadVSCode(version: string = 'latest'): Promise { - await this.checkCodeVersion(version); - - const literalVersion = version === 'latest' ? this.availableVersions[0] : version; - if (this.releaseType === ReleaseQuality.Stable && literalVersion !== this.availableVersions[0]) { - console.log( - '\x1b[33m%s\x1b[0m', - `\n\nWARNING: You are using the outdated VS Code version '${literalVersion}'. The latest stable version is '${this.availableVersions[0]}'.\n\n` - ); - } - - console.log(`Downloading VS Code: ${literalVersion} / ${this.releaseType}`); - if (!fs.existsSync(this.executablePath) || this.getExistingCodeVersion() !== literalVersion) { - fs.mkdirpSync(this.downloadFolder); - - const url = ['https://update.code.visualstudio.com', version, this.downloadPlatform, this.releaseType].join('/'); - const isTarGz = this.downloadPlatform.indexOf('linux') > -1; - const fileName = `${path.basename(url)}.${isTarGz ? 'tar.gz' : 'zip'}`; - - console.log(`Downloading VS Code from: ${url}`); - await Download.getFile(url, path.join(this.downloadFolder, fileName), true); - console.log(`Downloaded VS Code into ${path.join(this.downloadFolder, fileName)}`); - - console.log(`Unpacking VS Code into ${this.downloadFolder}`); - const target = await fs.mkdtemp('vscode'); - await Unpack.unpack(path.join(this.downloadFolder, fileName), target); - let rootDir = target; - const files = await fs.readdir(target); - - if (files.length === 1) { - rootDir = path.join(target, files[0]); - } - await fs.move(rootDir, this.codeFolder, { overwrite: true }); - await fs.remove(target); - - console.log('Success!'); - } else { - console.log('VS Code exists in local cache, skipping download'); - } - } - - /** - * Install your extension into the test instance of VS Code - */ - installExtension(vsix?: string, id?: string): void { - const pjson = require(path.resolve('package.json')); - if (id) { - return this.installExt(id); - } - const vsixPath = path.resolve(vsix ? vsix : `${pjson.name}-${pjson.version}.vsix`); - this.installExt(vsixPath); - } - - /** - * Install extension dependencies from marketplace - */ - installDependencies(): void { - const pjson = require(path.resolve('package.json')); - const deps = pjson.extensionDependencies; - if (!deps) { - return; - } - for (const id of deps as string[]) { - this.installExt(id); - } - } - - private getCliInitCommand(): string { - const cli = `${this.cliEnv} "${this.executablePath}" "${this.cliPath}"`; - if(this.getExistingCodeVersion() >= '1.86.0') { - return cli; - } else { - return `${cli} --ms-enable-electron-run-as-node`; - } - } - - private installExt(pathOrID: string): void { - let command = `${this.getCliInitCommand()} --force --install-extension "${pathOrID}"`; - if (this.extensionsFolder) { - command += ` --extensions-dir=${this.extensionsFolder}`; - } - childProcess.execSync(command, { stdio: 'inherit' }); - } - - /** - * Open files/folders in running vscode - * @param paths vararg paths to files or folders to open - */ - open(...paths: string[]): void { - const segments = paths.map(f => `"${f}"`).join(' '); - const command = `${this.getCliInitCommand()} -r ${segments} --user-data-dir="${path.join(this.downloadFolder, 'settings')}"`; - childProcess.execSync(command); - } - - /** - * Download a vsix file - * @param vsixURL URL of the vsix file - */ - async downloadExtension(vsixURL: string): Promise { - fs.mkdirpSync(this.downloadFolder); - const fileName = path.basename(vsixURL); - const target = path.join(this.downloadFolder, fileName); - if (!fileName.endsWith('.vsix')) { - throw new Error('The URL does not point to a vsix file'); - } - - console.log(`Downloading ${fileName}`); - await Download.getFile(vsixURL, target); - console.log('Success!'); - return target; - } - - /** - * Package extension into a vsix file - * @param useYarn false to use npm as packaging system, true to use yarn instead - */ - async packageExtension(useYarn?: boolean): Promise { - await vsce.createVSIX({ - useYarn - }); - } - - /** - * Uninstall the tested extension from the test instance of VS Code - * - * @param cleanup remove the extension's directory as well. - */ - uninstallExtension(cleanup?: boolean): void { - const pjson = require(path.resolve('package.json')); - const extension = `${pjson.publisher}.${pjson.name}`; - - if (cleanup) { - let command = `${this.getCliInitCommand()} --uninstall-extension "${extension}"`; - if (this.extensionsFolder) { - command += ` --extensions-dir=${this.extensionsFolder}`; - } - childProcess.execSync(command, { stdio: 'inherit' }); - } - } - - /** - * Run tests in your test environment using mocha - * - * @param testFilesPattern glob pattern of test files to run - * @param runOptions additional options for customizing the test run - * - * @return The exit code of the mocha process - */ - async runTests(testFilesPattern: string[], runOptions: RunOptions = DEFAULT_RUN_OPTIONS): Promise { - if (!runOptions.offline) { - await this.checkCodeVersion(runOptions.vscodeVersion ?? DEFAULT_RUN_OPTIONS.vscodeVersion); - } else { - this.availableVersions = [this.getExistingCodeVersion()]; - } - const literalVersion = runOptions.vscodeVersion === undefined || runOptions.vscodeVersion === 'latest' ? this.availableVersions[0] : runOptions.vscodeVersion; - - // add chromedriver to process' path - const finalEnv: NodeJS.ProcessEnv = {}; - Object.assign(finalEnv, process.env); - const key = 'PATH'; - finalEnv[key] = [this.downloadFolder, process.env[key]].join(path.delimiter); - - process.env = finalEnv; - process.env.TEST_RESOURCES = this.downloadFolder; - process.env.EXTENSIONS_FOLDER = this.extensionsFolder; - process.env.EXTENSION_DEV_PATH = this.coverage ? process.cwd() : undefined; - const runner = new VSRunner(this.executablePath, literalVersion, this.parseSettings(runOptions.settings ?? DEFAULT_RUN_OPTIONS.settings), runOptions.cleanup, this.releaseType, runOptions.config); - return await runner.runTests(testFilesPattern, this, runOptions.logLevel, runOptions.resources); - } - - /** - * Finds the version of chromium used for given VS Code version. - * Works only for versions 1.30+, older versions need to be checked manually - * - * @param codeVersion version of VS Code, default latest - * @param quality release stream, default stable - */ - async getChromiumVersion(codeVersion: string = 'latest'): Promise { - await this.checkCodeVersion(codeVersion); - const literalVersion = codeVersion === 'latest' ? this.availableVersions[0] : codeVersion; - let revision = literalVersion; - if (literalVersion.endsWith('-insider')) { - if (codeVersion === 'latest') { - revision = 'main'; - } else { - revision = literalVersion.substring(0, literalVersion.indexOf('-insider')); - revision = `release/${revision.substring(0, revision.lastIndexOf('.'))}`; - } - } else { - revision = `release/${revision.substring(0, revision.lastIndexOf('.'))}`; - } - - const fileName = 'manifest.json'; - const url = `https://raw.githubusercontent.com/Microsoft/vscode/${revision}/cgmanifest.json`; - await Download.getFile(url, path.join(this.downloadFolder, fileName)); - - try { - const manifest = require(path.join(this.downloadFolder, fileName)); - return manifest.registrations[0].version; - } catch (err) { - let version = ''; - if (await fs.pathExists(this.codeFolder)) { - version = this.getChromiumVersionOffline(); - } - if (version === '') { - throw new Error('Unable to determine required ChromeDriver version'); - } - return version; - } - } - - /** - * Check if VS Code exists in local cache along with an appropriate version of chromedriver - * without internet connection - */ - checkOfflineRequirements(): string { - try { - this.getExistingCodeVersion(); - } catch (err) { - console.log('ERROR: Cannot find a local copy of VS Code in offline mode, exiting.'); - throw (err); - } - return this.getChromiumVersionOffline(); - } - - /** - * Attempt to get chromium version from a downloaded copy of vs code - */ - getChromiumVersionOffline(): string { - const manifestPath = path.join(this.codeFolder, 'resources', 'app', 'ThirdPartyNotices.txt'); - const text = (fs.readFileSync(manifestPath)).toString(); - const matches = text.match(/chromium\sversion\s(.*)\s\(/); - if (matches && matches[1]) { - return matches[1]; - } - return ''; - } - - /** - * Get the root folder of VS Code instance - */ - getCodeFolder(): string { - return this.codeFolder; - } - - /** - * Getter for coverage enablement option - */ - get coverageEnabled() { - return this.coverage; - } - - /** - * Check if given version is available in the given stream - */ - private async checkCodeVersion(vscodeVersion: string): Promise { - if (this.availableVersions.length < 1) { - this.availableVersions = await this.getVSCodeVersions(); - } - if (vscodeVersion !== 'latest' && this.availableVersions.indexOf(vscodeVersion) < 0) { - throw new Error(`Version ${vscodeVersion} is not available in ${this.releaseType} stream`); - } - } - - /** - * Check what VS Code version is present in the testing folder - */ - private getExistingCodeVersion(): string { - const command = `${this.cliEnv} "${this.executablePath}" "${this.cliPath}"`; - let out: Buffer; - try { - out = childProcess.execSync(`${command} -v`); - } catch (error) { - out = childProcess.execSync(`${command} --ms-enable-electron-run-as-node -v`); - } - return out.toString().split('\n')[0]; - } - - /** - * Construct the platform string based on OS - */ - private getPlatform(): string { - let platform: string = process.platform; - const arch = process.arch; - this.cliEnv = 'ELECTRON_RUN_AS_NODE=1'; - - if (platform === 'linux') { - platform += arch === 'ia32' ? '-ia32' : `-${arch}`; - } else if (platform === 'win32') { - platform += arch === 'x64' ? `-${arch}` : ''; - platform += '-archive'; - this.cliEnv = `set ${this.cliEnv} &&`; - } else if (platform === 'darwin') { - platform += '-universal'; - } - - return platform; - } - - /** - * Setup paths specific to used OS - */ - private findExecutables(): void { - this.cliPath = path.join(this.codeFolder, 'resources', 'app', 'out', 'cli.js'); - switch (process.platform) { - case 'darwin': - this.executablePath = path.join(this.codeFolder, 'Contents', 'MacOS', 'Electron'); - this.cliPath = path.join(this.codeFolder, 'Contents', 'Resources', 'app', 'out', 'cli.js'); - break; - case 'win32': - this.executablePath = path.join(this.codeFolder, 'Code.exe'); - if (this.releaseType === ReleaseQuality.Insider) { - this.executablePath = path.join(this.codeFolder, 'Code - Insiders.exe'); - } - break; - case 'linux': - this.executablePath = path.join(this.codeFolder, 'code'); - if (this.releaseType === ReleaseQuality.Insider) { - this.executablePath = path.join(this.codeFolder, 'code-insiders'); - } - break; - } - } - - /** - * Parse JSON from a file - * @param path path to json file - */ - private parseSettings(path: string): object { - if (!path) { - return {}; - } - let text = ''; - try { - text = fs.readFileSync(path).toString(); - } catch (err) { - throw new Error(`Unable to read settings from ${path}:\n ${err}`); - } - try { - return JSON.parse(text); - } catch (err) { - throw new Error(`Error parsing the settings file from ${path}:\n ${err}`); - } - } + private codeFolder: string; + private downloadPlatform: string; + private downloadFolder: string; + private releaseType: ReleaseQuality; + private executablePath!: string; + private cliPath!: string; + private cliEnv!: string; + private availableVersions: string[]; + private extensionsFolder: string | undefined; + private coverage: boolean | undefined; + + /** + * Create an instance of code handler + * @param folder Path to folder where all the artifacts will be stored. + * @param extensionsFolder Path to use as extensions directory by VS Code + */ + constructor(folder: string = DEFAULT_STORAGE_FOLDER, type: ReleaseQuality = ReleaseQuality.Stable, extensionsFolder?: string, coverage?: boolean) { + this.availableVersions = []; + this.downloadPlatform = this.getPlatform(); + this.downloadFolder = path.resolve(folder); + this.extensionsFolder = extensionsFolder ? path.resolve(extensionsFolder) : undefined; + this.coverage = coverage; + this.releaseType = type; + + if (type === ReleaseQuality.Stable) { + this.codeFolder = path.join(this.downloadFolder, process.platform === 'darwin' ? 'Visual Studio Code.app' : `VSCode-${this.downloadPlatform}`); + } else { + this.codeFolder = path.join( + this.downloadFolder, + process.platform === 'darwin' ? 'Visual Studio Code - Insiders.app' : `VSCode-${this.downloadPlatform}-insider`, + ); + } + this.findExecutables(); + } + + /** + * Get all versions for the given release stream + */ + async getVSCodeVersions(): Promise { + const apiUrl = `https://update.code.visualstudio.com/api/releases/${this.releaseType}`; + const json = await Download.getText(apiUrl); + return json as unknown as string[]; + } + + /** + * Download and unpack VS Code for testing purposes + * + * @param version VS Code version to get, default latest + */ + async downloadVSCode(version: string = 'latest'): Promise { + await this.checkCodeVersion(version); + + const literalVersion = version === 'latest' ? this.availableVersions[0] : version; + if (this.releaseType === ReleaseQuality.Stable && literalVersion !== this.availableVersions[0]) { + console.log( + '\x1b[33m%s\x1b[0m', + `\n\nWARNING: You are using the outdated VS Code version '${literalVersion}'. The latest stable version is '${this.availableVersions[0]}'.\n\n`, + ); + } + + console.log(`Downloading VS Code: ${literalVersion} / ${this.releaseType}`); + if (!fs.existsSync(this.executablePath) || this.getExistingCodeVersion() !== literalVersion) { + fs.mkdirpSync(this.downloadFolder); + + const url = ['https://update.code.visualstudio.com', version, this.downloadPlatform, this.releaseType].join('/'); + const isTarGz = this.downloadPlatform.indexOf('linux') > -1; + const fileName = `${path.basename(url)}.${isTarGz ? 'tar.gz' : 'zip'}`; + + console.log(`Downloading VS Code from: ${url}`); + await Download.getFile(url, path.join(this.downloadFolder, fileName), true); + console.log(`Downloaded VS Code into ${path.join(this.downloadFolder, fileName)}`); + + console.log(`Unpacking VS Code into ${this.downloadFolder}`); + const target = await fs.mkdtemp('vscode'); + await Unpack.unpack(path.join(this.downloadFolder, fileName), target); + let rootDir = target; + const files = await fs.readdir(target); + + if (files.length === 1) { + rootDir = path.join(target, files[0]); + } + await fs.move(rootDir, this.codeFolder, { overwrite: true }); + await fs.remove(target); + + console.log('Success!'); + } else { + console.log('VS Code exists in local cache, skipping download'); + } + } + + /** + * Install your extension into the test instance of VS Code + */ + installExtension(vsix?: string, id?: string): void { + const pjson = require(path.resolve('package.json')); + if (id) { + return this.installExt(id); + } + const vsixPath = path.resolve(vsix ? vsix : `${pjson.name}-${pjson.version}.vsix`); + this.installExt(vsixPath); + } + + /** + * Install extension dependencies from marketplace + */ + installDependencies(): void { + const pjson = require(path.resolve('package.json')); + const deps = pjson.extensionDependencies; + if (!deps) { + return; + } + for (const id of deps as string[]) { + this.installExt(id); + } + } + + private getCliInitCommand(): string { + const cli = `${this.cliEnv} "${this.executablePath}" "${this.cliPath}"`; + if (this.getExistingCodeVersion() >= '1.86.0') { + return cli; + } else { + return `${cli} --ms-enable-electron-run-as-node`; + } + } + + private installExt(pathOrID: string): void { + let command = `${this.getCliInitCommand()} --force --install-extension "${pathOrID}"`; + if (this.extensionsFolder) { + command += ` --extensions-dir=${this.extensionsFolder}`; + } + childProcess.execSync(command, { stdio: 'inherit' }); + } + + /** + * Open files/folders in running vscode + * @param paths vararg paths to files or folders to open + */ + open(...paths: string[]): void { + const segments = paths.map((f) => `"${f}"`).join(' '); + const command = `${this.getCliInitCommand()} -r ${segments} --user-data-dir="${path.join(this.downloadFolder, 'settings')}"`; + childProcess.execSync(command); + } + + /** + * Download a vsix file + * @param vsixURL URL of the vsix file + */ + async downloadExtension(vsixURL: string): Promise { + fs.mkdirpSync(this.downloadFolder); + const fileName = path.basename(vsixURL); + const target = path.join(this.downloadFolder, fileName); + if (!fileName.endsWith('.vsix')) { + throw new Error('The URL does not point to a vsix file'); + } + + console.log(`Downloading ${fileName}`); + await Download.getFile(vsixURL, target); + console.log('Success!'); + return target; + } + + /** + * Package extension into a vsix file + * @param useYarn false to use npm as packaging system, true to use yarn instead + */ + async packageExtension(useYarn?: boolean): Promise { + await vsce.createVSIX({ + useYarn, + }); + } + + /** + * Uninstall the tested extension from the test instance of VS Code + * + * @param cleanup remove the extension's directory as well. + */ + uninstallExtension(cleanup?: boolean): void { + const pjson = require(path.resolve('package.json')); + const extension = `${pjson.publisher}.${pjson.name}`; + + if (cleanup) { + let command = `${this.getCliInitCommand()} --uninstall-extension "${extension}"`; + if (this.extensionsFolder) { + command += ` --extensions-dir=${this.extensionsFolder}`; + } + childProcess.execSync(command, { stdio: 'inherit' }); + } + } + + /** + * Run tests in your test environment using mocha + * + * @param testFilesPattern glob pattern of test files to run + * @param runOptions additional options for customizing the test run + * + * @return The exit code of the mocha process + */ + async runTests(testFilesPattern: string[], runOptions: RunOptions = DEFAULT_RUN_OPTIONS): Promise { + if (!runOptions.offline) { + await this.checkCodeVersion(runOptions.vscodeVersion ?? DEFAULT_RUN_OPTIONS.vscodeVersion); + } else { + this.availableVersions = [this.getExistingCodeVersion()]; + } + const literalVersion = + runOptions.vscodeVersion === undefined || runOptions.vscodeVersion === 'latest' ? this.availableVersions[0] : runOptions.vscodeVersion; + + // add chromedriver to process' path + const finalEnv: NodeJS.ProcessEnv = {}; + Object.assign(finalEnv, process.env); + const key = 'PATH'; + finalEnv[key] = [this.downloadFolder, process.env[key]].join(path.delimiter); + + process.env = finalEnv; + process.env.TEST_RESOURCES = this.downloadFolder; + process.env.EXTENSIONS_FOLDER = this.extensionsFolder; + process.env.EXTENSION_DEV_PATH = this.coverage ? process.cwd() : undefined; + const runner = new VSRunner( + this.executablePath, + literalVersion, + this.parseSettings(runOptions.settings ?? DEFAULT_RUN_OPTIONS.settings), + runOptions.cleanup, + this.releaseType, + runOptions.config, + ); + return await runner.runTests(testFilesPattern, this, runOptions.logLevel, runOptions.resources); + } + + /** + * Finds the version of chromium used for given VS Code version. + * Works only for versions 1.30+, older versions need to be checked manually + * + * @param codeVersion version of VS Code, default latest + * @param quality release stream, default stable + */ + async getChromiumVersion(codeVersion: string = 'latest'): Promise { + await this.checkCodeVersion(codeVersion); + const literalVersion = codeVersion === 'latest' ? this.availableVersions[0] : codeVersion; + let revision = literalVersion; + if (literalVersion.endsWith('-insider')) { + if (codeVersion === 'latest') { + revision = 'main'; + } else { + revision = literalVersion.substring(0, literalVersion.indexOf('-insider')); + revision = `release/${revision.substring(0, revision.lastIndexOf('.'))}`; + } + } else { + revision = `release/${revision.substring(0, revision.lastIndexOf('.'))}`; + } + + const fileName = 'manifest.json'; + const url = `https://raw.githubusercontent.com/Microsoft/vscode/${revision}/cgmanifest.json`; + await Download.getFile(url, path.join(this.downloadFolder, fileName)); + + try { + const manifest = require(path.join(this.downloadFolder, fileName)); + return manifest.registrations[0].version; + } catch (err) { + let version = ''; + if (await fs.pathExists(this.codeFolder)) { + version = this.getChromiumVersionOffline(); + } + if (version === '') { + throw new Error('Unable to determine required ChromeDriver version'); + } + return version; + } + } + + /** + * Check if VS Code exists in local cache along with an appropriate version of chromedriver + * without internet connection + */ + checkOfflineRequirements(): string { + try { + this.getExistingCodeVersion(); + } catch (err) { + console.log('ERROR: Cannot find a local copy of VS Code in offline mode, exiting.'); + throw err; + } + return this.getChromiumVersionOffline(); + } + + /** + * Attempt to get chromium version from a downloaded copy of vs code + */ + getChromiumVersionOffline(): string { + const manifestPath = path.join(this.codeFolder, 'resources', 'app', 'ThirdPartyNotices.txt'); + const text = fs.readFileSync(manifestPath).toString(); + const matches = text.match(/chromium\sversion\s(.*)\s\(/); + if (matches && matches[1]) { + return matches[1]; + } + return ''; + } + + /** + * Get the root folder of VS Code instance + */ + getCodeFolder(): string { + return this.codeFolder; + } + + /** + * Getter for coverage enablement option + */ + get coverageEnabled() { + return this.coverage; + } + + /** + * Check if given version is available in the given stream + */ + private async checkCodeVersion(vscodeVersion: string): Promise { + if (this.availableVersions.length < 1) { + this.availableVersions = await this.getVSCodeVersions(); + } + if (vscodeVersion !== 'latest' && this.availableVersions.indexOf(vscodeVersion) < 0) { + throw new Error(`Version ${vscodeVersion} is not available in ${this.releaseType} stream`); + } + } + + /** + * Check what VS Code version is present in the testing folder + */ + private getExistingCodeVersion(): string { + const command = `${this.cliEnv} "${this.executablePath}" "${this.cliPath}"`; + let out: Buffer; + try { + out = childProcess.execSync(`${command} -v`); + } catch (error) { + out = childProcess.execSync(`${command} --ms-enable-electron-run-as-node -v`); + } + return out.toString().split('\n')[0]; + } + + /** + * Construct the platform string based on OS + */ + private getPlatform(): string { + let platform: string = process.platform; + const arch = process.arch; + this.cliEnv = 'ELECTRON_RUN_AS_NODE=1'; + + if (platform === 'linux') { + platform += arch === 'ia32' ? '-ia32' : `-${arch}`; + } else if (platform === 'win32') { + platform += arch === 'x64' ? `-${arch}` : ''; + platform += '-archive'; + this.cliEnv = `set ${this.cliEnv} &&`; + } else if (platform === 'darwin') { + platform += '-universal'; + } + + return platform; + } + + /** + * Setup paths specific to used OS + */ + private findExecutables(): void { + this.cliPath = path.join(this.codeFolder, 'resources', 'app', 'out', 'cli.js'); + switch (process.platform) { + case 'darwin': + this.executablePath = path.join(this.codeFolder, 'Contents', 'MacOS', 'Electron'); + this.cliPath = path.join(this.codeFolder, 'Contents', 'Resources', 'app', 'out', 'cli.js'); + break; + case 'win32': + this.executablePath = path.join(this.codeFolder, 'Code.exe'); + if (this.releaseType === ReleaseQuality.Insider) { + this.executablePath = path.join(this.codeFolder, 'Code - Insiders.exe'); + } + break; + case 'linux': + this.executablePath = path.join(this.codeFolder, 'code'); + if (this.releaseType === ReleaseQuality.Insider) { + this.executablePath = path.join(this.codeFolder, 'code-insiders'); + } + break; + } + } + + /** + * Parse JSON from a file + * @param path path to json file + */ + private parseSettings(path: string): object { + if (!path) { + return {}; + } + let text = ''; + try { + text = fs.readFileSync(path).toString(); + } catch (err) { + throw new Error(`Unable to read settings from ${path}:\n ${err}`); + } + try { + return JSON.parse(text); + } catch (err) { + throw new Error(`Error parsing the settings file from ${path}:\n ${err}`); + } + } } diff --git a/packages/extester/src/util/coverage.ts b/packages/extester/src/util/coverage.ts index 63f980761..d717d04cb 100644 --- a/packages/extester/src/util/coverage.ts +++ b/packages/extester/src/util/coverage.ts @@ -18,94 +18,85 @@ import { join } from 'path'; * a variety of reporters. */ export class Coverage { - public readonly targetDir = join(tmpdir(), `vsc-coverage-${randomUUID()}`); - private userOptions: any; + public readonly targetDir = join(tmpdir(), `vsc-coverage-${randomUUID()}`); + private userOptions: any; - constructor() { - mkdirSync(this.targetDir, { recursive: true }); - } + constructor() { + mkdirSync(this.targetDir, { recursive: true }); + } - public async loadConfig() { - // Read nyc/c8 JSON configuration file for reading user-defined coverage report options. - const mod = await import("find-up"); - const config = mod.findUpSync(['.c8rc', '.c8rc.json', '.nycrc', '.nycrc.json']); - if (config) { - try { - const json = readFileSync(config).toString(); - this.userOptions = JSON.parse(json); - } catch (err) { - console.error(`An error was found in reading coverage configuration from ${config}`); - throw err; - } - } - } + public async loadConfig() { + // Read nyc/c8 JSON configuration file for reading user-defined coverage report options. + const mod = await import('find-up'); + const config = mod.findUpSync(['.c8rc', '.c8rc.json', '.nycrc', '.nycrc.json']); + if (config) { + try { + const json = readFileSync(config).toString(); + this.userOptions = JSON.parse(json); + } catch (err) { + console.error(`An error was found in reading coverage configuration from ${config}`); + throw err; + } + } + } - public async write() { - const reportOptions: any = { - "reporter": ["text", "html"], - "all": false, - "excludeNodeModules": true, - "include": [], - "exclude": [ - "coverage/**", - "packages/*/test{,s}/**", - "**/*.d.ts", - "test{,s}/**", - "test{,-*}.{js,cjs,mjs,ts,tsx,jsx}", - "**/*{.,-}test.{js,cjs,mjs,ts,tsx,jsx}", - "**/__tests__/**", - "**/{ava,babel,nyc}.config.{js,cjs,mjs}", - "**/jest.config.{js,cjs,mjs,ts}", - "**/{karma,rollup,webpack}.config.js", - "**/.{eslint,mocha}rc.{js,cjs}" - ], - "extension": [ - ".js", - ".cjs", - ".mjs", - ".ts", - ".tsx", - ".jsx" - ], - "excludeAfterRemap": false, - "skipFull": false, - "tempDirectory": this.targetDir, - "resolve": "", - "omitRelative": true, - "allowExternal": false, - }; + public async write() { + const reportOptions: any = { + reporter: ['text', 'html'], + all: false, + excludeNodeModules: true, + include: [], + exclude: [ + 'coverage/**', + 'packages/*/test{,s}/**', + '**/*.d.ts', + 'test{,s}/**', + 'test{,-*}.{js,cjs,mjs,ts,tsx,jsx}', + '**/*{.,-}test.{js,cjs,mjs,ts,tsx,jsx}', + '**/__tests__/**', + '**/{ava,babel,nyc}.config.{js,cjs,mjs}', + '**/jest.config.{js,cjs,mjs,ts}', + '**/{karma,rollup,webpack}.config.js', + '**/.{eslint,mocha}rc.{js,cjs}', + ], + extension: ['.js', '.cjs', '.mjs', '.ts', '.tsx', '.jsx'], + excludeAfterRemap: false, + skipFull: false, + tempDirectory: this.targetDir, + resolve: '', + omitRelative: true, + allowExternal: false, + }; - if (this.userOptions) { - Object.assign(reportOptions, this.userOptions); - // These two options require special treatments. - ["report-dir", "reports-dir"].forEach(key => { - if (this.userOptions[key]) { - reportOptions["reportsDirectory"] = this.userOptions[key]; - } - }); - } + if (this.userOptions) { + Object.assign(reportOptions, this.userOptions); + // These two options require special treatments. + ['report-dir', 'reports-dir'].forEach((key) => { + if (this.userOptions[key]) { + reportOptions['reportsDirectory'] = this.userOptions[key]; + } + }); + } - try { - const report = new Report(reportOptions); + try { + const report = new Report(reportOptions); - // A hacky fix due to an outstanding bug in Istanbul's exclusion testing - // code: its subdirectory checks are case-sensitive on Windows, but file - // URIs might have mixed casing. - // - // Setting `relativePath: false` on the exclude bypasses this code path. - // - // https://github.com/istanbuljs/test-exclude/issues/43 - // https://github.com/istanbuljs/test-exclude/blob/a5b1d07584109f5f553ccef97de64c6cbfca4764/index.js#L91 - (report as any).exclude.relativePath = false; + // A hacky fix due to an outstanding bug in Istanbul's exclusion testing + // code: its subdirectory checks are case-sensitive on Windows, but file + // URIs might have mixed casing. + // + // Setting `relativePath: false` on the exclude bypasses this code path. + // + // https://github.com/istanbuljs/test-exclude/issues/43 + // https://github.com/istanbuljs/test-exclude/blob/a5b1d07584109f5f553ccef97de64c6cbfca4764/index.js#L91 + (report as any).exclude.relativePath = false; - await report.run(); - } catch (e) { - // throw new CliExpectedError( - throw new Error( - `Coverage report generated failed, please file an issue with original reports located in ${this.targetDir}:\n\n${e}`, - ); - } + await report.run(); + } catch (e) { + // throw new CliExpectedError( + throw new Error(`Coverage report generated failed, please file an issue with original reports located in ${this.targetDir}:\n\n${e}`); + } - await fs.rm(this.targetDir, { recursive: true, force: true }); - } + await fs.rm(this.targetDir, { recursive: true, force: true }); + } } diff --git a/packages/extester/src/util/download.ts b/packages/extester/src/util/download.ts index d1a81a345..269ce8511 100644 --- a/packages/extester/src/util/download.ts +++ b/packages/extester/src/util/download.ts @@ -20,47 +20,50 @@ import { promisify } from 'util'; import stream from 'stream'; import { HttpProxyAgent, HttpsProxyAgent } from 'hpagent'; -const httpProxyAgent = !process.env.HTTP_PROXY ? undefined : new HttpProxyAgent({ - proxy: process.env.HTTP_PROXY -}); +const httpProxyAgent = !process.env.HTTP_PROXY + ? undefined + : new HttpProxyAgent({ + proxy: process.env.HTTP_PROXY, + }); -const httpsProxyAgent = !process.env.HTTPS_PROXY ? undefined : new HttpsProxyAgent({ - proxy: process.env.HTTPS_PROXY -}); +const httpsProxyAgent = !process.env.HTTPS_PROXY + ? undefined + : new HttpsProxyAgent({ + proxy: process.env.HTTPS_PROXY, + }); const options = { - headers: { - 'user-agent': 'nodejs' - }, - agent: { - http: httpProxyAgent, - https: httpsProxyAgent - } + headers: { + 'user-agent': 'nodejs', + }, + agent: { + http: httpProxyAgent, + https: httpsProxyAgent, + }, }; export class Download { + static async getText(uri: string): Promise { + const got = (await import('got')).default; + const body = await got(uri, options).text(); + return JSON.parse(body as string); + } - static async getText(uri: string): Promise { - const got = (await import('got')).default; - const body = await got(uri, options).text(); - return JSON.parse(body as string); - } + static async getFile(uri: string, destination: string, progress = false): Promise { + let lastTick = 0; + const got = (await import('got')).default; + const dlStream = got.stream(uri, options); + if (progress) { + dlStream.on('downloadProgress', ({ transferred, total, percent }) => { + const currentTime = Date.now(); + if (total > 0 && (lastTick === 0 || transferred === total || currentTime - lastTick >= 2000)) { + console.log(`progress: ${transferred}/${total} (${Math.floor(100 * percent)}%)`); + lastTick = currentTime; + } + }); + } + const writeStream = fs.createWriteStream(destination); - static async getFile(uri: string, destination: string, progress = false): Promise { - let lastTick = 0; - const got = (await import('got')).default; - const dlStream = got.stream(uri, options); - if (progress) { - dlStream.on('downloadProgress', ({ transferred, total, percent }) => { - const currentTime = Date.now(); - if (total > 0 && (lastTick === 0 || transferred === total || currentTime - lastTick >= 2000)) { - console.log(`progress: ${transferred}/${total} (${Math.floor(100 * percent)}%)`); - lastTick = currentTime; - } - }); - } - const writeStream = fs.createWriteStream(destination); - - return await promisify(stream.pipeline)(dlStream, writeStream); - } -} \ No newline at end of file + return await promisify(stream.pipeline)(dlStream, writeStream); + } +} diff --git a/packages/extester/src/util/driverUtil.ts b/packages/extester/src/util/driverUtil.ts index 9a34fba2f..7d6497da6 100644 --- a/packages/extester/src/util/driverUtil.ts +++ b/packages/extester/src/util/driverUtil.ts @@ -26,176 +26,175 @@ import { DEFAULT_STORAGE_FOLDER } from '../extester'; * Handles version checks and download of ChromeDriver */ export class DriverUtil { - private downloadFolder: string; - - /** - * Create an instance of chrome driver handler - * @param folder path to a folder to store all artifacts - */ - constructor(folder: string = DEFAULT_STORAGE_FOLDER) { - this.downloadFolder = path.resolve(folder); - } - - /** - * Find a matching ChromeDriver version for a given Chromium version and download it. - * @param chromiumVersion version of Chromium to match the ChromeDriver against - */ - async downloadChromeDriverForChromiumVersion(chromiumVersion: string): Promise { - const version = await this.getChromeDriverVersion(chromiumVersion); - return await this.downloadChromeDriver(version); - } - - /** - * Download a given version ChromeDriver - * @param version version to download - */ - async downloadChromeDriver(version: string): Promise { - const url = this.getChromeDriverURL(version); - const driverBinary = this.getChromeDriverBinaryPath(version); - if (fs.existsSync(driverBinary)) { - let localVersion = ''; - try { - localVersion = await this.getLocalDriverVersion(version); - } catch (err) { - // ignore and download - } - if (localVersion.startsWith(version)) { - console.log(`ChromeDriver ${version} exists in local cache, skipping download`); - return ''; - } - } - fs.mkdirpSync(this.downloadFolder); - - const fileName = path.join(this.downloadFolder, path.basename(url)); - console.log(`Downloading ChromeDriver ${version} from: ${url}`); - await Download.getFile(url, fileName, true); - - console.log(`Unpacking ChromeDriver ${version} into ${this.downloadFolder}`); - await Unpack.unpack(fileName, this.downloadFolder); - if (process.platform !== 'win32') { - fs.chmodSync(driverBinary, 0o755); - } - console.log('Success!'); - return driverBinary; - } - - private getChromeDriverBinaryPath(version: string): string { - const majorVersion = this.getMajorVersion(version); - const binary = process.platform === 'win32' ? 'chromedriver.exe' : 'chromedriver'; - let driverBinaryPath = path.join(this.downloadFolder, binary); - if (+majorVersion > 114) { - driverBinaryPath = path.join(this.downloadFolder, `chromedriver-${DriverUtil.getChromeDriverPlatform()}`, binary); - } - return driverBinaryPath; - } - - static getChromeDriverPlatform(): string | undefined { - switch (process.platform) { - case 'darwin': - return `mac-${process.arch}`; - case 'win32': - return process.arch === 'x64' ? 'win64' : 'win32'; - case 'linux': - return 'linux64'; - default: - break; - } - return undefined; - } - - private static getChromeDriverPlatformOLD(): string | undefined { - switch (process.platform) { - case 'darwin': - return process.arch === 'arm64' ? 'mac_arm64' : 'mac64'; - case 'win32': - return 'win32'; - case 'linux': - return 'linux64'; - default: - break; - } - return undefined; - } - - private getChromeDriverURL(version: string): string { - const majorVersion = this.getMajorVersion(version); - let driverPlatform = DriverUtil.getChromeDriverPlatformOLD(); - let url = `https://chromedriver.storage.googleapis.com/${version}/chromedriver_${driverPlatform}.zip`; - if (+majorVersion > 114) { - driverPlatform = DriverUtil.getChromeDriverPlatform(); - url = `https://edgedl.me.gvt1.com/edgedl/chrome/chrome-for-testing/${version}/${driverPlatform}/chromedriver-${driverPlatform}.zip`; - } - return url; - } - - async checkDriverVersionOffline(version: string): Promise { - try { - return await this.getLocalDriverVersion(version); - } catch (err) { - console.log('ERROR: Cannot find a copy of ChromeDriver in local cache in offline mode, exiting.'); - throw err; - } - } - - /** - * Check local chrome driver version - */ - private async getLocalDriverVersion(version: string): Promise { - const command = `${this.getChromeDriverBinaryPath(version)} -v`; - return new Promise((resolve, reject) => { - childProcess.exec(command, (err, stdout) => { - if (err) { - return reject(new Error(err.message)); - } - resolve(stdout.split(' ')[1]); - }); - }); - - } - - /** - * Find a matching version of ChromeDriver for a given Chromium version - * @param chromiumVersion Chromium version to check against - */ - private async getChromeDriverVersion(chromiumVersion: string): Promise { - const majorVersion = this.getMajorVersion(chromiumVersion); - - // chrome driver versioning has changed for chrome 70+ - if (+majorVersion < 70) { - if (this.chromiumVersionMap[+majorVersion]) { - return this.chromiumVersionMap[+majorVersion]; - } else { - throw new Error(`Chromium version ${chromiumVersion} not supported`); - } - } - let url = `https://chromedriver.storage.googleapis.com/LATEST_RELEASE_${majorVersion}`; - if (+majorVersion > 114) { - url = `https://googlechromelabs.github.io/chrome-for-testing/LATEST_RELEASE_${majorVersion}`; - } - const fileName = 'driverVersion'; - await Download.getFile(url, path.join(this.downloadFolder, fileName)); - return fs.readFileSync(path.join(this.downloadFolder, fileName)).toString(); - } - - private getMajorVersion(version: string): string { - return version.split('.')[0]; - } - - // older chromedriver versions do not match chrome versions - private readonly chromiumVersionMap: VersionMap = { - 69: '2.38', - 68: '2.38', - 67: '2.38', - 66: '2.38', - 65: '2.37', - 64: '2.36', - 63: '2.35', - 62: '2.34', - 61: '2.33', - 60: '2.32' - }; + private downloadFolder: string; + + /** + * Create an instance of chrome driver handler + * @param folder path to a folder to store all artifacts + */ + constructor(folder: string = DEFAULT_STORAGE_FOLDER) { + this.downloadFolder = path.resolve(folder); + } + + /** + * Find a matching ChromeDriver version for a given Chromium version and download it. + * @param chromiumVersion version of Chromium to match the ChromeDriver against + */ + async downloadChromeDriverForChromiumVersion(chromiumVersion: string): Promise { + const version = await this.getChromeDriverVersion(chromiumVersion); + return await this.downloadChromeDriver(version); + } + + /** + * Download a given version ChromeDriver + * @param version version to download + */ + async downloadChromeDriver(version: string): Promise { + const url = this.getChromeDriverURL(version); + const driverBinary = this.getChromeDriverBinaryPath(version); + if (fs.existsSync(driverBinary)) { + let localVersion = ''; + try { + localVersion = await this.getLocalDriverVersion(version); + } catch (err) { + // ignore and download + } + if (localVersion.startsWith(version)) { + console.log(`ChromeDriver ${version} exists in local cache, skipping download`); + return ''; + } + } + fs.mkdirpSync(this.downloadFolder); + + const fileName = path.join(this.downloadFolder, path.basename(url)); + console.log(`Downloading ChromeDriver ${version} from: ${url}`); + await Download.getFile(url, fileName, true); + + console.log(`Unpacking ChromeDriver ${version} into ${this.downloadFolder}`); + await Unpack.unpack(fileName, this.downloadFolder); + if (process.platform !== 'win32') { + fs.chmodSync(driverBinary, 0o755); + } + console.log('Success!'); + return driverBinary; + } + + private getChromeDriverBinaryPath(version: string): string { + const majorVersion = this.getMajorVersion(version); + const binary = process.platform === 'win32' ? 'chromedriver.exe' : 'chromedriver'; + let driverBinaryPath = path.join(this.downloadFolder, binary); + if (+majorVersion > 114) { + driverBinaryPath = path.join(this.downloadFolder, `chromedriver-${DriverUtil.getChromeDriverPlatform()}`, binary); + } + return driverBinaryPath; + } + + static getChromeDriverPlatform(): string | undefined { + switch (process.platform) { + case 'darwin': + return `mac-${process.arch}`; + case 'win32': + return process.arch === 'x64' ? 'win64' : 'win32'; + case 'linux': + return 'linux64'; + default: + break; + } + return undefined; + } + + private static getChromeDriverPlatformOLD(): string | undefined { + switch (process.platform) { + case 'darwin': + return process.arch === 'arm64' ? 'mac_arm64' : 'mac64'; + case 'win32': + return 'win32'; + case 'linux': + return 'linux64'; + default: + break; + } + return undefined; + } + + private getChromeDriverURL(version: string): string { + const majorVersion = this.getMajorVersion(version); + let driverPlatform = DriverUtil.getChromeDriverPlatformOLD(); + let url = `https://chromedriver.storage.googleapis.com/${version}/chromedriver_${driverPlatform}.zip`; + if (+majorVersion > 114) { + driverPlatform = DriverUtil.getChromeDriverPlatform(); + url = `https://edgedl.me.gvt1.com/edgedl/chrome/chrome-for-testing/${version}/${driverPlatform}/chromedriver-${driverPlatform}.zip`; + } + return url; + } + + async checkDriverVersionOffline(version: string): Promise { + try { + return await this.getLocalDriverVersion(version); + } catch (err) { + console.log('ERROR: Cannot find a copy of ChromeDriver in local cache in offline mode, exiting.'); + throw err; + } + } + + /** + * Check local chrome driver version + */ + private async getLocalDriverVersion(version: string): Promise { + const command = `${this.getChromeDriverBinaryPath(version)} -v`; + return new Promise((resolve, reject) => { + childProcess.exec(command, (err, stdout) => { + if (err) { + return reject(new Error(err.message)); + } + resolve(stdout.split(' ')[1]); + }); + }); + } + + /** + * Find a matching version of ChromeDriver for a given Chromium version + * @param chromiumVersion Chromium version to check against + */ + private async getChromeDriverVersion(chromiumVersion: string): Promise { + const majorVersion = this.getMajorVersion(chromiumVersion); + + // chrome driver versioning has changed for chrome 70+ + if (+majorVersion < 70) { + if (this.chromiumVersionMap[+majorVersion]) { + return this.chromiumVersionMap[+majorVersion]; + } else { + throw new Error(`Chromium version ${chromiumVersion} not supported`); + } + } + let url = `https://chromedriver.storage.googleapis.com/LATEST_RELEASE_${majorVersion}`; + if (+majorVersion > 114) { + url = `https://googlechromelabs.github.io/chrome-for-testing/LATEST_RELEASE_${majorVersion}`; + } + const fileName = 'driverVersion'; + await Download.getFile(url, path.join(this.downloadFolder, fileName)); + return fs.readFileSync(path.join(this.downloadFolder, fileName)).toString(); + } + + private getMajorVersion(version: string): string { + return version.split('.')[0]; + } + + // older chromedriver versions do not match chrome versions + private readonly chromiumVersionMap: VersionMap = { + 69: '2.38', + 68: '2.38', + 67: '2.38', + 66: '2.38', + 65: '2.37', + 64: '2.36', + 63: '2.35', + 62: '2.34', + 61: '2.33', + 60: '2.32', + }; } interface VersionMap { - [key:number]: string; -} \ No newline at end of file + [key: number]: string; +} diff --git a/packages/extester/src/util/unpack.ts b/packages/extester/src/util/unpack.ts index 812a13bf0..2bff0ebd9 100644 --- a/packages/extester/src/util/unpack.ts +++ b/packages/extester/src/util/unpack.ts @@ -20,45 +20,45 @@ import { exec } from 'child_process'; import targz from 'targz'; export class Unpack { - static unpack(input: fs.PathLike, target: fs.PathLike): Promise { - return new Promise((resolve, reject) => { - if (input.toString().endsWith('.tar.gz')) { - targz.decompress({ - src: input.toString(), - dest: target.toString() - }, (err: string | Error | null) => { - if(err) { - const errWho = err instanceof Error ? err : new Error(err); - reject(errWho); - } else { - resolve(); - } - }); - } - else if (input.toString().endsWith('.zip')) { - fs.mkdirpSync(target.toString()); - if(process.platform === 'darwin' || process.platform === 'linux') { - exec(`cd ${target} && unzip -qo ${input.toString()}`, (err) => { - if (err) { - reject(new Error(err.message)); - } else { - resolve(); - } - }); - } - else { - exec(`cd ${target} && tar -xvf ${input.toString()}`, (err) => { - if (err) { - reject(new Error(err.message)); - } else { - resolve(); - } - }); - } - } - else { - reject(`Unsupported extension for '${input}'`); - } - }); - } -} \ No newline at end of file + static unpack(input: fs.PathLike, target: fs.PathLike): Promise { + return new Promise((resolve, reject) => { + if (input.toString().endsWith('.tar.gz')) { + targz.decompress( + { + src: input.toString(), + dest: target.toString(), + }, + (err: string | Error | null) => { + if (err) { + const errWho = err instanceof Error ? err : new Error(err); + reject(errWho); + } else { + resolve(); + } + }, + ); + } else if (input.toString().endsWith('.zip')) { + fs.mkdirpSync(target.toString()); + if (process.platform === 'darwin' || process.platform === 'linux') { + exec(`cd ${target} && unzip -qo ${input.toString()}`, (err) => { + if (err) { + reject(new Error(err.message)); + } else { + resolve(); + } + }); + } else { + exec(`cd ${target} && tar -xvf ${input.toString()}`, (err) => { + if (err) { + reject(new Error(err.message)); + } else { + resolve(); + } + }); + } + } else { + reject(`Unsupported extension for '${input}'`); + } + }); + } +} diff --git a/packages/extester/tsconfig.json b/packages/extester/tsconfig.json index 6f2c131f7..214f41794 100644 --- a/packages/extester/tsconfig.json +++ b/packages/extester/tsconfig.json @@ -3,11 +3,7 @@ "compilerOptions": { "outDir": "out", "rootDir": "src", - "lib": [ - "dom" - ] + "lib": ["dom"] }, - "include": [ - "src" - ] -} \ No newline at end of file + "include": ["src"] +} diff --git a/packages/locators/index.ts b/packages/locators/index.ts index a04239f27..d1872c897 100644 --- a/packages/locators/index.ts +++ b/packages/locators/index.ts @@ -18,5 +18,5 @@ import * as path from 'path'; export function getLocatorsPath() { - return path.join(__dirname, 'lib'); -} \ No newline at end of file + return path.join(__dirname, 'lib'); +} diff --git a/packages/locators/lib/1.37.0.ts b/packages/locators/lib/1.37.0.ts index cb527958c..ddaeb37fb 100644 --- a/packages/locators/lib/1.37.0.ts +++ b/packages/locators/lib/1.37.0.ts @@ -15,507 +15,508 @@ * limitations under the License. */ -import { Locators, ViewSection, fromAttribute, fromText, hasAttribute, hasClass, hasElement, hasNotClass } from "@redhat-developer/page-objects"; -import { By, WebElement } from "selenium-webdriver"; +import { Locators, ViewSection, fromAttribute, fromText, hasAttribute, hasClass, hasElement, hasNotClass } from '@redhat-developer/page-objects'; +import { By, WebElement } from 'selenium-webdriver'; const abstractElement = { - AbstractElement: { - enabled: hasNotClass("disabled"), - selected: hasAttribute('aria-selected', 'true') - } + AbstractElement: { + enabled: hasNotClass('disabled'), + selected: hasAttribute('aria-selected', 'true'), + }, }; const activityBar = { - ActivityBar: { - constructor: By.id('workbench.parts.activitybar'), - viewContainer: By.xpath(`.//ul[@aria-label='Active View Switcher']`), - label: 'aria-label', - actionsContainer: By.xpath(`.//ul[@aria-label='Manage']`), - actionItem: By.className('action-item') - }, - ViewControl: { - attribute: 'class', - klass: 'checked', - scmId: By.id('workbench.view.scm'), - debugId: By.id('workbench.view.debug'), - badge: By.className('badge') - } + ActivityBar: { + constructor: By.id('workbench.parts.activitybar'), + viewContainer: By.xpath(`.//ul[@aria-label='Active View Switcher']`), + label: 'aria-label', + actionsContainer: By.xpath(`.//ul[@aria-label='Manage']`), + actionItem: By.className('action-item'), + }, + ViewControl: { + attribute: 'class', + klass: 'checked', + scmId: By.id('workbench.view.scm'), + debugId: By.id('workbench.view.debug'), + badge: By.className('badge'), + }, }; const bottomBar = { - BottomBarPanel: { - constructor: By.id('workbench.parts.panel'), - problemsTab: 'Problems', - outputTab: 'Output', - debugTab: 'Debug Console', - terminalTab: 'Terminal', - maximize: 'Maximize Panel Size', - restore: 'Restore Panel Size', - close: 'Close Panel', - tabContainer: By.className('panel-switcher-container'), - tab: (title: string) => By.xpath(`.//li[starts-with(@title, '${title}')]`), - actions: By.className('title-actions'), - globalActions: By.className('title-actions'), - action: (label: string) => By.xpath(`.//a[starts-with(@title, '${label}')]`), - closeAction: By.className('codicon-panel-close') - }, - BottomBarViews: { - actionsContainer: (label: string) => By.xpath(`.//ul[@aria-label='${label}']`), - channelOption: By.css('option'), - channelCombo: By.css('select'), - channelText: By.className('option-text'), - channelRow: By.className('monaco-list-row'), - textArea: By.css('textarea'), - clearText: By.className('clear-output') - }, - ProblemsView: { - constructor: By.id('workbench.panel.markers'), - markersFilter: By.className('markers-panel-action-filter'), - input: By.css('input'), - collapseAll: By.className('collapse-all'), - markerRow: By.className('monaco-list-row'), - rowLabel: 'aria-label', - label: By.className('monaco-highlighted-label'), - markerTwistie: By.className('monaco-tl-twistie'), - changeCount: By.className('monaco-count-badge') - }, - TerminalView: { - constructor: By.id('workbench.panel.terminal'), - actionsLabel: 'Terminal actions', - textArea: By.className('xterm-helper-textarea'), - killTerminal: By.xpath(`.//a[@title='Kill Terminal']`), - newTerminal: By.xpath(`.//a[starts-with(@title,'New Terminal')]`), - tabList: By.className('tabs-list'), - singleTab: By.className('single-terminal-tab'), - selectedRow: By.className('monaco-list-row selected'), - row: By.className('monaco-list-row'), - newCommand: 'terminal: create new integrated terminal' - }, - DebugConsoleView: { - constructor: By.id('workbench.panel.repl') - }, - OutputView: { - constructor: By.id('workbench.panel.output'), - actionsLabel: 'Output actions', - optionByName: (name: string) => By.xpath(`.//option[@value='${name}']`) - }, - WebviewView: { - iframe: By.xpath(`//div[not(@class)]/iframe[@class='webview ready' and not(@data-parent-flow-to-element-id)]`) - } + BottomBarPanel: { + constructor: By.id('workbench.parts.panel'), + problemsTab: 'Problems', + outputTab: 'Output', + debugTab: 'Debug Console', + terminalTab: 'Terminal', + maximize: 'Maximize Panel Size', + restore: 'Restore Panel Size', + close: 'Close Panel', + tabContainer: By.className('panel-switcher-container'), + tab: (title: string) => By.xpath(`.//li[starts-with(@title, '${title}')]`), + actions: By.className('title-actions'), + globalActions: By.className('title-actions'), + action: (label: string) => By.xpath(`.//a[starts-with(@title, '${label}')]`), + closeAction: By.className('codicon-panel-close'), + }, + BottomBarViews: { + actionsContainer: (label: string) => By.xpath(`.//ul[@aria-label='${label}']`), + channelOption: By.css('option'), + channelCombo: By.css('select'), + channelText: By.className('option-text'), + channelRow: By.className('monaco-list-row'), + textArea: By.css('textarea'), + clearText: By.className('clear-output'), + }, + ProblemsView: { + constructor: By.id('workbench.panel.markers'), + markersFilter: By.className('markers-panel-action-filter'), + input: By.css('input'), + collapseAll: By.className('collapse-all'), + markerRow: By.className('monaco-list-row'), + rowLabel: 'aria-label', + label: By.className('monaco-highlighted-label'), + markerTwistie: By.className('monaco-tl-twistie'), + changeCount: By.className('monaco-count-badge'), + }, + TerminalView: { + constructor: By.id('workbench.panel.terminal'), + actionsLabel: 'Terminal actions', + textArea: By.className('xterm-helper-textarea'), + killTerminal: By.xpath(`.//a[@title='Kill Terminal']`), + newTerminal: By.xpath(`.//a[starts-with(@title,'New Terminal')]`), + tabList: By.className('tabs-list'), + singleTab: By.className('single-terminal-tab'), + selectedRow: By.className('monaco-list-row selected'), + row: By.className('monaco-list-row'), + newCommand: 'terminal: create new integrated terminal', + }, + DebugConsoleView: { + constructor: By.id('workbench.panel.repl'), + }, + OutputView: { + constructor: By.id('workbench.panel.output'), + actionsLabel: 'Output actions', + optionByName: (name: string) => By.xpath(`.//option[@value='${name}']`), + }, + WebviewView: { + iframe: By.xpath(`//div[not(@class)]/iframe[@class='webview ready' and not(@data-parent-flow-to-element-id)]`), + }, }; const editor = { - EditorView: { - constructor: By.id('workbench.parts.editor'), - editorGroup: By.className('editor-group-container'), - settingsEditor: By.id('workbench.editor.settings2'), - webView: By.id('WebviewEditor'), - diffEditor: By.className('monaco-diff-editor'), - tab: By.className('tab'), - closeTab: By.className('tab-close'), - tabTitle: 'title', - tabSeparator: ', tab', - tabLabel: 'aria-label', - actionContainer: By.className('editor-actions'), - actionItem: By.className('action-label'), - attribute: 'title' - }, - Editor: { - constructor: By.className('editor-instance'), - inputArea: By.className('inputarea'), - title: By.className('label-name') - }, - TextEditor: { - activeTab: By.css('div.tab.active'), - breakpoint: { - pauseSelector: By.className('codicon-debug-stackframe'), - generalSelector: By.className('codicon-debug-breakpoint'), - properties: { - enabled: hasNotClass('codicon-debug-breakpoint-unverified'), - line: { - selector: By.className('line-numbers'), - number: (line: WebElement) => line.getText().then((line) => Number.parseInt(line)) - }, - paused: hasClass('codicon-debug-stackframe'), - } - }, - editorContainer: By.className('monaco-editor'), - dataUri: 'data-uri', - formatDoc: 'Format Document', - marginArea: By.className('margin-view-overlays'), - lineNumber: (line: number) => By.xpath(`.//div[contains(@class, 'line-numbers') and text() = '${line}']`), - lineOverlay: (line: number) => By.xpath(`.//div[contains(@class, 'line-numbers') and text() = '${line}']/..`), - debugHint: By.className('codicon-debug-hint'), - selection: By.className('cslr selected-text top-left-radius bottom-left-radius top-right-radius bottom-right-radius'), - findWidget: By.className('find-widget') - }, - FindWidget: { - toggleReplace: By.xpath(`.//div[@title="Toggle Replace mode"]`), - replacePart: By.className('replace-part'), - findPart: By.className('find-part'), - matchCount: By.className('matchesCount'), - input: By.css('textarea'), - content: By.className('mirror'), - button: (title: string) => By.xpath(`.//div[@role='button' and starts-with(@title, "${title}")]`), - checkbox: (title: string) => By.xpath(`.//div[@role='checkbox' and starts-with(@title, "${title}")]`) - }, - ContentAssist: { - constructor: By.className('suggest-widget'), - message: By.className('message'), - itemRows: By.className('monaco-list-rows'), - itemRow: By.className('monaco-list-row'), - itemLabel: By.className('label-name'), - itemText: By.xpath(`./span/span`), - itemList: By.className('monaco-list'), - firstItem: By.xpath(`.//div[@data-index='0']`) - }, - SettingsEditor: { - title: 'Settings', - itemRow: By.className('monaco-list-row'), - header: By.className('settings-header'), - tabs: By.className('settings-tabs-widget'), - actions: By.className('actions-container'), - action: (label: string) => By.xpath(`.//a[@title='${label}']`), - settingConstructor: (title: string, category: string) => By.xpath(`.//div[@class='monaco-tl-row' and .//span/text()='${title}' and .//span/text()='${category}: ']`), - settingDescription: By.className('setting-item-description'), - settingLabel: By.className('setting-item-label'), - settingCategory: By.className('setting-item-category'), - comboSetting: By.css('select'), - comboOption: By.css('option'), - comboValue: 'value', - textSetting: By.css('input'), - checkboxSetting: By.className('setting-value-checkbox'), - checkboxChecked: 'aria-checked', - linkButton: By.className('edit-in-settings-button'), - itemCount: By.className('settings-count-widget'), - arraySetting: By.className('setting-item-control'), - arrayRoot: By.xpath(`.//div[@role='list' and contains(@class, 'setting-list-widget')]`), - arrayRow: By.className('setting-list-row'), - arrayRowValue: By.className('setting-list-value'), - arrayNewRow: By.className('setting-list-new-row'), - arrayEditRow: By.className('setting-list-edit-row'), - arrayBtnConstructor: (label: string) => By.xpath(`.//a[contains(@role, 'button') and @aria-label='${label}']`), - arraySettingItem: { - btnConstructor: (label: string) => By.xpath(`.//a[contains(@role, 'button') and text()='${label}']`) - } - }, - DiffEditor: { - originalEditor: By.className('original-in-monaco-diff-editor'), - modifiedEditor: By.className('modified-in-monaco-diff-editor') - }, - WebView: { - iframe: By.css(`iframe[class='webview ready']`), - activeFrame: By.id('active-frame'), - container: (id: string) => By.id(id), - attribute: 'aria-flowto' - } + EditorView: { + constructor: By.id('workbench.parts.editor'), + editorGroup: By.className('editor-group-container'), + settingsEditor: By.id('workbench.editor.settings2'), + webView: By.id('WebviewEditor'), + diffEditor: By.className('monaco-diff-editor'), + tab: By.className('tab'), + closeTab: By.className('tab-close'), + tabTitle: 'title', + tabSeparator: ', tab', + tabLabel: 'aria-label', + actionContainer: By.className('editor-actions'), + actionItem: By.className('action-label'), + attribute: 'title', + }, + Editor: { + constructor: By.className('editor-instance'), + inputArea: By.className('inputarea'), + title: By.className('label-name'), + }, + TextEditor: { + activeTab: By.css('div.tab.active'), + breakpoint: { + pauseSelector: By.className('codicon-debug-stackframe'), + generalSelector: By.className('codicon-debug-breakpoint'), + properties: { + enabled: hasNotClass('codicon-debug-breakpoint-unverified'), + line: { + selector: By.className('line-numbers'), + number: (line: WebElement) => line.getText().then((line) => Number.parseInt(line)), + }, + paused: hasClass('codicon-debug-stackframe'), + }, + }, + editorContainer: By.className('monaco-editor'), + dataUri: 'data-uri', + formatDoc: 'Format Document', + marginArea: By.className('margin-view-overlays'), + lineNumber: (line: number) => By.xpath(`.//div[contains(@class, 'line-numbers') and text() = '${line}']`), + lineOverlay: (line: number) => By.xpath(`.//div[contains(@class, 'line-numbers') and text() = '${line}']/..`), + debugHint: By.className('codicon-debug-hint'), + selection: By.className('cslr selected-text top-left-radius bottom-left-radius top-right-radius bottom-right-radius'), + findWidget: By.className('find-widget'), + }, + FindWidget: { + toggleReplace: By.xpath(`.//div[@title="Toggle Replace mode"]`), + replacePart: By.className('replace-part'), + findPart: By.className('find-part'), + matchCount: By.className('matchesCount'), + input: By.css('textarea'), + content: By.className('mirror'), + button: (title: string) => By.xpath(`.//div[@role='button' and starts-with(@title, "${title}")]`), + checkbox: (title: string) => By.xpath(`.//div[@role='checkbox' and starts-with(@title, "${title}")]`), + }, + ContentAssist: { + constructor: By.className('suggest-widget'), + message: By.className('message'), + itemRows: By.className('monaco-list-rows'), + itemRow: By.className('monaco-list-row'), + itemLabel: By.className('label-name'), + itemText: By.xpath(`./span/span`), + itemList: By.className('monaco-list'), + firstItem: By.xpath(`.//div[@data-index='0']`), + }, + SettingsEditor: { + title: 'Settings', + itemRow: By.className('monaco-list-row'), + header: By.className('settings-header'), + tabs: By.className('settings-tabs-widget'), + actions: By.className('actions-container'), + action: (label: string) => By.xpath(`.//a[@title='${label}']`), + settingConstructor: (title: string, category: string) => + By.xpath(`.//div[@class='monaco-tl-row' and .//span/text()='${title}' and .//span/text()='${category}: ']`), + settingDescription: By.className('setting-item-description'), + settingLabel: By.className('setting-item-label'), + settingCategory: By.className('setting-item-category'), + comboSetting: By.css('select'), + comboOption: By.css('option'), + comboValue: 'value', + textSetting: By.css('input'), + checkboxSetting: By.className('setting-value-checkbox'), + checkboxChecked: 'aria-checked', + linkButton: By.className('edit-in-settings-button'), + itemCount: By.className('settings-count-widget'), + arraySetting: By.className('setting-item-control'), + arrayRoot: By.xpath(`.//div[@role='list' and contains(@class, 'setting-list-widget')]`), + arrayRow: By.className('setting-list-row'), + arrayRowValue: By.className('setting-list-value'), + arrayNewRow: By.className('setting-list-new-row'), + arrayEditRow: By.className('setting-list-edit-row'), + arrayBtnConstructor: (label: string) => By.xpath(`.//a[contains(@role, 'button') and @aria-label='${label}']`), + arraySettingItem: { + btnConstructor: (label: string) => By.xpath(`.//a[contains(@role, 'button') and text()='${label}']`), + }, + }, + DiffEditor: { + originalEditor: By.className('original-in-monaco-diff-editor'), + modifiedEditor: By.className('modified-in-monaco-diff-editor'), + }, + WebView: { + iframe: By.css(`iframe[class='webview ready']`), + activeFrame: By.id('active-frame'), + container: (id: string) => By.id(id), + attribute: 'aria-flowto', + }, }; const menu = { - ContextMenu: { - contextView: By.className('context-view'), - constructor: By.className('monaco-menu-container'), - itemConstructor: (label: string) => By.xpath(`.//li[a/span/@aria-label="${label}"]`), - itemElement: By.className('action-item'), - itemLabel: By.className('action-label'), - itemText: 'aria-label', - itemNesting: By.className('submenu-indicator'), - viewBlock: By.className('context-view-block') - }, - TitleBar: { - constructor: By.id('workbench.parts.titlebar'), - itemConstructor: (label: string) => By.xpath(`.//div[@aria-label="${label}"]`), - itemElement: By.className('menubar-menu-button'), - itemLabel: 'aria-label', - title: By.className('window-title') - }, - WindowControls: { - constructor: By.className('window-controls-container'), - minimize: By.className('window-minimize'), - maximize: By.className('window-maximize'), - restore: By.className('window-unmaximize'), - close: By.className('window-close') - } + ContextMenu: { + contextView: By.className('context-view'), + constructor: By.className('monaco-menu-container'), + itemConstructor: (label: string) => By.xpath(`.//li[a/span/@aria-label="${label}"]`), + itemElement: By.className('action-item'), + itemLabel: By.className('action-label'), + itemText: 'aria-label', + itemNesting: By.className('submenu-indicator'), + viewBlock: By.className('context-view-block'), + }, + TitleBar: { + constructor: By.id('workbench.parts.titlebar'), + itemConstructor: (label: string) => By.xpath(`.//div[@aria-label="${label}"]`), + itemElement: By.className('menubar-menu-button'), + itemLabel: 'aria-label', + title: By.className('window-title'), + }, + WindowControls: { + constructor: By.className('window-controls-container'), + minimize: By.className('window-minimize'), + maximize: By.className('window-maximize'), + restore: By.className('window-unmaximize'), + close: By.className('window-close'), + }, }; const sideBar = { - SideBarView: { - constructor: By.id('workbench.parts.sidebar') - }, - ViewTitlePart: { - constructor: By.className('composite title'), - title: By.css('h2'), - action: By.className(`action-label`), - actionLabel: 'title', - actionConstructor: (title: string) => By.xpath(`.//a[@title='${title}']`) - }, - ViewContent: { - constructor: By.className('content'), - progress: By.className('monaco-progress-container'), - section: By.className('split-view-view'), - defaultView: By.className('explorer-folders-view'), - extensionsView: By.className('extensions-list') - }, - ViewSection: { - title: By.className('title'), - titleText: 'textContent', - header: By.className('panel-header'), - headerExpanded: 'aria-expanded', - actions: By.className('actions'), - actionConstructor: (label: string) => By.xpath(`.//a[contains(@class, 'action-label') and @role='button' and @title='${label}']`), - button: By.xpath(`.//a[@role='button']`), - buttonLabel: 'title', - level: 'aria-level', - index: 'data-index', - welcomeContent: By.className('welcome-view') - }, - TreeItem: { - actions: By.className('actions-container'), - actionLabel: By.className('action-label'), - actionTitle: 'title', - twistie: By.className('monaco-tl-twistie') - }, - DefaultTreeSection: { - itemRow: By.className('monaco-list-row'), - itemLabel: 'aria-label', - rowContainer: By.className('monaco-list'), - rowWithLabel: (label: string) => By.xpath(`.//div[@role='treeitem' and @aria-label='${label}']`), - lastRow: By.xpath(`.//div[@data-last-element='true']`), - type: { - default: hasElement((locators: Locators) => locators.ViewContent.defaultView), - marketplace: { - extension: hasElement((locators: Locators) => locators.ViewContent.extensionsView) - } - } - }, - DefaultTreeItem: { - ctor: (label: string) => By.xpath(`.//div[@role='treeitem' and @aria-label='${label}']`), - twistie: By.className('monaco-tl-twistie'), - tooltip: By.className('explorer-item'), - labelAttribute: 'title' - }, - CustomTreeSection: { - itemRow: By.className('monaco-list-row'), - itemLabel: By.className('monaco-highlighted-label'), - rowContainer: By.className('monaco-list'), - rowWithLabel: (label: string) => By.xpath(`.//span[text()='${label}']`) - }, - CustomTreeItem: { - constructor: (label: string) => By.xpath(`.//div[@role='treeitem' and .//span[text()='${label}']]`), - tooltipAttribute: 'aria-label', - expandedAttr: 'aria-expanded', - expandedValue: 'true', - description: By.className('label-description'), - }, - DebugBreakpointSection: { - predicate: async (section: ViewSection) => (await section.getTitle()).toLowerCase() === 'breakpoints' - }, - BreakpointSectionItem: { - breakpoint: { - constructor: By.className('codicon') - }, - breakpointCheckbox: { - constructor: By.css('input[type=checkbox'), - value: (el: WebElement) => el.isSelected() - }, - label: { - constructor: By.className('name'), - value: fromText() - }, - filePath: { - constructor: By.className('file-path'), - value: fromText() - }, - lineNumber: { - constructor: By.className('line-number'), - value: fromText() - } - }, - DebugVariableSection: { - predicate: async (section: ViewSection) => (await section.getTitle()).toLowerCase() === 'variables' - }, - VariableSectionItem: { - label: fromText(By.className('monaco-highlighted-label')), - name: { - constructor: By.className('name'), - value: fromText(), - tooltip: fromAttribute('title', By.className('monaco-highlighted-label')) - }, - value: { - constructor: By.className('value'), - value: fromText(), - tooltip: fromAttribute('title') - } - }, - ExtensionsViewSection: { - items: By.className('monaco-list-rows'), - itemRow: By.className('monaco-list-row'), - itemTitle: By.className('name'), - searchBox: By.className('inputarea'), - textContainer: By.className('view-line'), - textField: By.className('mtk1') - }, - ExtensionsViewItem: { - version: By.className('version'), - author: By.className('author'), - description: By.className('description'), - install: By.className('install'), - manage: By.className('manage') - }, - ScmView: { - providerHeader: By.css(`div[class*='panel-header scm-provider']`), - providerRelative: By.xpath(`./..`), - initButton: By.xpath(`.//a[text()='Initialize Repository']`), - providerTitle: By.className('title'), - providerType: By.className('type'), - action: By.className('action-label'), - actionConstructor: (title: string) => By.xpath(`.//a[@title='${title}']`), - actionLabel: 'title', - inputField: By.css('textarea'), - changeItem: By.xpath(`.//div[@role='treeitem']`), - changeName: By.className('name'), - changeCount: By.className('monaco-count-badge'), - changeLabel: By.className('label-name'), - changeDesc: By.className('label-description'), - resource: By.className('resource'), - changes: By.xpath(`.//div[@role="treeitem" and .//div/text()="CHANGES"]`), - stagedChanges: By.xpath(`.//div[@role="treeitem" and .//div/text()="STAGED CHANGES"]`), - expand: By.className('monaco-tl-twistie'), - more: By.className('toolbar-toggle-more'), - multiMore: By.className('codicon-toolbar-more'), - multiScmProvider: By.className('scm-provider'), - singleScmProvider: By.className(`scm-view`), - multiProviderItem: By.xpath(`.//div[@role='treeitem' and @aria-level='1']`), - itemLevel: (level: number) => By.xpath(`.//div[@role='treeitem' and @aria-level='${level}']`), - itemIndex: (index: number) => By.xpath(`.//div[@role='treeitem' and @data-index='${index}']`) - }, - DebugView: { - launchCombo: By.className('start-debug-action-item'), - launchSelect: By.css('select'), - launchOption: By.css('option'), - optionByName: (name: string) => By.xpath(`.//option[@value='${name}']`), - startButton: By.className('codicon-debug-start') - } + SideBarView: { + constructor: By.id('workbench.parts.sidebar'), + }, + ViewTitlePart: { + constructor: By.className('composite title'), + title: By.css('h2'), + action: By.className(`action-label`), + actionLabel: 'title', + actionConstructor: (title: string) => By.xpath(`.//a[@title='${title}']`), + }, + ViewContent: { + constructor: By.className('content'), + progress: By.className('monaco-progress-container'), + section: By.className('split-view-view'), + defaultView: By.className('explorer-folders-view'), + extensionsView: By.className('extensions-list'), + }, + ViewSection: { + title: By.className('title'), + titleText: 'textContent', + header: By.className('panel-header'), + headerExpanded: 'aria-expanded', + actions: By.className('actions'), + actionConstructor: (label: string) => By.xpath(`.//a[contains(@class, 'action-label') and @role='button' and @title='${label}']`), + button: By.xpath(`.//a[@role='button']`), + buttonLabel: 'title', + level: 'aria-level', + index: 'data-index', + welcomeContent: By.className('welcome-view'), + }, + TreeItem: { + actions: By.className('actions-container'), + actionLabel: By.className('action-label'), + actionTitle: 'title', + twistie: By.className('monaco-tl-twistie'), + }, + DefaultTreeSection: { + itemRow: By.className('monaco-list-row'), + itemLabel: 'aria-label', + rowContainer: By.className('monaco-list'), + rowWithLabel: (label: string) => By.xpath(`.//div[@role='treeitem' and @aria-label='${label}']`), + lastRow: By.xpath(`.//div[@data-last-element='true']`), + type: { + default: hasElement((locators: Locators) => locators.ViewContent.defaultView), + marketplace: { + extension: hasElement((locators: Locators) => locators.ViewContent.extensionsView), + }, + }, + }, + DefaultTreeItem: { + ctor: (label: string) => By.xpath(`.//div[@role='treeitem' and @aria-label='${label}']`), + twistie: By.className('monaco-tl-twistie'), + tooltip: By.className('explorer-item'), + labelAttribute: 'title', + }, + CustomTreeSection: { + itemRow: By.className('monaco-list-row'), + itemLabel: By.className('monaco-highlighted-label'), + rowContainer: By.className('monaco-list'), + rowWithLabel: (label: string) => By.xpath(`.//span[text()='${label}']`), + }, + CustomTreeItem: { + constructor: (label: string) => By.xpath(`.//div[@role='treeitem' and .//span[text()='${label}']]`), + tooltipAttribute: 'aria-label', + expandedAttr: 'aria-expanded', + expandedValue: 'true', + description: By.className('label-description'), + }, + DebugBreakpointSection: { + predicate: async (section: ViewSection) => (await section.getTitle()).toLowerCase() === 'breakpoints', + }, + BreakpointSectionItem: { + breakpoint: { + constructor: By.className('codicon'), + }, + breakpointCheckbox: { + constructor: By.css('input[type=checkbox'), + value: (el: WebElement) => el.isSelected(), + }, + label: { + constructor: By.className('name'), + value: fromText(), + }, + filePath: { + constructor: By.className('file-path'), + value: fromText(), + }, + lineNumber: { + constructor: By.className('line-number'), + value: fromText(), + }, + }, + DebugVariableSection: { + predicate: async (section: ViewSection) => (await section.getTitle()).toLowerCase() === 'variables', + }, + VariableSectionItem: { + label: fromText(By.className('monaco-highlighted-label')), + name: { + constructor: By.className('name'), + value: fromText(), + tooltip: fromAttribute('title', By.className('monaco-highlighted-label')), + }, + value: { + constructor: By.className('value'), + value: fromText(), + tooltip: fromAttribute('title'), + }, + }, + ExtensionsViewSection: { + items: By.className('monaco-list-rows'), + itemRow: By.className('monaco-list-row'), + itemTitle: By.className('name'), + searchBox: By.className('inputarea'), + textContainer: By.className('view-line'), + textField: By.className('mtk1'), + }, + ExtensionsViewItem: { + version: By.className('version'), + author: By.className('author'), + description: By.className('description'), + install: By.className('install'), + manage: By.className('manage'), + }, + ScmView: { + providerHeader: By.css(`div[class*='panel-header scm-provider']`), + providerRelative: By.xpath(`./..`), + initButton: By.xpath(`.//a[text()='Initialize Repository']`), + providerTitle: By.className('title'), + providerType: By.className('type'), + action: By.className('action-label'), + actionConstructor: (title: string) => By.xpath(`.//a[@title='${title}']`), + actionLabel: 'title', + inputField: By.css('textarea'), + changeItem: By.xpath(`.//div[@role='treeitem']`), + changeName: By.className('name'), + changeCount: By.className('monaco-count-badge'), + changeLabel: By.className('label-name'), + changeDesc: By.className('label-description'), + resource: By.className('resource'), + changes: By.xpath(`.//div[@role="treeitem" and .//div/text()="CHANGES"]`), + stagedChanges: By.xpath(`.//div[@role="treeitem" and .//div/text()="STAGED CHANGES"]`), + expand: By.className('monaco-tl-twistie'), + more: By.className('toolbar-toggle-more'), + multiMore: By.className('codicon-toolbar-more'), + multiScmProvider: By.className('scm-provider'), + singleScmProvider: By.className(`scm-view`), + multiProviderItem: By.xpath(`.//div[@role='treeitem' and @aria-level='1']`), + itemLevel: (level: number) => By.xpath(`.//div[@role='treeitem' and @aria-level='${level}']`), + itemIndex: (index: number) => By.xpath(`.//div[@role='treeitem' and @data-index='${index}']`), + }, + DebugView: { + launchCombo: By.className('start-debug-action-item'), + launchSelect: By.css('select'), + launchOption: By.css('option'), + optionByName: (name: string) => By.xpath(`.//option[@value='${name}']`), + startButton: By.className('codicon-debug-start'), + }, }; const statusBar = { - StatusBar: { - constructor: By.id('workbench.parts.statusbar'), - language: By.id('status.editor.mode'), - lines: By.id('status.editor.eol'), - encoding: By.id('status.editor.encoding'), - indent: By.id('status.editor.indentation'), - selection: By.id('status.editor.selection'), - notifications: By.className('notifications-center'), - bell: By.id('status.notifications'), - item: By.className('statusbar-item'), - itemTitle: 'aria-label' - } + StatusBar: { + constructor: By.id('workbench.parts.statusbar'), + language: By.id('status.editor.mode'), + lines: By.id('status.editor.eol'), + encoding: By.id('status.editor.encoding'), + indent: By.id('status.editor.indentation'), + selection: By.id('status.editor.selection'), + notifications: By.className('notifications-center'), + bell: By.id('status.notifications'), + item: By.className('statusbar-item'), + itemTitle: 'aria-label', + }, }; const workbench = { - Workbench: { - constructor: By.className('monaco-workbench'), - notificationContainer: By.className('notification-toast-container'), - notificationItem: By.className('monaco-list-row') - }, - Notification: { - message: By.className('notification-list-item-message'), - icon: By.className('notification-list-item-icon'), - source: By.className('notification-list-item-source'), - progress: By.className('monaco-progress-container'), - dismiss: By.className('clear-notification-action'), - expand: By.className('codicon-notifications-expand'), - actions: By.className('notification-list-item-buttons-container'), - action: By.className('monaco-button'), - actionLabel: { - value: fromAttribute('title') - }, - standalone: (id: string) => By.xpath(`.//div[contains(@class, 'monaco-list-row') and @id='${id}']`), - standaloneContainer: By.className('notifications-toasts'), - center: (index: number) => By.xpath(`.//div[contains(@class, 'monaco-list-row') and @data-index='${index}']`), - buttonConstructor: (title: string) => By.xpath(`.//a[@role='button' and @title='${title}']`) - }, - NotificationsCenter: { - constructor: By.className('notifications-center'), - close: By.className('hide-all-notifications-action'), - clear: By.className('clear-all-notifications-action'), - row: By.className('monaco-list-row') - }, - DebugToolbar: { - ctor: By.className('debug-toolbar'), - button: (title: string) => By.className(`codicon-debug-${title}`) - } + Workbench: { + constructor: By.className('monaco-workbench'), + notificationContainer: By.className('notification-toast-container'), + notificationItem: By.className('monaco-list-row'), + }, + Notification: { + message: By.className('notification-list-item-message'), + icon: By.className('notification-list-item-icon'), + source: By.className('notification-list-item-source'), + progress: By.className('monaco-progress-container'), + dismiss: By.className('clear-notification-action'), + expand: By.className('codicon-notifications-expand'), + actions: By.className('notification-list-item-buttons-container'), + action: By.className('monaco-button'), + actionLabel: { + value: fromAttribute('title'), + }, + standalone: (id: string) => By.xpath(`.//div[contains(@class, 'monaco-list-row') and @id='${id}']`), + standaloneContainer: By.className('notifications-toasts'), + center: (index: number) => By.xpath(`.//div[contains(@class, 'monaco-list-row') and @data-index='${index}']`), + buttonConstructor: (title: string) => By.xpath(`.//a[@role='button' and @title='${title}']`), + }, + NotificationsCenter: { + constructor: By.className('notifications-center'), + close: By.className('hide-all-notifications-action'), + clear: By.className('clear-all-notifications-action'), + row: By.className('monaco-list-row'), + }, + DebugToolbar: { + ctor: By.className('debug-toolbar'), + button: (title: string) => By.className(`codicon-debug-${title}`), + }, }; const input = { - Input: { - inputBox: By.className('monaco-inputbox'), - input: By.className('input'), - quickPickIndex: (index: number) => By.xpath(`.//div[@role='treeitem' and @data-index='${index}']`), - quickPickPosition: (index: number) => By.xpath(`.//div[@role='treeitem' and @aria-posinset='${index}']`), - quickPickLabel: By.className('label-name'), - quickPickDescription: By.className('label-description'), - quickPickSelectAll: By.className('quick-input-check-all'), - titleBar: By.className('quick-input-titlebar'), - title: By.className('quick-input-title'), - backButton: By.className('codicon-quick-input-back'), - multiSelectIndex: (index: number) => By.xpath(`.//div[@role='treeitem' and @data-index='${index}']`) - }, - InputBox: { - constructor: By.className('quick-input-widget'), - message: By.className('quick-input-message'), - progress: By.className('quick-input-progress'), - quickList: By.className('quick-input-list'), - rows: By.className('monaco-list-rows'), - row: By.className('monaco-list-row') - }, - QuickOpenBox: { - constructor: By.className('monaco-quick-open-widget'), - progress: By.className('monaco-progress-container'), - quickList: By.className('quick-open-tree'), - row: By.xpath(`.//div[@role='treeitem']`) - } + Input: { + inputBox: By.className('monaco-inputbox'), + input: By.className('input'), + quickPickIndex: (index: number) => By.xpath(`.//div[@role='treeitem' and @data-index='${index}']`), + quickPickPosition: (index: number) => By.xpath(`.//div[@role='treeitem' and @aria-posinset='${index}']`), + quickPickLabel: By.className('label-name'), + quickPickDescription: By.className('label-description'), + quickPickSelectAll: By.className('quick-input-check-all'), + titleBar: By.className('quick-input-titlebar'), + title: By.className('quick-input-title'), + backButton: By.className('codicon-quick-input-back'), + multiSelectIndex: (index: number) => By.xpath(`.//div[@role='treeitem' and @data-index='${index}']`), + }, + InputBox: { + constructor: By.className('quick-input-widget'), + message: By.className('quick-input-message'), + progress: By.className('quick-input-progress'), + quickList: By.className('quick-input-list'), + rows: By.className('monaco-list-rows'), + row: By.className('monaco-list-row'), + }, + QuickOpenBox: { + constructor: By.className('monaco-quick-open-widget'), + progress: By.className('monaco-progress-container'), + quickList: By.className('quick-open-tree'), + row: By.xpath(`.//div[@role='treeitem']`), + }, }; const dialog = { - Dialog: { - constructor: By.className('monaco-dialog-box'), - message: By.className('dialog-message-text'), - details: By.className('dialog-message-detail'), - buttonContainer: By.className('dialog-buttons-row'), - button: By.className('monaco-text-button'), - closeButton: By.className('codicon-dialog-close'), - buttonLabel: { - value: fromAttribute('title') - } - } + Dialog: { + constructor: By.className('monaco-dialog-box'), + message: By.className('dialog-message-text'), + details: By.className('dialog-message-detail'), + buttonContainer: By.className('dialog-buttons-row'), + button: By.className('monaco-text-button'), + closeButton: By.className('codicon-dialog-close'), + buttonLabel: { + value: fromAttribute('title'), + }, + }, }; const welcomeContentButtonSelector = ".//a[@class='monaco-button monaco-text-button']"; -const welcomeContentTextSelector = ".//p"; +const welcomeContentTextSelector = './/p'; const welcome = { - WelcomeContent: { - button: By.xpath(welcomeContentButtonSelector), - buttonOrText: By.xpath(`${welcomeContentButtonSelector} | ${welcomeContentTextSelector}`), - text: By.xpath(welcomeContentTextSelector) - } + WelcomeContent: { + button: By.xpath(welcomeContentButtonSelector), + buttonOrText: By.xpath(`${welcomeContentButtonSelector} | ${welcomeContentTextSelector}`), + text: By.xpath(welcomeContentTextSelector), + }, }; /** * All available locators for vscode version 1.37.0 */ export const locators: Locators = { - ...abstractElement, - ...activityBar, - ...bottomBar, - ...editor, - ...menu, - ...sideBar, - ...statusBar, - ...workbench, - ...input, - ...dialog, - ...welcome + ...abstractElement, + ...activityBar, + ...bottomBar, + ...editor, + ...menu, + ...sideBar, + ...statusBar, + ...workbench, + ...input, + ...dialog, + ...welcome, }; diff --git a/packages/locators/lib/1.38.0.ts b/packages/locators/lib/1.38.0.ts index 95681e278..347e84da7 100644 --- a/packages/locators/lib/1.38.0.ts +++ b/packages/locators/lib/1.38.0.ts @@ -15,14 +15,14 @@ * limitations under the License. */ -import { LocatorDiff } from "@redhat-developer/page-objects"; -import { By } from "selenium-webdriver"; +import { LocatorDiff } from '@redhat-developer/page-objects'; +import { By } from 'selenium-webdriver'; export const diff: LocatorDiff = { - locators: { - EditorView: { - settingsEditor: By.xpath(`.//div[@data-editor-id='workbench.editor.settings2']`), - webView: By.xpath(`.//div[@data-editor-id='WebviewEditor']`) - } - } -}; \ No newline at end of file + locators: { + EditorView: { + settingsEditor: By.xpath(`.//div[@data-editor-id='workbench.editor.settings2']`), + webView: By.xpath(`.//div[@data-editor-id='WebviewEditor']`), + }, + }, +}; diff --git a/packages/locators/lib/1.39.0.ts b/packages/locators/lib/1.39.0.ts index d71446db6..01e62b10d 100644 --- a/packages/locators/lib/1.39.0.ts +++ b/packages/locators/lib/1.39.0.ts @@ -15,23 +15,23 @@ * limitations under the License. */ -import { LocatorDiff } from "@redhat-developer/page-objects"; -import { By } from "selenium-webdriver"; +import { LocatorDiff } from '@redhat-developer/page-objects'; +import { By } from 'selenium-webdriver'; export const diff: LocatorDiff = { - locators: { - BottomBarViews: { - clearText: By.className('codicon-clear-all') - }, - NotificationsCenter: { - close: By.className('codicon-chevron-down'), - clear: By.className('codicon-close-all') - }, - Notification: { - dismiss: By.className('codicon-close') - }, - ScmView: { - more: By.className('codicon-more') - } - } -}; \ No newline at end of file + locators: { + BottomBarViews: { + clearText: By.className('codicon-clear-all'), + }, + NotificationsCenter: { + close: By.className('codicon-chevron-down'), + clear: By.className('codicon-close-all'), + }, + Notification: { + dismiss: By.className('codicon-close'), + }, + ScmView: { + more: By.className('codicon-more'), + }, + }, +}; diff --git a/packages/locators/lib/1.40.0.ts b/packages/locators/lib/1.40.0.ts index 5620a15e0..204b678dc 100644 --- a/packages/locators/lib/1.40.0.ts +++ b/packages/locators/lib/1.40.0.ts @@ -15,14 +15,14 @@ * limitations under the License. */ -import { LocatorDiff } from "@redhat-developer/page-objects"; -import { By } from "selenium-webdriver"; +import { LocatorDiff } from '@redhat-developer/page-objects'; +import { By } from 'selenium-webdriver'; export const diff: LocatorDiff = { - locators: { - NotificationsCenter: { - close: By.className('codicon-close'), - clear: By.className('codicon-clear-all') - } - } -}; \ No newline at end of file + locators: { + NotificationsCenter: { + close: By.className('codicon-close'), + clear: By.className('codicon-clear-all'), + }, + }, +}; diff --git a/packages/locators/lib/1.41.0.ts b/packages/locators/lib/1.41.0.ts index 103fc3793..e77aaa7ac 100644 --- a/packages/locators/lib/1.41.0.ts +++ b/packages/locators/lib/1.41.0.ts @@ -15,16 +15,16 @@ * limitations under the License. */ -import { LocatorDiff } from "@redhat-developer/page-objects"; -import { By } from "selenium-webdriver"; +import { LocatorDiff } from '@redhat-developer/page-objects'; +import { By } from 'selenium-webdriver'; export const diff: LocatorDiff = { - locators: { - ViewSection: { - header: By.className('pane-header') - }, - ScmView: { - providerHeader: By.css(`div[class*='pane-header scm-provider']`) - } - } -}; \ No newline at end of file + locators: { + ViewSection: { + header: By.className('pane-header'), + }, + ScmView: { + providerHeader: By.css(`div[class*='pane-header scm-provider']`), + }, + }, +}; diff --git a/packages/locators/lib/1.43.0.ts b/packages/locators/lib/1.43.0.ts index b0db2cdc9..cd4fffa02 100644 --- a/packages/locators/lib/1.43.0.ts +++ b/packages/locators/lib/1.43.0.ts @@ -15,17 +15,17 @@ * limitations under the License. */ -import { LocatorDiff } from "@redhat-developer/page-objects"; -import { By } from "selenium-webdriver"; +import { LocatorDiff } from '@redhat-developer/page-objects'; +import { By } from 'selenium-webdriver'; export const diff: LocatorDiff = { - locators: { - Input: { - quickPickIndex: (index: number) => By.xpath(`.//div[@role='listitem' and @data-index='${index}']`), - multiSelectIndex: (index: number) => By.xpath(`.//div[@role='listitem' and @data-index='${index}']`) - }, - NotificationsCenter: { - close: By.className('codicon-chevron-down') - } - } -}; \ No newline at end of file + locators: { + Input: { + quickPickIndex: (index: number) => By.xpath(`.//div[@role='listitem' and @data-index='${index}']`), + multiSelectIndex: (index: number) => By.xpath(`.//div[@role='listitem' and @data-index='${index}']`), + }, + NotificationsCenter: { + close: By.className('codicon-chevron-down'), + }, + }, +}; diff --git a/packages/locators/lib/1.44.0.ts b/packages/locators/lib/1.44.0.ts index 4fdf20016..273f28a3a 100644 --- a/packages/locators/lib/1.44.0.ts +++ b/packages/locators/lib/1.44.0.ts @@ -15,14 +15,14 @@ * limitations under the License. */ -import { LocatorDiff } from "@redhat-developer/page-objects"; -import { By } from "selenium-webdriver"; +import { LocatorDiff } from '@redhat-developer/page-objects'; +import { By } from 'selenium-webdriver'; export const diff: LocatorDiff = { - locators: { - Input: { - quickPickIndex: (index: number) => By.xpath(`.//div[@role='option' and @data-index='${index}']`), - multiSelectIndex: (index: number) => By.xpath(`.//div[@role='option' and @data-index='${index}']`) - } - } -}; \ No newline at end of file + locators: { + Input: { + quickPickIndex: (index: number) => By.xpath(`.//div[@role='option' and @data-index='${index}']`), + multiSelectIndex: (index: number) => By.xpath(`.//div[@role='option' and @data-index='${index}']`), + }, + }, +}; diff --git a/packages/locators/lib/1.45.0.ts b/packages/locators/lib/1.45.0.ts index e1bc531f4..fc677d5c3 100644 --- a/packages/locators/lib/1.45.0.ts +++ b/packages/locators/lib/1.45.0.ts @@ -15,23 +15,23 @@ * limitations under the License. */ -import { LocatorDiff } from "@redhat-developer/page-objects"; -import { By } from "selenium-webdriver"; +import { LocatorDiff } from '@redhat-developer/page-objects'; +import { By } from 'selenium-webdriver'; export const diff: LocatorDiff = { - locators: { - EditorView: { - tabSeparator: '' - }, - NotificationsCenter: { - clear: By.className('codicon-notifications-clear-all'), - close: By.className('codicon-notifications-hide') - }, - Notification: { - dismiss: By.className('codicon-notifications-clear') - }, - ScmView: { - more: By.className('codicon-toolbar-more') - } - } -}; \ No newline at end of file + locators: { + EditorView: { + tabSeparator: '', + }, + NotificationsCenter: { + clear: By.className('codicon-notifications-clear-all'), + close: By.className('codicon-notifications-hide'), + }, + Notification: { + dismiss: By.className('codicon-notifications-clear'), + }, + ScmView: { + more: By.className('codicon-toolbar-more'), + }, + }, +}; diff --git a/packages/locators/lib/1.46.0.ts b/packages/locators/lib/1.46.0.ts index 8709b6eba..615262237 100644 --- a/packages/locators/lib/1.46.0.ts +++ b/packages/locators/lib/1.46.0.ts @@ -15,13 +15,13 @@ * limitations under the License. */ -import { LocatorDiff } from "@redhat-developer/page-objects"; -import { By } from "selenium-webdriver"; +import { LocatorDiff } from '@redhat-developer/page-objects'; +import { By } from 'selenium-webdriver'; export const diff: LocatorDiff = { - locators: { - CustomTreeItem: { - constructor: (label: string) => By.xpath(`.//div[@role='listitem' and .//span[text()='${label}']]`) - } - } -}; \ No newline at end of file + locators: { + CustomTreeItem: { + constructor: (label: string) => By.xpath(`.//div[@role='listitem' and .//span[text()='${label}']]`), + }, + }, +}; diff --git a/packages/locators/lib/1.47.0.ts b/packages/locators/lib/1.47.0.ts index c079a8537..e59e09db8 100644 --- a/packages/locators/lib/1.47.0.ts +++ b/packages/locators/lib/1.47.0.ts @@ -15,19 +15,19 @@ * limitations under the License. */ -import { LocatorDiff } from "@redhat-developer/page-objects"; -import { By } from "selenium-webdriver"; +import { LocatorDiff } from '@redhat-developer/page-objects'; +import { By } from 'selenium-webdriver'; export const diff: LocatorDiff = { - locators: { - CustomTreeItem: { - constructor: (label: string) => By.xpath(`.//div[@role='treeitem' and .//span[text()='${label}']]`) - }, - ScmView: { - changes: By.xpath(`.//div[@role="treeitem" and .//div/text()="Changes"]`), - stagedChanges: By.xpath(`.//div[@role="treeitem" and .//div/text()="Staged Changes"]`), - providerTitle: By.className('name'), - providerType: By.className('description'), - } - } -}; \ No newline at end of file + locators: { + CustomTreeItem: { + constructor: (label: string) => By.xpath(`.//div[@role='treeitem' and .//span[text()='${label}']]`), + }, + ScmView: { + changes: By.xpath(`.//div[@role="treeitem" and .//div/text()="Changes"]`), + stagedChanges: By.xpath(`.//div[@role="treeitem" and .//div/text()="Staged Changes"]`), + providerTitle: By.className('name'), + providerType: By.className('description'), + }, + }, +}; diff --git a/packages/locators/lib/1.49.0.ts b/packages/locators/lib/1.49.0.ts index 97e74b2a2..35211f5a3 100644 --- a/packages/locators/lib/1.49.0.ts +++ b/packages/locators/lib/1.49.0.ts @@ -15,13 +15,13 @@ * limitations under the License. */ -import { LocatorDiff } from "@redhat-developer/page-objects"; -import { By } from "selenium-webdriver"; +import { LocatorDiff } from '@redhat-developer/page-objects'; +import { By } from 'selenium-webdriver'; export const diff: LocatorDiff = { - locators: { - TerminalView: { - constructor: By.className('terminal-outer-container') - } - } -}; \ No newline at end of file + locators: { + TerminalView: { + constructor: By.className('terminal-outer-container'), + }, + }, +}; diff --git a/packages/locators/lib/1.50.0.ts b/packages/locators/lib/1.50.0.ts index d51a01c49..34f63e2f8 100644 --- a/packages/locators/lib/1.50.0.ts +++ b/packages/locators/lib/1.50.0.ts @@ -15,13 +15,13 @@ * limitations under the License. */ -import { LocatorDiff } from "@redhat-developer/page-objects"; -import { By } from "selenium-webdriver"; +import { LocatorDiff } from '@redhat-developer/page-objects'; +import { By } from 'selenium-webdriver'; export const diff: LocatorDiff = { - locators: { - EditorView: { - closeTab: By.className('codicon-close') - } - } -}; \ No newline at end of file + locators: { + EditorView: { + closeTab: By.className('codicon-close'), + }, + }, +}; diff --git a/packages/locators/lib/1.52.0.ts b/packages/locators/lib/1.52.0.ts index cdb6b5a15..3133712df 100644 --- a/packages/locators/lib/1.52.0.ts +++ b/packages/locators/lib/1.52.0.ts @@ -15,13 +15,13 @@ * limitations under the License. */ -import { LocatorDiff } from "@redhat-developer/page-objects"; -import { By } from "selenium-webdriver"; +import { LocatorDiff } from '@redhat-developer/page-objects'; +import { By } from 'selenium-webdriver'; export const diff: LocatorDiff = { - locators: { - DefaultTreeItem: { - tooltip: By.className('monaco-icon-label-container') - } - } -}; \ No newline at end of file + locators: { + DefaultTreeItem: { + tooltip: By.className('monaco-icon-label-container'), + }, + }, +}; diff --git a/packages/locators/lib/1.54.0.ts b/packages/locators/lib/1.54.0.ts index 561bbd294..a301f9cfe 100644 --- a/packages/locators/lib/1.54.0.ts +++ b/packages/locators/lib/1.54.0.ts @@ -15,14 +15,14 @@ * limitations under the License. */ -import { LocatorDiff } from "@redhat-developer/page-objects"; -import { By } from "selenium-webdriver"; +import { LocatorDiff } from '@redhat-developer/page-objects'; +import { By } from 'selenium-webdriver'; export const diff: LocatorDiff = { - locators: { - TerminalView: { - newTerminal: By.xpath(`.//a[starts-with(@title, 'Create New Integrated Terminal')]`), - killTerminal: By.xpath(`.//a[@title='Kill the Active Terminal Instance']`) - } - } -}; \ No newline at end of file + locators: { + TerminalView: { + newTerminal: By.xpath(`.//a[starts-with(@title, 'Create New Integrated Terminal')]`), + killTerminal: By.xpath(`.//a[@title='Kill the Active Terminal Instance']`), + }, + }, +}; diff --git a/packages/locators/lib/1.56.0.ts b/packages/locators/lib/1.56.0.ts index 5df207511..d2a70e2e0 100644 --- a/packages/locators/lib/1.56.0.ts +++ b/packages/locators/lib/1.56.0.ts @@ -15,16 +15,16 @@ * limitations under the License. */ -import { LocatorDiff } from "@redhat-developer/page-objects"; -import { By } from "selenium-webdriver"; +import { LocatorDiff } from '@redhat-developer/page-objects'; +import { By } from 'selenium-webdriver'; export const diff: LocatorDiff = { - locators: { - EditorView: { - webView: By.xpath(`.//div[starts-with(@id, 'webview-editor')]`) - }, - TerminalView: { - newTerminal: By.xpath(`.//a[@title='New Terminal']`) - } - } -}; \ No newline at end of file + locators: { + EditorView: { + webView: By.xpath(`.//div[starts-with(@id, 'webview-editor')]`), + }, + TerminalView: { + newTerminal: By.xpath(`.//a[@title='New Terminal']`), + }, + }, +}; diff --git a/packages/locators/lib/1.57.0.ts b/packages/locators/lib/1.57.0.ts index a08b8c852..70e194a2d 100644 --- a/packages/locators/lib/1.57.0.ts +++ b/packages/locators/lib/1.57.0.ts @@ -15,16 +15,16 @@ * limitations under the License. */ -import { LocatorDiff } from "@redhat-developer/page-objects"; -import { By } from "selenium-webdriver"; +import { LocatorDiff } from '@redhat-developer/page-objects'; +import { By } from 'selenium-webdriver'; export const diff: LocatorDiff = { - locators: { - EditorView: { - settingsEditor: By.className('settings-editor') - }, - TerminalView: { - constructor: By.className('integrated-terminal') - } - } -}; \ No newline at end of file + locators: { + EditorView: { + settingsEditor: By.className('settings-editor'), + }, + TerminalView: { + constructor: By.className('integrated-terminal'), + }, + }, +}; diff --git a/packages/locators/lib/1.59.0.ts b/packages/locators/lib/1.59.0.ts index 6ec91c304..b95572a07 100644 --- a/packages/locators/lib/1.59.0.ts +++ b/packages/locators/lib/1.59.0.ts @@ -15,12 +15,12 @@ * limitations under the License. */ -import { By, LocatorDiff } from "@redhat-developer/page-objects"; +import { By, LocatorDiff } from '@redhat-developer/page-objects'; export const diff: LocatorDiff = { - locators: { - FindWidget: { - toggleReplace: By.xpath(`.//div[@title="Toggle Replace"]`), - } - } -}; \ No newline at end of file + locators: { + FindWidget: { + toggleReplace: By.xpath(`.//div[@title="Toggle Replace"]`), + }, + }, +}; diff --git a/packages/locators/lib/1.60.0.ts b/packages/locators/lib/1.60.0.ts index 586834db8..834774a65 100644 --- a/packages/locators/lib/1.60.0.ts +++ b/packages/locators/lib/1.60.0.ts @@ -15,12 +15,12 @@ * limitations under the License. */ -import { LocatorDiff } from "@redhat-developer/page-objects"; +import { LocatorDiff } from '@redhat-developer/page-objects'; export const diff: LocatorDiff = { - locators: { - TerminalView: { - newCommand: 'terminal: create new terminal' - } - } -}; \ No newline at end of file + locators: { + TerminalView: { + newCommand: 'terminal: create new terminal', + }, + }, +}; diff --git a/packages/locators/lib/1.61.0.ts b/packages/locators/lib/1.61.0.ts index 7ebeadf75..c23afa8c6 100644 --- a/packages/locators/lib/1.61.0.ts +++ b/packages/locators/lib/1.61.0.ts @@ -15,15 +15,15 @@ * limitations under the License. */ -import { By, LocatorDiff } from "@redhat-developer/page-objects"; +import { By, LocatorDiff } from '@redhat-developer/page-objects'; export const diff: LocatorDiff = { - locators: { - BottomBarPanel: { - globalActions: By.className('global-actions') - }, - DefaultTreeItem: { - tooltip: By.className('monaco-icon-label') - } - } -}; \ No newline at end of file + locators: { + BottomBarPanel: { + globalActions: By.className('global-actions'), + }, + DefaultTreeItem: { + tooltip: By.className('monaco-icon-label'), + }, + }, +}; diff --git a/packages/locators/lib/1.66.0.ts b/packages/locators/lib/1.66.0.ts index 12a9687e6..a3f2b33f6 100644 --- a/packages/locators/lib/1.66.0.ts +++ b/packages/locators/lib/1.66.0.ts @@ -15,16 +15,16 @@ * limitations under the License. */ -import { By, LocatorDiff } from "@redhat-developer/page-objects"; +import { By, LocatorDiff } from '@redhat-developer/page-objects'; export const diff: LocatorDiff = { - locators: { - ContextMenu: { - constructor: By.className('monaco-menu') - }, - BottomBarPanel: { - close: 'Close Panel', - closeAction: By.className('codicon-panel-close') - } - } -}; \ No newline at end of file + locators: { + ContextMenu: { + constructor: By.className('monaco-menu'), + }, + BottomBarPanel: { + close: 'Close Panel', + closeAction: By.className('codicon-panel-close'), + }, + }, +}; diff --git a/packages/locators/lib/1.70.0.ts b/packages/locators/lib/1.70.0.ts index ac7454cab..dc70290a7 100644 --- a/packages/locators/lib/1.70.0.ts +++ b/packages/locators/lib/1.70.0.ts @@ -15,26 +15,26 @@ * limitations under the License. */ -import { By, LocatorDiff } from "@redhat-developer/page-objects"; +import { By, LocatorDiff } from '@redhat-developer/page-objects'; export const diff: LocatorDiff = { - locators: { - BottomBarPanel: { - action: (label: string) => By.xpath(`.//li[starts-with(@title, '${label}')]`) - }, - ViewSection: { - buttonLabel: 'aria-label' - }, - ViewTitlePart: { - action: By.className(`action-label`), - actionConstructor: (title: string) => By.xpath(`.//a[@title='${title}']`) - }, - ScmView: { - action: By.className('action-item menu-entry'), - actionConstructor: (title: string) => By.xpath(`.//li[@title='${title}']`) - }, - EditorView: { - attribute: 'aria-label' - } - } -}; \ No newline at end of file + locators: { + BottomBarPanel: { + action: (label: string) => By.xpath(`.//li[starts-with(@title, '${label}')]`), + }, + ViewSection: { + buttonLabel: 'aria-label', + }, + ViewTitlePart: { + action: By.className(`action-label`), + actionConstructor: (title: string) => By.xpath(`.//a[@title='${title}']`), + }, + ScmView: { + action: By.className('action-item menu-entry'), + actionConstructor: (title: string) => By.xpath(`.//li[@title='${title}']`), + }, + EditorView: { + attribute: 'aria-label', + }, + }, +}; diff --git a/packages/locators/lib/1.71.0.ts b/packages/locators/lib/1.71.0.ts index 0f15a7e76..7dea08847 100644 --- a/packages/locators/lib/1.71.0.ts +++ b/packages/locators/lib/1.71.0.ts @@ -15,19 +15,19 @@ * limitations under the License. */ -import { LocatorDiff } from "@redhat-developer/page-objects"; -import { By } from "selenium-webdriver"; +import { LocatorDiff } from '@redhat-developer/page-objects'; +import { By } from 'selenium-webdriver'; export const diff: LocatorDiff = { - locators: { - EditorView: { - attribute: 'aria-label' - }, - TreeItem: { - actionTitle: 'aria-label' - }, - Input: { - multiSelectIndex: (index: number) => By.xpath(`.//div[@role='checkbox' and @data-index='${index}']`) - } - } + locators: { + EditorView: { + attribute: 'aria-label', + }, + TreeItem: { + actionTitle: 'aria-label', + }, + Input: { + multiSelectIndex: (index: number) => By.xpath(`.//div[@role='checkbox' and @data-index='${index}']`), + }, + }, }; diff --git a/packages/locators/lib/1.73.0.ts b/packages/locators/lib/1.73.0.ts index 7bd6c48ab..cdc25c955 100644 --- a/packages/locators/lib/1.73.0.ts +++ b/packages/locators/lib/1.73.0.ts @@ -15,11 +15,11 @@ * limitations under the License. */ -import { By, LocatorDiff } from "@redhat-developer/page-objects"; +import { By, LocatorDiff } from '@redhat-developer/page-objects'; export const diff: LocatorDiff = { - locators: { - ProblemsView: { - markersFilter: By.className('viewpane-filter') - } - } -}; \ No newline at end of file + locators: { + ProblemsView: { + markersFilter: By.className('viewpane-filter'), + }, + }, +}; diff --git a/packages/locators/lib/1.74.0.ts b/packages/locators/lib/1.74.0.ts index f4b6df37c..b7b3e623e 100644 --- a/packages/locators/lib/1.74.0.ts +++ b/packages/locators/lib/1.74.0.ts @@ -15,12 +15,12 @@ * limitations under the License. */ -import { By, LocatorDiff } from "@redhat-developer/page-objects"; +import { By, LocatorDiff } from '@redhat-developer/page-objects'; export const diff: LocatorDiff = { - locators: { - WebView: { - container: (id: string) => By.xpath(`.//div[@data-parent-flow-to-element-id='${id}']`), - attribute: 'id' - } - } -}; \ No newline at end of file + locators: { + WebView: { + container: (id: string) => By.xpath(`.//div[@data-parent-flow-to-element-id='${id}']`), + attribute: 'id', + }, + }, +}; diff --git a/packages/locators/lib/1.83.0.ts b/packages/locators/lib/1.83.0.ts index ca6c48c98..3173fdc49 100644 --- a/packages/locators/lib/1.83.0.ts +++ b/packages/locators/lib/1.83.0.ts @@ -15,11 +15,11 @@ * limitations under the License. */ -import { By, LocatorDiff } from "@redhat-developer/page-objects"; +import { By, LocatorDiff } from '@redhat-developer/page-objects'; export const diff: LocatorDiff = { - locators: { - ViewSection: { - actionConstructor: (label: string) => By.xpath(`.//a[contains(@class, 'action-label') and @role='button' and @aria-label='${label}']`), - } - } -}; \ No newline at end of file + locators: { + ViewSection: { + actionConstructor: (label: string) => By.xpath(`.//a[contains(@class, 'action-label') and @role='button' and @aria-label='${label}']`), + }, + }, +}; diff --git a/packages/locators/lib/1.84.0.ts b/packages/locators/lib/1.84.0.ts index 40ea3bb53..2e045c9f4 100644 --- a/packages/locators/lib/1.84.0.ts +++ b/packages/locators/lib/1.84.0.ts @@ -15,11 +15,11 @@ * limitations under the License. */ -import { By, LocatorDiff } from "@redhat-developer/page-objects"; +import { By, LocatorDiff } from '@redhat-developer/page-objects'; export const diff: LocatorDiff = { - locators: { - BottomBarPanel: { - tabContainer: By.className('composite-bar-container') - } - } -}; \ No newline at end of file + locators: { + BottomBarPanel: { + tabContainer: By.className('composite-bar-container'), + }, + }, +}; diff --git a/packages/locators/lib/1.85.0.ts b/packages/locators/lib/1.85.0.ts index 6c6b42650..02165f306 100644 --- a/packages/locators/lib/1.85.0.ts +++ b/packages/locators/lib/1.85.0.ts @@ -15,14 +15,14 @@ * limitations under the License. */ -import { By, LocatorDiff } from "@redhat-developer/page-objects"; +import { By, LocatorDiff } from '@redhat-developer/page-objects'; export const diff: LocatorDiff = { - locators: { - Workbench: { - notificationContainer: By.className('notifications-list-container') - }, - ViewTitlePart: { - actionConstructor: (title: string) => By.xpath(`.//a[@aria-label='${title}']`) - }, - } -}; \ No newline at end of file + locators: { + Workbench: { + notificationContainer: By.className('notifications-list-container'), + }, + ViewTitlePart: { + actionConstructor: (title: string) => By.xpath(`.//a[@aria-label='${title}']`), + }, + }, +}; diff --git a/packages/locators/lib/1.87.0.ts b/packages/locators/lib/1.87.0.ts index 5ad3111f5..96ccb9d65 100644 --- a/packages/locators/lib/1.87.0.ts +++ b/packages/locators/lib/1.87.0.ts @@ -15,41 +15,41 @@ * limitations under the License. */ -import { By, fromText, LocatorDiff } from "@redhat-developer/page-objects"; +import { By, fromText, LocatorDiff } from '@redhat-developer/page-objects'; export const diff: LocatorDiff = { - locators: { - BottomBarPanel: { - close: 'Hide Panel', - globalActions: By.className('global-actions'), - action: (label: string) => By.xpath(`.//a[starts-with(@aria-label, '${label}')]`) - }, - SettingsEditor: { - comboValue: 'value' - }, - FindWidget: { - checkbox: (title: string) => By.xpath(`.//div[@role='checkbox' and starts-with(@aria-label, "${title}")]`) - }, - Notification: { - buttonConstructor: (title: string) => By.xpath(`.//a[@role='button' and text()='${title}']`), - actionLabel: { - value: fromText() - } - }, - ScmView: { - action: By.className('action-label'), - actionConstructor: (title: string) => By.xpath(`.//a[@aria-label='${title}']`), - actionLabel: 'aria-label' - }, - ViewTitlePart: { - actionLabel: 'aria-label' - }, - DefaultTreeItem: { - labelAttribute: 'aria-label' - }, - Dialog: { - buttonLabel: { - value: fromText() - } - } - } -}; \ No newline at end of file + locators: { + BottomBarPanel: { + close: 'Hide Panel', + globalActions: By.className('global-actions'), + action: (label: string) => By.xpath(`.//a[starts-with(@aria-label, '${label}')]`), + }, + SettingsEditor: { + comboValue: 'value', + }, + FindWidget: { + checkbox: (title: string) => By.xpath(`.//div[@role='checkbox' and starts-with(@aria-label, "${title}")]`), + }, + Notification: { + buttonConstructor: (title: string) => By.xpath(`.//a[@role='button' and text()='${title}']`), + actionLabel: { + value: fromText(), + }, + }, + ScmView: { + action: By.className('action-label'), + actionConstructor: (title: string) => By.xpath(`.//a[@aria-label='${title}']`), + actionLabel: 'aria-label', + }, + ViewTitlePart: { + actionLabel: 'aria-label', + }, + DefaultTreeItem: { + labelAttribute: 'aria-label', + }, + Dialog: { + buttonLabel: { + value: fromText(), + }, + }, + }, +}; diff --git a/packages/locators/lib/1.88.0.ts b/packages/locators/lib/1.88.0.ts index 873482edf..cf15f5783 100644 --- a/packages/locators/lib/1.88.0.ts +++ b/packages/locators/lib/1.88.0.ts @@ -15,13 +15,13 @@ * limitations under the License. */ -import { By, LocatorDiff } from "@redhat-developer/page-objects"; +import { By, LocatorDiff } from '@redhat-developer/page-objects'; export const diff: LocatorDiff = { - locators: { - FindWidget: { - toggleReplace: By.xpath(`.//div[@aria-label="Toggle Replace"]`), - button: (title: string) => By.xpath(`.//div[@role='button' and starts-with(@aria-label, "${title}")]`) - } - } -}; \ No newline at end of file + locators: { + FindWidget: { + toggleReplace: By.xpath(`.//div[@aria-label="Toggle Replace"]`), + button: (title: string) => By.xpath(`.//div[@role='button' and starts-with(@aria-label, "${title}")]`), + }, + }, +}; diff --git a/packages/locators/tsconfig.json b/packages/locators/tsconfig.json index 10268697f..09cfb3cfe 100644 --- a/packages/locators/tsconfig.json +++ b/packages/locators/tsconfig.json @@ -4,8 +4,5 @@ "outDir": "out", "rootDir": "." }, - "include": [ - "index.ts", - "lib" - ] -} \ No newline at end of file + "include": ["index.ts", "lib"] +} diff --git a/packages/page-objects/src/components/AbstractElement.ts b/packages/page-objects/src/components/AbstractElement.ts index bbb2acc34..8e23349ce 100644 --- a/packages/page-objects/src/components/AbstractElement.ts +++ b/packages/page-objects/src/components/AbstractElement.ts @@ -15,75 +15,74 @@ * limitations under the License. */ -import { WebElement, WebDriver, Locator, until, By, Key } from "selenium-webdriver"; -import { Locators } from "../locators/locators"; +import { WebElement, WebDriver, Locator, until, By, Key } from 'selenium-webdriver'; +import { Locators } from '../locators/locators'; /** * Default wrapper for webelement */ export abstract class AbstractElement extends WebElement { + public static readonly ctlKey = process.platform === 'darwin' ? Key.COMMAND : Key.CONTROL; + protected static driver: WebDriver; + protected static locators: Locators; + protected static versionInfo: { version: string; browser: string }; + protected enclosingItem: WebElement; - public static readonly ctlKey = process.platform === 'darwin' ? Key.COMMAND : Key.CONTROL; - protected static driver: WebDriver; - protected static locators: Locators; - protected static versionInfo: { version: string; browser: string }; - protected enclosingItem: WebElement; + /** + * Constructs a new element from a Locator or an existing WebElement + * @param base WebDriver compatible Locator for the given element or a reference to an existing WeBelement + * @param enclosingItem Locator or a WebElement reference to an element containing the element being constructed + * this will be used to narrow down the search for the underlying DOM element + */ + constructor(base: Locator | WebElement, enclosingItem?: WebElement | Locator) { + let item: WebElement = AbstractElement.driver.findElement(By.css('html')); + if (!enclosingItem) { + enclosingItem = item; + } + if (enclosingItem instanceof WebElement) { + item = enclosingItem; + } else { + item = AbstractElement.driver.findElement(enclosingItem); + } - /** - * Constructs a new element from a Locator or an existing WebElement - * @param base WebDriver compatible Locator for the given element or a reference to an existing WeBelement - * @param enclosingItem Locator or a WebElement reference to an element containing the element being constructed - * this will be used to narrow down the search for the underlying DOM element - */ - constructor(base: Locator | WebElement, enclosingItem?: WebElement | Locator) { - let item: WebElement = AbstractElement.driver.findElement(By.css('html')); - if (!enclosingItem) { - enclosingItem = item; - } - if (enclosingItem instanceof WebElement) { - item = enclosingItem; - } else { - item = AbstractElement.driver.findElement(enclosingItem); - } + if (base instanceof WebElement) { + super(AbstractElement.driver, base.getId()); + } else { + const toFind = item.findElement(base); + const id = toFind.getId(); + super(AbstractElement.driver, id); + } + this.enclosingItem = item; + } - if (base instanceof WebElement) { - super(AbstractElement.driver, base.getId()); - } else { - const toFind = item.findElement(base); - const id = toFind.getId(); - super(AbstractElement.driver, id); - } - this.enclosingItem = item; - } + async isEnabled(): Promise { + return (await super.isEnabled()) && (await AbstractElement.locators.AbstractElement.enabled(this)); + } - async isEnabled(): Promise { - return await super.isEnabled() && await AbstractElement.locators.AbstractElement.enabled(this); - } + async isSelected(): Promise { + return (await super.isSelected()) && (await AbstractElement.locators.AbstractElement.selected(this)); + } - async isSelected(): Promise { - return await super.isSelected() && await AbstractElement.locators.AbstractElement.selected(this); - } + /** + * Wait for the element to become visible + * @param timeout custom timeout for the wait + * @returns thenable self reference + */ + async wait(timeout: number = 5000): Promise { + await this.getDriver().wait(until.elementIsVisible(this), timeout); + return this; + } - /** - * Wait for the element to become visible - * @param timeout custom timeout for the wait - * @returns thenable self reference - */ - async wait(timeout: number = 5000): Promise { - await this.getDriver().wait(until.elementIsVisible(this), timeout); - return this; - } + /** + * Return a reference to the WebElement containing this element + */ + getEnclosingElement(): WebElement { + return this.enclosingItem; + } - /** - * Return a reference to the WebElement containing this element - */ - getEnclosingElement(): WebElement { - return this.enclosingItem; - } - - static init(locators: Locators, driver: WebDriver, browser: string, version: string) { - AbstractElement.locators = locators; - AbstractElement.driver = driver; - AbstractElement.versionInfo = { version: version, browser: browser}; - } -} \ No newline at end of file + static init(locators: Locators, driver: WebDriver, browser: string, version: string) { + AbstractElement.locators = locators; + AbstractElement.driver = driver; + AbstractElement.versionInfo = { version: version, browser: browser }; + } +} diff --git a/packages/page-objects/src/components/ElementWithContextMenu.ts b/packages/page-objects/src/components/ElementWithContextMenu.ts index a12cf1b1c..90f5c30de 100644 --- a/packages/page-objects/src/components/ElementWithContextMenu.ts +++ b/packages/page-objects/src/components/ElementWithContextMenu.ts @@ -15,39 +15,37 @@ * limitations under the License. */ -import { AbstractElement } from "./AbstractElement"; -import { ContextMenu } from ".."; +import { AbstractElement } from './AbstractElement'; +import { ContextMenu } from '..'; import { until, error } from 'selenium-webdriver'; /** * Abstract element that has a context menu */ export abstract class ElementWithContexMenu extends AbstractElement { + /** + * Open context menu on the element + */ + async openContextMenu(): Promise { + const workbench = await this.getDriver().findElement(ElementWithContexMenu.locators.Workbench.constructor); + const menus = await workbench.findElements(ElementWithContexMenu.locators.ContextMenu.contextView); - /** - * Open context menu on the element - */ - async openContextMenu(): Promise { - const workbench = await this.getDriver().findElement(ElementWithContexMenu.locators.Workbench.constructor); - const menus = await workbench.findElements(ElementWithContexMenu.locators.ContextMenu.contextView); + if (menus.length < 1) { + await this.getDriver().actions().contextClick(this).perform(); + await this.getDriver().wait(until.elementLocated(ElementWithContexMenu.locators.ContextMenu.contextView), 2000); + return new ContextMenu(workbench).wait(); + } else if ((await workbench.findElements(ElementWithContexMenu.locators.ContextMenu.viewBlock)).length > 0) { + await this.getDriver().actions().contextClick(this).perform(); + try { + await this.getDriver().wait(until.elementIsNotVisible(this), 1000); + } catch (err) { + if (!(err instanceof error.StaleElementReferenceError)) { + throw err; + } + } + } + await this.getDriver().actions().contextClick(this).perform(); - if (menus.length < 1) { - await this.getDriver().actions().contextClick(this).perform(); - await this.getDriver().wait(until.elementLocated(ElementWithContexMenu.locators.ContextMenu.contextView), 2000); - return new ContextMenu(workbench).wait(); - } else if ((await workbench.findElements(ElementWithContexMenu.locators.ContextMenu.viewBlock)).length > 0) { - - await this.getDriver().actions().contextClick(this).perform(); - try { - await this.getDriver().wait(until.elementIsNotVisible(this), 1000); - } catch (err) { - if (!(err instanceof error.StaleElementReferenceError)) { - throw err; - } - } - } - await this.getDriver().actions().contextClick(this).perform(); - - return new ContextMenu(workbench).wait(); - } -} \ No newline at end of file + return new ContextMenu(workbench).wait(); + } +} diff --git a/packages/page-objects/src/components/WebviewMixin.ts b/packages/page-objects/src/components/WebviewMixin.ts index 22e832a5e..8ce6e0211 100644 --- a/packages/page-objects/src/components/WebviewMixin.ts +++ b/packages/page-objects/src/components/WebviewMixin.ts @@ -15,8 +15,8 @@ * limitations under the License. */ -import { Locator, WebElement, until } from "selenium-webdriver"; -import { AbstractElement } from "./AbstractElement"; +import { Locator, WebElement, until } from 'selenium-webdriver'; +import { AbstractElement } from './AbstractElement'; /** * Heavily inspired by https://stackoverflow.com/a/65418734 @@ -28,17 +28,17 @@ type Constructor = new (...args: any[]) => T; * The interface that a class is required to have in order to use the Webview mixin. */ interface WebviewMixable extends AbstractElement { - getViewToSwitchTo(handle: string): Promise; + getViewToSwitchTo(handle: string): Promise; } /** * The interface that is exposed by applying this mixin. */ export interface WebviewMixinType { - findWebElement(locator: Locator): Promise; - findWebElements(locator: Locator): Promise; - switchToFrame(timeout?: number): Promise; - switchBack(): Promise; + findWebElement(locator: Locator): Promise; + findWebElements(locator: Locator): Promise; + switchToFrame(timeout?: number): Promise; + switchBack(): Promise; } /** @@ -47,76 +47,69 @@ export interface WebviewMixinType { * @param Base the class to mixin * @returns a class that has the ability to access a webview */ -export default function >( - Base: TBase -): Constructor & WebviewMixinType> { - return class extends Base implements WebviewMixinType { - /** - * Cannot use static element, since this class is unnamed. - */ - private handle: string | undefined; +export default function >(Base: TBase): Constructor & WebviewMixinType> { + return class extends Base implements WebviewMixinType { + /** + * Cannot use static element, since this class is unnamed. + */ + private handle: string | undefined; - /** - * Search for an element inside the webview iframe. - * Requires webdriver being switched to the webview iframe first. - * (Will attempt to search from the main DOM root otherwise) - * - * @param locator webdriver locator to search by - * @returns promise resolving to WebElement when found - */ - async findWebElement(locator: Locator): Promise { - return await this.getDriver().findElement(locator); - } + /** + * Search for an element inside the webview iframe. + * Requires webdriver being switched to the webview iframe first. + * (Will attempt to search from the main DOM root otherwise) + * + * @param locator webdriver locator to search by + * @returns promise resolving to WebElement when found + */ + async findWebElement(locator: Locator): Promise { + return await this.getDriver().findElement(locator); + } - /** - * Search for all element inside the webview iframe by a given locator - * Requires webdriver being switched to the webview iframe first. - * (Will attempt to search from the main DOM root otherwise) - * - * @param locator webdriver locator to search by - * @returns promise resolving to a list of WebElement objects - */ - async findWebElements(locator: Locator): Promise { - return await this.getDriver().findElements(locator); - } + /** + * Search for all element inside the webview iframe by a given locator + * Requires webdriver being switched to the webview iframe first. + * (Will attempt to search from the main DOM root otherwise) + * + * @param locator webdriver locator to search by + * @returns promise resolving to a list of WebElement objects + */ + async findWebElements(locator: Locator): Promise { + return await this.getDriver().findElements(locator); + } - /** - * Switch the underlying webdriver context to the webview iframe. - * This allows using the findWebElement methods. - * Note that only elements inside the webview iframe will be accessible. - * Use the switchBack method to switch to the original context. - */ - async switchToFrame(timeout: number = 5000): Promise { - if (!this.handle) { - this.handle = await this.getDriver().getWindowHandle(); - } + /** + * Switch the underlying webdriver context to the webview iframe. + * This allows using the findWebElement methods. + * Note that only elements inside the webview iframe will be accessible. + * Use the switchBack method to switch to the original context. + */ + async switchToFrame(timeout: number = 5000): Promise { + if (!this.handle) { + this.handle = await this.getDriver().getWindowHandle(); + } - const view = await this.getViewToSwitchTo(this.handle); + const view = await this.getViewToSwitchTo(this.handle); - if (!view) { - return; - } + if (!view) { + return; + } - await this.getDriver().switchTo().frame(view); + await this.getDriver().switchTo().frame(view); - await this.getDriver().wait( - until.elementLocated(AbstractElement.locators.WebView.activeFrame), - timeout - ); - const frame = await this.getDriver().findElement( - AbstractElement.locators.WebView.activeFrame - ); - await this.getDriver().switchTo().frame(frame); - } + await this.getDriver().wait(until.elementLocated(AbstractElement.locators.WebView.activeFrame), timeout); + const frame = await this.getDriver().findElement(AbstractElement.locators.WebView.activeFrame); + await this.getDriver().switchTo().frame(frame); + } - /** - * Switch the underlying webdriver back to the original window - */ - async switchBack(): Promise { - if (!this.handle) { - this.handle = await this.getDriver().getWindowHandle(); - } - return await this.getDriver().switchTo().window(this.handle); - } - } as unknown as Constructor & WebviewMixinType>; + /** + * Switch the underlying webdriver back to the original window + */ + async switchBack(): Promise { + if (!this.handle) { + this.handle = await this.getDriver().getWindowHandle(); + } + return await this.getDriver().switchTo().window(this.handle); + } + } as unknown as Constructor & WebviewMixinType>; } diff --git a/packages/page-objects/src/components/activityBar/ActionsControl.ts b/packages/page-objects/src/components/activityBar/ActionsControl.ts index 127bea3ec..97f59e626 100644 --- a/packages/page-objects/src/components/activityBar/ActionsControl.ts +++ b/packages/page-objects/src/components/activityBar/ActionsControl.ts @@ -15,30 +15,30 @@ * limitations under the License. */ -import { WebElement } from "selenium-webdriver"; -import { ActivityBar, ContextMenu } from "../.."; -import { ElementWithContexMenu } from "../ElementWithContextMenu"; +import { WebElement } from 'selenium-webdriver'; +import { ActivityBar, ContextMenu } from '../..'; +import { ElementWithContexMenu } from '../ElementWithContextMenu'; /** * Page object representing the global action controls on the bottom of the action bar */ export class ActionsControl extends ElementWithContexMenu { - constructor(element: WebElement, bar: ActivityBar) { - super(element, bar); - } + constructor(element: WebElement, bar: ActivityBar) { + super(element, bar); + } - /** - * Open the context menu bound to this global action - * @returns Promise resolving to ContextMenu object representing the action's menu - */ - async openActionMenu(): Promise { - return await this.openContextMenu(); - } + /** + * Open the context menu bound to this global action + * @returns Promise resolving to ContextMenu object representing the action's menu + */ + async openActionMenu(): Promise { + return await this.openContextMenu(); + } - /** - * Returns the title of the associated action - */ - async getTitle(): Promise { - return await this.getAttribute('aria-label'); - } -} \ No newline at end of file + /** + * Returns the title of the associated action + */ + async getTitle(): Promise { + return await this.getAttribute('aria-label'); + } +} diff --git a/packages/page-objects/src/components/activityBar/ActivityBar.ts b/packages/page-objects/src/components/activityBar/ActivityBar.ts index dff680974..d9bcd3959 100644 --- a/packages/page-objects/src/components/activityBar/ActivityBar.ts +++ b/packages/page-objects/src/components/activityBar/ActivityBar.ts @@ -15,74 +15,78 @@ * limitations under the License. */ -import { ActionsControl, ViewControl } from "../.."; -import { ElementWithContexMenu } from "../ElementWithContextMenu"; +import { ActionsControl, ViewControl } from '../..'; +import { ElementWithContexMenu } from '../ElementWithContextMenu'; /** * Page object representing the left side activity bar in VS Code */ export class ActivityBar extends ElementWithContexMenu { - constructor() { - super(ActivityBar.locators.ActivityBar.constructor, ActivityBar.locators.Workbench.constructor); - } + constructor() { + super(ActivityBar.locators.ActivityBar.constructor, ActivityBar.locators.Workbench.constructor); + } - /** - * Find all view containers displayed in the activity bar - * @returns Promise resolving to array of ViewControl objects - */ - async getViewControls(): Promise { - const views: ViewControl[] = []; - const viewContainer = await this.findElement(ActivityBar.locators.ActivityBar.viewContainer); - for(const element of await viewContainer.findElements(ActivityBar.locators.ActivityBar.actionItem)) { - views.push(await new ViewControl(element, this).wait()); - } - return views; - } + /** + * Find all view containers displayed in the activity bar + * @returns Promise resolving to array of ViewControl objects + */ + async getViewControls(): Promise { + const views: ViewControl[] = []; + const viewContainer = await this.findElement(ActivityBar.locators.ActivityBar.viewContainer); + for (const element of await viewContainer.findElements(ActivityBar.locators.ActivityBar.actionItem)) { + views.push(await new ViewControl(element, this).wait()); + } + return views; + } - /** - * Find a view container with a given title - * @param name title of the view - * @returns Promise resolving to ViewControl object representing the view selector, undefined if not found - */ - async getViewControl(name: string): Promise { - const controls = await this.getViewControls(); - const names = await Promise.all(controls.map(async (item) => { - return await item.getTitle(); - })); - const index = names.findIndex((value) => value.indexOf(name) > -1); - if (index > -1) { - return controls[index]; - } - return undefined; - } + /** + * Find a view container with a given title + * @param name title of the view + * @returns Promise resolving to ViewControl object representing the view selector, undefined if not found + */ + async getViewControl(name: string): Promise { + const controls = await this.getViewControls(); + const names = await Promise.all( + controls.map(async (item) => { + return await item.getTitle(); + }), + ); + const index = names.findIndex((value) => value.indexOf(name) > -1); + if (index > -1) { + return controls[index]; + } + return undefined; + } - /** - * Find all global action controls displayed on the bottom of the activity bar - * @returns Promise resolving to array of ActionsControl objects - */ - async getGlobalActions(): Promise { - const actions: ActionsControl[] = []; - const actionContainer = await this.findElement(ActivityBar.locators.ActivityBar.actionsContainer); - for(const element of await actionContainer.findElements(ActivityBar.locators.ActivityBar.actionItem)) { - actions.push(await new ActionsControl(element, this).wait()); - } - return actions; - } + /** + * Find all global action controls displayed on the bottom of the activity bar + * @returns Promise resolving to array of ActionsControl objects + */ + async getGlobalActions(): Promise { + const actions: ActionsControl[] = []; + const actionContainer = await this.findElement(ActivityBar.locators.ActivityBar.actionsContainer); + for (const element of await actionContainer.findElements(ActivityBar.locators.ActivityBar.actionItem)) { + actions.push(await new ActionsControl(element, this).wait()); + } + return actions; + } - /** - * Find an action control with a given title - * @param name title of the global action - * @returns Promise resolving to ActionsControl object representing the action selector, undefined if not found - */ - async getGlobalAction(name: string): Promise { - const actions = await this.getGlobalActions(); - const names = await Promise.all(actions.map(async (item) => { - return await item.getTitle(); - })); - const index = names.findIndex((value) => value.indexOf(name) > -1); - if (index > -1) { - return actions[index]; - } - return undefined; - } -} \ No newline at end of file + /** + * Find an action control with a given title + * @param name title of the global action + * @returns Promise resolving to ActionsControl object representing the action selector, undefined if not found + */ + async getGlobalAction(name: string): Promise { + const actions = await this.getGlobalActions(); + const names = await Promise.all( + actions.map(async (item) => { + return await item.getTitle(); + }), + ); + const index = names.findIndex((value) => value.indexOf(name) > -1); + if (index > -1) { + return actions[index]; + } + return undefined; + } +} diff --git a/packages/page-objects/src/components/activityBar/ViewControl.ts b/packages/page-objects/src/components/activityBar/ViewControl.ts index 6498a74ff..07b97fad6 100644 --- a/packages/page-objects/src/components/activityBar/ViewControl.ts +++ b/packages/page-objects/src/components/activityBar/ViewControl.ts @@ -15,59 +15,59 @@ * limitations under the License. */ -import { ActivityBar, DebugView, SideBarView, ScmView } from "../.."; -import { ElementWithContexMenu } from "../ElementWithContextMenu"; -import { WebElement } from "selenium-webdriver"; -import { NewScmView } from "../sidebar/scm/NewScmView"; +import { ActivityBar, DebugView, SideBarView, ScmView } from '../..'; +import { ElementWithContexMenu } from '../ElementWithContextMenu'; +import { WebElement } from 'selenium-webdriver'; +import { NewScmView } from '../sidebar/scm/NewScmView'; /** * Page object representing a view container item in the activity bar */ export class ViewControl extends ElementWithContexMenu { - constructor(element: WebElement, bar: ActivityBar) { - super(element, bar); - } + constructor(element: WebElement, bar: ActivityBar) { + super(element, bar); + } - /** - * Opens the associated view if not already open - * @returns Promise resolving to SideBarView object representing the opened view - */ - async openView(): Promise { - // Check whether view is already open - const klass = await this.getAttribute(ViewControl.locators.ViewControl.attribute); - if (klass.indexOf(ViewControl.locators.ViewControl.klass) < 0) { - await this.click(); - await ViewControl.driver.sleep(500); - } - const view = await new SideBarView().wait(); - if ((await view.findElements(ViewControl.locators.ViewControl.scmId)).length > 0) { - if (ViewControl.versionInfo.browser === 'vscode' && ViewControl.versionInfo.version >= '1.47.0') { - return new NewScmView().wait(); - } - return new ScmView().wait(); - } - if ((await view.findElements(ViewControl.locators.ViewControl.debugId)).length > 0) { - return new DebugView().wait(); - } - return view; - } + /** + * Opens the associated view if not already open + * @returns Promise resolving to SideBarView object representing the opened view + */ + async openView(): Promise { + // Check whether view is already open + const klass = await this.getAttribute(ViewControl.locators.ViewControl.attribute); + if (klass.indexOf(ViewControl.locators.ViewControl.klass) < 0) { + await this.click(); + await ViewControl.driver.sleep(500); + } + const view = await new SideBarView().wait(); + if ((await view.findElements(ViewControl.locators.ViewControl.scmId)).length > 0) { + if (ViewControl.versionInfo.browser === 'vscode' && ViewControl.versionInfo.version >= '1.47.0') { + return new NewScmView().wait(); + } + return new ScmView().wait(); + } + if ((await view.findElements(ViewControl.locators.ViewControl.debugId)).length > 0) { + return new DebugView().wait(); + } + return view; + } - /** - * Closes the associated view if not already closed - * @returns Promise resolving when the view closes - */ - async closeView(): Promise { - const klass = await this.getAttribute(ViewControl.locators.ViewControl.attribute); - if (klass.indexOf(ViewControl.locators.ViewControl.klass) > -1) { - await this.click(); - } - } + /** + * Closes the associated view if not already closed + * @returns Promise resolving when the view closes + */ + async closeView(): Promise { + const klass = await this.getAttribute(ViewControl.locators.ViewControl.attribute); + if (klass.indexOf(ViewControl.locators.ViewControl.klass) > -1) { + await this.click(); + } + } - /** - * Returns the title of the associated view - */ - async getTitle(): Promise { - const badge = await this.findElement(ViewControl.locators.ViewControl.badge); - return await badge.getAttribute('aria-label'); - } -} \ No newline at end of file + /** + * Returns the title of the associated view + */ + async getTitle(): Promise { + const badge = await this.findElement(ViewControl.locators.ViewControl.badge); + return await badge.getAttribute('aria-label'); + } +} diff --git a/packages/page-objects/src/components/bottomBar/AbstractViews.ts b/packages/page-objects/src/components/bottomBar/AbstractViews.ts index c7337331e..4a03f1c4a 100644 --- a/packages/page-objects/src/components/bottomBar/AbstractViews.ts +++ b/packages/page-objects/src/components/bottomBar/AbstractViews.ts @@ -16,94 +16,101 @@ */ import { Key } from 'selenium-webdriver'; -import { ElementWithContexMenu } from "../ElementWithContextMenu"; +import { ElementWithContexMenu } from '../ElementWithContextMenu'; /** * View with channel selector */ export abstract class ChannelView extends ElementWithContexMenu { - protected actionsLabel!: string; + protected actionsLabel!: string; - /** - * Get names of all selectable channels - * @returns Promise resolving to array of strings - channel names - */ - async getChannelNames(): Promise { - const names: string[] = []; - const elements = await (await this.enclosingItem.findElement(ChannelView.locators.BottomBarViews.actionsContainer(this.actionsLabel))) - .findElements(ChannelView.locators.BottomBarViews.channelOption); + /** + * Get names of all selectable channels + * @returns Promise resolving to array of strings - channel names + */ + async getChannelNames(): Promise { + const names: string[] = []; + const elements = await ( + await this.enclosingItem.findElement(ChannelView.locators.BottomBarViews.actionsContainer(this.actionsLabel)) + ).findElements(ChannelView.locators.BottomBarViews.channelOption); - await Promise.all(elements.map(async element => { - const disabled = await element.getAttribute('disabled'); - if (!disabled) { - names.push(await element.getAttribute('value')); - } - })); + await Promise.all( + elements.map(async (element) => { + const disabled = await element.getAttribute('disabled'); + if (!disabled) { + names.push(await element.getAttribute('value')); + } + }), + ); - return names; - } + return names; + } - /** - * Get name of the current channel - * @returns Promise resolving to the current channel name - * @deprecated For VS Code 1.88+ this method won't be working any more - */ - async getCurrentChannel(): Promise { - if(ChannelView.versionInfo.version >= '1.87.0' && process.platform !== 'darwin') { - throw Error(`DEPRECATED METHOD! The 'ChannelView.getCurrentChannel' method is broken! Read more information in 'Known Issues > Limitations in testing with VS Code 1.87+' - https://github.com/microsoft/vscode/issues/206897.`); - } - const combo = await this.enclosingItem.findElement(ChannelView.locators.BottomBarViews.channelCombo); - return await combo.getAttribute('title'); - } + /** + * Get name of the current channel + * @returns Promise resolving to the current channel name + * @deprecated For VS Code 1.88+ this method won't be working any more + */ + async getCurrentChannel(): Promise { + if (ChannelView.versionInfo.version >= '1.87.0' && process.platform !== 'darwin') { + throw Error( + `DEPRECATED METHOD! The 'ChannelView.getCurrentChannel' method is broken! Read more information in 'Known Issues > Limitations in testing with VS Code 1.87+' - https://github.com/microsoft/vscode/issues/206897.`, + ); + } + const combo = await this.enclosingItem.findElement(ChannelView.locators.BottomBarViews.channelCombo); + return await combo.getAttribute('title'); + } - /** - * Select a channel using the selector combo - * @param name name of the channel to open - */ - async selectChannel(name: string): Promise { - const combo = await this.enclosingItem.findElement(ChannelView.locators.BottomBarViews.channelCombo); - const option = await combo.findElement(ChannelView.locators.OutputView.optionByName(name)); - await option.click(); - } + /** + * Select a channel using the selector combo + * @param name name of the channel to open + */ + async selectChannel(name: string): Promise { + const combo = await this.enclosingItem.findElement(ChannelView.locators.BottomBarViews.channelCombo); + const option = await combo.findElement(ChannelView.locators.OutputView.optionByName(name)); + await option.click(); + } } /** * View with channel selection and text area */ export abstract class TextView extends ChannelView { - declare protected actionsLabel: string; + protected declare actionsLabel: string; - /** - * Get all text from the currently open channel - * @returns Promise resolving to the view's text - */ - async getText(): Promise { - const clipboard = (await import('clipboardy')).default; - let originalClipboard = ''; - try { - originalClipboard = clipboard.readSync(); - } catch (error) { - // workaround issue https://github.com/redhat-developer/vscode-extension-tester/issues/835 - // do not fail if clipboard is empty - } - const textarea = await this.findElement(ChannelView.locators.BottomBarViews.textArea); - await textarea.sendKeys(Key.chord(TextView.ctlKey, 'a')); - await textarea.sendKeys(Key.chord(TextView.ctlKey, 'c')); - const text = clipboard.readSync(); - // workaround as we are getting "element click intercepted" during the send keys actions. - // await textarea.click(); - if(originalClipboard.length > 0) { - clipboard.writeSync(originalClipboard); - } - return text; - } + /** + * Get all text from the currently open channel + * @returns Promise resolving to the view's text + */ + async getText(): Promise { + const clipboard = (await import('clipboardy')).default; + let originalClipboard = ''; + try { + originalClipboard = clipboard.readSync(); + } catch (error) { + // workaround issue https://github.com/redhat-developer/vscode-extension-tester/issues/835 + // do not fail if clipboard is empty + } + const textarea = await this.findElement(ChannelView.locators.BottomBarViews.textArea); + await textarea.sendKeys(Key.chord(TextView.ctlKey, 'a')); + await textarea.sendKeys(Key.chord(TextView.ctlKey, 'c')); + const text = clipboard.readSync(); + // workaround as we are getting "element click intercepted" during the send keys actions. + // await textarea.click(); + if (originalClipboard.length > 0) { + clipboard.writeSync(originalClipboard); + } + return text; + } - /** - * Clear the text in the current channel - * @returns Promise resolving when the clear text button is pressed - */ - async clearText(): Promise { - await this.enclosingItem.findElement(ChannelView.locators.BottomBarViews.actionsContainer(this.actionsLabel)) - .findElement(ChannelView.locators.BottomBarViews.clearText).click(); - } + /** + * Clear the text in the current channel + * @returns Promise resolving when the clear text button is pressed + */ + async clearText(): Promise { + await this.enclosingItem + .findElement(ChannelView.locators.BottomBarViews.actionsContainer(this.actionsLabel)) + .findElement(ChannelView.locators.BottomBarViews.clearText) + .click(); + } } diff --git a/packages/page-objects/src/components/bottomBar/BottomBarPanel.ts b/packages/page-objects/src/components/bottomBar/BottomBarPanel.ts index 0af81eded..be60e3c75 100644 --- a/packages/page-objects/src/components/bottomBar/BottomBarPanel.ts +++ b/packages/page-objects/src/components/bottomBar/BottomBarPanel.ts @@ -15,127 +15,128 @@ * limitations under the License. */ -import { AbstractElement } from "../AbstractElement"; -import { By, until, WebElement } from "selenium-webdriver"; -import { ProblemsView, OutputView, DebugConsoleView, TerminalView, EditorView, Workbench } from "../.."; +import { AbstractElement } from '../AbstractElement'; +import { By, until, WebElement } from 'selenium-webdriver'; +import { ProblemsView, OutputView, DebugConsoleView, TerminalView, EditorView, Workbench } from '../..'; /** * Page object for the bottom view panel */ export class BottomBarPanel extends AbstractElement { - constructor() { - super(BottomBarPanel.locators.BottomBarPanel.constructor, BottomBarPanel.locators.Workbench.constructor); - } + constructor() { + super(BottomBarPanel.locators.BottomBarPanel.constructor, BottomBarPanel.locators.Workbench.constructor); + } - /** - * Open/Close the bottom bar panel - * @param open true to open. false to close - * @returns Promise resolving when the view visibility is toggled - */ - async toggle(open: boolean): Promise { - try { - const tab = await new EditorView().getActiveTab(); - await tab?.click(); - } catch (err) { - // ignore and move on - } - const height = (await this.getRect()).height; - if ((open && height === 0) || !open && height > 0) { - if (open) { - await this.getDriver().actions().clear(); - await this.getDriver().actions().keyDown(BottomBarPanel.ctlKey).sendKeys('j').perform(); - await this.wait(); - } else { - await this.closePanel(); - await this.getDriver().wait(until.elementIsNotVisible(this)); - } - } - } + /** + * Open/Close the bottom bar panel + * @param open true to open. false to close + * @returns Promise resolving when the view visibility is toggled + */ + async toggle(open: boolean): Promise { + try { + const tab = await new EditorView().getActiveTab(); + await tab?.click(); + } catch (err) { + // ignore and move on + } + const height = (await this.getRect()).height; + if ((open && height === 0) || (!open && height > 0)) { + if (open) { + await this.getDriver().actions().clear(); + await this.getDriver().actions().keyDown(BottomBarPanel.ctlKey).sendKeys('j').perform(); + await this.wait(); + } else { + await this.closePanel(); + await this.getDriver().wait(until.elementIsNotVisible(this)); + } + } + } - /** - * Open the Problems view in the bottom panel - * @returns Promise resolving to a ProblemsView object - */ - async openProblemsView(): Promise { - await this.openTab(BottomBarPanel.locators.BottomBarPanel.problemsTab); - return new ProblemsView(this).wait(); - } + /** + * Open the Problems view in the bottom panel + * @returns Promise resolving to a ProblemsView object + */ + async openProblemsView(): Promise { + await this.openTab(BottomBarPanel.locators.BottomBarPanel.problemsTab); + return new ProblemsView(this).wait(); + } - /** - * Open the Output view in the bottom panel - * @returns Promise resolving to OutputView object - */ - async openOutputView(): Promise { - await this.openTab(BottomBarPanel.locators.BottomBarPanel.outputTab); - return new OutputView(this).wait(); - } + /** + * Open the Output view in the bottom panel + * @returns Promise resolving to OutputView object + */ + async openOutputView(): Promise { + await this.openTab(BottomBarPanel.locators.BottomBarPanel.outputTab); + return new OutputView(this).wait(); + } - /** - * Open the Debug Console view in the bottom panel - * @returns Promise resolving to DebugConsoleView object - */ - async openDebugConsoleView(): Promise { - await this.openTab(BottomBarPanel.locators.BottomBarPanel.debugTab); - return new DebugConsoleView(this).wait(); - } + /** + * Open the Debug Console view in the bottom panel + * @returns Promise resolving to DebugConsoleView object + */ + async openDebugConsoleView(): Promise { + await this.openTab(BottomBarPanel.locators.BottomBarPanel.debugTab); + return new DebugConsoleView(this).wait(); + } - /** - * Open the Terminal view in the bottom panel - * @returns Promise resolving to TerminalView object - */ - async openTerminalView(): Promise { - await this.openTab(BottomBarPanel.locators.BottomBarPanel.terminalTab); - return new TerminalView(this).wait(); - } + /** + * Open the Terminal view in the bottom panel + * @returns Promise resolving to TerminalView object + */ + async openTerminalView(): Promise { + await this.openTab(BottomBarPanel.locators.BottomBarPanel.terminalTab); + return new TerminalView(this).wait(); + } - /** - * Maximize the the bottom panel if not maximized - * @returns Promise resolving when the maximize button is pressed - */ - async maximize(): Promise { - await this.resize(BottomBarPanel.locators.BottomBarPanel.maximize); - } + /** + * Maximize the the bottom panel if not maximized + * @returns Promise resolving when the maximize button is pressed + */ + async maximize(): Promise { + await this.resize(BottomBarPanel.locators.BottomBarPanel.maximize); + } - /** - * Restore the the bottom panel if maximized - * @returns Promise resolving when the restore button is pressed - */ - async restore(): Promise { - await this.resize(BottomBarPanel.locators.BottomBarPanel.restore); - } + /** + * Restore the the bottom panel if maximized + * @returns Promise resolving when the restore button is pressed + */ + async restore(): Promise { + await this.resize(BottomBarPanel.locators.BottomBarPanel.restore); + } - private async openTab(title: string) { - await this.toggle(true); - const tabContainer = await this.findElement(BottomBarPanel.locators.BottomBarPanel.tabContainer); - try { - const tabs = await tabContainer.findElements(BottomBarPanel.locators.BottomBarPanel.tab(title)); - if (tabs.length > 0) { - await tabs[0].click(); - } else { - const label = await tabContainer.findElement(By.xpath(`.//a[starts-with(@aria-label, '${title}')]`)); - await label.click(); - } - } catch (err) { - await new Workbench().executeCommand(`${title}: Focus on ${title} View`); - } - } + private async openTab(title: string) { + await this.toggle(true); + const tabContainer = await this.findElement(BottomBarPanel.locators.BottomBarPanel.tabContainer); + try { + const tabs = await tabContainer.findElements(BottomBarPanel.locators.BottomBarPanel.tab(title)); + if (tabs.length > 0) { + await tabs[0].click(); + } else { + const label = await tabContainer.findElement(By.xpath(`.//a[starts-with(@aria-label, '${title}')]`)); + await label.click(); + } + } catch (err) { + await new Workbench().executeCommand(`${title}: Focus on ${title} View`); + } + } - private async resize(label: string) { - await this.toggle(true); - let action!: WebElement; - try { - action = await this.findElement(BottomBarPanel.locators.BottomBarPanel.globalActions) - .findElement(BottomBarPanel.locators.BottomBarPanel.action(label)); - } catch (err) { - // the panel is already maximized - } - if (action) { - await action.click(); - } - } + private async resize(label: string) { + await this.toggle(true); + let action!: WebElement; + try { + action = await this.findElement(BottomBarPanel.locators.BottomBarPanel.globalActions).findElement( + BottomBarPanel.locators.BottomBarPanel.action(label), + ); + } catch (err) { + // the panel is already maximized + } + if (action) { + await action.click(); + } + } - public async closePanel() { - const closeButton = await this.findElement(BottomBarPanel.locators.BottomBarPanel.closeAction); - await closeButton.click(); - } -} \ No newline at end of file + public async closePanel() { + const closeButton = await this.findElement(BottomBarPanel.locators.BottomBarPanel.closeAction); + await closeButton.click(); + } +} diff --git a/packages/page-objects/src/components/bottomBar/ProblemsView.ts b/packages/page-objects/src/components/bottomBar/ProblemsView.ts index 200627f8a..d53042c9d 100644 --- a/packages/page-objects/src/components/bottomBar/ProblemsView.ts +++ b/packages/page-objects/src/components/bottomBar/ProblemsView.ts @@ -15,146 +15,147 @@ * limitations under the License. */ -import { BottomBarPanel } from "../.."; -import { AbstractElement } from "../AbstractElement"; +import { BottomBarPanel } from '../..'; +import { AbstractElement } from '../AbstractElement'; import { WebElement } from 'selenium-webdriver'; -import { ElementWithContexMenu } from "../ElementWithContextMenu"; +import { ElementWithContexMenu } from '../ElementWithContextMenu'; /** * Problems view in the bottom panel */ export class ProblemsView extends AbstractElement { - constructor(panel: BottomBarPanel = new BottomBarPanel()) { - super(ProblemsView.locators.ProblemsView.constructor, panel); - } + constructor(panel: BottomBarPanel = new BottomBarPanel()) { + super(ProblemsView.locators.ProblemsView.constructor, panel); + } - /** - * Set the filter using the input box on the problems view - * @param pattern filter to use, preferably a glob pattern - * @returns Promise resolving when the filter pattern is filled in - */ - async setFilter(pattern: string): Promise { - const filterField = await this.clearFilter(); - await filterField.sendKeys(pattern); - } + /** + * Set the filter using the input box on the problems view + * @param pattern filter to use, preferably a glob pattern + * @returns Promise resolving when the filter pattern is filled in + */ + async setFilter(pattern: string): Promise { + const filterField = await this.clearFilter(); + await filterField.sendKeys(pattern); + } - /** - * Clear all filters - * @returns Promise resolving to the filter field WebElement - */ - async clearFilter(): Promise { - const filterField = await this.enclosingItem.findElement(ProblemsView.locators.BottomBarPanel.actions) - .findElement(ProblemsView.locators.ProblemsView.markersFilter) - .findElement(ProblemsView.locators.ProblemsView.input); - await filterField.clear(); - return filterField; - } + /** + * Clear all filters + * @returns Promise resolving to the filter field WebElement + */ + async clearFilter(): Promise { + const filterField = await this.enclosingItem + .findElement(ProblemsView.locators.BottomBarPanel.actions) + .findElement(ProblemsView.locators.ProblemsView.markersFilter) + .findElement(ProblemsView.locators.ProblemsView.input); + await filterField.clear(); + return filterField; + } - /** - * Collapse all collapsible markers in the problems view - * @returns Promise resolving when the collapse all button is pressed - */ - async collapseAll(): Promise { - const button = await this.enclosingItem.findElement(ProblemsView.locators.BottomBarPanel.actions) - .findElement(ProblemsView.locators.ProblemsView.collapseAll); - await button.click(); - } + /** + * Collapse all collapsible markers in the problems view + * @returns Promise resolving when the collapse all button is pressed + */ + async collapseAll(): Promise { + const button = await this.enclosingItem + .findElement(ProblemsView.locators.BottomBarPanel.actions) + .findElement(ProblemsView.locators.ProblemsView.collapseAll); + await button.click(); + } - /** - * @deprecated The method should not be used and getAllVisibleMarkers() should be used instead. - */ - async getAllMarkers(type: MarkerType): Promise { - return this.getAllVisibleMarkers(type); - } + /** + * @deprecated The method should not be used and getAllVisibleMarkers() should be used instead. + */ + async getAllMarkers(type: MarkerType): Promise { + return this.getAllVisibleMarkers(type); + } - /** - * Get all visible markers from the problems view with the given type. - * Warning: this only returns the markers that are visible, and not the - * entire list, so calls to this function may change depending on the - * environment in which the tests are running in. - * To get all markers regardless of type, use MarkerType.Any - * @param type type of markers to retrieve - * @returns Promise resolving to array of Marker objects - */ - async getAllVisibleMarkers(type: MarkerType): Promise { - const markers: Marker[] = []; - const elements = await this.findElements(ProblemsView.locators.ProblemsView.markerRow); - for (const element of elements) { - const marker = await new Marker(element, this).wait(); - if (type === MarkerType.Any || type === await marker.getType()) { - markers.push(marker); - } - } - return markers; - } + /** + * Get all visible markers from the problems view with the given type. + * Warning: this only returns the markers that are visible, and not the + * entire list, so calls to this function may change depending on the + * environment in which the tests are running in. + * To get all markers regardless of type, use MarkerType.Any + * @param type type of markers to retrieve + * @returns Promise resolving to array of Marker objects + */ + async getAllVisibleMarkers(type: MarkerType): Promise { + const markers: Marker[] = []; + const elements = await this.findElements(ProblemsView.locators.ProblemsView.markerRow); + for (const element of elements) { + const marker = await new Marker(element, this).wait(); + if (type === MarkerType.Any || type === (await marker.getType())) { + markers.push(marker); + } + } + return markers; + } - /** - * Gets the count badge - * @returns Promise resolving to the WebElement representing the count badge - */ - async getCountBadge(): Promise { - return await this.findElement(ProblemsView.locators.ProblemsView.changeCount); - } + /** + * Gets the count badge + * @returns Promise resolving to the WebElement representing the count badge + */ + async getCountBadge(): Promise { + return await this.findElement(ProblemsView.locators.ProblemsView.changeCount); + } } /** * Page object for a Marker in Problems view */ export class Marker extends ElementWithContexMenu { + constructor(element: WebElement, view: ProblemsView) { + super(element, view); + } - constructor(element: WebElement, view: ProblemsView) { - super(element, view); - } + /** + * Get the type of the marker. Possible types are: + * - File + * - Error + * - Warning + * @returns Promise resolving to a MarkerType + */ + async getType(): Promise { + const twist = await this.findElement(ProblemsView.locators.ProblemsView.markerTwistie); + if ((await twist.getAttribute('class')).indexOf('collapsible') > -1) { + return MarkerType.File; + } + const text = await this.getText(); + if (text.startsWith('Error')) { + return MarkerType.Error; + } else { + return MarkerType.Warning; + } + } - /** - * Get the type of the marker. Possible types are: - * - File - * - Error - * - Warning - * @returns Promise resolving to a MarkerType - */ - async getType(): Promise { - const twist = await this.findElement(ProblemsView.locators.ProblemsView.markerTwistie); - if ((await twist.getAttribute('class')).indexOf('collapsible') > -1) { - return MarkerType.File; - } - const text = await this.getText(); - if (text.startsWith('Error')) { - return MarkerType.Error; - } else { - return MarkerType.Warning; - } - } + /** + * Get the full text of the Marker row + * @returns Promise resolving to a Marker row text + */ + async getText(): Promise { + return await this.getAttribute(ProblemsView.locators.ProblemsView.rowLabel); + } - /** - * Get the full text of the Marker row - * @returns Promise resolving to a Marker row text - */ - async getText(): Promise { - return await this.getAttribute(ProblemsView.locators.ProblemsView.rowLabel); - } + /** + * Get the Marker label text + * @returns Promise resolving to a Marker label + */ + async getLabel(): Promise { + return await (await this.findElement(ProblemsView.locators.ProblemsView.label)).getText(); + } - /** - * Get the Marker label text - * @returns Promise resolving to a Marker label - */ - async getLabel(): Promise { - return await (await this.findElement(ProblemsView.locators.ProblemsView.label)).getText(); - } - - /** - * Expand/Collapse the Marker if possible - * @param expand True to expand, False to collapse - * @returns Promise resolving when the expand/collapse twistie is clicked - */ - async toggleExpand(expand: boolean): Promise { - if (await this.getType() === MarkerType.File) { - const klass = await this.findElement(ProblemsView.locators.ProblemsView.markerTwistie).getAttribute('class'); - if ((klass.indexOf('collapsed') > -1) === expand) { - await this.click(); - } - } - } + /** + * Expand/Collapse the Marker if possible + * @param expand True to expand, False to collapse + * @returns Promise resolving when the expand/collapse twistie is clicked + */ + async toggleExpand(expand: boolean): Promise { + if ((await this.getType()) === MarkerType.File) { + const klass = await this.findElement(ProblemsView.locators.ProblemsView.markerTwistie).getAttribute('class'); + if (klass.indexOf('collapsed') > -1 === expand) { + await this.click(); + } + } + } } /** @@ -165,8 +166,8 @@ export class Marker extends ElementWithContexMenu { * - Any = any of the above */ export enum MarkerType { - File = 'file', - Error = 'error', - Warning = 'warning', - Any = 'any' + File = 'file', + Error = 'error', + Warning = 'warning', + Any = 'any', } diff --git a/packages/page-objects/src/components/bottomBar/Views.ts b/packages/page-objects/src/components/bottomBar/Views.ts index 2aa08e262..8c85ccd5e 100644 --- a/packages/page-objects/src/components/bottomBar/Views.ts +++ b/packages/page-objects/src/components/bottomBar/Views.ts @@ -15,27 +15,27 @@ * limitations under the License. */ -import { Key, until } from "selenium-webdriver"; -import { BottomBarPanel, ContentAssist, Workbench } from "../.."; -import { TextView, ChannelView } from "./AbstractViews"; -import { ElementWithContexMenu } from "../ElementWithContextMenu"; +import { Key, until } from 'selenium-webdriver'; +import { BottomBarPanel, ContentAssist, Workbench } from '../..'; +import { TextView, ChannelView } from './AbstractViews'; +import { ElementWithContexMenu } from '../ElementWithContextMenu'; /** * Output view of the bottom panel */ export class OutputView extends TextView { - constructor(panel: BottomBarPanel = new BottomBarPanel()) { - super(OutputView.locators.OutputView.constructor, panel); - this.actionsLabel = OutputView.locators.OutputView.actionsLabel; - } - - /** - * Select a channel using the selector combo - * @param name name of the channel to open - */ - async selectChannel(name: string): Promise { - await super.selectChannel(name); - } + constructor(panel: BottomBarPanel = new BottomBarPanel()) { + super(OutputView.locators.OutputView.constructor, panel); + this.actionsLabel = OutputView.locators.OutputView.actionsLabel; + } + + /** + * Select a channel using the selector combo + * @param name name of the channel to open + */ + async selectChannel(name: string): Promise { + await super.selectChannel(name); + } } /** @@ -43,181 +43,181 @@ export class OutputView extends TextView { * Most functionality will only be available when a debug session is running */ export class DebugConsoleView extends ElementWithContexMenu { - constructor(panel: BottomBarPanel = new BottomBarPanel()) { - super(DebugConsoleView.locators.DebugConsoleView.constructor, panel); - } - - /** - * Clear the console of all text - */ - async clearText(): Promise { - const menu = await this.openContextMenu(); - await menu.select('Clear Console'); - } - - /** - * Type an expression into the debug console text area - * @param expression expression in form of a string - */ - async setExpression(expression: string): Promise { - const textarea = await this.findElement(DebugConsoleView.locators.BottomBarViews.textArea); - await textarea.clear(); - await textarea.sendKeys(expression); - } - - /** - * Evaluate an expression: - * - if no argument is supplied, evaluate the current expression present in debug console text area - * - if a string argument is supplied, replace the current expression with the `expression` argument and evaluate - * - * @param expression expression to evaluate. To use existing contents of the debug console text area instead, don't define this argument - */ - async evaluateExpression(expression?: string): Promise { - const textarea = await this.findElement(DebugConsoleView.locators.BottomBarViews.textArea); - if (expression) { - await this.setExpression(expression); - } - await textarea.sendKeys(Key.ENTER); - } - - /** - * Create a content assist page object - * @returns promise resolving to ContentAssist object - */ - async getContentAssist(): Promise { - await this.getDriver().wait(until.elementLocated(ContentAssist.locators.ContentAssist.constructor), 1000); - return new ContentAssist(this).wait(); - } + constructor(panel: BottomBarPanel = new BottomBarPanel()) { + super(DebugConsoleView.locators.DebugConsoleView.constructor, panel); + } + + /** + * Clear the console of all text + */ + async clearText(): Promise { + const menu = await this.openContextMenu(); + await menu.select('Clear Console'); + } + + /** + * Type an expression into the debug console text area + * @param expression expression in form of a string + */ + async setExpression(expression: string): Promise { + const textarea = await this.findElement(DebugConsoleView.locators.BottomBarViews.textArea); + await textarea.clear(); + await textarea.sendKeys(expression); + } + + /** + * Evaluate an expression: + * - if no argument is supplied, evaluate the current expression present in debug console text area + * - if a string argument is supplied, replace the current expression with the `expression` argument and evaluate + * + * @param expression expression to evaluate. To use existing contents of the debug console text area instead, don't define this argument + */ + async evaluateExpression(expression?: string): Promise { + const textarea = await this.findElement(DebugConsoleView.locators.BottomBarViews.textArea); + if (expression) { + await this.setExpression(expression); + } + await textarea.sendKeys(Key.ENTER); + } + + /** + * Create a content assist page object + * @returns promise resolving to ContentAssist object + */ + async getContentAssist(): Promise { + await this.getDriver().wait(until.elementLocated(ContentAssist.locators.ContentAssist.constructor), 1000); + return new ContentAssist(this).wait(); + } } /** * Terminal view on the bottom panel */ export class TerminalView extends ChannelView { - constructor(panel: BottomBarPanel = new BottomBarPanel()) { - super(TerminalView.locators.TerminalView.constructor, panel); - this.actionsLabel = TerminalView.locators.TerminalView.actionsLabel; - } - - /** - * Execute command in the internal terminal and wait for results - * @param command text of the command - * @param timeout optional maximum time to wait for completion in milliseconds, 0 for unlimited - * @returns Promise resolving when the command is finished - */ - async executeCommand(command: string, timeout: number = 0): Promise { - const input = await this.findElement(TerminalView.locators.TerminalView.textArea); - - try { - await input.clear(); - } catch (err) { - // try clearing, ignore if not available - } - await input.sendKeys(command, Key.ENTER); - - let timer = 0; - let style = await input.getCssValue('left'); - do { - if (timeout > 0 && timer > timeout) { - throw new Error(`Timeout of ${timeout}ms exceeded`); - } - await new Promise(res => setTimeout(res, 500)); - timer += 500; - style = await input.getCssValue('left'); - } while (style === '0px'); - } - - /** - * Get all text from the internal terminal - * Beware, no formatting. - * - * @returns Promise resolving to all terminal text - */ - async getText(): Promise { - const clipboard = (await import('clipboardy')).default; - let originalClipboard = ''; - try { - originalClipboard = clipboard.readSync(); - } catch (error) { - // workaround issue https://github.com/redhat-developer/vscode-extension-tester/issues/835 - // do not fail if clipboard is empty - } - const workbench = new Workbench(); - await workbench.executeCommand('terminal select all'); - await workbench.getDriver().sleep(500); - const text = clipboard.readSync(); - if (originalClipboard.length > 0) { - clipboard.writeSync(originalClipboard); - } - return text; - } - - /** - * Destroy the currently open terminal - * @returns Promise resolving when Kill Terminal button is pressed - */ - async killTerminal(): Promise { - await new Workbench().executeCommand('terminal: kill the active terminal instance'); - } - - /** - * Initiate new terminal creation - * @returns Promise resolving when New Terminal button is pressed - */ - async newTerminal(): Promise { - await new Workbench().executeCommand(TerminalView.locators.TerminalView.newCommand); - const combo = await this.enclosingItem.findElements(ChannelView.locators.BottomBarViews.channelCombo); - if (combo.length < 1) { - await this.getDriver().wait(async () => { - const list = await this.findElements(TerminalView.locators.TerminalView.tabList); - return list.length > 0; - }, 5000); - } - } - - async getCurrentChannel(): Promise { - const combo = await this.enclosingItem.findElements(ChannelView.locators.BottomBarViews.channelCombo); - if (combo.length > 0) { - return await super.getCurrentChannel(); - } - const singleTerm = await this.enclosingItem.findElements(TerminalView.locators.TerminalView.singleTab); - if (singleTerm.length > 0) { - return await singleTerm[0].getText(); - } - const list = await this.findElement(TerminalView.locators.TerminalView.tabList); - const row = await list.findElement(TerminalView.locators.TerminalView.selectedRow); - await this.getDriver().sleep(1000); - const label = (await row.getAttribute('aria-label')).split(' '); - - return `${label[1]}: ${label[2]}`; - } - - async selectChannel(name: string): Promise { - const combo = await this.enclosingItem.findElements(ChannelView.locators.BottomBarViews.channelCombo); - if (combo.length > 0) { - return await super.selectChannel(name); - } - const singleTerm = await this.enclosingItem.findElements(TerminalView.locators.TerminalView.singleTab); - if (singleTerm.length > 0) { - return; - } - - const matches = /.*(\d+).?\s.*/.exec(name); - if (matches === null || !matches[1]) { - throw new Error(`Channel ${name} not found`); - } - const channelNumber = matches[1]; - - const list = await this.findElement(TerminalView.locators.TerminalView.tabList); - const rows = await list.findElements(TerminalView.locators.TerminalView.row); - - for (const row of rows) { - const label = await row.getAttribute('aria-label'); - if (label.includes(channelNumber)) { - await row.click(); - return; - } - } - throw new Error(`Channel ${name} not found`); - } -} \ No newline at end of file + constructor(panel: BottomBarPanel = new BottomBarPanel()) { + super(TerminalView.locators.TerminalView.constructor, panel); + this.actionsLabel = TerminalView.locators.TerminalView.actionsLabel; + } + + /** + * Execute command in the internal terminal and wait for results + * @param command text of the command + * @param timeout optional maximum time to wait for completion in milliseconds, 0 for unlimited + * @returns Promise resolving when the command is finished + */ + async executeCommand(command: string, timeout: number = 0): Promise { + const input = await this.findElement(TerminalView.locators.TerminalView.textArea); + + try { + await input.clear(); + } catch (err) { + // try clearing, ignore if not available + } + await input.sendKeys(command, Key.ENTER); + + let timer = 0; + let style = await input.getCssValue('left'); + do { + if (timeout > 0 && timer > timeout) { + throw new Error(`Timeout of ${timeout}ms exceeded`); + } + await new Promise((res) => setTimeout(res, 500)); + timer += 500; + style = await input.getCssValue('left'); + } while (style === '0px'); + } + + /** + * Get all text from the internal terminal + * Beware, no formatting. + * + * @returns Promise resolving to all terminal text + */ + async getText(): Promise { + const clipboard = (await import('clipboardy')).default; + let originalClipboard = ''; + try { + originalClipboard = clipboard.readSync(); + } catch (error) { + // workaround issue https://github.com/redhat-developer/vscode-extension-tester/issues/835 + // do not fail if clipboard is empty + } + const workbench = new Workbench(); + await workbench.executeCommand('terminal select all'); + await workbench.getDriver().sleep(500); + const text = clipboard.readSync(); + if (originalClipboard.length > 0) { + clipboard.writeSync(originalClipboard); + } + return text; + } + + /** + * Destroy the currently open terminal + * @returns Promise resolving when Kill Terminal button is pressed + */ + async killTerminal(): Promise { + await new Workbench().executeCommand('terminal: kill the active terminal instance'); + } + + /** + * Initiate new terminal creation + * @returns Promise resolving when New Terminal button is pressed + */ + async newTerminal(): Promise { + await new Workbench().executeCommand(TerminalView.locators.TerminalView.newCommand); + const combo = await this.enclosingItem.findElements(ChannelView.locators.BottomBarViews.channelCombo); + if (combo.length < 1) { + await this.getDriver().wait(async () => { + const list = await this.findElements(TerminalView.locators.TerminalView.tabList); + return list.length > 0; + }, 5000); + } + } + + async getCurrentChannel(): Promise { + const combo = await this.enclosingItem.findElements(ChannelView.locators.BottomBarViews.channelCombo); + if (combo.length > 0) { + return await super.getCurrentChannel(); + } + const singleTerm = await this.enclosingItem.findElements(TerminalView.locators.TerminalView.singleTab); + if (singleTerm.length > 0) { + return await singleTerm[0].getText(); + } + const list = await this.findElement(TerminalView.locators.TerminalView.tabList); + const row = await list.findElement(TerminalView.locators.TerminalView.selectedRow); + await this.getDriver().sleep(1000); + const label = (await row.getAttribute('aria-label')).split(' '); + + return `${label[1]}: ${label[2]}`; + } + + async selectChannel(name: string): Promise { + const combo = await this.enclosingItem.findElements(ChannelView.locators.BottomBarViews.channelCombo); + if (combo.length > 0) { + return await super.selectChannel(name); + } + const singleTerm = await this.enclosingItem.findElements(TerminalView.locators.TerminalView.singleTab); + if (singleTerm.length > 0) { + return; + } + + const matches = /.*(\d+).?\s.*/.exec(name); + if (matches === null || !matches[1]) { + throw new Error(`Channel ${name} not found`); + } + const channelNumber = matches[1]; + + const list = await this.findElement(TerminalView.locators.TerminalView.tabList); + const rows = await list.findElements(TerminalView.locators.TerminalView.row); + + for (const row of rows) { + const label = await row.getAttribute('aria-label'); + if (label.includes(channelNumber)) { + await row.click(); + return; + } + } + throw new Error(`Channel ${name} not found`); + } +} diff --git a/packages/page-objects/src/components/bottomBar/WebviewView.ts b/packages/page-objects/src/components/bottomBar/WebviewView.ts index 1513799cf..3c929e871 100644 --- a/packages/page-objects/src/components/bottomBar/WebviewView.ts +++ b/packages/page-objects/src/components/bottomBar/WebviewView.ts @@ -16,25 +16,23 @@ */ /* eslint-disable no-redeclare */ -import { WebElement } from "selenium-webdriver"; -import { AbstractElement } from "../AbstractElement"; -import WebviewMixin from "../WebviewMixin"; +import { WebElement } from 'selenium-webdriver'; +import { AbstractElement } from '../AbstractElement'; +import WebviewMixin from '../WebviewMixin'; /** * Page object representing a user-contributed panel implemented using a Webview. */ class WebviewViewBase extends AbstractElement { + constructor() { + super(WebviewViewBase.locators.Workbench.constructor); + } - constructor() { - super(WebviewViewBase.locators.Workbench.constructor); - } - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - async getViewToSwitchTo(handle: string): Promise { - return await this.getDriver().findElement(WebviewViewBase.locators.WebviewView.iframe); - } - + // eslint-disable-next-line @typescript-eslint/no-unused-vars + async getViewToSwitchTo(handle: string): Promise { + return await this.getDriver().findElement(WebviewViewBase.locators.WebviewView.iframe); + } } export const WebviewView = WebviewMixin(WebviewViewBase); -export type WebviewView = InstanceType; \ No newline at end of file +export type WebviewView = InstanceType; diff --git a/packages/page-objects/src/components/dialog/ModalDialog.ts b/packages/page-objects/src/components/dialog/ModalDialog.ts index 1b899c2f7..ec03c8ae3 100644 --- a/packages/page-objects/src/components/dialog/ModalDialog.ts +++ b/packages/page-objects/src/components/dialog/ModalDialog.ts @@ -15,62 +15,61 @@ * limitations under the License. */ -import { WebElement } from "selenium-webdriver"; -import { AbstractElement } from "../AbstractElement"; +import { WebElement } from 'selenium-webdriver'; +import { AbstractElement } from '../AbstractElement'; /** * Page Object for Custom Style Modal Dialogs (non-native) */ export class ModalDialog extends AbstractElement { + constructor() { + super(ModalDialog.locators.Dialog.constructor); + } - constructor() { - super(ModalDialog.locators.Dialog.constructor); - } + /** + * Get the dialog's message in a Promise + */ + async getMessage(): Promise { + const message = await this.findElement(ModalDialog.locators.Dialog.message); + return await message.getText(); + } - /** - * Get the dialog's message in a Promise - */ - async getMessage(): Promise { - const message = await this.findElement(ModalDialog.locators.Dialog.message); - return await message.getText(); - } + /** + * Get the details message in a Promise + */ + async getDetails(): Promise { + const details = await this.findElement(ModalDialog.locators.Dialog.details); + return await details.getText(); + } - /** - * Get the details message in a Promise - */ - async getDetails(): Promise { - const details = await this.findElement(ModalDialog.locators.Dialog.details); - return await details.getText(); - } + /** + * Get the list of buttons as WebElements + * + * @returns Promise resolving to Array of WebElement items representing the buttons + */ + async getButtons(): Promise { + return await this.findElement(ModalDialog.locators.Dialog.buttonContainer).findElements(ModalDialog.locators.Dialog.button); + } - /** - * Get the list of buttons as WebElements - * - * @returns Promise resolving to Array of WebElement items representing the buttons - */ - async getButtons(): Promise { - return await this.findElement(ModalDialog.locators.Dialog.buttonContainer).findElements(ModalDialog.locators.Dialog.button); - } + /** + * Push a button with given title if it exists + * + * @param title title/text of the button + */ + async pushButton(title: string): Promise { + const buttons = await this.getButtons(); + const titles = await Promise.all(buttons.map(async (btn) => ModalDialog.locators.Dialog.buttonLabel.value(btn))); + const index = titles.findIndex((value) => value === title); + if (index > -1) { + await buttons[index].click(); + } + } - /** - * Push a button with given title if it exists - * - * @param title title/text of the button - */ - async pushButton(title: string): Promise { - const buttons = await this.getButtons(); - const titles = await Promise.all(buttons.map(async btn => ModalDialog.locators.Dialog.buttonLabel.value(btn))); - const index = titles.findIndex(value => value === title); - if (index > -1) { - await buttons[index].click(); - } - } - - /** - * Close the dialog using the 'cross' button - */ - async close(): Promise { - const btn = await this.findElement(ModalDialog.locators.Dialog.closeButton); - return await btn.click(); - } -} \ No newline at end of file + /** + * Close the dialog using the 'cross' button + */ + async close(): Promise { + const btn = await this.findElement(ModalDialog.locators.Dialog.closeButton); + return await btn.click(); + } +} diff --git a/packages/page-objects/src/components/editor/Breakpoint.ts b/packages/page-objects/src/components/editor/Breakpoint.ts index 476fcd409..2cb61a86e 100644 --- a/packages/page-objects/src/components/editor/Breakpoint.ts +++ b/packages/page-objects/src/components/editor/Breakpoint.ts @@ -19,35 +19,38 @@ import { until, WebElement } from 'selenium-webdriver'; import { AbstractElement } from '../AbstractElement'; export class Breakpoint extends AbstractElement { - constructor(breakpoint: WebElement, private lineElement: WebElement) { - super(breakpoint, lineElement); - } + constructor( + breakpoint: WebElement, + private lineElement: WebElement, + ) { + super(breakpoint, lineElement); + } - async isEnabled(): Promise { - return await Breakpoint.locators.TextEditor.breakpoint.properties.enabled(this); - } + async isEnabled(): Promise { + return await Breakpoint.locators.TextEditor.breakpoint.properties.enabled(this); + } - async isPaused(): Promise { - return await Breakpoint.locators.TextEditor.breakpoint.properties.paused(this); - } + async isPaused(): Promise { + return await Breakpoint.locators.TextEditor.breakpoint.properties.paused(this); + } - /** - * Return line number of the breakpoint. - * @returns number indicating line where breakpoint is set - */ - async getLineNumber(): Promise { - const breakpointLocators = Breakpoint.locators.TextEditor.breakpoint; - const line = await this.lineElement.findElement(breakpointLocators.properties.line.selector); - const lineNumber = await breakpointLocators.properties.line.number(line); - return lineNumber; - } + /** + * Return line number of the breakpoint. + * @returns number indicating line where breakpoint is set + */ + async getLineNumber(): Promise { + const breakpointLocators = Breakpoint.locators.TextEditor.breakpoint; + const line = await this.lineElement.findElement(breakpointLocators.properties.line.selector); + const lineNumber = await breakpointLocators.properties.line.number(line); + return lineNumber; + } - /** - * Remove breakpoint. - * @param timeout time in ms when operation is considered to be unsuccessful - */ - async remove(timeout: number = 5000): Promise { - await this.click(); - await this.getDriver().wait(until.stalenessOf(this), timeout); - } -} \ No newline at end of file + /** + * Remove breakpoint. + * @param timeout time in ms when operation is considered to be unsuccessful + */ + async remove(timeout: number = 5000): Promise { + await this.click(); + await this.getDriver().wait(until.stalenessOf(this), timeout); + } +} diff --git a/packages/page-objects/src/components/editor/ContentAssist.ts b/packages/page-objects/src/components/editor/ContentAssist.ts index 4040b54c1..2c7146bf6 100644 --- a/packages/page-objects/src/components/editor/ContentAssist.ts +++ b/packages/page-objects/src/components/editor/ContentAssist.ts @@ -15,101 +15,102 @@ * limitations under the License. */ -import { TextEditor, Menu, MenuItem, DebugConsoleView } from "../.."; +import { TextEditor, Menu, MenuItem, DebugConsoleView } from '../..'; import { error, Key, WebElement } from 'selenium-webdriver'; /** * Page object representing the content assistant */ export class ContentAssist extends Menu { - constructor(parent: TextEditor | DebugConsoleView) { - super(ContentAssist.locators.ContentAssist.constructor, parent); - } + constructor(parent: TextEditor | DebugConsoleView) { + super(ContentAssist.locators.ContentAssist.constructor, parent); + } - /** - * Get content assist item by name/text, scroll through the list - * until the item is found, or the end is reached - * - * @param name name/text to search by - * @returns Promise resolving to ContentAssistItem object if found, undefined otherwise - */ - async getItem(name: string): Promise { - let lastItem = false; - const scrollable = await this.findElement(ContentAssist.locators.ContentAssist.itemList); + /** + * Get content assist item by name/text, scroll through the list + * until the item is found, or the end is reached + * + * @param name name/text to search by + * @returns Promise resolving to ContentAssistItem object if found, undefined otherwise + */ + async getItem(name: string): Promise { + let lastItem = false; + const scrollable = await this.findElement(ContentAssist.locators.ContentAssist.itemList); - let firstItem = await this.findElements(ContentAssist.locators.ContentAssist.firstItem); - while(firstItem.length < 1) { - await scrollable.sendKeys(Key.PAGE_UP, Key.NULL); - firstItem = await this.findElements(ContentAssist.locators.ContentAssist.firstItem); - } + let firstItem = await this.findElements(ContentAssist.locators.ContentAssist.firstItem); + while (firstItem.length < 1) { + await scrollable.sendKeys(Key.PAGE_UP, Key.NULL); + firstItem = await this.findElements(ContentAssist.locators.ContentAssist.firstItem); + } - while(!lastItem) { - const items = await this.getItems(); - - for (const item of items) { - if (await item.getLabel() === name) { - return item; - } - lastItem = lastItem || (await item.getAttribute('data-last-element')) === 'true'; - } - if (!lastItem) { - await scrollable.sendKeys(Key.PAGE_DOWN); - await new Promise(res => setTimeout(res, 100)); - } - } - } + while (!lastItem) { + const items = await this.getItems(); - /** - * Get all visible content assist items - * @returns Promise resolving to array of ContentAssistItem objects - */ - async getItems(): Promise { - await this.getDriver().wait(async () => { return await this.isLoaded(); }); + for (const item of items) { + if ((await item.getLabel()) === name) { + return item; + } + lastItem = lastItem || (await item.getAttribute('data-last-element')) === 'true'; + } + if (!lastItem) { + await scrollable.sendKeys(Key.PAGE_DOWN); + await new Promise((res) => setTimeout(res, 100)); + } + } + } - const elements = await this.findElement(ContentAssist.locators.ContentAssist.itemRows) - .findElements(ContentAssist.locators.ContentAssist.itemRow); - const items: ContentAssistItem[] = []; + /** + * Get all visible content assist items + * @returns Promise resolving to array of ContentAssistItem objects + */ + async getItems(): Promise { + await this.getDriver().wait(async () => { + return await this.isLoaded(); + }); - for (const item of elements) { - try { - items.push(await new ContentAssistItem(item, this).wait()); - } catch (err) { - if (!(err instanceof error.StaleElementReferenceError)) { - throw err; - } - } - } - return items; - } + const elements = await this.findElement(ContentAssist.locators.ContentAssist.itemRows).findElements(ContentAssist.locators.ContentAssist.itemRow); + const items: ContentAssistItem[] = []; - /** - * Find if the content assist is still loading the suggestions - * @returns promise that resolves to true when suggestions are done loading, - * to false otherwise - */ - async isLoaded(): Promise { - const message = await this.findElement(ContentAssist.locators.ContentAssist.message); - if (await message.isDisplayed()) { - if ((await message.getText()).startsWith('No suggestions')) { - return true; - } - return false; - } - return true; - } + for (const item of elements) { + try { + items.push(await new ContentAssistItem(item, this).wait()); + } catch (err) { + if (!(err instanceof error.StaleElementReferenceError)) { + throw err; + } + } + } + return items; + } + + /** + * Find if the content assist is still loading the suggestions + * @returns promise that resolves to true when suggestions are done loading, + * to false otherwise + */ + async isLoaded(): Promise { + const message = await this.findElement(ContentAssist.locators.ContentAssist.message); + if (await message.isDisplayed()) { + if ((await message.getText()).startsWith('No suggestions')) { + return true; + } + return false; + } + return true; + } } /** * Page object for a content assist item */ export class ContentAssistItem extends MenuItem { - constructor(item: WebElement, contentAssist: ContentAssist) { - super(item, contentAssist); - this.parent = contentAssist; - } + constructor(item: WebElement, contentAssist: ContentAssist) { + super(item, contentAssist); + this.parent = contentAssist; + } - async getLabel(): Promise { - const labelDiv = await this.findElement(ContentAssist.locators.ContentAssist.itemLabel); - return await labelDiv.getText(); - } -} \ No newline at end of file + async getLabel(): Promise { + const labelDiv = await this.findElement(ContentAssist.locators.ContentAssist.itemLabel); + return await labelDiv.getText(); + } +} diff --git a/packages/page-objects/src/components/editor/CustomEditor.ts b/packages/page-objects/src/components/editor/CustomEditor.ts index 762602a52..05337defd 100644 --- a/packages/page-objects/src/components/editor/CustomEditor.ts +++ b/packages/page-objects/src/components/editor/CustomEditor.ts @@ -15,48 +15,47 @@ * limitations under the License. */ -import { Key } from "selenium-webdriver"; -import { Editor, InputBox, WebView } from "../.."; +import { Key } from 'selenium-webdriver'; +import { Editor, InputBox, WebView } from '../..'; /** * Page object for custom editors */ export class CustomEditor extends Editor { + /** + * Get the WebView object contained in the editor + * @returns WebView page object + */ + getWebView(): WebView { + return new WebView(); + } - /** - * Get the WebView object contained in the editor - * @returns WebView page object - */ - getWebView(): WebView { - return new WebView(); - } + /** + * Check if the editor has unsaved changes + * @returns Promise resolving to true if there are unsaved changes, false otherwise + */ + async isDirty(): Promise { + const tab = await this.getTab(); + const klass = await tab.getAttribute('class'); + return klass.includes('dirty'); + } - /** - * Check if the editor has unsaved changes - * @returns Promise resolving to true if there are unsaved changes, false otherwise - */ - async isDirty(): Promise { - const tab = await this.getTab(); - const klass = await tab.getAttribute('class'); - return klass.includes('dirty'); - } + /** + * Save the editor + */ + async save(): Promise { + const tab = await this.getTab(); + await tab.sendKeys(Key.chord(CustomEditor.ctlKey, 's')); + } - /** - * Save the editor - */ - async save(): Promise { - const tab = await this.getTab(); - await tab.sendKeys(Key.chord(CustomEditor.ctlKey, 's')); - } - - /** - * Open the Save as prompt - * - * @returns InputBox serving as a simple file dialog - */ - async saveAs(): Promise { - const tab = await this.getTab(); - await tab.sendKeys(Key.chord(CustomEditor.ctlKey, Key.SHIFT, 's')); - return await InputBox.create(); - } -} \ No newline at end of file + /** + * Open the Save as prompt + * + * @returns InputBox serving as a simple file dialog + */ + async saveAs(): Promise { + const tab = await this.getTab(); + await tab.sendKeys(Key.chord(CustomEditor.ctlKey, Key.SHIFT, 's')); + return await InputBox.create(); + } +} diff --git a/packages/page-objects/src/components/editor/DiffEditor.ts b/packages/page-objects/src/components/editor/DiffEditor.ts index e58cd54d3..d085b4c39 100644 --- a/packages/page-objects/src/components/editor/DiffEditor.ts +++ b/packages/page-objects/src/components/editor/DiffEditor.ts @@ -23,24 +23,23 @@ import { EditorView } from './EditorView'; * Page object representing a diff editor */ export class DiffEditor extends Editor { + /** + * Gets the text editor corresponding to the originalside. + * (The left side of the diff editor) + * @returns Promise resolving to TextEditor object + */ + async getOriginalEditor(): Promise { + const element = await this.getEnclosingElement().findElement(DiffEditor.locators.DiffEditor.originalEditor); + return new TextEditor(new EditorView(), element); + } - /** - * Gets the text editor corresponding to the originalside. - * (The left side of the diff editor) - * @returns Promise resolving to TextEditor object - */ - async getOriginalEditor(): Promise { - const element = await this.getEnclosingElement().findElement(DiffEditor.locators.DiffEditor.originalEditor); - return new TextEditor(new EditorView(), element); - } - - /** - * Gets the text editor corresponding to the modified side. - * (The right side of the diff editor) - * @returns Promise resolving to TextEditor object - */ - async getModifiedEditor(): Promise { - const element = await this.getEnclosingElement().findElement(DiffEditor.locators.DiffEditor.modifiedEditor); - return new TextEditor(new EditorView(), element); - } -} \ No newline at end of file + /** + * Gets the text editor corresponding to the modified side. + * (The right side of the diff editor) + * @returns Promise resolving to TextEditor object + */ + async getModifiedEditor(): Promise { + const element = await this.getEnclosingElement().findElement(DiffEditor.locators.DiffEditor.modifiedEditor); + return new TextEditor(new EditorView(), element); + } +} diff --git a/packages/page-objects/src/components/editor/Editor.ts b/packages/page-objects/src/components/editor/Editor.ts index 3e5998113..1b48ef722 100644 --- a/packages/page-objects/src/components/editor/Editor.ts +++ b/packages/page-objects/src/components/editor/Editor.ts @@ -15,32 +15,31 @@ * limitations under the License. */ -import { ElementWithContexMenu } from "../ElementWithContextMenu"; -import { EditorTab, EditorView, EditorGroup } from "../.."; +import { ElementWithContexMenu } from '../ElementWithContextMenu'; +import { EditorTab, EditorView, EditorGroup } from '../..'; import { WebElement, Locator } from 'selenium-webdriver'; /** * Abstract representation of an editor tab */ export abstract class Editor extends ElementWithContexMenu { + constructor(view: EditorView | EditorGroup = new EditorView(), base: Locator | WebElement = Editor.locators.Editor.constructor) { + super(base, view); + } - constructor(view: EditorView | EditorGroup = new EditorView(), base: Locator | WebElement = Editor.locators.Editor.constructor) { - super(base, view); - } + /** + * Get title/name of the open editor + */ + async getTitle(): Promise { + const tab = await this.getTab(); + return await tab.getTitle(); + } - /** - * Get title/name of the open editor - */ - async getTitle(): Promise { - const tab = await this.getTab(); - return await tab.getTitle(); - } - - /** - * Get the corresponding editor tab - */ - async getTab(): Promise { - const element = this.enclosingItem as EditorView | EditorGroup; - return await element.getActiveTab() as EditorTab; - } -} \ No newline at end of file + /** + * Get the corresponding editor tab + */ + async getTab(): Promise { + const element = this.enclosingItem as EditorView | EditorGroup; + return (await element.getActiveTab()) as EditorTab; + } +} diff --git a/packages/page-objects/src/components/editor/EditorAction.ts b/packages/page-objects/src/components/editor/EditorAction.ts index 1c9877549..7160b6f20 100644 --- a/packages/page-objects/src/components/editor/EditorAction.ts +++ b/packages/page-objects/src/components/editor/EditorAction.ts @@ -15,19 +15,19 @@ * limitations under the License. */ -import { EditorGroup } from "./EditorView"; -import { AbstractElement } from "../AbstractElement"; -import { WebElement } from "../.."; +import { EditorGroup } from './EditorView'; +import { AbstractElement } from '../AbstractElement'; +import { WebElement } from '../..'; export class EditorAction extends AbstractElement { - constructor(element: WebElement, parent: EditorGroup) { - super(element, parent); - } + constructor(element: WebElement, parent: EditorGroup) { + super(element, parent); + } - /** - * Get text description of the action. - */ - async getTitle(): Promise { - return await this.getAttribute(EditorAction.locators.EditorView.attribute); - } + /** + * Get text description of the action. + */ + async getTitle(): Promise { + return await this.getAttribute(EditorAction.locators.EditorView.attribute); + } } diff --git a/packages/page-objects/src/components/editor/EditorView.ts b/packages/page-objects/src/components/editor/EditorView.ts index 383f8685e..d52680ad5 100644 --- a/packages/page-objects/src/components/editor/EditorView.ts +++ b/packages/page-objects/src/components/editor/EditorView.ts @@ -15,377 +15,387 @@ * limitations under the License. */ -import { error, WebElement } from "selenium-webdriver"; -import { TextEditor } from "../.."; -import { AbstractElement } from "../AbstractElement"; -import { ElementWithContexMenu } from "../ElementWithContextMenu"; +import { error, WebElement } from 'selenium-webdriver'; +import { TextEditor } from '../..'; +import { AbstractElement } from '../AbstractElement'; +import { ElementWithContexMenu } from '../ElementWithContextMenu'; import { DiffEditor } from './DiffEditor'; -import { Editor } from "./Editor"; -import { EditorAction } from "./EditorAction"; -import { SettingsEditor } from "./SettingsEditor"; -import { WebView } from "./WebView"; +import { Editor } from './Editor'; +import { EditorAction } from './EditorAction'; +import { SettingsEditor } from './SettingsEditor'; +import { WebView } from './WebView'; export class EditorTabNotFound extends Error { - constructor(title: string, group: number) { - super(`No editor with title '${title}' in group '${group}' available`); - } + constructor(title: string, group: number) { + super(`No editor with title '${title}' in group '${group}' available`); + } } /** * View handling the open editors */ export class EditorView extends AbstractElement { - constructor() { - super(EditorView.locators.EditorView.constructor, EditorView.locators.Workbench.constructor); - } - - /** - * Switch to an editor tab with the given title - * @param title title of the tab - * @param groupIndex zero based index for the editor group (0 for the left most group) - * @returns Promise resolving to Editor object - */ - async openEditor(title: string, groupIndex: number = 0): Promise { - const group = await this.getEditorGroup(groupIndex); - return group.openEditor(title); - } - - /** - * Close an editor tab with the given title - * @param title title of the tab - * @param groupIndex zero based index for the editor group (0 for the left most group) - * @returns Promise resolving when the tab's close button is pressed - */ - async closeEditor(title: string, groupIndex: number = 0): Promise { - const group = await this.getEditorGroup(groupIndex); - return group.closeEditor(title); - } - - /** - * Close all open editor tabs - * @param groupIndex optional index to specify an editor group - * @returns Promise resolving once all tabs have had their close button pressed - */ - async closeAllEditors(groupIndex?: number): Promise { - let groups = await this.getEditorGroups(); - if (groupIndex !== undefined) { - return groups[groupIndex].closeAllEditors(); - } - - while (groups.length > 0 && (await groups[0].getOpenEditorTitles()).length > 0) { - await groups[0].closeAllEditors(); - await new Promise(res => setTimeout(res, 1000)); - groups = await this.getEditorGroups(); - } - } - - /** - * Retrieve all open editor tab titles in an array - * @param groupIndex optional index to specify an editor group, if left empty will search all groups - * @returns Promise resolving to array of editor titles - */ - async getOpenEditorTitles(groupIndex?: number): Promise { - const groups = await this.getEditorGroups(); - if (groupIndex !== undefined) { - return groups[groupIndex].getOpenEditorTitles(); - } - const titles: string[] = []; - for (const group of groups) { - titles.push(...(await group.getOpenEditorTitles())); - } - return titles; - } - - /** - * Retrieve an editor tab from a given group by title - * @param title title of the tab - * @param groupIndex zero based index of the editor group, default 0 (leftmost one) - * @returns promise resolving to EditorTab object - */ - async getTabByTitle(title: string, groupIndex: number = 0): Promise { - const group = await this.getEditorGroup(groupIndex); - return group.getTabByTitle(title); - } - - /** - * Retrieve all open editor tabs - * @param groupIndex index of group to search for tabs, if left undefined, all groups are searched - * @returns promise resolving to EditorTab list - */ - async getOpenTabs(groupIndex?: number): Promise { - const groups = await this.getEditorGroups(); - if (groupIndex !== undefined) { - return groups[groupIndex].getOpenTabs(); - } - const tabs: EditorTab[] = []; - for (const group of groups) { - tabs.push(...(await group.getOpenTabs())); - } - return tabs; - } - - /** - * Retrieve the active editor tab - * @returns promise resolving to EditorTab object, undefined if no tab is active - */ - async getActiveTab(): Promise { - const tabs = await this.getOpenTabs(); - - for (const tab of tabs) { - if (await tab.isSelected()) { - return tab; - } - } - - return undefined; - } - - /** - * Retrieve all editor groups in a list, sorted left to right - * @returns promise resolving to an array of EditorGroup objects - */ - async getEditorGroups(): Promise { - const elements = await this.findElements(EditorGroup.locators.EditorView.editorGroup); - const groups = await Promise.all(elements.map(async (element, index) => new EditorGroup(element, this, index).wait())); - - // sort the groups by x coordinates, so the leftmost is always at index 0 - for (let i = 0; i < groups.length - 1; i++) { - for (let j = 0; j < groups.length - i - 1; j++) { - if ((await groups[j].getRect()).x > (await groups[j + 1].getRect()).x) { - const temp = groups[j]; - groups[j] = groups[j + 1]; - groups[j + 1] = temp; - } - } - } - return groups; - } - - /** - * Retrieve an editor group with a given index (counting from left to right) - * @param index zero based index of the editor group (leftmost group has index 0) - * @returns promise resolving to an EditorGroup object - */ - async getEditorGroup(index: number): Promise { - return (await this.getEditorGroups())[index]; - } - - /** - * Get editor actions of a select editor group - * @param groupIndex zero based index of the editor group (leftmost group has index 0), default 0 - * @returns promise resolving to list of EditorAction objects - */ - async getActions(groupIndex: number = 0): Promise { - const group = await this.getEditorGroup(groupIndex); - return group.getActions(); - } - - /** - * Get editor action of a select editor group, search by title or predicate - * @param predicateOrTitle title or predicate to be used in search process - * @param groupIndex zero based index of the editor group (leftmost group has index 0), default 0 - * @returns promise resolving to EditorAction object if found, undefined otherwise - */ - async getAction(predicateOrTitle: string | ((action: EditorAction) => boolean | PromiseLike), groupIndex: number = 0): Promise { - const group = await this.getEditorGroup(groupIndex); - return group.getAction(predicateOrTitle); - } + constructor() { + super(EditorView.locators.EditorView.constructor, EditorView.locators.Workbench.constructor); + } + + /** + * Switch to an editor tab with the given title + * @param title title of the tab + * @param groupIndex zero based index for the editor group (0 for the left most group) + * @returns Promise resolving to Editor object + */ + async openEditor(title: string, groupIndex: number = 0): Promise { + const group = await this.getEditorGroup(groupIndex); + return group.openEditor(title); + } + + /** + * Close an editor tab with the given title + * @param title title of the tab + * @param groupIndex zero based index for the editor group (0 for the left most group) + * @returns Promise resolving when the tab's close button is pressed + */ + async closeEditor(title: string, groupIndex: number = 0): Promise { + const group = await this.getEditorGroup(groupIndex); + return group.closeEditor(title); + } + + /** + * Close all open editor tabs + * @param groupIndex optional index to specify an editor group + * @returns Promise resolving once all tabs have had their close button pressed + */ + async closeAllEditors(groupIndex?: number): Promise { + let groups = await this.getEditorGroups(); + if (groupIndex !== undefined) { + return groups[groupIndex].closeAllEditors(); + } + + while (groups.length > 0 && (await groups[0].getOpenEditorTitles()).length > 0) { + await groups[0].closeAllEditors(); + await new Promise((res) => setTimeout(res, 1000)); + groups = await this.getEditorGroups(); + } + } + + /** + * Retrieve all open editor tab titles in an array + * @param groupIndex optional index to specify an editor group, if left empty will search all groups + * @returns Promise resolving to array of editor titles + */ + async getOpenEditorTitles(groupIndex?: number): Promise { + const groups = await this.getEditorGroups(); + if (groupIndex !== undefined) { + return groups[groupIndex].getOpenEditorTitles(); + } + const titles: string[] = []; + for (const group of groups) { + titles.push(...(await group.getOpenEditorTitles())); + } + return titles; + } + + /** + * Retrieve an editor tab from a given group by title + * @param title title of the tab + * @param groupIndex zero based index of the editor group, default 0 (leftmost one) + * @returns promise resolving to EditorTab object + */ + async getTabByTitle(title: string, groupIndex: number = 0): Promise { + const group = await this.getEditorGroup(groupIndex); + return group.getTabByTitle(title); + } + + /** + * Retrieve all open editor tabs + * @param groupIndex index of group to search for tabs, if left undefined, all groups are searched + * @returns promise resolving to EditorTab list + */ + async getOpenTabs(groupIndex?: number): Promise { + const groups = await this.getEditorGroups(); + if (groupIndex !== undefined) { + return groups[groupIndex].getOpenTabs(); + } + const tabs: EditorTab[] = []; + for (const group of groups) { + tabs.push(...(await group.getOpenTabs())); + } + return tabs; + } + + /** + * Retrieve the active editor tab + * @returns promise resolving to EditorTab object, undefined if no tab is active + */ + async getActiveTab(): Promise { + const tabs = await this.getOpenTabs(); + + for (const tab of tabs) { + if (await tab.isSelected()) { + return tab; + } + } + + return undefined; + } + + /** + * Retrieve all editor groups in a list, sorted left to right + * @returns promise resolving to an array of EditorGroup objects + */ + async getEditorGroups(): Promise { + const elements = await this.findElements(EditorGroup.locators.EditorView.editorGroup); + const groups = await Promise.all(elements.map(async (element, index) => new EditorGroup(element, this, index).wait())); + + // sort the groups by x coordinates, so the leftmost is always at index 0 + for (let i = 0; i < groups.length - 1; i++) { + for (let j = 0; j < groups.length - i - 1; j++) { + if ((await groups[j].getRect()).x > (await groups[j + 1].getRect()).x) { + const temp = groups[j]; + groups[j] = groups[j + 1]; + groups[j + 1] = temp; + } + } + } + return groups; + } + + /** + * Retrieve an editor group with a given index (counting from left to right) + * @param index zero based index of the editor group (leftmost group has index 0) + * @returns promise resolving to an EditorGroup object + */ + async getEditorGroup(index: number): Promise { + return (await this.getEditorGroups())[index]; + } + + /** + * Get editor actions of a select editor group + * @param groupIndex zero based index of the editor group (leftmost group has index 0), default 0 + * @returns promise resolving to list of EditorAction objects + */ + async getActions(groupIndex: number = 0): Promise { + const group = await this.getEditorGroup(groupIndex); + return group.getActions(); + } + + /** + * Get editor action of a select editor group, search by title or predicate + * @param predicateOrTitle title or predicate to be used in search process + * @param groupIndex zero based index of the editor group (leftmost group has index 0), default 0 + * @returns promise resolving to EditorAction object if found, undefined otherwise + */ + async getAction( + predicateOrTitle: string | ((action: EditorAction) => boolean | PromiseLike), + groupIndex: number = 0, + ): Promise { + const group = await this.getEditorGroup(groupIndex); + return group.getAction(predicateOrTitle); + } } /** * Page object representing an editor group */ export class EditorGroup extends AbstractElement { - constructor(element: WebElement, view: EditorView = new EditorView(), private index: number = 0) { - super(element, view); - } - - /** - * Switch to an editor tab with the given title - * @param title title of the tab - * @returns Promise resolving to Editor object - */ - async openEditor(title: string): Promise { - const tab = await this.getTabByTitle(title); - await tab.select(); - - try { - await this.findElement(EditorView.locators.EditorView.settingsEditor); - return new SettingsEditor(this).wait(); - } catch (err) { - try { - await this.findElement(EditorView.locators.EditorView.webView); - return new WebView(this).wait(); - } catch (err) { - try { - await this.findElement(EditorView.locators.EditorView.diffEditor); - return new DiffEditor(this).wait(); - } catch (err) { - return new TextEditor(this).wait(); - } - } - } - } - - /** - * Close an editor tab with the given title - * @param title title of the tab - * @returns Promise resolving when the tab's close button is pressed - */ - async closeEditor(title: string): Promise { - const tab = await this.getTabByTitle(title); - const closeButton = await tab.findElement(EditorView.locators.EditorView.closeTab); - await closeButton.click(); - } - - /** - * Close all open editor tabs - * @returns Promise resolving once all tabs have had their close button pressed - */ - async closeAllEditors(): Promise { - let titles = await this.getOpenEditorTitles(); - while (titles.length > 0) { - await this.closeEditor(titles[0]); - try { - // check if the group still exists - await this.getTagName(); - } catch (err) { - break; - } - titles = await this.getOpenEditorTitles(); - } - } - - /** - * Retrieve all open editor tab titles in an array - * @returns Promise resolving to array of editor titles - */ - async getOpenEditorTitles(): Promise { - const tabs = await this.findElements(EditorView.locators.EditorView.tab); - const titles = []; - for (const tab of tabs) { - try { - const title = await new EditorTab(tab, this.enclosingItem as EditorView).getTitle(); - titles.push(title); - } - catch (e) { - if (e instanceof error.StaleElementReferenceError) { - continue; - } - throw e; - } - } - return titles; - } - - /** - * Retrieve an editor tab by title - * @param title title of the tab - * @returns promise resolving to EditorTab object - */ - async getTabByTitle(title: string): Promise { - const tabs = await this.findElements(EditorView.locators.EditorView.tab); - for (const element of tabs) { - try { - const tab = new EditorTab(element, this.enclosingItem as EditorView); - const label = await tab.getTitle(); - if (label === title) { - return tab; - } - } - catch (e) { - if (e instanceof error.StaleElementReferenceError) { - continue; - } - throw e; - } - } - throw new EditorTabNotFound(title, this.index); - } - - /** - * Retrieve all open editor tabs - * @returns promise resolving to EditorTab list - */ - async getOpenTabs(): Promise { - const tabs = await this.findElements(EditorView.locators.EditorView.tab); - return Promise.all(tabs.map(async tab => new EditorTab(tab, this.enclosingItem as EditorView).wait())); - } - - /** - * Retrieve the active editor tab - * @returns promise resolving to EditorTab object, undefined if no tab is active - */ - async getActiveTab(): Promise { - const tabs = await this.getOpenTabs(); - - for (const tab of tabs) { - if (await tab.isSelected()) { - return tab; - } - } - - return undefined; - } - - /** - * Retrieve the editor action buttons as EditorActions - * @returns promise resolving to list of EditorAction objects - */ - async getActions(): Promise { - const actions = await - this.findElement(EditorGroup.locators.EditorView.actionContainer) - .findElements(EditorGroup.locators.EditorView.actionItem); - return actions.map((action) => new EditorAction(action, this)); - } - - /** - * Find an editor action button by predicate or title - * @param predicateOrTitle predicate/title to be used - * @returns promise resolving to EditorAction representing the button if found, undefined otherwise - */ - async getAction(predicateOrTitle: string | ((action: EditorAction) => boolean | PromiseLike)): Promise { - const predicate = (typeof predicateOrTitle === 'string') ? - (async (action: EditorAction) => await action.getTitle() === predicateOrTitle) : (predicateOrTitle); - - const actions = await this.getActions(); - - for (const action of actions) { - if (await predicate(action)) { - return action; - } - } - return undefined; - } + constructor( + element: WebElement, + view: EditorView = new EditorView(), + private index: number = 0, + ) { + super(element, view); + } + + /** + * Switch to an editor tab with the given title + * @param title title of the tab + * @returns Promise resolving to Editor object + */ + async openEditor(title: string): Promise { + const tab = await this.getTabByTitle(title); + await tab.select(); + + try { + await this.findElement(EditorView.locators.EditorView.settingsEditor); + return new SettingsEditor(this).wait(); + } catch (err) { + try { + await this.findElement(EditorView.locators.EditorView.webView); + return new WebView(this).wait(); + } catch (err) { + try { + await this.findElement(EditorView.locators.EditorView.diffEditor); + return new DiffEditor(this).wait(); + } catch (err) { + return new TextEditor(this).wait(); + } + } + } + } + + /** + * Close an editor tab with the given title + * @param title title of the tab + * @returns Promise resolving when the tab's close button is pressed + */ + async closeEditor(title: string): Promise { + const tab = await this.getTabByTitle(title); + const closeButton = await tab.findElement(EditorView.locators.EditorView.closeTab); + await closeButton.click(); + } + + /** + * Close all open editor tabs + * @returns Promise resolving once all tabs have had their close button pressed + */ + async closeAllEditors(): Promise { + let titles = await this.getOpenEditorTitles(); + while (titles.length > 0) { + await this.closeEditor(titles[0]); + try { + // check if the group still exists + await this.getTagName(); + } catch (err) { + break; + } + titles = await this.getOpenEditorTitles(); + } + } + + /** + * Retrieve all open editor tab titles in an array + * @returns Promise resolving to array of editor titles + */ + async getOpenEditorTitles(): Promise { + const tabs = await this.findElements(EditorView.locators.EditorView.tab); + const titles = []; + for (const tab of tabs) { + try { + const title = await new EditorTab(tab, this.enclosingItem as EditorView).getTitle(); + titles.push(title); + } catch (e) { + if (e instanceof error.StaleElementReferenceError) { + continue; + } + throw e; + } + } + return titles; + } + + /** + * Retrieve an editor tab by title + * @param title title of the tab + * @returns promise resolving to EditorTab object + */ + async getTabByTitle(title: string): Promise { + const tabs = await this.findElements(EditorView.locators.EditorView.tab); + for (const element of tabs) { + try { + const tab = new EditorTab(element, this.enclosingItem as EditorView); + const label = await tab.getTitle(); + if (label === title) { + return tab; + } + } catch (e) { + if (e instanceof error.StaleElementReferenceError) { + continue; + } + throw e; + } + } + throw new EditorTabNotFound(title, this.index); + } + + /** + * Retrieve all open editor tabs + * @returns promise resolving to EditorTab list + */ + async getOpenTabs(): Promise { + const tabs = await this.findElements(EditorView.locators.EditorView.tab); + return Promise.all(tabs.map(async (tab) => new EditorTab(tab, this.enclosingItem as EditorView).wait())); + } + + /** + * Retrieve the active editor tab + * @returns promise resolving to EditorTab object, undefined if no tab is active + */ + async getActiveTab(): Promise { + const tabs = await this.getOpenTabs(); + + for (const tab of tabs) { + if (await tab.isSelected()) { + return tab; + } + } + + return undefined; + } + + /** + * Retrieve the editor action buttons as EditorActions + * @returns promise resolving to list of EditorAction objects + */ + async getActions(): Promise { + const actions = await this.findElement(EditorGroup.locators.EditorView.actionContainer).findElements(EditorGroup.locators.EditorView.actionItem); + return actions.map((action) => new EditorAction(action, this)); + } + + /** + * Find an editor action button by predicate or title + * @param predicateOrTitle predicate/title to be used + * @returns promise resolving to EditorAction representing the button if found, undefined otherwise + */ + async getAction(predicateOrTitle: string | ((action: EditorAction) => boolean | PromiseLike)): Promise { + const predicate = + typeof predicateOrTitle === 'string' ? async (action: EditorAction) => (await action.getTitle()) === predicateOrTitle : predicateOrTitle; + + const actions = await this.getActions(); + + for (const action of actions) { + if (await predicate(action)) { + return action; + } + } + return undefined; + } } /** * Page object for editor view tab */ export class EditorTab extends ElementWithContexMenu { - constructor(element: WebElement, view: EditorView) { - super(element, view); - } - - /** - * Get the tab title as string - */ - async getTitle(): Promise { - const label = await this.findElement(EditorTab.locators.Editor.title); - return await label.getText(); - } - - /** - * Select (click) the tab - */ - async select(): Promise { - const tabCoords = await this.getRect(); - await this.getDriver().actions().move({ x: Math.ceil(tabCoords.x + 10), y: Math.ceil(tabCoords.y + 10) }).click().perform(); - } - - async isSelected(): Promise { - const klass = await this.getAttribute('class'); - const segments = klass?.split(/\s+/g) ?? []; - return await super.isSelected() || segments.includes('active'); - } -} \ No newline at end of file + constructor(element: WebElement, view: EditorView) { + super(element, view); + } + + /** + * Get the tab title as string + */ + async getTitle(): Promise { + const label = await this.findElement(EditorTab.locators.Editor.title); + return await label.getText(); + } + + /** + * Select (click) the tab + */ + async select(): Promise { + const tabCoords = await this.getRect(); + await this.getDriver() + .actions() + .move({ + x: Math.ceil(tabCoords.x + 10), + y: Math.ceil(tabCoords.y + 10), + }) + .click() + .perform(); + } + + async isSelected(): Promise { + const klass = await this.getAttribute('class'); + const segments = klass?.split(/\s+/g) ?? []; + return (await super.isSelected()) || segments.includes('active'); + } +} diff --git a/packages/page-objects/src/components/editor/SettingsEditor.ts b/packages/page-objects/src/components/editor/SettingsEditor.ts index 59743a1f3..f8636acf8 100644 --- a/packages/page-objects/src/components/editor/SettingsEditor.ts +++ b/packages/page-objects/src/components/editor/SettingsEditor.ts @@ -15,153 +15,155 @@ * limitations under the License. */ -import { Editor } from "./Editor"; -import { ContextMenu } from "../menu/ContextMenu"; -import { WebElement, Key, By } from "selenium-webdriver"; -import { AbstractElement } from "../AbstractElement"; -import { EditorView, EditorGroup } from "../.."; +import { Editor } from './Editor'; +import { ContextMenu } from '../menu/ContextMenu'; +import { WebElement, Key, By } from 'selenium-webdriver'; +import { AbstractElement } from '../AbstractElement'; +import { EditorView, EditorGroup } from '../..'; /** * Page object representing the internal VS Code settings editor */ export class SettingsEditor extends Editor { - - private divider = SettingsEditor.versionInfo.version >= '1.83.0' ? 'â€ē' : ' â€ē '; - - constructor(view: EditorView | EditorGroup = new EditorView()) { - super(view); - } - - /** - * Search for a setting with a particular title and category. - * Returns an appropriate Setting object if the label is found, - * undefined otherwise. - * - * If your setting has nested categories (i.e `example.general.test`), - * pass in each category as a separate string. - * - * @param title title of the setting - * @param categories category of the setting - * @returns Promise resolving to a Setting object if found, undefined otherwise - */ - async findSetting(title: string, ...categories: string[]): Promise { - const category = categories.join(this.divider); - const searchBox = await this.findElement(SettingsEditor.locators.Editor.inputArea); - await searchBox.sendKeys(Key.chord(SettingsEditor.ctlKey, 'a')); - await searchBox.sendKeys(`${category}${this.divider}${title}`); - - return await this._getSettingItem(title, category); - } - - /** - * Search for a setting with a precise ID. - * Returns an appropriate Setting object if it exists, - * undefined otherwise. - * - * @param id of the setting - * @returns Promise resolving to a Setting object if found, undefined otherwise - */ - async findSettingByID(id: string): Promise { - const searchBox = await this.findElement(SettingsEditor.locators.Editor.inputArea); - await searchBox.sendKeys(Key.chord(SettingsEditor.ctlKey, 'a')); - await searchBox.sendKeys(id); - - const title = id.split('.').pop(); - return await this._getSettingItem(title); - } - - private async _getSettingItem(title: string = '', category: string = ''): Promise { - const count = await this.findElement(SettingsEditor.locators.SettingsEditor.itemCount); - let textCount = await count.getText(); - await this.getDriver().wait(async function() { - await new Promise(res => setTimeout(res, 1500)); - const text = await count.getText(); - if (text !== textCount) { - textCount = text; - return false; - } - return true; - }); - - let setting!: Setting; - const items = await this.findElements(SettingsEditor.locators.SettingsEditor.itemRow); - for (const item of items) { - try { - const itemCategory = (await (await item.findElement(SettingsEditor.locators.SettingsEditor.settingCategory)).getText()).replace(':', ''); - const itemTitle = await (await item.findElement(SettingsEditor.locators.SettingsEditor.settingLabel)).getText(); - if(category !== '') { - if(category.toLowerCase().replace(this.divider, '').replace(/\s/g, '').trim() !== itemCategory.toLowerCase().replace(this.divider, '').replace(/\s/g, '').trim()) { - continue; - } - } - if(title !== '') { - if(title.toLowerCase().replace(/\s/g, '').trim() !== itemTitle.toLowerCase().replace(/\s/g, '').trim()) { - continue; - } - } - return await (await this.createSetting(item, itemTitle, itemCategory)).wait(); - } catch (err) { - // do nothing - } - } - return setting; - } - - /** - * Switch between settings perspectives - * Works only if your vscode instance has both user and workspace settings available - * - * @param perspective User or Workspace - * @returns Promise that resolves when the appropriate button is clicked - */ - async switchToPerspective(perspective: 'User' | 'Workspace'): Promise { - const actions = await this.findElement(SettingsEditor.locators.SettingsEditor.header) - .findElement(SettingsEditor.locators.SettingsEditor.tabs) - .findElement(SettingsEditor.locators.SettingsEditor.actions); - await actions.findElement(SettingsEditor.locators.SettingsEditor.action(perspective)).click(); - } - - /** - * Context menu is disabled in this editor, throw an error - */ - async openContextMenu(): Promise { - throw new Error('Operation not supported!'); - } - - private async createSetting(element: WebElement, title: string, category: string): Promise { - await element.findElement(SettingsEditor.locators.SettingsEditor.settingConstructor(title, category)); - try { - // try a combo setting - await element.findElement(SettingsEditor.locators.SettingsEditor.comboSetting); - return new ComboSetting(SettingsEditor.locators.SettingsEditor.settingConstructor(title, category), this); - } catch (err) { - try { - // try text setting - await element.findElement(SettingsEditor.locators.SettingsEditor.textSetting); - return new TextSetting(SettingsEditor.locators.SettingsEditor.settingConstructor(title, category), this); - } catch (err) { - try { - // try checkbox setting - await element.findElement(SettingsEditor.locators.SettingsEditor.checkboxSetting); - return new CheckboxSetting(SettingsEditor.locators.SettingsEditor.settingConstructor(title, category), this); - } catch (err) { - // try link setting - try { - await element.findElement(SettingsEditor.locators.SettingsEditor.linkButton); - return new LinkSetting(SettingsEditor.locators.SettingsEditor.settingConstructor(title, category), this); - } catch (err) { - // try array setting - try { - await element.findElement(SettingsEditor.locators.SettingsEditor.arraySetting); - return new ArraySetting(SettingsEditor.locators.SettingsEditor.settingConstructor(title, category), this); - } catch (err) { - throw new Error('Setting type not supported!'); - } - } - } - } - } - } + private divider = SettingsEditor.versionInfo.version >= '1.83.0' ? 'â€ē' : ' â€ē '; + + constructor(view: EditorView | EditorGroup = new EditorView()) { + super(view); + } + + /** + * Search for a setting with a particular title and category. + * Returns an appropriate Setting object if the label is found, + * undefined otherwise. + * + * If your setting has nested categories (i.e `example.general.test`), + * pass in each category as a separate string. + * + * @param title title of the setting + * @param categories category of the setting + * @returns Promise resolving to a Setting object if found, undefined otherwise + */ + async findSetting(title: string, ...categories: string[]): Promise { + const category = categories.join(this.divider); + const searchBox = await this.findElement(SettingsEditor.locators.Editor.inputArea); + await searchBox.sendKeys(Key.chord(SettingsEditor.ctlKey, 'a')); + await searchBox.sendKeys(`${category}${this.divider}${title}`); + + return await this._getSettingItem(title, category); + } + + /** + * Search for a setting with a precise ID. + * Returns an appropriate Setting object if it exists, + * undefined otherwise. + * + * @param id of the setting + * @returns Promise resolving to a Setting object if found, undefined otherwise + */ + async findSettingByID(id: string): Promise { + const searchBox = await this.findElement(SettingsEditor.locators.Editor.inputArea); + await searchBox.sendKeys(Key.chord(SettingsEditor.ctlKey, 'a')); + await searchBox.sendKeys(id); + + const title = id.split('.').pop(); + return await this._getSettingItem(title); + } + + private async _getSettingItem(title: string = '', category: string = ''): Promise { + const count = await this.findElement(SettingsEditor.locators.SettingsEditor.itemCount); + let textCount = await count.getText(); + await this.getDriver().wait(async function () { + await new Promise((res) => setTimeout(res, 1500)); + const text = await count.getText(); + if (text !== textCount) { + textCount = text; + return false; + } + return true; + }); + + let setting!: Setting; + const items = await this.findElements(SettingsEditor.locators.SettingsEditor.itemRow); + for (const item of items) { + try { + const itemCategory = (await (await item.findElement(SettingsEditor.locators.SettingsEditor.settingCategory)).getText()).replace(':', ''); + const itemTitle = await (await item.findElement(SettingsEditor.locators.SettingsEditor.settingLabel)).getText(); + if (category !== '') { + if ( + category.toLowerCase().replace(this.divider, '').replace(/\s/g, '').trim() !== + itemCategory.toLowerCase().replace(this.divider, '').replace(/\s/g, '').trim() + ) { + continue; + } + } + if (title !== '') { + if (title.toLowerCase().replace(/\s/g, '').trim() !== itemTitle.toLowerCase().replace(/\s/g, '').trim()) { + continue; + } + } + return await (await this.createSetting(item, itemTitle, itemCategory)).wait(); + } catch (err) { + // do nothing + } + } + return setting; + } + + /** + * Switch between settings perspectives + * Works only if your vscode instance has both user and workspace settings available + * + * @param perspective User or Workspace + * @returns Promise that resolves when the appropriate button is clicked + */ + async switchToPerspective(perspective: 'User' | 'Workspace'): Promise { + const actions = await this.findElement(SettingsEditor.locators.SettingsEditor.header) + .findElement(SettingsEditor.locators.SettingsEditor.tabs) + .findElement(SettingsEditor.locators.SettingsEditor.actions); + await actions.findElement(SettingsEditor.locators.SettingsEditor.action(perspective)).click(); + } + + /** + * Context menu is disabled in this editor, throw an error + */ + async openContextMenu(): Promise { + throw new Error('Operation not supported!'); + } + + private async createSetting(element: WebElement, title: string, category: string): Promise { + await element.findElement(SettingsEditor.locators.SettingsEditor.settingConstructor(title, category)); + try { + // try a combo setting + await element.findElement(SettingsEditor.locators.SettingsEditor.comboSetting); + return new ComboSetting(SettingsEditor.locators.SettingsEditor.settingConstructor(title, category), this); + } catch (err) { + try { + // try text setting + await element.findElement(SettingsEditor.locators.SettingsEditor.textSetting); + return new TextSetting(SettingsEditor.locators.SettingsEditor.settingConstructor(title, category), this); + } catch (err) { + try { + // try checkbox setting + await element.findElement(SettingsEditor.locators.SettingsEditor.checkboxSetting); + return new CheckboxSetting(SettingsEditor.locators.SettingsEditor.settingConstructor(title, category), this); + } catch (err) { + // try link setting + try { + await element.findElement(SettingsEditor.locators.SettingsEditor.linkButton); + return new LinkSetting(SettingsEditor.locators.SettingsEditor.settingConstructor(title, category), this); + } catch (err) { + // try array setting + try { + await element.findElement(SettingsEditor.locators.SettingsEditor.arraySetting); + return new ArraySetting(SettingsEditor.locators.SettingsEditor.settingConstructor(title, category), this); + } catch (err) { + throw new Error('Setting type not supported!'); + } + } + } + } + } + } } /** @@ -169,353 +171,346 @@ export class SettingsEditor extends Editor { * an input element (combo/textbox/checkbox/link/array) */ export abstract class Setting extends AbstractElement { - - constructor(settingsConstructor: By, settings: SettingsEditor) { - super(settingsConstructor, settings); - } - - /** - * Get the value of the setting based on its input type - * - * @returns promise that resolves to the current value of the setting - */ - abstract getValue(): Promise; - - /** - * Set the value of the setting based on its input type - * - * @param value boolean for checkboxes, string otherwise - */ - abstract setValue(value: string | boolean): Promise; - - /** - * Get the category of the setting - * All settings are labeled as Category: Title - */ - async getCategory(): Promise { - return await (await this.findElement(SettingsEditor.locators.SettingsEditor.settingCategory)).getText(); - } - - /** - * Get description of the setting - * @returns Promise resolving to setting description - */ - async getDescription(): Promise { - return await (await this.findElement(SettingsEditor.locators.SettingsEditor.settingDescription)).getText(); - } - - /** - * Get title of the setting - */ - async getTitle(): Promise { - return await (await this.findElement(SettingsEditor.locators.SettingsEditor.settingLabel)).getText(); - } + constructor(settingsConstructor: By, settings: SettingsEditor) { + super(settingsConstructor, settings); + } + + /** + * Get the value of the setting based on its input type + * + * @returns promise that resolves to the current value of the setting + */ + abstract getValue(): Promise; + + /** + * Set the value of the setting based on its input type + * + * @param value boolean for checkboxes, string otherwise + */ + abstract setValue(value: string | boolean): Promise; + + /** + * Get the category of the setting + * All settings are labeled as Category: Title + */ + async getCategory(): Promise { + return await (await this.findElement(SettingsEditor.locators.SettingsEditor.settingCategory)).getText(); + } + + /** + * Get description of the setting + * @returns Promise resolving to setting description + */ + async getDescription(): Promise { + return await (await this.findElement(SettingsEditor.locators.SettingsEditor.settingDescription)).getText(); + } + + /** + * Get title of the setting + */ + async getTitle(): Promise { + return await (await this.findElement(SettingsEditor.locators.SettingsEditor.settingLabel)).getText(); + } } /** - * Setting with a combo box + * Setting with a combo box */ export class ComboSetting extends Setting { - - async getValue(): Promise { - const combo = await this.findElement(SettingsEditor.locators.SettingsEditor.comboSetting); - return await combo.getAttribute(SettingsEditor.locators.SettingsEditor.comboValue); - } - - async setValue(value: string): Promise { - const rows = await this.getOptions(); - for (const row of rows) { - if ((await row.getAttribute('class')).indexOf('disabled') < 0) { - const text = await row.getAttribute(SettingsEditor.locators.SettingsEditor.comboValue); - if (value === text) { - return await row.click(); - } - } - } - } - - /** - * Get the labels of all options from the combo - * @returns Promise resolving to array of string values - */ - async getValues(): Promise { - const values = []; - const rows = await this.getOptions(); - for (const row of rows) { - values.push(await row.getAttribute(SettingsEditor.locators.SettingsEditor.comboValue)); - } - return values; - } - - private async getOptions(): Promise { - const combo = await this.findElement(SettingsEditor.locators.SettingsEditor.comboSetting); - return await combo.findElements(SettingsEditor.locators.SettingsEditor.comboOption); - } + async getValue(): Promise { + const combo = await this.findElement(SettingsEditor.locators.SettingsEditor.comboSetting); + return await combo.getAttribute(SettingsEditor.locators.SettingsEditor.comboValue); + } + + async setValue(value: string): Promise { + const rows = await this.getOptions(); + for (const row of rows) { + if ((await row.getAttribute('class')).indexOf('disabled') < 0) { + const text = await row.getAttribute(SettingsEditor.locators.SettingsEditor.comboValue); + if (value === text) { + return await row.click(); + } + } + } + } + + /** + * Get the labels of all options from the combo + * @returns Promise resolving to array of string values + */ + async getValues(): Promise { + const values = []; + const rows = await this.getOptions(); + for (const row of rows) { + values.push(await row.getAttribute(SettingsEditor.locators.SettingsEditor.comboValue)); + } + return values; + } + + private async getOptions(): Promise { + const combo = await this.findElement(SettingsEditor.locators.SettingsEditor.comboSetting); + return await combo.findElements(SettingsEditor.locators.SettingsEditor.comboOption); + } } /** * Setting with a text box input */ export class TextSetting extends Setting { - - async getValue(): Promise { - const input = await this.findElement(SettingsEditor.locators.SettingsEditor.textSetting); - return await input.getAttribute('value'); - } - - async setValue(value: string): Promise { - const input = await this.findElement(SettingsEditor.locators.SettingsEditor.textSetting); - await input.clear(); - await input.sendKeys(value); - } + async getValue(): Promise { + const input = await this.findElement(SettingsEditor.locators.SettingsEditor.textSetting); + return await input.getAttribute('value'); + } + + async setValue(value: string): Promise { + const input = await this.findElement(SettingsEditor.locators.SettingsEditor.textSetting); + await input.clear(); + await input.sendKeys(value); + } } /** * Setting with a checkbox */ export class CheckboxSetting extends Setting { - - async getValue(): Promise { - const checkbox = await this.findElement(SettingsEditor.locators.SettingsEditor.checkboxSetting); - const checked = await checkbox.getAttribute(SettingsEditor.locators.SettingsEditor.checkboxChecked); - if (checked === 'true') { - return true; - } - return false; - } - - async setValue(value: boolean): Promise { - const checkbox = await this.findElement(SettingsEditor.locators.SettingsEditor.checkboxSetting); - if (await this.getValue() !== value) { - await checkbox.click(); - } - } + async getValue(): Promise { + const checkbox = await this.findElement(SettingsEditor.locators.SettingsEditor.checkboxSetting); + const checked = await checkbox.getAttribute(SettingsEditor.locators.SettingsEditor.checkboxChecked); + if (checked === 'true') { + return true; + } + return false; + } + + async setValue(value: boolean): Promise { + const checkbox = await this.findElement(SettingsEditor.locators.SettingsEditor.checkboxSetting); + if ((await this.getValue()) !== value) { + await checkbox.click(); + } + } } /** * Setting with no value, with a link to settings.json instead */ export class LinkSetting extends Setting { - - async getValue(): Promise { - throw new Error('Method getValue is not available for LinkSetting!'); - } - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - async setValue(value: string | boolean): Promise { - throw new Error('Method setValue is not available for LinkSetting!'); - } - - /** - * Open the link that leads to the value in settings.json - * @returns Promise resolving when the link has been clicked - */ - async openLink(): Promise { - const link = await this.findElement(SettingsEditor.locators.SettingsEditor.linkButton); - await link.click(); - } + async getValue(): Promise { + throw new Error('Method getValue is not available for LinkSetting!'); + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + async setValue(value: string | boolean): Promise { + throw new Error('Method setValue is not available for LinkSetting!'); + } + + /** + * Open the link that leads to the value in settings.json + * @returns Promise resolving when the link has been clicked + */ + async openLink(): Promise { + const link = await this.findElement(SettingsEditor.locators.SettingsEditor.linkButton); + await link.click(); + } } /** * Setting with an array of string values (rows) */ export class ArraySetting extends Setting { - - /** - * @deprecated Method 'getValue' is not available for ArraySetting! - */ - async getValue(): Promise { - throw new Error('Method \'getValue\' is not available for ArraySetting!'); - } - - /** - * @deprecated Method 'setValue' is not available for ArraySetting! - */ - // eslint-disable-next-line @typescript-eslint/no-unused-vars - async setValue(value: string | boolean): Promise { - throw new Error('Method \'setValue\' is not available for ArraySetting!'); - } - - /** - * Select a row of a setting array of string values - * @param item label | index - */ - async select(item: string | number): Promise { - const toSelect = await this.getItem(item); - await toSelect?.select(); - } - - /** - * Get a row as new ArraySettingItem object - * @param item label or index - * @returns ArraySettingItem | undefined - */ - async getItem(item: string | number): Promise { - const row = await this.findRow(item); - if(row) { - return new ArraySettingItem(row, this); - } - return undefined; - } - - /** - * Get a list of all rows as ArraySettingItem objects - * @returns ArraySettingItem[] - */ - async getItems(): Promise { - const listRows = await this.getRows(); - const items: ArraySettingItem[] = []; - for(const row of listRows) { - items.push(new ArraySettingItem(row, this)); - } - return items; - } - - /** - * Get a list of all rows values - * @returns string[] - */ - async getValues(): Promise { - const items = await this.getItems(); - const values: string[] = []; - for(const item of items) { - values.push(await item.getValue()); - } - return values; - } - - /** - * Click 'Add Item' and get row as ArraySettingItem in edit mode - * @returns ArraySettingItem - */ - async add(): Promise { - // click 'Add Item' button - const button = await this.findElement(SettingsEditor.locators.SettingsEditor.arrayNewRow).findElement(By.className('monaco-button')); - await button.click(); - await new Promise(sleep => setTimeout(sleep, 1_000)); // need to force some time to allow DOM rerender elements - - // get item row switched to 'edit' mode - const list = await this.getListRootElement(); - const editRow = await list.findElement(SettingsEditor.locators.SettingsEditor.arrayEditRow); - return new ArraySettingItem(editRow, this); - } - - /** - * Select a row, then click 'Edit Item' and get row as ArraySettingItem in edit mode - * @param item label | index - * @returns ArraySettingItem | undefined - */ - async edit(item: string | number): Promise { - const row = await this.findRow(item); - if(row) { - // select item row - const toEdit = new ArraySettingItem(row, this); - await toEdit.select(); - - // click 'Edit Item' button - const edit = await toEdit.findElement(SettingsEditor.locators.SettingsEditor.arrayBtnConstructor('Edit Item')); - await edit.click(); - await new Promise(sleep => setTimeout(sleep, 1_000)); // need to force some time to allow DOM rerender elements - - // get item row switched to 'edit' mode - const list = await this.getListRootElement(); - const editRow = await list.findElement(SettingsEditor.locators.SettingsEditor.arrayEditRow); - return new ArraySettingItem(editRow, this); - } - return undefined; - } - - private async getListRootElement(): Promise { - return await this.findElement(SettingsEditor.locators.SettingsEditor.arrayRoot); - } - - private async getRows(): Promise { - const list = await this.getListRootElement(); - return await list.findElements(SettingsEditor.locators.SettingsEditor.arrayRow); - } - - private async findRow(item: string | number): Promise { - const listRows = await this.getRows(); - if(Number.isInteger(item)) { - const index = +item; - if (index < 0 || index > listRows.length - 1) { - throw Error(`Index '${index}' is of bounds! Found items have length = ${listRows.length}.`); - } - return listRows[index]; - } else { - for(const row of listRows) { - const li = await row.findElement(SettingsEditor.locators.SettingsEditor.arrayRowValue); - if(await li.getText() === item) { - return row; - } - } - } - return undefined; - } + /** + * @deprecated Method 'getValue' is not available for ArraySetting! + */ + async getValue(): Promise { + throw new Error("Method 'getValue' is not available for ArraySetting!"); + } + + /** + * @deprecated Method 'setValue' is not available for ArraySetting! + */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars + async setValue(value: string | boolean): Promise { + throw new Error("Method 'setValue' is not available for ArraySetting!"); + } + + /** + * Select a row of a setting array of string values + * @param item label | index + */ + async select(item: string | number): Promise { + const toSelect = await this.getItem(item); + await toSelect?.select(); + } + + /** + * Get a row as new ArraySettingItem object + * @param item label or index + * @returns ArraySettingItem | undefined + */ + async getItem(item: string | number): Promise { + const row = await this.findRow(item); + if (row) { + return new ArraySettingItem(row, this); + } + return undefined; + } + + /** + * Get a list of all rows as ArraySettingItem objects + * @returns ArraySettingItem[] + */ + async getItems(): Promise { + const listRows = await this.getRows(); + const items: ArraySettingItem[] = []; + for (const row of listRows) { + items.push(new ArraySettingItem(row, this)); + } + return items; + } + + /** + * Get a list of all rows values + * @returns string[] + */ + async getValues(): Promise { + const items = await this.getItems(); + const values: string[] = []; + for (const item of items) { + values.push(await item.getValue()); + } + return values; + } + + /** + * Click 'Add Item' and get row as ArraySettingItem in edit mode + * @returns ArraySettingItem + */ + async add(): Promise { + // click 'Add Item' button + const button = await this.findElement(SettingsEditor.locators.SettingsEditor.arrayNewRow).findElement(By.className('monaco-button')); + await button.click(); + await new Promise((sleep) => setTimeout(sleep, 1_000)); // need to force some time to allow DOM rerender elements + + // get item row switched to 'edit' mode + const list = await this.getListRootElement(); + const editRow = await list.findElement(SettingsEditor.locators.SettingsEditor.arrayEditRow); + return new ArraySettingItem(editRow, this); + } + + /** + * Select a row, then click 'Edit Item' and get row as ArraySettingItem in edit mode + * @param item label | index + * @returns ArraySettingItem | undefined + */ + async edit(item: string | number): Promise { + const row = await this.findRow(item); + if (row) { + // select item row + const toEdit = new ArraySettingItem(row, this); + await toEdit.select(); + + // click 'Edit Item' button + const edit = await toEdit.findElement(SettingsEditor.locators.SettingsEditor.arrayBtnConstructor('Edit Item')); + await edit.click(); + await new Promise((sleep) => setTimeout(sleep, 1_000)); // need to force some time to allow DOM rerender elements + + // get item row switched to 'edit' mode + const list = await this.getListRootElement(); + const editRow = await list.findElement(SettingsEditor.locators.SettingsEditor.arrayEditRow); + return new ArraySettingItem(editRow, this); + } + return undefined; + } + + private async getListRootElement(): Promise { + return await this.findElement(SettingsEditor.locators.SettingsEditor.arrayRoot); + } + + private async getRows(): Promise { + const list = await this.getListRootElement(); + return await list.findElements(SettingsEditor.locators.SettingsEditor.arrayRow); + } + + private async findRow(item: string | number): Promise { + const listRows = await this.getRows(); + if (Number.isInteger(item)) { + const index = +item; + if (index < 0 || index > listRows.length - 1) { + throw Error(`Index '${index}' is of bounds! Found items have length = ${listRows.length}.`); + } + return listRows[index]; + } else { + for (const row of listRows) { + const li = await row.findElement(SettingsEditor.locators.SettingsEditor.arrayRowValue); + if ((await li.getText()) === item) { + return row; + } + } + } + return undefined; + } } /** * Represents each row of an ArraySetting array of strings */ export class ArraySettingItem extends AbstractElement { - - constructor(element: WebElement, setting: ArraySetting) { - super(element, setting); - } - - /** - * Select an item - */ - async select(): Promise { - await this.click(); - await new Promise(sleep => setTimeout(sleep, 500)); - } - - /** - * Get item text value - * @returns string - */ - async getValue(): Promise { - return await this.getText(); - } - - /** - * Set item text value - * @param value string - */ - async setValue(value: string): Promise { - const input = await this.findElement(SettingsEditor.locators.SettingsEditor.textSetting); - await input.clear(); - await input.sendKeys(value); - } - - /** - * Click 'Remove Item' button - */ - async remove(): Promise { - await this.select(); - const remove = await this.findElement(SettingsEditor.locators.SettingsEditor.arrayBtnConstructor('Remove Item')); - await remove.click(); - await new Promise(sleep => setTimeout(sleep, 500)); - } - - /** - * Click 'OK' button - * @description Only when the item is in edit mode - */ - async ok(): Promise { - const ok = await this.findElement(SettingsEditor.locators.SettingsEditor.arraySettingItem.btnConstructor('OK')); - await ok.click(); - await new Promise(sleep => setTimeout(sleep, 500)); - } - - /** - * Click 'Cancel' button - * @description Only when the item is in edit mode - */ - async cancel(): Promise { - const cancel = await this.findElement(SettingsEditor.locators.SettingsEditor.arraySettingItem.btnConstructor('Cancel')); - await cancel.click(); - await new Promise(sleep => setTimeout(sleep, 500)); - } + constructor(element: WebElement, setting: ArraySetting) { + super(element, setting); + } + + /** + * Select an item + */ + async select(): Promise { + await this.click(); + await new Promise((sleep) => setTimeout(sleep, 500)); + } + + /** + * Get item text value + * @returns string + */ + async getValue(): Promise { + return await this.getText(); + } + + /** + * Set item text value + * @param value string + */ + async setValue(value: string): Promise { + const input = await this.findElement(SettingsEditor.locators.SettingsEditor.textSetting); + await input.clear(); + await input.sendKeys(value); + } + + /** + * Click 'Remove Item' button + */ + async remove(): Promise { + await this.select(); + const remove = await this.findElement(SettingsEditor.locators.SettingsEditor.arrayBtnConstructor('Remove Item')); + await remove.click(); + await new Promise((sleep) => setTimeout(sleep, 500)); + } + + /** + * Click 'OK' button + * @description Only when the item is in edit mode + */ + async ok(): Promise { + const ok = await this.findElement(SettingsEditor.locators.SettingsEditor.arraySettingItem.btnConstructor('OK')); + await ok.click(); + await new Promise((sleep) => setTimeout(sleep, 500)); + } + + /** + * Click 'Cancel' button + * @description Only when the item is in edit mode + */ + async cancel(): Promise { + const cancel = await this.findElement(SettingsEditor.locators.SettingsEditor.arraySettingItem.btnConstructor('Cancel')); + await cancel.click(); + await new Promise((sleep) => setTimeout(sleep, 500)); + } } diff --git a/packages/page-objects/src/components/editor/TextEditor.ts b/packages/page-objects/src/components/editor/TextEditor.ts index 412abd9a7..3afb939c6 100644 --- a/packages/page-objects/src/components/editor/TextEditor.ts +++ b/packages/page-objects/src/components/editor/TextEditor.ts @@ -15,764 +15,766 @@ * limitations under the License. */ -import { ContentAssist, ContextMenu, InputBox, Workbench } from "../.."; -import { By, ChromiumWebDriver, Key, until, WebElement } from "selenium-webdriver"; -import { fileURLToPath } from "url"; -import { StatusBar } from "../statusBar/StatusBar"; -import { Editor } from "./Editor"; -import { ElementWithContexMenu } from "../ElementWithContextMenu"; -import { AbstractElement } from "../AbstractElement"; -import { Breakpoint } from "./Breakpoint"; +import { ContentAssist, ContextMenu, InputBox, Workbench } from '../..'; +import { By, ChromiumWebDriver, Key, until, WebElement } from 'selenium-webdriver'; +import { fileURLToPath } from 'url'; +import { StatusBar } from '../statusBar/StatusBar'; +import { Editor } from './Editor'; +import { ElementWithContexMenu } from '../ElementWithContextMenu'; +import { AbstractElement } from '../AbstractElement'; +import { Breakpoint } from './Breakpoint'; -export class BreakpointError extends Error { } +export class BreakpointError extends Error {} /** * Page object representing the active text editor */ export class TextEditor extends Editor { - breakPoints: number[] = []; - - /** - * Find whether the active editor has unsaved changes - * @returns Promise resolving to true/false - */ - async isDirty(): Promise { - const tab = await this.enclosingItem.findElement(TextEditor.locators.TextEditor.activeTab); - const klass = await tab.getAttribute('class'); - return klass.indexOf('dirty') >= 0; - } - - /** - * Saves the active editor - * @returns Promise resolving when ctrl+s is invoked - */ - async save(): Promise { - const inputarea = await this.findElement(TextEditor.locators.Editor.inputArea); - await inputarea.sendKeys(Key.chord(TextEditor.ctlKey, 's')); - } - - /** - * Open the Save as prompt - * - * @returns InputBox serving as a simple file dialog - */ - async saveAs(): Promise { - const tab = await this.getTab(); - await tab.sendKeys(Key.chord(TextEditor.ctlKey, Key.SHIFT, 's')); - return await InputBox.create(); - } - - /** - * Retrieve the Uri of the file opened in the active editor - * @returns Promise resolving to editor's underlying Uri - */ - async getFileUri(): Promise { - const ed = await this.findElement(TextEditor.locators.TextEditor.editorContainer); - return await ed.getAttribute(TextEditor.locators.TextEditor.dataUri); - } - - /** - * Retrieve the path to the file opened in the active editor - * @returns Promise resolving to editor's underlying file path - */ - async getFilePath(): Promise { - return fileURLToPath(await this.getFileUri()); - } - - /** - * Open/Close the content assistant at the current position in the editor by sending the default - * keyboard shortcut signal - * @param open true to open, false to close - * @returns Promise resolving to ContentAssist object when opening, void otherwise - */ - async toggleContentAssist(open: boolean): Promise { - let isHidden = true; - try { - const assist = await this.findElement(TextEditor.locators.ContentAssist.constructor); - const klass = await assist.getAttribute('class'); - const visibility = await assist.getCssValue('visibility'); - isHidden = klass.indexOf('visible') < 0 || visibility === 'hidden'; - } catch (err) { - isHidden = true; - } - const inputarea = await this.findElement(TextEditor.locators.Editor.inputArea); - - if (open) { - if (isHidden) { - await inputarea.sendKeys(Key.chord(Key.CONTROL, Key.SPACE)); - await this.getDriver().wait(until.elementLocated(TextEditor.locators.ContentAssist.constructor), 2000); - } - const assist = await new ContentAssist(this).wait(); - await this.getDriver().wait(() => { return assist.isLoaded(); }, 10000); - return assist; - } else { - if (!isHidden) { - await inputarea.sendKeys(Key.ESCAPE); - } - } - } - - /** - * Get all text from the editor - * @returns Promise resolving to editor text - */ - async getText(): Promise { - const clipboard = (await import('clipboardy')).default; - let originalClipboard = ''; - try { - originalClipboard = clipboard.readSync(); - } catch (error) { - // workaround issue https://github.com/redhat-developer/vscode-extension-tester/issues/835 - // do not fail if clipboard is empty - } - const inputarea = await this.findElement(TextEditor.locators.Editor.inputArea); - await inputarea.sendKeys(Key.chord(TextEditor.ctlKey, 'a'), Key.chord(TextEditor.ctlKey, 'c')); - await new Promise(res => setTimeout(res, 500)); - const text = clipboard.readSync(); - await inputarea.sendKeys(Key.UP); - if (originalClipboard.length > 0) { - clipboard.writeSync(originalClipboard); - } - return text; - } - - /** - * Replace the contents of the editor with a given text - * @param text text to type into the editor - * @param formatText format the new text, default false - * @returns Promise resolving once the new text is copied over - */ - async setText(text: string, formatText: boolean = false): Promise { - const clipboard = (await import('clipboardy')).default; - let originalClipboard = ''; - try { - originalClipboard = clipboard.readSync(); - } catch (error) { - // workaround issue https://github.com/redhat-developer/vscode-extension-tester/issues/835 - // do not fail if clipboard is empty - } - const inputarea = await this.findElement(TextEditor.locators.Editor.inputArea); - clipboard.writeSync(text); - await inputarea.sendKeys(Key.chord(TextEditor.ctlKey, 'a'), Key.chord(TextEditor.ctlKey, 'v')); - if (originalClipboard.length > 0) { - clipboard.writeSync(originalClipboard); - } - if (formatText) { - await this.formatDocument(); - } - } - - /** - * Deletes all text within the editor - * @returns Promise resolving once the text is deleted - */ - async clearText(): Promise { - const inputarea = await this.findElement(TextEditor.locators.Editor.inputArea); - await inputarea.sendKeys(Key.chord(TextEditor.ctlKey, 'a')); - await inputarea.sendKeys(Key.BACK_SPACE); - } - - /** - * Get text from a given line - * @param line number of the line to retrieve - * @returns Promise resolving to text at the given line number - */ - async getTextAtLine(line: number): Promise { - const text = await this.getText(); - const lines = text.split('\n'); - if (line < 1 || line > lines.length) { - throw new Error(`Line number ${line} does not exist`); - } - return lines[line - 1]; - } - - /** - * Replace the contents of a line with a given text - * @param line number of the line to edit - * @param text text to set at the line - * @returns Promise resolving when the text is typed in - */ - async setTextAtLine(line: number, text: string): Promise { - if (line < 1 || line > await this.getNumberOfLines()) { - throw new Error(`Line number ${line} does not exist`); - } - const lines = (await this.getText()).split('\n'); - lines[line - 1] = text; - await this.setText(lines.join('\n')); - } - - /** - * Get line number that contains the given text. Not suitable for multi line inputs. - * - * @param text text to search for - * @param occurrence select which occurrence of the search text to look for in case there are multiple in the document, defaults to 1 (the first instance) - * - * @returns Number of the line that contains the start of the given text. -1 if no such text is found. - * If occurrence number is specified, searches until it finds as many instances of the given text. - * Returns the line number that holds the last occurrence found this way. - */ - async getLineOfText(text: string, occurrence = 1): Promise { - let lineNum = -1; - let found = 0; - const lines = (await this.getText()).split('\n'); - - for (let i = 0; i < lines.length; i++) { - if (lines[i].includes(text)) { - found++; - lineNum = i + 1; - if (found >= occurrence) { - break; - } - } - } - return lineNum; - } - - /** - * Find and select a given text. Not usable for multi line selection. - * - * @param text text to select - * @param occurrence specify which onccurrence of text to select if multiple are present in the document - */ - async selectText(text: string, occurrence = 1): Promise { - const lineNum = await this.getLineOfText(text, occurrence); - if (lineNum < 1) { - throw new Error(`Text '${text}' not found`); - } - - const line = await this.getTextAtLine(lineNum); - const column = line.indexOf(text) + 1; - - await this.moveCursor(lineNum, column); - - let actions = this.getDriver().actions(); - await actions.clear(); - actions.keyDown(Key.SHIFT); - for (let i = 0; i < text.length; i++) { - actions = actions.sendKeys(Key.RIGHT); - } - actions = actions.keyUp(Key.SHIFT); - await actions.perform(); - await new Promise(res => setTimeout(res, 500)); - } - - /** - * Get the text that is currently selected as string - */ - async getSelectedText(): Promise { - const clipboard = (await import('clipboardy')).default; - let originalClipboard = ''; - try { - originalClipboard = clipboard.readSync(); - } catch (error) { - // workaround issue https://github.com/redhat-developer/vscode-extension-tester/issues/835 - // do not fail if clipboard is empty - } - if (process.platform !== 'darwin') { - const selection = await this.getSelection(); - if (!selection) { - return ''; - } - const menu = await selection.openContextMenu(); - await menu.select('Copy'); - } else { - const inputarea = await this.findElement(TextEditor.locators.Editor.inputArea); - await inputarea.sendKeys(Key.chord(TextEditor.ctlKey, 'c')); - await new Promise(res => setTimeout(res, 500)); - await inputarea.sendKeys(Key.UP); - } - await new Promise(res => setTimeout(res, 500)); - const text = clipboard.readSync(); - if (originalClipboard.length > 0) { - clipboard.writeSync(originalClipboard); - } - return text; - } - - /** - * Get the selection block as a page object - * @returns Selection page object - */ - async getSelection(): Promise { - const selection = await this.findElements(TextEditor.locators.TextEditor.selection); - if (selection.length < 1) { - return undefined; - } - return new Selection(selection[0], this); - } - - async openFindWidget(): Promise { - const actions = this.getDriver().actions(); - await actions.clear(); - await actions.keyDown(TextEditor.ctlKey).sendKeys('f').keyUp(TextEditor.ctlKey).perform(); - const widget = await this.getDriver().wait(until.elementLocated(TextEditor.locators.TextEditor.findWidget), 2000); - await this.getDriver().wait(until.elementIsVisible(widget), 2000); - - return new FindWidget(widget, this); - } - - /** - * Add the given text to the given coordinates - * @param line number of the line to type into - * @param column number of the column to start typing at - * @param text text to add - * @returns Promise resolving when the text is typed in - */ - async typeTextAt(line: number, column: number, text: string): Promise { - await this.moveCursor(line, column); - await this.typeText(text); - } - - /** - * Type given text at the current coordinates - * @param text text to type - * @returns promise resolving when the text is typed in - */ - async typeText(text: string): Promise { - const inputarea = await this.findElement(TextEditor.locators.Editor.inputArea); - await inputarea.sendKeys(text); - } - - /** - * Move the cursor to the given coordinates - * @param line line number to move to - * @param column column number to move to - * @returns Promise resolving when the cursor has reached the given coordinates - */ - async moveCursor(line: number, column: number): Promise { - if (line < 1 || line > await this.getNumberOfLines()) { - throw new Error(`Line number ${line} does not exist`); - } - if (column < 1) { - throw new Error(`Column number ${column} does not exist`); - } - if (process.platform === 'darwin') { - const input = await new Workbench().openCommandPrompt(); - await input.setText(`:${line},${column}`); - await input.confirm(); - } else { - const inputarea = await this.findElement(TextEditor.locators.Editor.inputArea); - let coordinates = await this.getCoordinates(); - const lineGap = coordinates[0] - line; - const lineKey = lineGap >= 0 ? Key.UP : Key.DOWN; - for (let i = 0; i < Math.abs(lineGap); i++) { - await inputarea.sendKeys(lineKey); - } - - coordinates = await this.getCoordinates(); - const columnGap = coordinates[1] - column; - const columnKey = columnGap >= 0 ? Key.LEFT : Key.RIGHT; - for (let i = 0; i < Math.abs(columnGap); i++) { - await inputarea.sendKeys(columnKey); - const actualCoordinates = (await this.getCoordinates())[0]; - if (actualCoordinates !== coordinates[0]) { - throw new Error(`Column number ${column} is not accessible on line ${line}`); - } - } - } - await this.getDriver().wait(async () => { - const coor = await this.getCoordinates(); - return coor[0] === line && coor[1] === column; - }, 10000, `Unable to set cursor at position ${column}:${line}`); - } - - /** - * Get number of lines in the editor - * @returns Promise resolving to number of lines - */ - async getNumberOfLines(): Promise { - const lines = (await this.getText()).split('\n'); - return lines.length; - } - - /** - * Use the built-in 'Format Document' option to format the text - * @returns Promise resolving when the Format Document command is invoked - */ - async formatDocument(): Promise { - const menu = await this.openContextMenu(); - try { - await menu.select('Format Document'); - } catch (err) { - console.log('Warn: Format Document not available for selected language'); - if (await menu.isDisplayed()) { - await menu.close(); - } - } - } - - async openContextMenu(): Promise { - await this.getDriver().actions().contextClick(this).perform(); - const shadowRootHost = await this.enclosingItem.findElements(By.className('shadow-root-host')); - - if (shadowRootHost.length > 0) { - let shadowRoot; - const webdriverCapabilities = await (this.getDriver() as ChromiumWebDriver).getCapabilities(); - const chromiumVersion = webdriverCapabilities.getBrowserVersion(); - if (chromiumVersion && parseInt(chromiumVersion.split('.')[0]) >= 96) { - shadowRoot = await shadowRootHost[0].getShadowRoot(); - return new ContextMenu(await shadowRoot.findElement(By.className('monaco-menu-container'))).wait(); - } else { - shadowRoot = await this.getDriver().executeScript('return arguments[0].shadowRoot', shadowRootHost[0]) as WebElement; - return new ContextMenu(shadowRoot).wait(); - } - - } - return await super.openContextMenu(); - } - - /** - * Get the cursor's coordinates as an array of two numbers: `[line, column]` - * - * **Caution** line & column coordinates do not start at `0` but at `1`! - */ - async getCoordinates(): Promise<[number, number]> { - const coords: number[] = []; - const statusBar = new StatusBar(); - const coordinates = (await statusBar.getCurrentPosition()).match(/\d+/g); - for (const c of coordinates) { - coords.push(+c); - } - return [coords[0], coords[1]]; - } - - /** - * Toggle breakpoint on a given line - * - * @param line target line number - * @returns promise resolving to True when a breakpoint was added, False when removed - */ - async toggleBreakpoint(line: number): Promise { - const margin = await this.findElement(TextEditor.locators.TextEditor.marginArea); - const lineNum = await margin.findElement(TextEditor.locators.TextEditor.lineNumber(line)); - await this.getDriver().actions().move({ origin: lineNum }).perform(); - - const lineOverlay = await margin.findElement(TextEditor.locators.TextEditor.lineOverlay(line)); - const breakpointContainer = TextEditor.versionInfo.version >= '1.80.0' ? await this.findElement(By.className('glyph-margin-widgets')) : lineOverlay; - const breakPoint = await breakpointContainer.findElements(TextEditor.locators.TextEditor.breakpoint.generalSelector); - if (breakPoint.length > 0) { - if (this.breakPoints.indexOf(line) !== -1) { - await breakPoint[this.breakPoints.indexOf(line)].click(); - await new Promise(res => setTimeout(res, 200)); - this.breakPoints.splice(this.breakPoints.indexOf(line), 1); - return false; - } - } - const noBreak = await breakpointContainer.findElements(TextEditor.locators.TextEditor.debugHint); - if (noBreak.length > 0) { - await noBreak[0].click(); - await new Promise(res => setTimeout(res, 200)); - this.breakPoints.push(line); - return true; - } - return false; - } - - /** - * Get paused breakpoint if available. Otherwise, return undefined. - * @returns promise which resolves to either Breakpoint page object or undefined - */ - async getPausedBreakpoint(): Promise { - const breakpointLocators = Breakpoint.locators.TextEditor.breakpoint; - const breakpointContainer = TextEditor.versionInfo.version >= '1.80.0' ? await this.findElement(By.className('glyph-margin-widgets')) : this; - const breakpoints = await breakpointContainer.findElements(breakpointLocators.pauseSelector); - - if (breakpoints.length === 0) { - return undefined; - } - - if (breakpoints.length > 1) { - throw new BreakpointError(`unexpected number of paused breakpoints: ${breakpoints.length}; expected 1 at most`); - } - - // get parent - let lineElement: WebElement; - if (TextEditor.versionInfo.version >= '1.80.0') { - const styleTopAttr = await breakpoints[0].getCssValue('top'); - lineElement = await this.findElement(TextEditor.locators.TextEditor.marginArea).findElement(By.xpath(`.//div[contains(@style, "${styleTopAttr}")]`)); - } else { - lineElement = await breakpoints[0].findElement(By.xpath('./..')); - } - return new Breakpoint(breakpoints[0], lineElement); - } - - /** - * Get all code lenses within the editor - * @returns list of CodeLens page objects - */ - async getCodeLenses(): Promise { - const lenses: CodeLens[] = []; - const widgets = await this.findElement(By.className('contentWidgets')); - const items = await widgets.findElements(By.xpath(`.//span[contains(@widgetid, 'codelens.widget')]/a[@id]`)); - for (const item of items) { - lenses.push(await new CodeLens(item, this).wait()); - } - return lenses; - } - - /** - * Get a code lens based on title, or zero based index - * - * @param indexOrTitle zero based index (counting from the top of the editor), or partial title of the code lens - * @returns CodeLens object if such a code lens exists, undefined otherwise - */ - async getCodeLens(indexOrTitle: number | string): Promise { - const lenses = await this.getCodeLenses(); - - if (typeof (indexOrTitle) === 'string') { - for (const lens of lenses) { - const title = await lens.getText(); - const match = title.match(indexOrTitle); - if (match && match.length > 0) { - return lens; - } - } - } else if (lenses[indexOrTitle]) { - return lenses[indexOrTitle]; - } - return undefined; - } + breakPoints: number[] = []; + + /** + * Find whether the active editor has unsaved changes + * @returns Promise resolving to true/false + */ + async isDirty(): Promise { + const tab = await this.enclosingItem.findElement(TextEditor.locators.TextEditor.activeTab); + const klass = await tab.getAttribute('class'); + return klass.indexOf('dirty') >= 0; + } + + /** + * Saves the active editor + * @returns Promise resolving when ctrl+s is invoked + */ + async save(): Promise { + const inputarea = await this.findElement(TextEditor.locators.Editor.inputArea); + await inputarea.sendKeys(Key.chord(TextEditor.ctlKey, 's')); + } + + /** + * Open the Save as prompt + * + * @returns InputBox serving as a simple file dialog + */ + async saveAs(): Promise { + const tab = await this.getTab(); + await tab.sendKeys(Key.chord(TextEditor.ctlKey, Key.SHIFT, 's')); + return await InputBox.create(); + } + + /** + * Retrieve the Uri of the file opened in the active editor + * @returns Promise resolving to editor's underlying Uri + */ + async getFileUri(): Promise { + const ed = await this.findElement(TextEditor.locators.TextEditor.editorContainer); + return await ed.getAttribute(TextEditor.locators.TextEditor.dataUri); + } + + /** + * Retrieve the path to the file opened in the active editor + * @returns Promise resolving to editor's underlying file path + */ + async getFilePath(): Promise { + return fileURLToPath(await this.getFileUri()); + } + + /** + * Open/Close the content assistant at the current position in the editor by sending the default + * keyboard shortcut signal + * @param open true to open, false to close + * @returns Promise resolving to ContentAssist object when opening, void otherwise + */ + async toggleContentAssist(open: boolean): Promise { + let isHidden = true; + try { + const assist = await this.findElement(TextEditor.locators.ContentAssist.constructor); + const klass = await assist.getAttribute('class'); + const visibility = await assist.getCssValue('visibility'); + isHidden = klass.indexOf('visible') < 0 || visibility === 'hidden'; + } catch (err) { + isHidden = true; + } + const inputarea = await this.findElement(TextEditor.locators.Editor.inputArea); + + if (open) { + if (isHidden) { + await inputarea.sendKeys(Key.chord(Key.CONTROL, Key.SPACE)); + await this.getDriver().wait(until.elementLocated(TextEditor.locators.ContentAssist.constructor), 2000); + } + const assist = await new ContentAssist(this).wait(); + await this.getDriver().wait(() => { + return assist.isLoaded(); + }, 10000); + return assist; + } else { + if (!isHidden) { + await inputarea.sendKeys(Key.ESCAPE); + } + } + } + + /** + * Get all text from the editor + * @returns Promise resolving to editor text + */ + async getText(): Promise { + const clipboard = (await import('clipboardy')).default; + let originalClipboard = ''; + try { + originalClipboard = clipboard.readSync(); + } catch (error) { + // workaround issue https://github.com/redhat-developer/vscode-extension-tester/issues/835 + // do not fail if clipboard is empty + } + const inputarea = await this.findElement(TextEditor.locators.Editor.inputArea); + await inputarea.sendKeys(Key.chord(TextEditor.ctlKey, 'a'), Key.chord(TextEditor.ctlKey, 'c')); + await new Promise((res) => setTimeout(res, 500)); + const text = clipboard.readSync(); + await inputarea.sendKeys(Key.UP); + if (originalClipboard.length > 0) { + clipboard.writeSync(originalClipboard); + } + return text; + } + + /** + * Replace the contents of the editor with a given text + * @param text text to type into the editor + * @param formatText format the new text, default false + * @returns Promise resolving once the new text is copied over + */ + async setText(text: string, formatText: boolean = false): Promise { + const clipboard = (await import('clipboardy')).default; + let originalClipboard = ''; + try { + originalClipboard = clipboard.readSync(); + } catch (error) { + // workaround issue https://github.com/redhat-developer/vscode-extension-tester/issues/835 + // do not fail if clipboard is empty + } + const inputarea = await this.findElement(TextEditor.locators.Editor.inputArea); + clipboard.writeSync(text); + await inputarea.sendKeys(Key.chord(TextEditor.ctlKey, 'a'), Key.chord(TextEditor.ctlKey, 'v')); + if (originalClipboard.length > 0) { + clipboard.writeSync(originalClipboard); + } + if (formatText) { + await this.formatDocument(); + } + } + + /** + * Deletes all text within the editor + * @returns Promise resolving once the text is deleted + */ + async clearText(): Promise { + const inputarea = await this.findElement(TextEditor.locators.Editor.inputArea); + await inputarea.sendKeys(Key.chord(TextEditor.ctlKey, 'a')); + await inputarea.sendKeys(Key.BACK_SPACE); + } + + /** + * Get text from a given line + * @param line number of the line to retrieve + * @returns Promise resolving to text at the given line number + */ + async getTextAtLine(line: number): Promise { + const text = await this.getText(); + const lines = text.split('\n'); + if (line < 1 || line > lines.length) { + throw new Error(`Line number ${line} does not exist`); + } + return lines[line - 1]; + } + + /** + * Replace the contents of a line with a given text + * @param line number of the line to edit + * @param text text to set at the line + * @returns Promise resolving when the text is typed in + */ + async setTextAtLine(line: number, text: string): Promise { + if (line < 1 || line > (await this.getNumberOfLines())) { + throw new Error(`Line number ${line} does not exist`); + } + const lines = (await this.getText()).split('\n'); + lines[line - 1] = text; + await this.setText(lines.join('\n')); + } + + /** + * Get line number that contains the given text. Not suitable for multi line inputs. + * + * @param text text to search for + * @param occurrence select which occurrence of the search text to look for in case there are multiple in the document, defaults to 1 (the first instance) + * + * @returns Number of the line that contains the start of the given text. -1 if no such text is found. + * If occurrence number is specified, searches until it finds as many instances of the given text. + * Returns the line number that holds the last occurrence found this way. + */ + async getLineOfText(text: string, occurrence = 1): Promise { + let lineNum = -1; + let found = 0; + const lines = (await this.getText()).split('\n'); + + for (let i = 0; i < lines.length; i++) { + if (lines[i].includes(text)) { + found++; + lineNum = i + 1; + if (found >= occurrence) { + break; + } + } + } + return lineNum; + } + + /** + * Find and select a given text. Not usable for multi line selection. + * + * @param text text to select + * @param occurrence specify which onccurrence of text to select if multiple are present in the document + */ + async selectText(text: string, occurrence = 1): Promise { + const lineNum = await this.getLineOfText(text, occurrence); + if (lineNum < 1) { + throw new Error(`Text '${text}' not found`); + } + + const line = await this.getTextAtLine(lineNum); + const column = line.indexOf(text) + 1; + + await this.moveCursor(lineNum, column); + + let actions = this.getDriver().actions(); + await actions.clear(); + actions.keyDown(Key.SHIFT); + for (let i = 0; i < text.length; i++) { + actions = actions.sendKeys(Key.RIGHT); + } + actions = actions.keyUp(Key.SHIFT); + await actions.perform(); + await new Promise((res) => setTimeout(res, 500)); + } + + /** + * Get the text that is currently selected as string + */ + async getSelectedText(): Promise { + const clipboard = (await import('clipboardy')).default; + let originalClipboard = ''; + try { + originalClipboard = clipboard.readSync(); + } catch (error) { + // workaround issue https://github.com/redhat-developer/vscode-extension-tester/issues/835 + // do not fail if clipboard is empty + } + if (process.platform !== 'darwin') { + const selection = await this.getSelection(); + if (!selection) { + return ''; + } + const menu = await selection.openContextMenu(); + await menu.select('Copy'); + } else { + const inputarea = await this.findElement(TextEditor.locators.Editor.inputArea); + await inputarea.sendKeys(Key.chord(TextEditor.ctlKey, 'c')); + await new Promise((res) => setTimeout(res, 500)); + await inputarea.sendKeys(Key.UP); + } + await new Promise((res) => setTimeout(res, 500)); + const text = clipboard.readSync(); + if (originalClipboard.length > 0) { + clipboard.writeSync(originalClipboard); + } + return text; + } + + /** + * Get the selection block as a page object + * @returns Selection page object + */ + async getSelection(): Promise { + const selection = await this.findElements(TextEditor.locators.TextEditor.selection); + if (selection.length < 1) { + return undefined; + } + return new Selection(selection[0], this); + } + + async openFindWidget(): Promise { + const actions = this.getDriver().actions(); + await actions.clear(); + await actions.keyDown(TextEditor.ctlKey).sendKeys('f').keyUp(TextEditor.ctlKey).perform(); + const widget = await this.getDriver().wait(until.elementLocated(TextEditor.locators.TextEditor.findWidget), 2000); + await this.getDriver().wait(until.elementIsVisible(widget), 2000); + + return new FindWidget(widget, this); + } + + /** + * Add the given text to the given coordinates + * @param line number of the line to type into + * @param column number of the column to start typing at + * @param text text to add + * @returns Promise resolving when the text is typed in + */ + async typeTextAt(line: number, column: number, text: string): Promise { + await this.moveCursor(line, column); + await this.typeText(text); + } + + /** + * Type given text at the current coordinates + * @param text text to type + * @returns promise resolving when the text is typed in + */ + async typeText(text: string): Promise { + const inputarea = await this.findElement(TextEditor.locators.Editor.inputArea); + await inputarea.sendKeys(text); + } + + /** + * Move the cursor to the given coordinates + * @param line line number to move to + * @param column column number to move to + * @returns Promise resolving when the cursor has reached the given coordinates + */ + async moveCursor(line: number, column: number): Promise { + if (line < 1 || line > (await this.getNumberOfLines())) { + throw new Error(`Line number ${line} does not exist`); + } + if (column < 1) { + throw new Error(`Column number ${column} does not exist`); + } + if (process.platform === 'darwin') { + const input = await new Workbench().openCommandPrompt(); + await input.setText(`:${line},${column}`); + await input.confirm(); + } else { + const inputarea = await this.findElement(TextEditor.locators.Editor.inputArea); + let coordinates = await this.getCoordinates(); + const lineGap = coordinates[0] - line; + const lineKey = lineGap >= 0 ? Key.UP : Key.DOWN; + for (let i = 0; i < Math.abs(lineGap); i++) { + await inputarea.sendKeys(lineKey); + } + + coordinates = await this.getCoordinates(); + const columnGap = coordinates[1] - column; + const columnKey = columnGap >= 0 ? Key.LEFT : Key.RIGHT; + for (let i = 0; i < Math.abs(columnGap); i++) { + await inputarea.sendKeys(columnKey); + const actualCoordinates = (await this.getCoordinates())[0]; + if (actualCoordinates !== coordinates[0]) { + throw new Error(`Column number ${column} is not accessible on line ${line}`); + } + } + } + await this.getDriver().wait( + async () => { + const coor = await this.getCoordinates(); + return coor[0] === line && coor[1] === column; + }, + 10000, + `Unable to set cursor at position ${column}:${line}`, + ); + } + + /** + * Get number of lines in the editor + * @returns Promise resolving to number of lines + */ + async getNumberOfLines(): Promise { + const lines = (await this.getText()).split('\n'); + return lines.length; + } + + /** + * Use the built-in 'Format Document' option to format the text + * @returns Promise resolving when the Format Document command is invoked + */ + async formatDocument(): Promise { + const menu = await this.openContextMenu(); + try { + await menu.select('Format Document'); + } catch (err) { + console.log('Warn: Format Document not available for selected language'); + if (await menu.isDisplayed()) { + await menu.close(); + } + } + } + + async openContextMenu(): Promise { + await this.getDriver().actions().contextClick(this).perform(); + const shadowRootHost = await this.enclosingItem.findElements(By.className('shadow-root-host')); + + if (shadowRootHost.length > 0) { + let shadowRoot; + const webdriverCapabilities = await (this.getDriver() as ChromiumWebDriver).getCapabilities(); + const chromiumVersion = webdriverCapabilities.getBrowserVersion(); + if (chromiumVersion && parseInt(chromiumVersion.split('.')[0]) >= 96) { + shadowRoot = await shadowRootHost[0].getShadowRoot(); + return new ContextMenu(await shadowRoot.findElement(By.className('monaco-menu-container'))).wait(); + } else { + shadowRoot = (await this.getDriver().executeScript('return arguments[0].shadowRoot', shadowRootHost[0])) as WebElement; + return new ContextMenu(shadowRoot).wait(); + } + } + return await super.openContextMenu(); + } + + /** + * Get the cursor's coordinates as an array of two numbers: `[line, column]` + * + * **Caution** line & column coordinates do not start at `0` but at `1`! + */ + async getCoordinates(): Promise<[number, number]> { + const coords: number[] = []; + const statusBar = new StatusBar(); + const coordinates = (await statusBar.getCurrentPosition()).match(/\d+/g); + for (const c of coordinates) { + coords.push(+c); + } + return [coords[0], coords[1]]; + } + + /** + * Toggle breakpoint on a given line + * + * @param line target line number + * @returns promise resolving to True when a breakpoint was added, False when removed + */ + async toggleBreakpoint(line: number): Promise { + const margin = await this.findElement(TextEditor.locators.TextEditor.marginArea); + const lineNum = await margin.findElement(TextEditor.locators.TextEditor.lineNumber(line)); + await this.getDriver().actions().move({ origin: lineNum }).perform(); + + const lineOverlay = await margin.findElement(TextEditor.locators.TextEditor.lineOverlay(line)); + const breakpointContainer = TextEditor.versionInfo.version >= '1.80.0' ? await this.findElement(By.className('glyph-margin-widgets')) : lineOverlay; + const breakPoint = await breakpointContainer.findElements(TextEditor.locators.TextEditor.breakpoint.generalSelector); + if (breakPoint.length > 0) { + if (this.breakPoints.indexOf(line) !== -1) { + await breakPoint[this.breakPoints.indexOf(line)].click(); + await new Promise((res) => setTimeout(res, 200)); + this.breakPoints.splice(this.breakPoints.indexOf(line), 1); + return false; + } + } + const noBreak = await breakpointContainer.findElements(TextEditor.locators.TextEditor.debugHint); + if (noBreak.length > 0) { + await noBreak[0].click(); + await new Promise((res) => setTimeout(res, 200)); + this.breakPoints.push(line); + return true; + } + return false; + } + + /** + * Get paused breakpoint if available. Otherwise, return undefined. + * @returns promise which resolves to either Breakpoint page object or undefined + */ + async getPausedBreakpoint(): Promise { + const breakpointLocators = Breakpoint.locators.TextEditor.breakpoint; + const breakpointContainer = TextEditor.versionInfo.version >= '1.80.0' ? await this.findElement(By.className('glyph-margin-widgets')) : this; + const breakpoints = await breakpointContainer.findElements(breakpointLocators.pauseSelector); + + if (breakpoints.length === 0) { + return undefined; + } + + if (breakpoints.length > 1) { + throw new BreakpointError(`unexpected number of paused breakpoints: ${breakpoints.length}; expected 1 at most`); + } + + // get parent + let lineElement: WebElement; + if (TextEditor.versionInfo.version >= '1.80.0') { + const styleTopAttr = await breakpoints[0].getCssValue('top'); + lineElement = await this.findElement(TextEditor.locators.TextEditor.marginArea).findElement( + By.xpath(`.//div[contains(@style, "${styleTopAttr}")]`), + ); + } else { + lineElement = await breakpoints[0].findElement(By.xpath('./..')); + } + return new Breakpoint(breakpoints[0], lineElement); + } + + /** + * Get all code lenses within the editor + * @returns list of CodeLens page objects + */ + async getCodeLenses(): Promise { + const lenses: CodeLens[] = []; + const widgets = await this.findElement(By.className('contentWidgets')); + const items = await widgets.findElements(By.xpath(`.//span[contains(@widgetid, 'codelens.widget')]/a[@id]`)); + for (const item of items) { + lenses.push(await new CodeLens(item, this).wait()); + } + return lenses; + } + + /** + * Get a code lens based on title, or zero based index + * + * @param indexOrTitle zero based index (counting from the top of the editor), or partial title of the code lens + * @returns CodeLens object if such a code lens exists, undefined otherwise + */ + async getCodeLens(indexOrTitle: number | string): Promise { + const lenses = await this.getCodeLenses(); + + if (typeof indexOrTitle === 'string') { + for (const lens of lenses) { + const title = await lens.getText(); + const match = title.match(indexOrTitle); + if (match && match.length > 0) { + return lens; + } + } + } else if (lenses[indexOrTitle]) { + return lenses[indexOrTitle]; + } + return undefined; + } } /** * Text selection block */ class Selection extends ElementWithContexMenu { - - constructor(el: WebElement, editor: TextEditor) { - super(el, editor); - } - - async openContextMenu(): Promise { - const ed = this.getEnclosingElement() as TextEditor; - await this.getDriver().actions().contextClick(this).perform(); - const shadowRootHost = await ed.getEnclosingElement().findElements(By.className('shadow-root-host')); - - if (shadowRootHost.length > 0) { - let shadowRoot; - const webdriverCapabilities = await (this.getDriver() as ChromiumWebDriver).getCapabilities(); - const chromiumVersion = webdriverCapabilities.getBrowserVersion(); - if (chromiumVersion && parseInt(chromiumVersion.split('.')[0]) >= 96) { - shadowRoot = await shadowRootHost[0].getShadowRoot(); - return new ContextMenu(await shadowRoot.findElement(By.className('monaco-menu-container'))).wait(); - } else { - shadowRoot = await this.getDriver().executeScript('return arguments[0].shadowRoot', shadowRootHost[0]) as WebElement; - return new ContextMenu(shadowRoot).wait(); - } - } - return await super.openContextMenu(); - } + constructor(el: WebElement, editor: TextEditor) { + super(el, editor); + } + + async openContextMenu(): Promise { + const ed = this.getEnclosingElement() as TextEditor; + await this.getDriver().actions().contextClick(this).perform(); + const shadowRootHost = await ed.getEnclosingElement().findElements(By.className('shadow-root-host')); + + if (shadowRootHost.length > 0) { + let shadowRoot; + const webdriverCapabilities = await (this.getDriver() as ChromiumWebDriver).getCapabilities(); + const chromiumVersion = webdriverCapabilities.getBrowserVersion(); + if (chromiumVersion && parseInt(chromiumVersion.split('.')[0]) >= 96) { + shadowRoot = await shadowRootHost[0].getShadowRoot(); + return new ContextMenu(await shadowRoot.findElement(By.className('monaco-menu-container'))).wait(); + } else { + shadowRoot = (await this.getDriver().executeScript('return arguments[0].shadowRoot', shadowRootHost[0])) as WebElement; + return new ContextMenu(shadowRoot).wait(); + } + } + return await super.openContextMenu(); + } } /** * Page object for Code Lens inside a text editor */ export class CodeLens extends AbstractElement { - - /** - * Get tooltip of the code lens - * @returns tooltip as string - */ - async getTooltip(): Promise { - return await this.getAttribute('title'); - } + /** + * Get tooltip of the code lens + * @returns tooltip as string + */ + async getTooltip(): Promise { + return await this.getAttribute('title'); + } } /** * Text Editor's Find Widget */ export class FindWidget extends AbstractElement { - - constructor(element: WebElement, editor: TextEditor) { - super(element, editor); - } - - /** - * Toggle between find and replace mode - * @param replace true for replace, false for find - */ - async toggleReplace(replace: boolean): Promise { - const btn = await this.findElement(FindWidget.locators.FindWidget.toggleReplace); - const klass = await btn.getAttribute('class'); - - if (replace && klass.includes('collapsed') || !replace && !klass.includes('collapsed')) { - await btn.sendKeys(Key.SPACE); - const repl = await this.getDriver().wait(until.elementLocated(FindWidget.locators.FindWidget.replacePart), 2000); - if (replace) { - await this.getDriver().wait(until.elementIsVisible(repl), 2000); - } else { - await this.getDriver().wait(until.elementIsNotVisible(repl), 2000); - } - } - } - - /** - * Set text in the search box - * @param text text to fill in - */ - async setSearchText(text: string): Promise { - const findPart = await this.findElement(FindWidget.locators.FindWidget.findPart); - await this.setText(text, findPart); - } - - /** - * Get text from Find input box - * @returns value of find input as string - */ - async getSearchText(): Promise { - const findPart = await this.findElement(FindWidget.locators.FindWidget.findPart); - return await this.getInputText(findPart); - } - - /** - * Set text in the replace box. Will toggle replace mode on if called in find mode. - * @param text text to fill in - */ - async setReplaceText(text: string): Promise { - await this.toggleReplace(true); - const replacePart = await this.findElement(FindWidget.locators.FindWidget.replacePart); - await this.setText(text, replacePart); - } - - - /** - * Get text from Replace input box - * @returns value of replace input as string - */ - async getReplaceText(): Promise { - const replacePart = await this.findElement(FindWidget.locators.FindWidget.replacePart); - return await this.getInputText(replacePart); - } - - /** - * Click 'Next match' - */ - async nextMatch(): Promise { - const name = TextEditor.versionInfo.version < '1.59.0' ? 'Next match' : 'Next Match'; - await this.clickButton(name, 'find'); - } - - /** - * Click 'Previous match' - */ - async previousMatch(): Promise { - const name = TextEditor.versionInfo.version < '1.59.0' ? 'Previous match' : 'Previous Match'; - await this.clickButton(name, 'find'); - } - - /** - * Click 'Replace'. Only works in replace mode. - */ - async replace(): Promise { - await this.clickButton('Replace', 'replace'); - } - - - /** - * Click 'Replace All'. Only works in replace mode. - */ - async replaceAll(): Promise { - await this.clickButton('Replace All', 'replace'); - } - - /** - * Close the widget. - */ - async close(): Promise { - const part = TextEditor.versionInfo.version >= '1,80.0' ? 'close' : 'find'; - await this.clickButton('Close', part); - } - - /** - * Get the number of results as an ordered pair of numbers - * @returns pair in form of [current result index, total number of results] - */ - async getResultCount(): Promise<[number, number]> { - const count = await this.findElement(FindWidget.locators.FindWidget.matchCount); - const text = await count.getText(); - - if (text.includes('No results')) { - return [0, 0]; - } - const numbers = text.split(' of '); - return [+numbers[0], +numbers[1]]; - } - - /** - * Toggle the search to match case - * @param toggle true to turn on, false to turn off - */ - async toggleMatchCase(toggle: boolean) { - await this.toggleControl('Match Case', 'find', toggle); - } - - /** - * Toggle the search to match whole words - * @param toggle true to turn on, false to turn off - */ - async toggleMatchWholeWord(toggle: boolean) { - await this.toggleControl('Match Whole Word', 'find', toggle); - } - - /** - * Toggle the search to use regular expressions - * @param toggle true to turn on, false to turn off - */ - async toggleUseRegularExpression(toggle: boolean) { - await this.toggleControl('Use Regular Expression', 'find', toggle); - } - - /** - * Toggle the replace to preserve case - * @param toggle true to turn on, false to turn off - */ - async togglePreserveCase(toggle: boolean) { - await this.toggleControl('Preserve Case', 'replace', toggle); - } - - private async toggleControl(title: string, part: 'find' | 'replace', toggle: boolean) { - let element!: WebElement; - if (part === 'find') { - element = await this.findElement(FindWidget.locators.FindWidget.findPart); - } - if (part === 'replace') { - element = await this.findElement(FindWidget.locators.FindWidget.replacePart); - await this.toggleReplace(true); - } - - const control = await element.findElement(FindWidget.locators.FindWidget.checkbox(title)); - const checked = await control.getAttribute('aria-checked'); - if ((toggle && checked !== 'true') || (!toggle && checked === 'true')) { - await control.click(); - } - } - - private async clickButton(title: string, part: 'find' | 'replace' | 'close') { - let element!: WebElement; - if (part === 'find') { - element = await this.findElement(FindWidget.locators.FindWidget.findPart); - } - if (part === 'replace') { - element = await this.findElement(FindWidget.locators.FindWidget.replacePart); - await this.toggleReplace(true); - } - if (part === 'close') { - element = this; - } - - const btn = await element.findElement(FindWidget.locators.FindWidget.button(title)); - await btn.click(); - await this.getDriver().sleep(100); - } - - private async setText(text: string, composite: WebElement) { - const input = await composite.findElement(FindWidget.locators.FindWidget.input); - await input.clear(); - await input.sendKeys(text); - } - - private async getInputText(composite: WebElement) { - const input = await composite.findElement(FindWidget.locators.FindWidget.content); - return await input.getAttribute('innerHTML'); - } + constructor(element: WebElement, editor: TextEditor) { + super(element, editor); + } + + /** + * Toggle between find and replace mode + * @param replace true for replace, false for find + */ + async toggleReplace(replace: boolean): Promise { + const btn = await this.findElement(FindWidget.locators.FindWidget.toggleReplace); + const klass = await btn.getAttribute('class'); + + if ((replace && klass.includes('collapsed')) || (!replace && !klass.includes('collapsed'))) { + await btn.sendKeys(Key.SPACE); + const repl = await this.getDriver().wait(until.elementLocated(FindWidget.locators.FindWidget.replacePart), 2000); + if (replace) { + await this.getDriver().wait(until.elementIsVisible(repl), 2000); + } else { + await this.getDriver().wait(until.elementIsNotVisible(repl), 2000); + } + } + } + + /** + * Set text in the search box + * @param text text to fill in + */ + async setSearchText(text: string): Promise { + const findPart = await this.findElement(FindWidget.locators.FindWidget.findPart); + await this.setText(text, findPart); + } + + /** + * Get text from Find input box + * @returns value of find input as string + */ + async getSearchText(): Promise { + const findPart = await this.findElement(FindWidget.locators.FindWidget.findPart); + return await this.getInputText(findPart); + } + + /** + * Set text in the replace box. Will toggle replace mode on if called in find mode. + * @param text text to fill in + */ + async setReplaceText(text: string): Promise { + await this.toggleReplace(true); + const replacePart = await this.findElement(FindWidget.locators.FindWidget.replacePart); + await this.setText(text, replacePart); + } + + /** + * Get text from Replace input box + * @returns value of replace input as string + */ + async getReplaceText(): Promise { + const replacePart = await this.findElement(FindWidget.locators.FindWidget.replacePart); + return await this.getInputText(replacePart); + } + + /** + * Click 'Next match' + */ + async nextMatch(): Promise { + const name = TextEditor.versionInfo.version < '1.59.0' ? 'Next match' : 'Next Match'; + await this.clickButton(name, 'find'); + } + + /** + * Click 'Previous match' + */ + async previousMatch(): Promise { + const name = TextEditor.versionInfo.version < '1.59.0' ? 'Previous match' : 'Previous Match'; + await this.clickButton(name, 'find'); + } + + /** + * Click 'Replace'. Only works in replace mode. + */ + async replace(): Promise { + await this.clickButton('Replace', 'replace'); + } + + /** + * Click 'Replace All'. Only works in replace mode. + */ + async replaceAll(): Promise { + await this.clickButton('Replace All', 'replace'); + } + + /** + * Close the widget. + */ + async close(): Promise { + const part = TextEditor.versionInfo.version >= '1,80.0' ? 'close' : 'find'; + await this.clickButton('Close', part); + } + + /** + * Get the number of results as an ordered pair of numbers + * @returns pair in form of [current result index, total number of results] + */ + async getResultCount(): Promise<[number, number]> { + const count = await this.findElement(FindWidget.locators.FindWidget.matchCount); + const text = await count.getText(); + + if (text.includes('No results')) { + return [0, 0]; + } + const numbers = text.split(' of '); + return [+numbers[0], +numbers[1]]; + } + + /** + * Toggle the search to match case + * @param toggle true to turn on, false to turn off + */ + async toggleMatchCase(toggle: boolean) { + await this.toggleControl('Match Case', 'find', toggle); + } + + /** + * Toggle the search to match whole words + * @param toggle true to turn on, false to turn off + */ + async toggleMatchWholeWord(toggle: boolean) { + await this.toggleControl('Match Whole Word', 'find', toggle); + } + + /** + * Toggle the search to use regular expressions + * @param toggle true to turn on, false to turn off + */ + async toggleUseRegularExpression(toggle: boolean) { + await this.toggleControl('Use Regular Expression', 'find', toggle); + } + + /** + * Toggle the replace to preserve case + * @param toggle true to turn on, false to turn off + */ + async togglePreserveCase(toggle: boolean) { + await this.toggleControl('Preserve Case', 'replace', toggle); + } + + private async toggleControl(title: string, part: 'find' | 'replace', toggle: boolean) { + let element!: WebElement; + if (part === 'find') { + element = await this.findElement(FindWidget.locators.FindWidget.findPart); + } + if (part === 'replace') { + element = await this.findElement(FindWidget.locators.FindWidget.replacePart); + await this.toggleReplace(true); + } + + const control = await element.findElement(FindWidget.locators.FindWidget.checkbox(title)); + const checked = await control.getAttribute('aria-checked'); + if ((toggle && checked !== 'true') || (!toggle && checked === 'true')) { + await control.click(); + } + } + + private async clickButton(title: string, part: 'find' | 'replace' | 'close') { + let element!: WebElement; + if (part === 'find') { + element = await this.findElement(FindWidget.locators.FindWidget.findPart); + } + if (part === 'replace') { + element = await this.findElement(FindWidget.locators.FindWidget.replacePart); + await this.toggleReplace(true); + } + if (part === 'close') { + element = this; + } + + const btn = await element.findElement(FindWidget.locators.FindWidget.button(title)); + await btn.click(); + await this.getDriver().sleep(100); + } + + private async setText(text: string, composite: WebElement) { + const input = await composite.findElement(FindWidget.locators.FindWidget.input); + await input.clear(); + await input.sendKeys(text); + } + + private async getInputText(composite: WebElement) { + const input = await composite.findElement(FindWidget.locators.FindWidget.content); + return await input.getAttribute('innerHTML'); + } } diff --git a/packages/page-objects/src/components/editor/WebView.ts b/packages/page-objects/src/components/editor/WebView.ts index b0911b503..b9e2c91de 100644 --- a/packages/page-objects/src/components/editor/WebView.ts +++ b/packages/page-objects/src/components/editor/WebView.ts @@ -16,42 +16,43 @@ */ /* eslint-disable no-redeclare */ -import { until, WebElement } from "selenium-webdriver"; -import WebviewMixin from "../WebviewMixin"; -import { Editor } from "./Editor"; +import { until, WebElement } from 'selenium-webdriver'; +import WebviewMixin from '../WebviewMixin'; +import { Editor } from './Editor'; /** * Page object representing an open editor containing a web view */ class WebViewBase extends Editor { - - async getViewToSwitchTo(handle: string): Promise { - const handles = await this.getDriver().getAllWindowHandles(); - for (const handle of handles) { - await this.getDriver().switchTo().window(handle); - - if ((await this.getDriver().getTitle()).includes('Virtual Document')) { - await this.getDriver().switchTo().frame(0); - return; - } - } - await this.getDriver().switchTo().window(handle); - - const reference = await this.findElement(WebViewBase.locators.EditorView.webView); - const containers = await this.getDriver().wait(until.elementsLocated(WebViewBase.locators.WebView.container(await reference.getAttribute(WebViewBase.locators.WebView.attribute))), 5000); - - return await containers[0].getDriver().wait(async () => { - for (const container of containers) { - const tries = await container.findElements(WebViewBase.locators.WebView.iframe); - if (tries.length > 0) { - return tries[0]; - } - } - return undefined; - }, 5000) as WebElement; - } - + async getViewToSwitchTo(handle: string): Promise { + const handles = await this.getDriver().getAllWindowHandles(); + for (const handle of handles) { + await this.getDriver().switchTo().window(handle); + + if ((await this.getDriver().getTitle()).includes('Virtual Document')) { + await this.getDriver().switchTo().frame(0); + return; + } + } + await this.getDriver().switchTo().window(handle); + + const reference = await this.findElement(WebViewBase.locators.EditorView.webView); + const containers = await this.getDriver().wait( + until.elementsLocated(WebViewBase.locators.WebView.container(await reference.getAttribute(WebViewBase.locators.WebView.attribute))), + 5000, + ); + + return (await containers[0].getDriver().wait(async () => { + for (const container of containers) { + const tries = await container.findElements(WebViewBase.locators.WebView.iframe); + if (tries.length > 0) { + return tries[0]; + } + } + return undefined; + }, 5000)) as WebElement; + } } export const WebView = WebviewMixin(WebViewBase); -export type WebView = InstanceType; \ No newline at end of file +export type WebView = InstanceType; diff --git a/packages/page-objects/src/components/menu/ContextMenu.ts b/packages/page-objects/src/components/menu/ContextMenu.ts index 1b4e8c066..389b84329 100644 --- a/packages/page-objects/src/components/menu/ContextMenu.ts +++ b/packages/page-objects/src/components/menu/ContextMenu.ts @@ -15,125 +15,124 @@ * limitations under the License. */ -import { Menu, MenuItem } from "../.."; -import { WebElement, Key, until, error } from "selenium-webdriver"; +import { Menu, MenuItem } from '../..'; +import { WebElement, Key, until, error } from 'selenium-webdriver'; /** * Object representing a context menu */ export class ContextMenu extends Menu { - constructor(containingElement: WebElement) { - super(ContextMenu.locators.ContextMenu.constructor, containingElement); - } + constructor(containingElement: WebElement) { + super(ContextMenu.locators.ContextMenu.constructor, containingElement); + } - /** - * Get context menu item by name - * @param name name of the item to search by - * @returns Promise resolving to ContextMenuItem object - */ - async getItem(name: string): Promise { - try { - const items = await this.getItems(); - for (const item of items) { - if (await item.getLabel() === name) { - return item; - } - } - } catch (err) { - return undefined; - } - } + /** + * Get context menu item by name + * @param name name of the item to search by + * @returns Promise resolving to ContextMenuItem object + */ + async getItem(name: string): Promise { + try { + const items = await this.getItems(); + for (const item of items) { + if ((await item.getLabel()) === name) { + return item; + } + } + } catch (err) { + return undefined; + } + } - /** - * Get all context menu items - * @returns Promise resolving to array of ContextMenuItem objects - */ - async getItems(): Promise { - const items: ContextMenuItem[] = []; - const elements = await this.findElements(ContextMenu.locators.ContextMenu.itemElement); + /** + * Get all context menu items + * @returns Promise resolving to array of ContextMenuItem objects + */ + async getItems(): Promise { + const items: ContextMenuItem[] = []; + const elements = await this.findElements(ContextMenu.locators.ContextMenu.itemElement); - for (const element of elements) { - const klass = await element.getAttribute('class'); - if (klass.indexOf('disabled') < 0) { - items.push(await new ContextMenuItem(element, this).wait()); - } - } - return items; - } + for (const element of elements) { + const klass = await element.getAttribute('class'); + if (klass.indexOf('disabled') < 0) { + items.push(await new ContextMenuItem(element, this).wait()); + } + } + return items; + } - /** - * Close the context menu - * @returns Promise resolving when the menu is closed - */ - async close(): Promise { - const actions = this.getDriver().actions(); - await actions.clear(); - await this.getDriver().actions().sendKeys(Key.ESCAPE).perform(); - try { - await this.getDriver().wait(until.elementIsNotVisible(this)); - } catch (err) { - if (!(err instanceof error.StaleElementReferenceError)) { - throw err; - } - } - } + /** + * Close the context menu + * @returns Promise resolving when the menu is closed + */ + async close(): Promise { + const actions = this.getDriver().actions(); + await actions.clear(); + await this.getDriver().actions().sendKeys(Key.ESCAPE).perform(); + try { + await this.getDriver().wait(until.elementIsNotVisible(this)); + } catch (err) { + if (!(err instanceof error.StaleElementReferenceError)) { + throw err; + } + } + } - /** - * Wait for the menu to appear and load all its items - */ - async wait(timeout: number = 5000): Promise { - await this.getDriver().wait(until.elementIsVisible(this), timeout); - let items = (await this.getItems()).length; - try { - await this.getDriver().wait(async () => { - const temp = (await this.getItems()).length; - if (temp === items) { - return true; - } else { - items = temp; - return false; - } - }, 1000); - } catch (err) { - if (err instanceof error.TimeoutError) { - // ignore timeout - } else { - throw err; - } - } - return this; - } + /** + * Wait for the menu to appear and load all its items + */ + async wait(timeout: number = 5000): Promise { + await this.getDriver().wait(until.elementIsVisible(this), timeout); + let items = (await this.getItems()).length; + try { + await this.getDriver().wait(async () => { + const temp = (await this.getItems()).length; + if (temp === items) { + return true; + } else { + items = temp; + return false; + } + }, 1000); + } catch (err) { + if (err instanceof error.TimeoutError) { + // ignore timeout + } else { + throw err; + } + } + return this; + } } /** * Object representing an item of a context menu */ export class ContextMenuItem extends MenuItem { + constructor(item: WebElement, parent: Menu) { + super(item, parent); + this.parent = parent; + } - constructor(item: WebElement, parent: Menu) { - super(item, parent); - this.parent = parent; - } + async select(): Promise { + await this.click(); + await new Promise((res) => setTimeout(res, 500)); + if (await this.isNesting()) { + return await new ContextMenu(this).wait(); + } + return undefined; + } - async select(): Promise { - await this.click(); - await new Promise(res => setTimeout(res, 500)); - if (await this.isNesting()) { - return await new ContextMenu(this).wait(); - } - return undefined; - } + async getLabel(): Promise { + const labelItem = await this.findElement(ContextMenu.locators.ContextMenu.itemLabel); + return await labelItem.getAttribute(ContextMenu.locators.ContextMenu.itemText); + } - async getLabel(): Promise { - const labelItem = await this.findElement(ContextMenu.locators.ContextMenu.itemLabel); - return await labelItem.getAttribute(ContextMenu.locators.ContextMenu.itemText); - } - - private async isNesting(): Promise { - try { - return await this.findElement(ContextMenu.locators.ContextMenu.itemNesting).isDisplayed(); - } catch (err) { - return false; - } - } -} \ No newline at end of file + private async isNesting(): Promise { + try { + return await this.findElement(ContextMenu.locators.ContextMenu.itemNesting).isDisplayed(); + } catch (err) { + return false; + } + } +} diff --git a/packages/page-objects/src/components/menu/MacTitleBar.ts b/packages/page-objects/src/components/menu/MacTitleBar.ts index 21eda2a2e..62e5a3a38 100644 --- a/packages/page-objects/src/components/menu/MacTitleBar.ts +++ b/packages/page-objects/src/components/menu/MacTitleBar.ts @@ -15,41 +15,36 @@ * limitations under the License. */ -import { execSync } from "child_process"; +import { execSync } from 'child_process'; /** - * Handler object for macOS based title bar + * Handler object for macOS based title bar */ export class MacTitleBar { - - /** - * Select an item from the mac menu bar by its path, - * does not actually visibly open the menus. - * - * @param items varargs path to the given menu item - * each argument serves as a part of the path in order, - * - * e.g. ('File', 'Save') will select the 'Save' item from - * the 'File' submenu - */ - static select(...items: string[]): void { - let menuCounter = 0; - const commands = [ - `tell application "System Events"`, - `tell process "Code"`, - `tell menu bar item "${items[0]}" of menu bar 1` - ]; - for (let i = 1; i < items.length - 1; i++) { - commands.push(`tell menu item "${items[i]}" of menu 1`); - ++menuCounter; - } - if (items.length > 1) { - commands.push(`click menu item "${items[items.length - 1]}" of menu 1`); - } - for (let i = 0; i < menuCounter + 3; i++) { - commands.push(`end tell`); - } - const command = `osascript -e '${commands.join('\n')}'`; - execSync(command); - } -} \ No newline at end of file + /** + * Select an item from the mac menu bar by its path, + * does not actually visibly open the menus. + * + * @param items varargs path to the given menu item + * each argument serves as a part of the path in order, + * + * e.g. ('File', 'Save') will select the 'Save' item from + * the 'File' submenu + */ + static select(...items: string[]): void { + let menuCounter = 0; + const commands = [`tell application "System Events"`, `tell process "Code"`, `tell menu bar item "${items[0]}" of menu bar 1`]; + for (let i = 1; i < items.length - 1; i++) { + commands.push(`tell menu item "${items[i]}" of menu 1`); + ++menuCounter; + } + if (items.length > 1) { + commands.push(`click menu item "${items[items.length - 1]}" of menu 1`); + } + for (let i = 0; i < menuCounter + 3; i++) { + commands.push(`end tell`); + } + const command = `osascript -e '${commands.join('\n')}'`; + execSync(command); + } +} diff --git a/packages/page-objects/src/components/menu/Menu.ts b/packages/page-objects/src/components/menu/Menu.ts index 0a9c521ba..0792a31ab 100644 --- a/packages/page-objects/src/components/menu/Menu.ts +++ b/packages/page-objects/src/components/menu/Menu.ts @@ -15,67 +15,66 @@ * limitations under the License. */ -import { AbstractElement } from "../AbstractElement"; -import { MenuItem } from "./MenuItem"; +import { AbstractElement } from '../AbstractElement'; +import { MenuItem } from './MenuItem'; /** * Abstract element representing a menu */ export abstract class Menu extends AbstractElement { - - /** - * Find whether the menu has an item of a given name - * @param name name of the item to search for - * @returns true if menu has an item with the given name, false otherwise - */ - async hasItem(name: string): Promise { - const item = await this.getItem(name); - return !!item && (item).isDisplayed(); - } + /** + * Find whether the menu has an item of a given name + * @param name name of the item to search for + * @returns true if menu has an item with the given name, false otherwise + */ + async hasItem(name: string): Promise { + const item = await this.getItem(name); + return !!item && item.isDisplayed(); + } - /** - * Return a menu item of a given name, undefined if not found - * @param name name of the item to search for - */ - abstract getItem(name: string): Promise; + /** + * Return a menu item of a given name, undefined if not found + * @param name name of the item to search for + */ + abstract getItem(name: string): Promise; - /** - * Get all items of a menu - * @returns array of MenuItem object representing the menu items - */ - abstract getItems(): Promise; + /** + * Get all items of a menu + * @returns array of MenuItem object representing the menu items + */ + abstract getItems(): Promise; - /** - * Recursively select an item with a given path. - * - * E.g. calling select('File', 'Preferences', 'Settings') will - * open the 'File' -> 'Preferences' submenus and then click on 'Settings'. - * - * Selection happens in order of the arguments, if one of the items in the middle - * of the path has no children, the consequent path arguments will be ignored. - * - * - * @param path path to the item to select, represented by a sequence of strings - * @returns void if the last clicked item is a leaf, Menu item representing - * its submenu otherwise - */ - async select(...path: string[]): Promise { - let parent: Menu = this; - for (const label of path) { + /** + * Recursively select an item with a given path. + * + * E.g. calling select('File', 'Preferences', 'Settings') will + * open the 'File' -> 'Preferences' submenus and then click on 'Settings'. + * + * Selection happens in order of the arguments, if one of the items in the middle + * of the path has no children, the consequent path arguments will be ignored. + * + * + * @param path path to the item to select, represented by a sequence of strings + * @returns void if the last clicked item is a leaf, Menu item representing + * its submenu otherwise + */ + async select(...path: string[]): Promise { + let parent: Menu = this; + for (const label of path) { const item = await parent.getItem(label); - if (!item) { - return parent; - } - await Menu.driver.wait(async function () { - return await item.isDisplayed() && await item.isEnabled(); - }); - const submenu = await item.select(); - if (submenu) { - parent = submenu; - } else { - return; - } - } - return parent; - } -} \ No newline at end of file + if (!item) { + return parent; + } + await Menu.driver.wait(async function () { + return (await item.isDisplayed()) && (await item.isEnabled()); + }); + const submenu = await item.select(); + if (submenu) { + parent = submenu; + } else { + return; + } + } + return parent; + } +} diff --git a/packages/page-objects/src/components/menu/MenuItem.ts b/packages/page-objects/src/components/menu/MenuItem.ts index 1529b5078..5117f572d 100644 --- a/packages/page-objects/src/components/menu/MenuItem.ts +++ b/packages/page-objects/src/components/menu/MenuItem.ts @@ -15,40 +15,39 @@ * limitations under the License. */ -import { AbstractElement } from "../AbstractElement"; -import { Menu } from "./Menu"; +import { AbstractElement } from '../AbstractElement'; +import { Menu } from './Menu'; /** * Abstract element representing a menu item */ export abstract class MenuItem extends AbstractElement { + protected parent!: Menu; + protected label!: string; - protected parent!: Menu; - protected label!: string; + /** + * Use the given menu item: Opens the submenu if the item has children, + * otherwise simply click the item. + * + * @returns Menu object representing the submenu if the item has children, void otherwise. + */ + async select(): Promise { + await this.click(); + await new Promise((res) => setTimeout(res, 500)); + return undefined; + } - /** - * Use the given menu item: Opens the submenu if the item has children, - * otherwise simply click the item. - * - * @returns Menu object representing the submenu if the item has children, void otherwise. - */ - async select(): Promise { - await this.click(); - await new Promise(res => setTimeout(res, 500)); - return undefined; - } + /** + * Return the Menu object representing the menu this item belongs to + */ + getParent(): Menu { + return this.parent; + } - /** - * Return the Menu object representing the menu this item belongs to - */ - getParent(): Menu { - return this.parent; - } - - /** - * Returns the label of the menu item - */ - getLabel(): string | Promise { - return this.label; - } -} \ No newline at end of file + /** + * Returns the label of the menu item + */ + getLabel(): string | Promise { + return this.label; + } +} diff --git a/packages/page-objects/src/components/menu/TitleBar.ts b/packages/page-objects/src/components/menu/TitleBar.ts index ec6fb590f..9d22d93f0 100644 --- a/packages/page-objects/src/components/menu/TitleBar.ts +++ b/packages/page-objects/src/components/menu/TitleBar.ts @@ -15,84 +15,82 @@ * limitations under the License. */ -import { Key } from "selenium-webdriver"; -import { WindowControls, ContextMenu } from "../.."; -import { Menu } from "./Menu"; -import { MenuItem } from "./MenuItem"; +import { Key } from 'selenium-webdriver'; +import { WindowControls, ContextMenu } from '../..'; +import { Menu } from './Menu'; +import { MenuItem } from './MenuItem'; /** * Page object representing the custom VS Code title bar */ export class TitleBar extends Menu { + constructor() { + super(TitleBar.locators.TitleBar.constructor, TitleBar.locators.Workbench.constructor); + } - constructor() { - super(TitleBar.locators.TitleBar.constructor, TitleBar.locators.Workbench.constructor); - } + /** + * Get title bar item by name + * @param name name of the item to search by + * @returns Promise resolving to TitleBarItem object + */ + async getItem(name: string): Promise { + try { + await this.findElement(TitleBar.locators.TitleBar.itemConstructor(name)); + return await new TitleBarItem(name, this).wait(); + } catch (err) { + return undefined; + } + } - /** - * Get title bar item by name - * @param name name of the item to search by - * @returns Promise resolving to TitleBarItem object - */ - async getItem(name: string): Promise { - try { - await this.findElement(TitleBar.locators.TitleBar.itemConstructor(name)); - return await new TitleBarItem(name, this).wait(); - } catch (err) { - return undefined; - } - } + /** + * Get all title bar items + * @returns Promise resolving to array of TitleBarItem objects + */ + async getItems(): Promise { + const items: TitleBarItem[] = []; + const elements = await this.findElements(TitleBar.locators.TitleBar.itemElement); - /** - * Get all title bar items - * @returns Promise resolving to array of TitleBarItem objects - */ - async getItems(): Promise { - const items: TitleBarItem[] = []; - const elements = await this.findElements(TitleBar.locators.TitleBar.itemElement); + for (const element of elements) { + if (await element.isDisplayed()) { + items.push(await new TitleBarItem(await element.getAttribute(TitleBar.locators.TitleBar.itemLabel), this).wait()); + } + } + return items; + } - for (const element of elements) { - if (await element.isDisplayed()) { - items.push(await new TitleBarItem(await element.getAttribute(TitleBar.locators.TitleBar.itemLabel), this).wait()); - } - } - return items; - } + /** + * Get the window title + * @returns Promise resolving to the window title + */ + async getTitle(): Promise { + return await this.findElement(TitleBar.locators.TitleBar.title).getText(); + } - /** - * Get the window title - * @returns Promise resolving to the window title - */ - async getTitle(): Promise { - return await this.findElement(TitleBar.locators.TitleBar.title).getText(); - } - - /** - * Get a reference to the WindowControls - */ - getWindowControls(): WindowControls { - return new WindowControls(this); - } + /** + * Get a reference to the WindowControls + */ + getWindowControls(): WindowControls { + return new WindowControls(this); + } } /** * Page object representing an item of the custom VS Code title bar */ export class TitleBarItem extends MenuItem { + constructor(label: string, parent: Menu) { + super(TitleBar.locators.TitleBar.itemConstructor(label), parent); + this.parent = parent; + this.label = label; + } - constructor(label: string, parent: Menu) { - super(TitleBar.locators.TitleBar.itemConstructor(label), parent); - this.parent = parent; - this.label = label; - } - - async select(): Promise { - const openMenus = await this.getDriver().findElements(TitleBar.locators.ContextMenu.constructor); - if (openMenus.length > 0 && await openMenus[0].isDisplayed()) { - await this.getDriver().actions().sendKeys(Key.ESCAPE).perform(); - } - await this.click(); - await new Promise(res => setTimeout(res, 500)); - return new ContextMenu(this).wait(); - } + async select(): Promise { + const openMenus = await this.getDriver().findElements(TitleBar.locators.ContextMenu.constructor); + if (openMenus.length > 0 && (await openMenus[0].isDisplayed())) { + await this.getDriver().actions().sendKeys(Key.ESCAPE).perform(); + } + await this.click(); + await new Promise((res) => setTimeout(res, 500)); + return new ContextMenu(this).wait(); + } } diff --git a/packages/page-objects/src/components/menu/WindowControls.ts b/packages/page-objects/src/components/menu/WindowControls.ts index fb32e053d..3f88f72d4 100644 --- a/packages/page-objects/src/components/menu/WindowControls.ts +++ b/packages/page-objects/src/components/menu/WindowControls.ts @@ -15,61 +15,61 @@ * limitations under the License. */ -import { AbstractElement } from "../AbstractElement"; -import { TitleBar } from "../.."; -import { WebElement } from "selenium-webdriver"; +import { AbstractElement } from '../AbstractElement'; +import { TitleBar } from '../..'; +import { WebElement } from 'selenium-webdriver'; /** * Page object for the windows controls part of the title bar */ export class WindowControls extends AbstractElement { - constructor(bar: TitleBar = new TitleBar()) { - super(WindowControls.locators.WindowControls.constructor, bar); - } + constructor(bar: TitleBar = new TitleBar()) { + super(WindowControls.locators.WindowControls.constructor, bar); + } - /** - * Use the minimize window button - * @returns Promise resolving when minimize button is pressed - */ - async minimize(): Promise { - const minButton = await this.findElement(WindowControls.locators.WindowControls.minimize); - await minButton.click(); - } + /** + * Use the minimize window button + * @returns Promise resolving when minimize button is pressed + */ + async minimize(): Promise { + const minButton = await this.findElement(WindowControls.locators.WindowControls.minimize); + await minButton.click(); + } - /** - * Use the maximize window button if the window is not maximized - * @returns Promise resolving when maximize button is pressed - */ - async maximize(): Promise { - let maxButton: WebElement; - try { - maxButton = await this.findElement(WindowControls.locators.WindowControls.maximize); - await maxButton.click(); - } catch (err) { - console.log('Window is already maximized'); - } - } + /** + * Use the maximize window button if the window is not maximized + * @returns Promise resolving when maximize button is pressed + */ + async maximize(): Promise { + let maxButton: WebElement; + try { + maxButton = await this.findElement(WindowControls.locators.WindowControls.maximize); + await maxButton.click(); + } catch (err) { + console.log('Window is already maximized'); + } + } - /** - * Use the restore window button if the window is maximized - * @returns Promise resolving when restore button is pressed - */ - async restore(): Promise { - let maxButton: WebElement; - try { - maxButton = await this.findElement(WindowControls.locators.WindowControls.restore); - await maxButton.click(); - } catch (err) { - console.log('Window is not maximized'); - } - } + /** + * Use the restore window button if the window is maximized + * @returns Promise resolving when restore button is pressed + */ + async restore(): Promise { + let maxButton: WebElement; + try { + maxButton = await this.findElement(WindowControls.locators.WindowControls.restore); + await maxButton.click(); + } catch (err) { + console.log('Window is not maximized'); + } + } - /** - * Use the window close button. Use at your own risk. - * @returns Promise resolving when close button is pressed - */ - async close(): Promise { - const closeButton = await this.findElement(WindowControls.locators.WindowControls.close); - await closeButton.click(); - } -} \ No newline at end of file + /** + * Use the window close button. Use at your own risk. + * @returns Promise resolving when close button is pressed + */ + async close(): Promise { + const closeButton = await this.findElement(WindowControls.locators.WindowControls.close); + await closeButton.click(); + } +} diff --git a/packages/page-objects/src/components/sidebar/SideBarView.ts b/packages/page-objects/src/components/sidebar/SideBarView.ts index 4add1e140..68ad606b7 100644 --- a/packages/page-objects/src/components/sidebar/SideBarView.ts +++ b/packages/page-objects/src/components/sidebar/SideBarView.ts @@ -15,30 +15,30 @@ * limitations under the License. */ -import { AbstractElement } from "../AbstractElement"; -import { ViewTitlePart, ViewContent } from "../.."; +import { AbstractElement } from '../AbstractElement'; +import { ViewTitlePart, ViewContent } from '../..'; /** * Page object for the side bar view */ export class SideBarView extends AbstractElement { - constructor() { - super(SideBarView.locators.SideBarView.constructor, SideBarView.locators.Workbench.constructor); - } + constructor() { + super(SideBarView.locators.SideBarView.constructor, SideBarView.locators.Workbench.constructor); + } - /** - * Get the top part of the open view (contains title and possibly some buttons) - * @returns ViewTitlePart object - */ - getTitlePart(): ViewTitlePart { - return new ViewTitlePart(this); - } + /** + * Get the top part of the open view (contains title and possibly some buttons) + * @returns ViewTitlePart object + */ + getTitlePart(): ViewTitlePart { + return new ViewTitlePart(this); + } - /** - * Get the content part of the open view - * @returns ViewContent object - */ - getContent(): ViewContent { - return new ViewContent(this); - } -} \ No newline at end of file + /** + * Get the content part of the open view + * @returns ViewContent object + */ + getContent(): ViewContent { + return new ViewContent(this); + } +} diff --git a/packages/page-objects/src/components/sidebar/ViewContent.ts b/packages/page-objects/src/components/sidebar/ViewContent.ts index c6981dddc..3895032f7 100644 --- a/packages/page-objects/src/components/sidebar/ViewContent.ts +++ b/packages/page-objects/src/components/sidebar/ViewContent.ts @@ -15,120 +15,120 @@ * limitations under the License. */ -import { CustomTreeSection, DefaultTreeSection, ExtensionsViewSection, SideBarView, ViewSection, ViewSectionConstructor } from "../.."; -import { WebElement, error } from "selenium-webdriver"; -import { AbstractElement } from "../AbstractElement"; +import { CustomTreeSection, DefaultTreeSection, ExtensionsViewSection, SideBarView, ViewSection, ViewSectionConstructor } from '../..'; +import { WebElement, error } from 'selenium-webdriver'; +import { AbstractElement } from '../AbstractElement'; /** * Page object representing the view container of a side bar view */ export class ViewContent extends AbstractElement { - constructor(view: SideBarView = new SideBarView()) { - super(ViewContent.locators.ViewContent.constructor, view); - } + constructor(view: SideBarView = new SideBarView()) { + super(ViewContent.locators.ViewContent.constructor, view); + } - /** - * Finds whether a progress bar is active at the top of the view - * @returns Promise resolving to true/false - */ - async hasProgress(): Promise { - const progress = await this.findElement(ViewContent.locators.ViewContent.progress); - const hidden = await progress.getAttribute('aria-hidden'); - if (hidden === 'true') { - return false; - } - return true; - } + /** + * Finds whether a progress bar is active at the top of the view + * @returns Promise resolving to true/false + */ + async hasProgress(): Promise { + const progress = await this.findElement(ViewContent.locators.ViewContent.progress); + const hidden = await progress.getAttribute('aria-hidden'); + if (hidden === 'true') { + return false; + } + return true; + } - /** - * Retrieves a collapsible view content section by its title. - * Generic parameter allows caller to cast returned section to - * desired type, however it is caller's responsibility to check - * whether type is compatible with desired type. - * @param title Title of the section - * @returns Promise resolving to ViewSection object - */ - getSection(title: string): Promise; - /** - * Retrieves a collapsible view content section by its title. - * Type parameter allows caller to use custom section implementation, - * however it is caller's responsibility to check - * whether type is compatible with desired type. - * @param title Title of the section - * @param type ViewSection constructor to be used - * @returns Promise resolving to object specified by type - */ - getSection(title: string, type: ViewSectionConstructor): Promise; - /** - * Retrieves a collapsible view content section by predicate. - * Generic parameter allows caller to cast returned section to - * desired type, however it is caller's responsibility to check - * whether type is compatible with desired type. - * @param predicate Predicate to be used when searching - * @returns Promise resolving to ViewSection object - */ - getSection(predicate: ((section: ViewSection) => boolean | PromiseLike)): Promise; - /** - * Retrieves a collapsible view content section by predicate. - * Type parameter allows caller to use custom section implementation, - * however it is caller's responsibility to check - * whether type is compatible with desired type. - * @param predicate Predicate to be used when searching - * @param type ViewSection constructor to be used - * @returns Promise resolving to object specified by type - */ - getSection(predicate: ((section: ViewSection) => boolean | PromiseLike), type: ViewSectionConstructor): Promise; - async getSection(titleOrPredicate: string | ((section: ViewSection) => boolean | PromiseLike), type?: ViewSectionConstructor): Promise { - const sections = await this.getSections(); - const predicate = typeof titleOrPredicate === 'string' ? - (async (section: ViewSection) => await section.getTitle() === titleOrPredicate) : titleOrPredicate; + /** + * Retrieves a collapsible view content section by its title. + * Generic parameter allows caller to cast returned section to + * desired type, however it is caller's responsibility to check + * whether type is compatible with desired type. + * @param title Title of the section + * @returns Promise resolving to ViewSection object + */ + getSection(title: string): Promise; + /** + * Retrieves a collapsible view content section by its title. + * Type parameter allows caller to use custom section implementation, + * however it is caller's responsibility to check + * whether type is compatible with desired type. + * @param title Title of the section + * @param type ViewSection constructor to be used + * @returns Promise resolving to object specified by type + */ + getSection(title: string, type: ViewSectionConstructor): Promise; + /** + * Retrieves a collapsible view content section by predicate. + * Generic parameter allows caller to cast returned section to + * desired type, however it is caller's responsibility to check + * whether type is compatible with desired type. + * @param predicate Predicate to be used when searching + * @returns Promise resolving to ViewSection object + */ + getSection(predicate: (section: ViewSection) => boolean | PromiseLike): Promise; + /** + * Retrieves a collapsible view content section by predicate. + * Type parameter allows caller to use custom section implementation, + * however it is caller's responsibility to check + * whether type is compatible with desired type. + * @param predicate Predicate to be used when searching + * @param type ViewSection constructor to be used + * @returns Promise resolving to object specified by type + */ + getSection(predicate: (section: ViewSection) => boolean | PromiseLike, type: ViewSectionConstructor): Promise; + async getSection( + titleOrPredicate: string | ((section: ViewSection) => boolean | PromiseLike), + type?: ViewSectionConstructor, + ): Promise { + const sections = await this.getSections(); + const predicate = + typeof titleOrPredicate === 'string' ? async (section: ViewSection) => (await section.getTitle()) === titleOrPredicate : titleOrPredicate; - for (const section of sections) { - if (await predicate(section)) { - if (type !== undefined && !(section instanceof type)) { - return new type(section, this); - } - return section as T; - } - } - if (typeof titleOrPredicate === 'string') { - throw new error.NoSuchElementError(`No section with title '${titleOrPredicate}' found`); - } - else { - throw new error.NoSuchElementError(`No section satisfying predicate found`); - } - } + for (const section of sections) { + if (await predicate(section)) { + if (type !== undefined && !(section instanceof type)) { + return new type(section, this); + } + return section as T; + } + } + if (typeof titleOrPredicate === 'string') { + throw new error.NoSuchElementError(`No section with title '${titleOrPredicate}' found`); + } else { + throw new error.NoSuchElementError(`No section satisfying predicate found`); + } + } - /** - * Retrieves all the collapsible view content sections - * @returns Promise resolving to array of ViewSection objects - */ - async getSections(): Promise { - const sections: ViewSection[] = []; - const elements = await this.findElements(ViewContent.locators.ViewContent.section); - for (const element of elements) { - sections.push(await this.createSection(element)); - } - return sections; - } + /** + * Retrieves all the collapsible view content sections + * @returns Promise resolving to array of ViewSection objects + */ + async getSections(): Promise { + const sections: ViewSection[] = []; + const elements = await this.findElements(ViewContent.locators.ViewContent.section); + for (const element of elements) { + sections.push(await this.createSection(element)); + } + return sections; + } - private async createSection(panel: WebElement, type?: ViewSectionConstructor): Promise { - if (type !== undefined) { - return new type(panel, this); - } + private async createSection(panel: WebElement, type?: ViewSectionConstructor): Promise { + if (type !== undefined) { + return new type(panel, this); + } - const section: ViewSection = new DefaultTreeSection(panel, this); - const types = ViewContent.locators.DefaultTreeSection.type; - const locators = ViewContent.locators; + const section: ViewSection = new DefaultTreeSection(panel, this); + const types = ViewContent.locators.DefaultTreeSection.type; + const locators = ViewContent.locators; - if (await types.default(section, locators)) { - return section; - } - else if (await types.marketplace.extension(section, locators)) { - return new ExtensionsViewSection(panel, this); - } - else { - return new CustomTreeSection(panel, this); - } - } + if (await types.default(section, locators)) { + return section; + } else if (await types.marketplace.extension(section, locators)) { + return new ExtensionsViewSection(panel, this); + } else { + return new CustomTreeSection(panel, this); + } + } } diff --git a/packages/page-objects/src/components/sidebar/ViewItem.ts b/packages/page-objects/src/components/sidebar/ViewItem.ts index 90431e26d..35f801e79 100644 --- a/packages/page-objects/src/components/sidebar/ViewItem.ts +++ b/packages/page-objects/src/components/sidebar/ViewItem.ts @@ -15,236 +15,238 @@ * limitations under the License. */ -import { ElementWithContexMenu } from "../ElementWithContextMenu"; -import { AbstractElement } from "../AbstractElement"; -import { WebElement, By, error } from "selenium-webdriver"; -import { NullAttributeError } from "../../errors/NullAttributeError"; +import { ElementWithContexMenu } from '../ElementWithContextMenu'; +import { AbstractElement } from '../AbstractElement'; +import { WebElement, By, error } from 'selenium-webdriver'; +import { NullAttributeError } from '../../errors/NullAttributeError'; /** * Arbitrary item in the side bar view */ export abstract class ViewItem extends ElementWithContexMenu { - /** - * Select the item in the view. - * Note that selecting the item will toggle its expand state when applicable. - * @returns Promise resolving when the item has been clicked - */ - async select(): Promise { - await this.click(); - } + /** + * Select the item in the view. + * Note that selecting the item will toggle its expand state when applicable. + * @returns Promise resolving when the item has been clicked + */ + async select(): Promise { + await this.click(); + } } - /** * Abstract representation of a row in the tree inside a view content section */ export abstract class TreeItem extends ViewItem { - /** - * Retrieves the label of this view item - */ - abstract getLabel(): Promise; - - /** - * Retrieves the tooltip of this TreeItem. - * @returns A promise resolving to the tooltip or undefined if the TreeItem has no tooltip. - */ - async getTooltip(): Promise { - return undefined; - } - - /** - * Retrieves the description of this TreeItem. - * @returns A promise resolving to the tooltip or undefined if the TreeItem has no description. - */ - async getDescription(): Promise { - return undefined; - } - - /** - * Finds if the item has children by actually counting the child items - * Note that this will expand the item if it was collapsed - * @returns Promise resolving to true/false - */ - async hasChildren(): Promise { - const children = await this.getChildren(); - return children && children.length > 0; - } - - /** - * Finds whether the item is expanded. Always returns false if item has no children. - * @returns Promise resolving to true/false - */ - abstract isExpanded(): Promise; - - /** - * Find children of an item, will try to expand the item in the process - * @returns Promise resolving to array of TreeItem objects, empty array if item has no children - */ - abstract getChildren(): Promise; - - /** - * Finds if the item is expandable/collapsible - * @returns Promise resolving to true/false - */ - abstract isExpandable(): Promise; - - /** - * Expands the current item, if it can be expanded and is collapsed. - */ - async expand(): Promise { - if (await this.isExpandable() && !await this.isExpanded()) { - await (await this.findTwistie()).click(); - } - } - - /** - * Find a child item with the given name - * @returns Promise resolving to TreeItem object if the child item exists, undefined otherwise - */ - async findChildItem(name: string): Promise { - const children = await this.getChildren(); - for (const item of children) { - if (await item.getLabel() === name) { - return item; - } - } - } - - /** - * Collapse the item if expanded - */ - async collapse(): Promise { - if (await this.isExpandable() && await this.isExpanded()) { - await (await this.findTwistie()).click(); - } - } - - /** - * Find all action buttons bound to the view item - * - * @returns array of ViewItemAction objects, empty array if item has no - * actions associated - */ - async getActionButtons(): Promise { - await this.getDriver().actions().move({ origin: this }).perform(); - - let container: WebElement; - try { - container = await this.findElement(TreeItem.locators.TreeItem.actions); - } catch (e) { - if (e instanceof error.NoSuchElementError) { - return []; - } - throw e; - } - - const actions: ViewItemAction[] = []; - const items = await container.findElements(TreeItem.locators.TreeItem.actionLabel); - - for (const item of items) { - const label = await item.getAttribute(TreeItem.locators.TreeItem.actionTitle); - - if (label === '' || label === null) { - // unknown, skip the item - continue; - } - - try { - actions.push(new ViewItemAction(ViewItemAction.locators.ViewSection.actionConstructor(label), this)); - } - catch (e) { - // the item was destroyed in meantime - if (e instanceof error.NoSuchElementError) { - continue; - } - - if (e instanceof error.StaleElementReferenceError) { - console.warn('ViewItem has become stale'); - } - - throw e; - } - } - - return actions; - } - - /** - * Find action button for view item by label - * @param label label of the button to search by - * - * @returns ViewItemAction object if such button exists, undefined otherwise - */ - async getActionButton(label: string): Promise { - const actions = await this.getActionButtons(); - - for (const action of actions) { - try { - if ((await action.getLabel()).includes(label)) { - return action; - } - } - catch (e) { - if (e instanceof NullAttributeError || e instanceof error.StaleElementReferenceError) { - continue; - } - throw e; - } - } - - return undefined; - } - - /** - * Find all child elements of a tree item - * @param locator locator of a given type of tree item - */ - protected async getChildItems(locator: By): Promise { - const items: WebElement[] = []; - await this.expand(); - - const rows = await this.enclosingItem.findElements(locator); - const baseIndex = +await this.getAttribute(TreeItem.locators.ViewSection.index); - const baseLevel = +await this.getAttribute(TreeItem.locators.ViewSection.level); - - for (const row of rows) { - const level = +await row.getAttribute(TreeItem.locators.ViewSection.level); - const index = +await row.getAttribute(TreeItem.locators.ViewSection.index); - - if (index <= baseIndex) { continue; } - if (level > baseLevel + 1) { continue; } - if (level <= baseLevel) { break; } - - items.push(row); - } - - return items; - } - - protected async findTwistie(): Promise { - return await this.findElement(TreeItem.locators.TreeItem.twistie); - } + /** + * Retrieves the label of this view item + */ + abstract getLabel(): Promise; + + /** + * Retrieves the tooltip of this TreeItem. + * @returns A promise resolving to the tooltip or undefined if the TreeItem has no tooltip. + */ + async getTooltip(): Promise { + return undefined; + } + + /** + * Retrieves the description of this TreeItem. + * @returns A promise resolving to the tooltip or undefined if the TreeItem has no description. + */ + async getDescription(): Promise { + return undefined; + } + + /** + * Finds if the item has children by actually counting the child items + * Note that this will expand the item if it was collapsed + * @returns Promise resolving to true/false + */ + async hasChildren(): Promise { + const children = await this.getChildren(); + return children && children.length > 0; + } + + /** + * Finds whether the item is expanded. Always returns false if item has no children. + * @returns Promise resolving to true/false + */ + abstract isExpanded(): Promise; + + /** + * Find children of an item, will try to expand the item in the process + * @returns Promise resolving to array of TreeItem objects, empty array if item has no children + */ + abstract getChildren(): Promise; + + /** + * Finds if the item is expandable/collapsible + * @returns Promise resolving to true/false + */ + abstract isExpandable(): Promise; + + /** + * Expands the current item, if it can be expanded and is collapsed. + */ + async expand(): Promise { + if ((await this.isExpandable()) && !(await this.isExpanded())) { + await (await this.findTwistie()).click(); + } + } + + /** + * Find a child item with the given name + * @returns Promise resolving to TreeItem object if the child item exists, undefined otherwise + */ + async findChildItem(name: string): Promise { + const children = await this.getChildren(); + for (const item of children) { + if ((await item.getLabel()) === name) { + return item; + } + } + } + + /** + * Collapse the item if expanded + */ + async collapse(): Promise { + if ((await this.isExpandable()) && (await this.isExpanded())) { + await (await this.findTwistie()).click(); + } + } + + /** + * Find all action buttons bound to the view item + * + * @returns array of ViewItemAction objects, empty array if item has no + * actions associated + */ + async getActionButtons(): Promise { + await this.getDriver().actions().move({ origin: this }).perform(); + + let container: WebElement; + try { + container = await this.findElement(TreeItem.locators.TreeItem.actions); + } catch (e) { + if (e instanceof error.NoSuchElementError) { + return []; + } + throw e; + } + + const actions: ViewItemAction[] = []; + const items = await container.findElements(TreeItem.locators.TreeItem.actionLabel); + + for (const item of items) { + const label = await item.getAttribute(TreeItem.locators.TreeItem.actionTitle); + + if (label === '' || label === null) { + // unknown, skip the item + continue; + } + + try { + actions.push(new ViewItemAction(ViewItemAction.locators.ViewSection.actionConstructor(label), this)); + } catch (e) { + // the item was destroyed in meantime + if (e instanceof error.NoSuchElementError) { + continue; + } + + if (e instanceof error.StaleElementReferenceError) { + console.warn('ViewItem has become stale'); + } + + throw e; + } + } + + return actions; + } + + /** + * Find action button for view item by label + * @param label label of the button to search by + * + * @returns ViewItemAction object if such button exists, undefined otherwise + */ + async getActionButton(label: string): Promise { + const actions = await this.getActionButtons(); + + for (const action of actions) { + try { + if ((await action.getLabel()).includes(label)) { + return action; + } + } catch (e) { + if (e instanceof NullAttributeError || e instanceof error.StaleElementReferenceError) { + continue; + } + throw e; + } + } + + return undefined; + } + + /** + * Find all child elements of a tree item + * @param locator locator of a given type of tree item + */ + protected async getChildItems(locator: By): Promise { + const items: WebElement[] = []; + await this.expand(); + + const rows = await this.enclosingItem.findElements(locator); + const baseIndex = +(await this.getAttribute(TreeItem.locators.ViewSection.index)); + const baseLevel = +(await this.getAttribute(TreeItem.locators.ViewSection.level)); + + for (const row of rows) { + const level = +(await row.getAttribute(TreeItem.locators.ViewSection.level)); + const index = +(await row.getAttribute(TreeItem.locators.ViewSection.index)); + + if (index <= baseIndex) { + continue; + } + if (level > baseLevel + 1) { + continue; + } + if (level <= baseLevel) { + break; + } + + items.push(row); + } + + return items; + } + + protected async findTwistie(): Promise { + return await this.findElement(TreeItem.locators.TreeItem.twistie); + } } /** * Action button bound to a view item */ export class ViewItemAction extends AbstractElement { - - constructor(actionConstructor: By, viewItem: TreeItem) { - super(actionConstructor, viewItem); - } - - /** - * Get label of the action button - */ - async getLabel(): Promise { - const value = await this.getAttribute(ViewItemAction.locators.ViewSection.buttonLabel); - - if (value === null) { - throw new NullAttributeError(`${this.constructor.name}.getLabel returned null`); - } - - return value; - } -} \ No newline at end of file + constructor(actionConstructor: By, viewItem: TreeItem) { + super(actionConstructor, viewItem); + } + + /** + * Get label of the action button + */ + async getLabel(): Promise { + const value = await this.getAttribute(ViewItemAction.locators.ViewSection.buttonLabel); + + if (value === null) { + throw new NullAttributeError(`${this.constructor.name}.getLabel returned null`); + } + + return value; + } +} diff --git a/packages/page-objects/src/components/sidebar/ViewSection.ts b/packages/page-objects/src/components/sidebar/ViewSection.ts index 7a649a419..d1bf4b630 100644 --- a/packages/page-objects/src/components/sidebar/ViewSection.ts +++ b/packages/page-objects/src/components/sidebar/ViewSection.ts @@ -15,211 +15,212 @@ * limitations under the License. */ -import { By, ChromiumWebDriver, until, WebElement } from "selenium-webdriver"; -import { ContextMenu, ViewContent, ViewItem, waitForAttributeValue, WelcomeContentSection } from "../.."; -import { AbstractElement } from "../AbstractElement"; -import { ElementWithContexMenu } from "../ElementWithContextMenu"; +import { By, ChromiumWebDriver, until, WebElement } from 'selenium-webdriver'; +import { ContextMenu, ViewContent, ViewItem, waitForAttributeValue, WelcomeContentSection } from '../..'; +import { AbstractElement } from '../AbstractElement'; +import { ElementWithContexMenu } from '../ElementWithContextMenu'; -export type ViewSectionConstructor = { new(rootElement: WebElement, tree: ViewContent): T }; +export type ViewSectionConstructor = { + new (rootElement: WebElement, tree: ViewContent): T; +}; /** * Page object representing a collapsible content section of the side bar view */ export abstract class ViewSection extends AbstractElement { - - constructor(panel: WebElement, content: ViewContent) { - super(panel, content); - } - - /** - * Get the title of the section as string - * @returns Promise resolving to section title - */ - async getTitle(): Promise { - const title = await this.findElement(ViewSection.locators.ViewSection.title); - return await title.getAttribute(ViewSection.locators.ViewSection.titleText); - } - - /** - * Expand the section if collapsed - * @returns Promise resolving when the section is expanded - */ - async expand(): Promise { - if (await this.isHeaderHidden()) { - return; - } - if (!await this.isExpanded()) { - const panel = await this.findElement(ViewSection.locators.ViewSection.header); - await panel.click(); - await this.getDriver().wait(waitForAttributeValue(panel, ViewSection.locators.ViewSection.headerExpanded, 'true'), 1000); - } - } - - /** - * Collapse the section if expanded - * @returns Promise resolving when the section is collapsed - */ - async collapse(): Promise { - if (await this.isHeaderHidden()) { - return; - } - if (await this.isExpanded()) { - const panel = await this.findElement(ViewSection.locators.ViewSection.header); - await panel.click(); - await this.getDriver().wait(waitForAttributeValue(panel, ViewSection.locators.ViewSection.headerExpanded, 'false'), 1000); - } - } - - /** - * Finds whether the section is expanded - * @returns Promise resolving to true/false - */ - async isExpanded(): Promise { - const header = await this.findElement(ViewSection.locators.ViewSection.header); - const expanded = await header.getAttribute(ViewSection.locators.ViewSection.headerExpanded); - return expanded === 'true'; - } - - /** - * Finds [Welcome Content](https://code.visualstudio.com/api/extension-guides/tree-view#welcome-content) - * present in this ViewSection and returns it. If none is found, then `undefined` is returned - * - */ - public async findWelcomeContent(): Promise { - try { - const res = await this.findElement(ViewSection.locators.ViewSection.welcomeContent); - if (!await res.isDisplayed()) { - return undefined; - } - return new WelcomeContentSection(res, this); - } catch (_err) { - return undefined; - } - } - - /** - * Retrieve all items currently visible in the view section. - * Note that any item currently beyond the visible list, i.e. not scrolled to, will not be retrieved. - * @returns Promise resolving to array of ViewItem objects - */ - abstract getVisibleItems(): Promise; - - /** - * Find an item in this view section by label. Does not perform recursive search through the whole tree. - * Does however scroll through all the expanded content. Will find items beyond the current scroll range. - * @param label Label of the item to search for. - * @param maxLevel Limit how deep the algorithm should look into any expanded items, default unlimited (0) - * @returns Promise resolving to ViewItem object is such item exists, undefined otherwise - */ - abstract findItem(label: string, maxLevel?: number): Promise; - - /** - * Open an item with a given path represented by a sequence of labels - * - * e.g to open 'file' inside 'folder', call - * openItem('folder', 'file') - * - * The first item is only searched for directly within the root element (depth 1). - * The label sequence is handled in order. If a leaf item (a file for example) is found in the middle - * of the sequence, the rest is ignored. - * - * If the item structure is flat, use the item's title to search by. - * - * @param path Sequence of labels that make up the path to a given item. - * @returns Promise resolving to array of ViewItem objects representing the last item's children. - * If the last item is a leaf, empty array is returned. - */ - abstract openItem(...path: string[]): Promise; - - /** - * Retrieve the action buttons on the section's header - * @returns Promise resolving to array of ViewPanelAction objects - */ - async getActions(): Promise { - const actions: ViewPanelAction[] = []; - - if (!await this.isHeaderHidden()) { - const header = await this.findElement(ViewSection.locators.ViewSection.header); - const act = await header.findElement(ViewSection.locators.ViewSection.actions); - const elements = await act.findElements(ViewSection.locators.ViewSection.button); - - for (const element of elements) { - actions.push(await new ViewPanelAction(element, this).wait()); - } - } - return actions; - } - - /** - * Retrieve an action button on the sections's header by its label - * @param label label/title of the button - * @returns ViewPanelAction object if found, undefined otherwise - */ - async getAction(label: string): Promise { - const actions = await this.getActions(); - for (const action of actions) { - if (await action.getLabel() === label) { - return action; - } - } - } - - /** - * Click on the More Actions... item if it exists - * - * @returns ContextMenu page object if the action succeeds, undefined otherwise - */ - async moreActions(): Promise { - const more = await this.getAction('More Actions...'); - if (!more) { - return undefined; - } - const section = this; - const btn = new class extends ElementWithContexMenu { - async openContextMenu() { - await this.click(); - const shadowRootHost = await section.findElements(By.className('shadow-root-host')); - if (shadowRootHost.length > 0) { - let shadowRoot; - const webdriverCapabilities = await (this.getDriver() as ChromiumWebDriver).getCapabilities(); - const chromiumVersion = webdriverCapabilities.getBrowserVersion(); - if (chromiumVersion && parseInt(chromiumVersion.split('.')[0]) >= 96) { - shadowRoot = await shadowRootHost[0].getShadowRoot(); - return new ContextMenu(await shadowRoot.findElement(By.className('monaco-menu-container'))).wait(); - } else { - shadowRoot = await this.getDriver().executeScript('return arguments[0].shadowRoot', shadowRootHost[0]) as WebElement; - return new ContextMenu(shadowRoot).wait(); - } - } - return await super.openContextMenu(); - } - }(more, this); - return await btn.openContextMenu(); - } - - private async isHeaderHidden(): Promise { - const header = await this.findElement(ViewSection.locators.ViewSection.header); - return (await header.getAttribute('class')).indexOf('hidden') > -1; - } + constructor(panel: WebElement, content: ViewContent) { + super(panel, content); + } + + /** + * Get the title of the section as string + * @returns Promise resolving to section title + */ + async getTitle(): Promise { + const title = await this.findElement(ViewSection.locators.ViewSection.title); + return await title.getAttribute(ViewSection.locators.ViewSection.titleText); + } + + /** + * Expand the section if collapsed + * @returns Promise resolving when the section is expanded + */ + async expand(): Promise { + if (await this.isHeaderHidden()) { + return; + } + if (!(await this.isExpanded())) { + const panel = await this.findElement(ViewSection.locators.ViewSection.header); + await panel.click(); + await this.getDriver().wait(waitForAttributeValue(panel, ViewSection.locators.ViewSection.headerExpanded, 'true'), 1000); + } + } + + /** + * Collapse the section if expanded + * @returns Promise resolving when the section is collapsed + */ + async collapse(): Promise { + if (await this.isHeaderHidden()) { + return; + } + if (await this.isExpanded()) { + const panel = await this.findElement(ViewSection.locators.ViewSection.header); + await panel.click(); + await this.getDriver().wait(waitForAttributeValue(panel, ViewSection.locators.ViewSection.headerExpanded, 'false'), 1000); + } + } + + /** + * Finds whether the section is expanded + * @returns Promise resolving to true/false + */ + async isExpanded(): Promise { + const header = await this.findElement(ViewSection.locators.ViewSection.header); + const expanded = await header.getAttribute(ViewSection.locators.ViewSection.headerExpanded); + return expanded === 'true'; + } + + /** + * Finds [Welcome Content](https://code.visualstudio.com/api/extension-guides/tree-view#welcome-content) + * present in this ViewSection and returns it. If none is found, then `undefined` is returned + * + */ + public async findWelcomeContent(): Promise { + try { + const res = await this.findElement(ViewSection.locators.ViewSection.welcomeContent); + if (!(await res.isDisplayed())) { + return undefined; + } + return new WelcomeContentSection(res, this); + } catch (_err) { + return undefined; + } + } + + /** + * Retrieve all items currently visible in the view section. + * Note that any item currently beyond the visible list, i.e. not scrolled to, will not be retrieved. + * @returns Promise resolving to array of ViewItem objects + */ + abstract getVisibleItems(): Promise; + + /** + * Find an item in this view section by label. Does not perform recursive search through the whole tree. + * Does however scroll through all the expanded content. Will find items beyond the current scroll range. + * @param label Label of the item to search for. + * @param maxLevel Limit how deep the algorithm should look into any expanded items, default unlimited (0) + * @returns Promise resolving to ViewItem object is such item exists, undefined otherwise + */ + abstract findItem(label: string, maxLevel?: number): Promise; + + /** + * Open an item with a given path represented by a sequence of labels + * + * e.g to open 'file' inside 'folder', call + * openItem('folder', 'file') + * + * The first item is only searched for directly within the root element (depth 1). + * The label sequence is handled in order. If a leaf item (a file for example) is found in the middle + * of the sequence, the rest is ignored. + * + * If the item structure is flat, use the item's title to search by. + * + * @param path Sequence of labels that make up the path to a given item. + * @returns Promise resolving to array of ViewItem objects representing the last item's children. + * If the last item is a leaf, empty array is returned. + */ + abstract openItem(...path: string[]): Promise; + + /** + * Retrieve the action buttons on the section's header + * @returns Promise resolving to array of ViewPanelAction objects + */ + async getActions(): Promise { + const actions: ViewPanelAction[] = []; + + if (!(await this.isHeaderHidden())) { + const header = await this.findElement(ViewSection.locators.ViewSection.header); + const act = await header.findElement(ViewSection.locators.ViewSection.actions); + const elements = await act.findElements(ViewSection.locators.ViewSection.button); + + for (const element of elements) { + actions.push(await new ViewPanelAction(element, this).wait()); + } + } + return actions; + } + + /** + * Retrieve an action button on the sections's header by its label + * @param label label/title of the button + * @returns ViewPanelAction object if found, undefined otherwise + */ + async getAction(label: string): Promise { + const actions = await this.getActions(); + for (const action of actions) { + if ((await action.getLabel()) === label) { + return action; + } + } + } + + /** + * Click on the More Actions... item if it exists + * + * @returns ContextMenu page object if the action succeeds, undefined otherwise + */ + async moreActions(): Promise { + const more = await this.getAction('More Actions...'); + if (!more) { + return undefined; + } + const section = this; + const btn = new (class extends ElementWithContexMenu { + async openContextMenu() { + await this.click(); + const shadowRootHost = await section.findElements(By.className('shadow-root-host')); + if (shadowRootHost.length > 0) { + let shadowRoot; + const webdriverCapabilities = await (this.getDriver() as ChromiumWebDriver).getCapabilities(); + const chromiumVersion = webdriverCapabilities.getBrowserVersion(); + if (chromiumVersion && parseInt(chromiumVersion.split('.')[0]) >= 96) { + shadowRoot = await shadowRootHost[0].getShadowRoot(); + return new ContextMenu(await shadowRoot.findElement(By.className('monaco-menu-container'))).wait(); + } else { + shadowRoot = (await this.getDriver().executeScript('return arguments[0].shadowRoot', shadowRootHost[0])) as WebElement; + return new ContextMenu(shadowRoot).wait(); + } + } + return await super.openContextMenu(); + } + })(more, this); + return await btn.openContextMenu(); + } + + private async isHeaderHidden(): Promise { + const header = await this.findElement(ViewSection.locators.ViewSection.header); + return (await header.getAttribute('class')).indexOf('hidden') > -1; + } } /** * Action button on the header of a view section */ export class ViewPanelAction extends AbstractElement { - constructor(element: WebElement, viewPart: ViewSection) { - super(element, viewPart); - } - - /** - * Get label of the action button - */ - async getLabel(): Promise { - return await this.getAttribute(ViewSection.locators.ViewSection.buttonLabel); - } - - async wait(timeout: number = 1000): Promise { - await this.getDriver().wait(until.elementIsEnabled(this), timeout); - return this; - } -} \ No newline at end of file + constructor(element: WebElement, viewPart: ViewSection) { + super(element, viewPart); + } + + /** + * Get label of the action button + */ + async getLabel(): Promise { + return await this.getAttribute(ViewSection.locators.ViewSection.buttonLabel); + } + + async wait(timeout: number = 1000): Promise { + await this.getDriver().wait(until.elementIsEnabled(this), timeout); + return this; + } +} diff --git a/packages/page-objects/src/components/sidebar/ViewTitlePart.ts b/packages/page-objects/src/components/sidebar/ViewTitlePart.ts index 1e5551649..1196bc010 100644 --- a/packages/page-objects/src/components/sidebar/ViewTitlePart.ts +++ b/packages/page-objects/src/components/sidebar/ViewTitlePart.ts @@ -15,63 +15,62 @@ * limitations under the License. */ -import { ElementWithContexMenu } from "../ElementWithContextMenu"; -import { AbstractElement } from "../AbstractElement"; -import { By, SideBarView } from "../.."; +import { ElementWithContexMenu } from '../ElementWithContextMenu'; +import { AbstractElement } from '../AbstractElement'; +import { By, SideBarView } from '../..'; /** * Page object representing the top (title) part of a side bar view */ export class ViewTitlePart extends ElementWithContexMenu { - constructor(view: SideBarView = new SideBarView()) { - super(ViewTitlePart.locators.ViewTitlePart.constructor, view); - } + constructor(view: SideBarView = new SideBarView()) { + super(ViewTitlePart.locators.ViewTitlePart.constructor, view); + } - /** - * Returns the displayed title of the view - * @returns Promise resolving to displayed title - */ - async getTitle(): Promise { - return await this.findElement(ViewTitlePart.locators.ViewTitlePart.title).getText(); - } + /** + * Returns the displayed title of the view + * @returns Promise resolving to displayed title + */ + async getTitle(): Promise { + return await this.findElement(ViewTitlePart.locators.ViewTitlePart.title).getText(); + } - /** - * Finds action buttons inside the view title part - * @returns Promise resolving to array of TitleActionButton objects - */ - async getActions(): Promise { - const actions: TitleActionButton[] = []; - const elements = await this.findElements(ViewTitlePart.locators.ViewTitlePart.action); - for (const element of elements) { - const title = await element.getAttribute(ViewTitlePart.locators.ViewTitlePart.actionLabel); - actions.push(await new TitleActionButton(TitleActionButton.locators.ViewTitlePart.actionConstructor(title), this).wait()); - } - return actions; - } + /** + * Finds action buttons inside the view title part + * @returns Promise resolving to array of TitleActionButton objects + */ + async getActions(): Promise { + const actions: TitleActionButton[] = []; + const elements = await this.findElements(ViewTitlePart.locators.ViewTitlePart.action); + for (const element of elements) { + const title = await element.getAttribute(ViewTitlePart.locators.ViewTitlePart.actionLabel); + actions.push(await new TitleActionButton(TitleActionButton.locators.ViewTitlePart.actionConstructor(title), this).wait()); + } + return actions; + } - /** - * Finds an action button by title - * @param title title of the button to search for - * @returns Promise resolving to TitleActionButton object - */ - async getAction(title: string): Promise { - return new TitleActionButton(TitleActionButton.locators.ViewTitlePart.actionConstructor(title), this); - } + /** + * Finds an action button by title + * @param title title of the button to search for + * @returns Promise resolving to TitleActionButton object + */ + async getAction(title: string): Promise { + return new TitleActionButton(TitleActionButton.locators.ViewTitlePart.actionConstructor(title), this); + } } /** * Page object representing a button inside the view title part */ export class TitleActionButton extends AbstractElement { + constructor(actionConstructor: By, viewTitle: ViewTitlePart) { + super(actionConstructor, viewTitle); + } - constructor(actionConstructor: By, viewTitle: ViewTitlePart) { - super(actionConstructor, viewTitle); - } - - /** - * Get title of the button - */ - async getTitle(): Promise { - return await this.getAttribute(TitleActionButton.locators.ViewTitlePart.actionLabel); - } + /** + * Get title of the button + */ + async getTitle(): Promise { + return await this.getAttribute(TitleActionButton.locators.ViewTitlePart.actionLabel); + } } diff --git a/packages/page-objects/src/components/sidebar/WelcomeContent.ts b/packages/page-objects/src/components/sidebar/WelcomeContent.ts index 771604ab0..9eb8d9a4f 100644 --- a/packages/page-objects/src/components/sidebar/WelcomeContent.ts +++ b/packages/page-objects/src/components/sidebar/WelcomeContent.ts @@ -15,9 +15,9 @@ * limitations under the License. */ -import { WebElement } from "selenium-webdriver"; -import { AbstractElement } from "../AbstractElement"; -import { ViewSection } from "../.."; +import { WebElement } from 'selenium-webdriver'; +import { AbstractElement } from '../AbstractElement'; +import { ViewSection } from '../..'; /** * A button that appears in the welcome content and can be clicked to execute a command. @@ -25,18 +25,18 @@ import { ViewSection } from "../.."; * To execute the command bound to this button simply run: `await button.click();`. */ export class WelcomeContentButton extends AbstractElement { - /** - * @param panel The panel containing the button in the welcome section - * @param welcomeSection The enclosing welcome section - */ - constructor(panel: WebElement, welcomeSection: WelcomeContentSection) { - super(panel, welcomeSection); - } + /** + * @param panel The panel containing the button in the welcome section + * @param welcomeSection The enclosing welcome section + */ + constructor(panel: WebElement, welcomeSection: WelcomeContentSection) { + super(panel, welcomeSection); + } - /** Return the title displayed on this button */ - public async getTitle(): Promise { - return await this.getText(); - } + /** Return the title displayed on this button */ + public async getTitle(): Promise { + return await this.getText(); + } } /** @@ -52,43 +52,41 @@ export class WelcomeContentButton extends AbstractElement { * now must use typechecks to find out what you got). */ export class WelcomeContentSection extends AbstractElement { - /** - * @param panel The panel containing the welcome content. - * @param parent The webelement in which the welcome content is embedded. - */ - constructor(panel: WebElement, parent: ViewSection) { - super(panel, parent); - } + /** + * @param panel The panel containing the welcome content. + * @param parent The webelement in which the welcome content is embedded. + */ + constructor(panel: WebElement, parent: ViewSection) { + super(panel, parent); + } - /** - * Combination of [[getButtons]] and [[getTextSections]]: returns all entries in the welcome view in the order that they appear. - */ - public async getContents(): Promise<(WelcomeContentButton|string)[]> { - const elements = await this.findElements(WelcomeContentSection.locators.WelcomeContent.buttonOrText); - return Promise.all(elements.map(async (e) => { - const tagName = await e.getTagName(); - if (tagName === "p") { - return await e.getText(); - } else { - return new WelcomeContentButton(e, this); - } - })); - } + /** + * Combination of [[getButtons]] and [[getTextSections]]: returns all entries in the welcome view in the order that they appear. + */ + public async getContents(): Promise<(WelcomeContentButton | string)[]> { + const elements = await this.findElements(WelcomeContentSection.locators.WelcomeContent.buttonOrText); + return Promise.all( + elements.map(async (e) => { + const tagName = await e.getTagName(); + if (tagName === 'p') { + return await e.getText(); + } else { + return new WelcomeContentButton(e, this); + } + }), + ); + } - /** Finds all buttons in the welcome content */ - public async getButtons(): Promise { - return ( - await this.findElements(WelcomeContentSection.locators.WelcomeContent.button) - ).map((elem) => new WelcomeContentButton(elem, this)); - } + /** Finds all buttons in the welcome content */ + public async getButtons(): Promise { + return (await this.findElements(WelcomeContentSection.locators.WelcomeContent.button)).map((elem) => new WelcomeContentButton(elem, this)); + } - /** - * Finds all text entries in the welcome content and returns each line as an - * element in an array. - */ - public async getTextSections(): Promise { - return await Promise.all( - (await this.findElements(WelcomeContentSection.locators.WelcomeContent.text)).map(async(elem) => await elem.getText()) - ); - } + /** + * Finds all text entries in the welcome content and returns each line as an + * element in an array. + */ + public async getTextSections(): Promise { + return await Promise.all((await this.findElements(WelcomeContentSection.locators.WelcomeContent.text)).map(async (elem) => await elem.getText())); + } } diff --git a/packages/page-objects/src/components/sidebar/debug/DebugView.ts b/packages/page-objects/src/components/sidebar/debug/DebugView.ts index e4eac43a9..838f88a60 100644 --- a/packages/page-objects/src/components/sidebar/debug/DebugView.ts +++ b/packages/page-objects/src/components/sidebar/debug/DebugView.ts @@ -15,78 +15,79 @@ * limitations under the License. */ -import { SideBarView } from "../SideBarView"; -import { DebugBreakpointSection } from "../tree/debug/DebugBreakpointSection"; -import { DebugVariableSection } from "../tree/debug/DebugVariablesSection"; +import { SideBarView } from '../SideBarView'; +import { DebugBreakpointSection } from '../tree/debug/DebugBreakpointSection'; +import { DebugVariableSection } from '../tree/debug/DebugVariablesSection'; /** * Page object representing the Run/Debug view in the side bar */ export class DebugView extends SideBarView { - - /** - * Get the title of the selected launch configuration - * @returns Promise resolving to the title - * @deprecated For VS Code 1.88+ this method won't be working any more - */ - async getLaunchConfiguration(): Promise { - if(DebugView.versionInfo.version >= '1.87.0' && process.platform !== 'darwin') { - throw Error(`DEPRECATED METHOD! The 'DebugView.getLaunchConfiguration' method is broken! Read more information in 'Known Issues > Limitations in testing with VS Code 1.87+' - https://github.com/microsoft/vscode/issues/206897.`); - } - const action = await this.getTitlePart().findElement(DebugView.locators.DebugView.launchCombo); - const combo = await action.findElement(DebugView.locators.DebugView.launchSelect); - return await combo.getAttribute('title'); - } + /** + * Get the title of the selected launch configuration + * @returns Promise resolving to the title + * @deprecated For VS Code 1.88+ this method won't be working any more + */ + async getLaunchConfiguration(): Promise { + if (DebugView.versionInfo.version >= '1.87.0' && process.platform !== 'darwin') { + throw Error( + `DEPRECATED METHOD! The 'DebugView.getLaunchConfiguration' method is broken! Read more information in 'Known Issues > Limitations in testing with VS Code 1.87+' - https://github.com/microsoft/vscode/issues/206897.`, + ); + } + const action = await this.getTitlePart().findElement(DebugView.locators.DebugView.launchCombo); + const combo = await action.findElement(DebugView.locators.DebugView.launchSelect); + return await combo.getAttribute('title'); + } - /** - * Get titles of all available launch configurations - * @returns Promise resolving to list of titles - */ - async getLaunchConfigurations(): Promise { - const action = await this.getTitlePart().findElement(DebugView.locators.DebugView.launchCombo); - const combo = await action.findElement(DebugView.locators.DebugView.launchSelect); - const configs: string[] = []; - const options = await combo.findElements(DebugView.locators.DebugView.launchOption); + /** + * Get titles of all available launch configurations + * @returns Promise resolving to list of titles + */ + async getLaunchConfigurations(): Promise { + const action = await this.getTitlePart().findElement(DebugView.locators.DebugView.launchCombo); + const combo = await action.findElement(DebugView.locators.DebugView.launchSelect); + const configs: string[] = []; + const options = await combo.findElements(DebugView.locators.DebugView.launchOption); - for (const option of options) { - if (await option.isEnabled()) { - configs.push(await option.getAttribute('value')); - } - } + for (const option of options) { + if (await option.isEnabled()) { + configs.push(await option.getAttribute('value')); + } + } - return configs; - } + return configs; + } - async getVariablesSection(): Promise { - const content = this.getContent(); - return content.getSection(DebugVariableSection.locators.DebugVariableSection.predicate, DebugVariableSection); - } + async getVariablesSection(): Promise { + const content = this.getContent(); + return content.getSection(DebugVariableSection.locators.DebugVariableSection.predicate, DebugVariableSection); + } - /** - * Get section which holds information about breakpoints. - * @returns DebugBreakpointSection page object - */ - async getBreakpointSection(): Promise { - const content = this.getContent(); - return content.getSection(DebugBreakpointSection.locators.DebugBreakpointSection.predicate, DebugBreakpointSection); - } + /** + * Get section which holds information about breakpoints. + * @returns DebugBreakpointSection page object + */ + async getBreakpointSection(): Promise { + const content = this.getContent(); + return content.getSection(DebugBreakpointSection.locators.DebugBreakpointSection.predicate, DebugBreakpointSection); + } - /** - * Select a given launch configuration - * @param title title of the configuration to select - */ - async selectLaunchConfiguration(title: string): Promise { - const action = await this.getTitlePart().findElement(DebugView.locators.DebugView.launchCombo); - const combo = await action.findElement(DebugView.locators.DebugView.launchSelect); - const option = await combo.findElement(DebugView.locators.DebugView.optionByName(title)); - await option.click(); - } + /** + * Select a given launch configuration + * @param title title of the configuration to select + */ + async selectLaunchConfiguration(title: string): Promise { + const action = await this.getTitlePart().findElement(DebugView.locators.DebugView.launchCombo); + const combo = await action.findElement(DebugView.locators.DebugView.launchSelect); + const option = await combo.findElement(DebugView.locators.DebugView.optionByName(title)); + await option.click(); + } - /** - * Start Debugging using the current launch configuration - */ - async start(): Promise { - const action = await this.getTitlePart().findElement(DebugView.locators.DebugView.launchCombo); - await action.findElement(DebugView.locators.DebugView.startButton).click(); - } -} \ No newline at end of file + /** + * Start Debugging using the current launch configuration + */ + async start(): Promise { + const action = await this.getTitlePart().findElement(DebugView.locators.DebugView.launchCombo); + await action.findElement(DebugView.locators.DebugView.startButton).click(); + } +} diff --git a/packages/page-objects/src/components/sidebar/extensions/ExtensionsViewItem.ts b/packages/page-objects/src/components/sidebar/extensions/ExtensionsViewItem.ts index a4764b356..7e23ad9b1 100644 --- a/packages/page-objects/src/components/sidebar/extensions/ExtensionsViewItem.ts +++ b/packages/page-objects/src/components/sidebar/extensions/ExtensionsViewItem.ts @@ -15,104 +15,103 @@ * limitations under the License. */ -import { ViewItem } from "../ViewItem"; -import { until, WebElement } from "selenium-webdriver"; -import { ContextMenu } from "../../menu/ContextMenu"; -import { ExtensionsViewSection } from "./ExtensionsViewSection"; +import { ViewItem } from '../ViewItem'; +import { until, WebElement } from 'selenium-webdriver'; +import { ContextMenu } from '../../menu/ContextMenu'; +import { ExtensionsViewSection } from './ExtensionsViewSection'; /** * Page object representing an extension in the extensions view */ export class ExtensionsViewItem extends ViewItem { + constructor(extensionElement: WebElement, section: ExtensionsViewSection) { + super(extensionElement, section); + } - constructor(extensionElement: WebElement, section: ExtensionsViewSection) { - super(extensionElement, section); - } + /** + * Get title of the extension + */ + async getTitle(): Promise { + const title = await this.findElement(ExtensionsViewItem.locators.ExtensionsViewSection.itemTitle); + return await title.getText(); + } - /** - * Get title of the extension - */ - async getTitle(): Promise { - const title = await this.findElement(ExtensionsViewItem.locators.ExtensionsViewSection.itemTitle); - return await title.getText(); - } + /** + * Get version of the extension + * @returns Promise resolving to version string + */ + async getVersion(): Promise { + const version = await this.findElements(ExtensionsViewItem.locators.ExtensionsViewItem.version); + if (version.length > 0) { + return await version[0].getText(); + } + const label = await this.getAttribute('aria-label'); + const ver = label.split(',')[1].trim(); - /** - * Get version of the extension - * @returns Promise resolving to version string - */ - async getVersion(): Promise { - const version = await this.findElements(ExtensionsViewItem.locators.ExtensionsViewItem.version); - if (version.length > 0) { - return await version[0].getText(); - } - const label = await this.getAttribute('aria-label'); - const ver = label.split(',')[1].trim(); + return ver; + } - return ver; - } + /** + * Get the author of the extension + * @returns Promise resolving to displayed author + */ + async getAuthor(): Promise { + const author = await this.findElement(ExtensionsViewItem.locators.ExtensionsViewItem.author); + return await author.getText(); + } - /** - * Get the author of the extension - * @returns Promise resolving to displayed author - */ - async getAuthor(): Promise { - const author = await this.findElement(ExtensionsViewItem.locators.ExtensionsViewItem.author); - return await author.getText(); - } + /** + * Get the description of the extension + * @returns Promise resolving to description + */ + async getDescription(): Promise { + const description = await this.findElement(ExtensionsViewItem.locators.ExtensionsViewItem.description); + return await description.getText(); + } - /** - * Get the description of the extension - * @returns Promise resolving to description - */ - async getDescription(): Promise { - const description = await this.findElement(ExtensionsViewItem.locators.ExtensionsViewItem.description); - return await description.getText(); - } - - /** - * Find if the extension is installed - * @returns Promise resolving to true/false - */ - async isInstalled(): Promise { - const button = await this.findElement(ExtensionsViewItem.locators.ExtensionsViewItem.install); - if ((await button.getAttribute('class')).indexOf('disabled') > -1) { - return true; - } - return false; - } + /** + * Find if the extension is installed + * @returns Promise resolving to true/false + */ + async isInstalled(): Promise { + const button = await this.findElement(ExtensionsViewItem.locators.ExtensionsViewItem.install); + if ((await button.getAttribute('class')).indexOf('disabled') > -1) { + return true; + } + return false; + } - /** - * Open the management context menu if the extension is installed - * @returns Promise resolving to ContextMenu object - */ - async manage(): Promise { - await this.getDriver().wait(until.elementLocated(ExtensionsViewItem.locators.ExtensionsViewItem.manage), 1000); - const button = await this.enclosingItem.findElement(ExtensionsViewItem.locators.ExtensionsViewItem.manage); - if ((await button.getAttribute('class')).indexOf('disabled') > -1) { - throw new Error(`Extension '${await this.getTitle()}' is not installed`); - } - return await this.openContextMenu(); - } + /** + * Open the management context menu if the extension is installed + * @returns Promise resolving to ContextMenu object + */ + async manage(): Promise { + await this.getDriver().wait(until.elementLocated(ExtensionsViewItem.locators.ExtensionsViewItem.manage), 1000); + const button = await this.enclosingItem.findElement(ExtensionsViewItem.locators.ExtensionsViewItem.manage); + if ((await button.getAttribute('class')).indexOf('disabled') > -1) { + throw new Error(`Extension '${await this.getTitle()}' is not installed`); + } + return await this.openContextMenu(); + } - /** - * Install the extension if not installed already. - * - * Will wait for the extension to finish installing. To skip the wait, set timeout to 0. - * - * @param timeout timeout to wait for the installation in milliseconds, default unlimited, set to 0 to skip waiting - * @returns Promise resolving when the installation finishes or is skipped - */ - async install(timeout: number = 300000): Promise { - if (await this.isInstalled()) { - return; - } - const button = await this.findElement(ExtensionsViewItem.locators.ExtensionsViewItem.install); - const manage = await this.findElement(ExtensionsViewItem.locators.ExtensionsViewItem.manage); - await button.click(); + /** + * Install the extension if not installed already. + * + * Will wait for the extension to finish installing. To skip the wait, set timeout to 0. + * + * @param timeout timeout to wait for the installation in milliseconds, default unlimited, set to 0 to skip waiting + * @returns Promise resolving when the installation finishes or is skipped + */ + async install(timeout: number = 300000): Promise { + if (await this.isInstalled()) { + return; + } + const button = await this.findElement(ExtensionsViewItem.locators.ExtensionsViewItem.install); + const manage = await this.findElement(ExtensionsViewItem.locators.ExtensionsViewItem.manage); + await button.click(); - if (timeout > 0) { - await this.getDriver().wait(until.elementIsVisible(manage), timeout); - } - } -} \ No newline at end of file + if (timeout > 0) { + await this.getDriver().wait(until.elementIsVisible(manage), timeout); + } + } +} diff --git a/packages/page-objects/src/components/sidebar/extensions/ExtensionsViewSection.ts b/packages/page-objects/src/components/sidebar/extensions/ExtensionsViewSection.ts index fb87aabf5..248b85fa9 100644 --- a/packages/page-objects/src/components/sidebar/extensions/ExtensionsViewSection.ts +++ b/packages/page-objects/src/components/sidebar/extensions/ExtensionsViewSection.ts @@ -15,128 +15,127 @@ * limitations under the License. */ -import { ViewSection } from "../ViewSection"; -import { ExtensionsViewItem } from "./ExtensionsViewItem"; -import { until, Key } from "selenium-webdriver"; -import { ViewContent } from "../ViewContent"; +import { ViewSection } from '../ViewSection'; +import { ExtensionsViewItem } from './ExtensionsViewItem'; +import { until, Key } from 'selenium-webdriver'; +import { ViewContent } from '../ViewContent'; /** * Categories of extensions to search for */ enum ExtensionCategory { - Installed = '@installed', - Enabled = '@enabled', - Disabled = '@disabled', - Outdated = '@outdated', - Recommended = '@recommended' + Installed = '@installed', + Enabled = '@enabled', + Disabled = '@disabled', + Outdated = '@outdated', + Recommended = '@recommended', } /** * View section containing extensions */ export class ExtensionsViewSection extends ViewSection { + async getVisibleItems(): Promise { + const extensionTable = await this.findElement(ExtensionsViewSection.locators.ExtensionsViewSection.items); + const extensionRows = await extensionTable.findElements(ExtensionsViewSection.locators.ExtensionsViewSection.itemRow); - async getVisibleItems(): Promise { - const extensionTable = await this.findElement(ExtensionsViewSection.locators.ExtensionsViewSection.items); - const extensionRows = await extensionTable.findElements(ExtensionsViewSection.locators.ExtensionsViewSection.itemRow); + return await Promise.all(extensionRows.map(async (row) => new ExtensionsViewItem(row, this).wait())); + } - return await Promise.all(extensionRows.map(async row => new ExtensionsViewItem(row, this).wait())); - } + /** + * Search for an extension by title. This utilizes the search bar + * in the Extensions view, which switches the perspective to the + * section representing the chosen category and temporarily hides all other sections. + * If you wish to continue working with the initial view section + * (i.e. Enabled), use the clearSearch method to reset it back to default + * + * @param title title to search for in '@category name' format, + * e.g '@installed extension'. If no @category is present, marketplace will be searched + * + * @returns Promise resolving to ExtensionsViewItem if such item exists, undefined otherwise + */ + async findItem(title: string): Promise { + await this.clearSearch(); + const progress = await this.enclosingItem.findElement(ExtensionsViewSection.locators.ViewContent.progress); + const searchField = await this.enclosingItem.findElement(ExtensionsViewSection.locators.ExtensionsViewSection.searchBox); + await searchField.sendKeys(title); + try { + await this.getDriver().wait(until.elementIsVisible(progress), 1000); + } catch (err) { + if ((err as Error).name !== 'TimeoutError') { + throw err; + } + } + await this.getDriver().wait(until.elementIsNotVisible(progress)); - /** - * Search for an extension by title. This utilizes the search bar - * in the Extensions view, which switches the perspective to the - * section representing the chosen category and temporarily hides all other sections. - * If you wish to continue working with the initial view section - * (i.e. Enabled), use the clearSearch method to reset it back to default - * - * @param title title to search for in '@category name' format, - * e.g '@installed extension'. If no @category is present, marketplace will be searched - * - * @returns Promise resolving to ExtensionsViewItem if such item exists, undefined otherwise - */ - async findItem(title: string): Promise { - await this.clearSearch(); - const progress = await this.enclosingItem.findElement(ExtensionsViewSection.locators.ViewContent.progress); - const searchField = await this.enclosingItem.findElement(ExtensionsViewSection.locators.ExtensionsViewSection.searchBox); - await searchField.sendKeys(title); - try { - await this.getDriver().wait(until.elementIsVisible(progress), 1000); - } catch (err) { - if ((err as Error).name !== "TimeoutError"){ - throw err; - } - } - await this.getDriver().wait(until.elementIsNotVisible(progress)); + const parent = this.enclosingItem as ViewContent; + const sectionTitle = this.getSectionForCategory(title); - const parent = this.enclosingItem as ViewContent; - const sectionTitle = this.getSectionForCategory(title); + const section = (await parent.getSection(sectionTitle)) as ExtensionsViewSection; - const section = await parent.getSection(sectionTitle) as ExtensionsViewSection; - - const titleParts = title.split(' '); - if (titleParts[0].startsWith('@')) { - title = titleParts.slice(1).join(' '); - } + const titleParts = title.split(' '); + if (titleParts[0].startsWith('@')) { + title = titleParts.slice(1).join(' '); + } - const extensions = await section.getVisibleItems(); + const extensions = await section.getVisibleItems(); - for (const extension of extensions) { - if (await extension.getTitle() === title) { - return extension; - } - } + for (const extension of extensions) { + if ((await extension.getTitle()) === title) { + return extension; + } + } - return undefined; - } + return undefined; + } - /** - * Clears the search bar on top of the view - * @returns Promise resolving when the search box is cleared - */ - async clearSearch(): Promise { - const progress = await this.enclosingItem.findElement(ExtensionsViewSection.locators.ViewContent.progress); - const searchField = await this.enclosingItem.findElement(ExtensionsViewSection.locators.ExtensionsViewSection.searchBox); - const textField = await this.enclosingItem.findElement(ExtensionsViewSection.locators.ExtensionsViewSection.textContainer); + /** + * Clears the search bar on top of the view + * @returns Promise resolving when the search box is cleared + */ + async clearSearch(): Promise { + const progress = await this.enclosingItem.findElement(ExtensionsViewSection.locators.ViewContent.progress); + const searchField = await this.enclosingItem.findElement(ExtensionsViewSection.locators.ExtensionsViewSection.searchBox); + const textField = await this.enclosingItem.findElement(ExtensionsViewSection.locators.ExtensionsViewSection.textContainer); - try { - await textField.findElement(ExtensionsViewSection.locators.ExtensionsViewSection.textField); - await searchField.sendKeys(Key.chord(ExtensionsViewItem.ctlKey, 'a'), Key.BACK_SPACE); - await this.getDriver().wait(until.elementIsVisible(progress)); - await this.getDriver().wait(until.elementIsNotVisible(progress)); - } catch (err) { - // do nothing, the text field is empty - } - } + try { + await textField.findElement(ExtensionsViewSection.locators.ExtensionsViewSection.textField); + await searchField.sendKeys(Key.chord(ExtensionsViewItem.ctlKey, 'a'), Key.BACK_SPACE); + await this.getDriver().wait(until.elementIsVisible(progress)); + await this.getDriver().wait(until.elementIsNotVisible(progress)); + } catch (err) { + // do nothing, the text field is empty + } + } - /** - * Find and open an extension item - * @param title title of the extension - * @returns Promise resolving when the item is clicked - */ - async openItem(title: string): Promise { - const item = await this.findItem(title); - if (item) { - await item.click(); - } - return []; - } + /** + * Find and open an extension item + * @param title title of the extension + * @returns Promise resolving when the item is clicked + */ + async openItem(title: string): Promise { + const item = await this.findItem(title); + if (item) { + await item.click(); + } + return []; + } - private getSectionForCategory(title: string): string { - const category = title.split(' ')[0].toLowerCase(); - switch(category) { - case ExtensionCategory.Disabled: - return 'Disabled'; - case ExtensionCategory.Enabled: - return 'Enabled'; - case ExtensionCategory.Installed: - return 'Installed'; - case ExtensionCategory.Outdated: - return 'Outdated'; - case ExtensionCategory.Recommended: - return 'Other Recommendations'; - default: - return 'Marketplace'; - } - } -} \ No newline at end of file + private getSectionForCategory(title: string): string { + const category = title.split(' ')[0].toLowerCase(); + switch (category) { + case ExtensionCategory.Disabled: + return 'Disabled'; + case ExtensionCategory.Enabled: + return 'Enabled'; + case ExtensionCategory.Installed: + return 'Installed'; + case ExtensionCategory.Outdated: + return 'Outdated'; + case ExtensionCategory.Recommended: + return 'Other Recommendations'; + default: + return 'Marketplace'; + } + } +} diff --git a/packages/page-objects/src/components/sidebar/scm/NewScmView.ts b/packages/page-objects/src/components/sidebar/scm/NewScmView.ts index 4306b74e9..682483211 100644 --- a/packages/page-objects/src/components/sidebar/scm/NewScmView.ts +++ b/packages/page-objects/src/components/sidebar/scm/NewScmView.ts @@ -15,177 +15,174 @@ * limitations under the License. */ -import { ScmView, ScmProvider, MoreAction, ScmChange } from "./ScmView"; -import { WebElement, Key } from "selenium-webdriver"; -import { ContextMenu } from "../../menu/ContextMenu"; -import { ElementWithContexMenu } from "../../ElementWithContextMenu"; -import { TitleActionButton } from "../ViewTitlePart"; +import { ScmView, ScmProvider, MoreAction, ScmChange } from './ScmView'; +import { WebElement, Key } from 'selenium-webdriver'; +import { ContextMenu } from '../../menu/ContextMenu'; +import { ElementWithContexMenu } from '../../ElementWithContextMenu'; +import { TitleActionButton } from '../ViewTitlePart'; /** * New SCM view for code 1.47 onwards */ export class NewScmView extends ScmView { - - async getProviders(): Promise { - const inputs = await this.findElements(NewScmView.locators.ScmView.inputField); - if (inputs.length < 1) { - return []; - } - - const providers = await this.findElements(NewScmView.locators.ScmView.multiScmProvider); - if (inputs.length === 1 && providers.length < 1) { - const element = await this.findElement(NewScmView.locators.ScmView.singleScmProvider); - return [await new SingleScmProvider(element, this).wait()]; - } - - const elements = await this.findElements(NewScmView.locators.ScmView.multiProviderItem); - return await Promise.all(elements.map(async element => new MultiScmProvider(element, this).wait())); - } + async getProviders(): Promise { + const inputs = await this.findElements(NewScmView.locators.ScmView.inputField); + if (inputs.length < 1) { + return []; + } + + const providers = await this.findElements(NewScmView.locators.ScmView.multiScmProvider); + if (inputs.length === 1 && providers.length < 1) { + const element = await this.findElement(NewScmView.locators.ScmView.singleScmProvider); + return [await new SingleScmProvider(element, this).wait()]; + } + + const elements = await this.findElements(NewScmView.locators.ScmView.multiProviderItem); + return await Promise.all(elements.map(async (element) => new MultiScmProvider(element, this).wait())); + } } /** * Implementation for a single SCM provider */ export class SingleScmProvider extends ScmProvider { - - /** - * There is no title available for a single provider - */ - async getTitle(): Promise { - return ''; - } - - /** - * No title available for single provider - */ - async getType(): Promise { - return ''; - } - - async takeAction(title: string): Promise { - const view = this.enclosingItem as NewScmView; - const titlePart = view.getTitlePart(); - const elements = await titlePart.findElements(ScmView.locators.ScmView.action); - const buttons: TitleActionButton[] = []; - for (const element of elements) { - const title = await element.getAttribute(ScmView.locators.ScmView.actionLabel); - buttons.push(await new TitleActionButton(ScmView.locators.ScmView.actionConstructor(title), titlePart).wait()); - } - const names = await Promise.all(buttons.map(async button => button.getTitle())); - const index = names.findIndex(name => name === title); - if (index > -1) { - await buttons[index].click(); - return true; - } - return false; - } - - async openMoreActions(): Promise { - const view = this.enclosingItem as NewScmView; - return await new MoreAction(view).openContextMenu(); - } - - async getChanges(staged: boolean = false): Promise { - const count = await this.getChangeCount(staged); - const elements: WebElement[] = []; - - if (count > 0) { - const locator = staged ? ScmProvider.locators.ScmView.stagedChanges : ScmProvider.locators.ScmView.changes; - const header = await this.findElement(locator); - const startIndex = +await header.getAttribute('data-index'); - const depth = +await header.getAttribute('aria-level') + 1; - - const items = await this.findElements(NewScmView.locators.ScmView.itemLevel(depth)); - for (const item of items) { - const index = +await item.getAttribute('data-index'); - if (index > startIndex && index <= startIndex + count) { - elements.push(item); - } - } - } - return Promise.all(elements.map(async element => new ScmChange(element, this).wait())); - } + /** + * There is no title available for a single provider + */ + async getTitle(): Promise { + return ''; + } + + /** + * No title available for single provider + */ + async getType(): Promise { + return ''; + } + + async takeAction(title: string): Promise { + const view = this.enclosingItem as NewScmView; + const titlePart = view.getTitlePart(); + const elements = await titlePart.findElements(ScmView.locators.ScmView.action); + const buttons: TitleActionButton[] = []; + for (const element of elements) { + const title = await element.getAttribute(ScmView.locators.ScmView.actionLabel); + buttons.push(await new TitleActionButton(ScmView.locators.ScmView.actionConstructor(title), titlePart).wait()); + } + const names = await Promise.all(buttons.map(async (button) => button.getTitle())); + const index = names.findIndex((name) => name === title); + if (index > -1) { + await buttons[index].click(); + return true; + } + return false; + } + + async openMoreActions(): Promise { + const view = this.enclosingItem as NewScmView; + return await new MoreAction(view).openContextMenu(); + } + + async getChanges(staged: boolean = false): Promise { + const count = await this.getChangeCount(staged); + const elements: WebElement[] = []; + + if (count > 0) { + const locator = staged ? ScmProvider.locators.ScmView.stagedChanges : ScmProvider.locators.ScmView.changes; + const header = await this.findElement(locator); + const startIndex = +(await header.getAttribute('data-index')); + const depth = +(await header.getAttribute('aria-level')) + 1; + + const items = await this.findElements(NewScmView.locators.ScmView.itemLevel(depth)); + for (const item of items) { + const index = +(await item.getAttribute('data-index')); + if (index > startIndex && index <= startIndex + count) { + elements.push(item); + } + } + } + return Promise.all(elements.map(async (element) => new ScmChange(element, this).wait())); + } } /** * Implementation of an SCM provider when multiple providers are available */ export class MultiScmProvider extends ScmProvider { - - async takeAction(title: string): Promise { - const actions = await this.findElements(ScmProvider.locators.ScmView.action); - const names = await Promise.all(actions.map(async action => await action.getAttribute('title'))); - const index = names.findIndex(item => item === title); - - if (index > -1) { - await actions[index].click(); - return true; - } - return false; - } - - async openMoreActions(): Promise { - return await new MultiMoreAction(this).openContextMenu(); - } - - async commitChanges(message: string): Promise { - const index = +await this.getAttribute('data-index') + 1; - const input = await this.enclosingItem.findElement(NewScmView.locators.ScmView.itemIndex(index)); - await input.clear(); - await input.sendKeys(message); - await input.sendKeys(Key.chord(ScmProvider.ctlKey, Key.ENTER)); - } - - async getChanges(staged: boolean = false): Promise { - const count = await this.getChangeCount(staged); - const elements: WebElement[] = []; - - if (count > 0) { - const index = +await this.getAttribute('data-index'); - const locator = staged ? ScmProvider.locators.ScmView.stagedChanges : ScmProvider.locators.ScmView.changes; - const headers = await this.enclosingItem.findElements(locator); - let header!: WebElement; - - for (const item of headers) { - if (+await item.getAttribute('data-index') > index) { - header = item; - } - } - if (!header) { - return []; - } - - const startIndex = +await header.getAttribute('data-index'); - const depth = +await header.getAttribute('aria-level') + 1; - - const items = await this.enclosingItem.findElements(NewScmView.locators.ScmView.itemLevel(depth)); - for (const item of items) { - const index = +await item.getAttribute('data-index'); - if (index > startIndex && index <= startIndex + count) { - elements.push(item); - } - } - } - return await Promise.all(elements.map(async element => new ScmChange(element, this).wait())); - } - - async getChangeCount(staged: boolean = false): Promise { - const locator = staged ? ScmProvider.locators.ScmView.stagedChanges : ScmProvider.locators.ScmView.changes; - const rows = await this.enclosingItem.findElements(locator); - const index = +await this.getAttribute('data-index'); - - for (const row of rows) { - if (+await row.getAttribute('data-index') > index) { - const count = await rows[0].findElement(ScmChange.locators.ScmView.changeCount); - return +await count.getText(); - } - } - return 0; - } + async takeAction(title: string): Promise { + const actions = await this.findElements(ScmProvider.locators.ScmView.action); + const names = await Promise.all(actions.map(async (action) => await action.getAttribute('title'))); + const index = names.findIndex((item) => item === title); + + if (index > -1) { + await actions[index].click(); + return true; + } + return false; + } + + async openMoreActions(): Promise { + return await new MultiMoreAction(this).openContextMenu(); + } + + async commitChanges(message: string): Promise { + const index = +(await this.getAttribute('data-index')) + 1; + const input = await this.enclosingItem.findElement(NewScmView.locators.ScmView.itemIndex(index)); + await input.clear(); + await input.sendKeys(message); + await input.sendKeys(Key.chord(ScmProvider.ctlKey, Key.ENTER)); + } + + async getChanges(staged: boolean = false): Promise { + const count = await this.getChangeCount(staged); + const elements: WebElement[] = []; + + if (count > 0) { + const index = +(await this.getAttribute('data-index')); + const locator = staged ? ScmProvider.locators.ScmView.stagedChanges : ScmProvider.locators.ScmView.changes; + const headers = await this.enclosingItem.findElements(locator); + let header!: WebElement; + + for (const item of headers) { + if (+(await item.getAttribute('data-index')) > index) { + header = item; + } + } + if (!header) { + return []; + } + + const startIndex = +(await header.getAttribute('data-index')); + const depth = +(await header.getAttribute('aria-level')) + 1; + + const items = await this.enclosingItem.findElements(NewScmView.locators.ScmView.itemLevel(depth)); + for (const item of items) { + const index = +(await item.getAttribute('data-index')); + if (index > startIndex && index <= startIndex + count) { + elements.push(item); + } + } + } + return await Promise.all(elements.map(async (element) => new ScmChange(element, this).wait())); + } + + async getChangeCount(staged: boolean = false): Promise { + const locator = staged ? ScmProvider.locators.ScmView.stagedChanges : ScmProvider.locators.ScmView.changes; + const rows = await this.enclosingItem.findElements(locator); + const index = +(await this.getAttribute('data-index')); + + for (const row of rows) { + if (+(await row.getAttribute('data-index')) > index) { + const count = await rows[0].findElement(ScmChange.locators.ScmView.changeCount); + return +(await count.getText()); + } + } + return 0; + } } class MultiMoreAction extends ElementWithContexMenu { - constructor(scm: ScmProvider) { - super(MoreAction.locators.ScmView.multiMore, scm); - } -} \ No newline at end of file + constructor(scm: ScmProvider) { + super(MoreAction.locators.ScmView.multiMore, scm); + } +} diff --git a/packages/page-objects/src/components/sidebar/scm/ScmView.ts b/packages/page-objects/src/components/sidebar/scm/ScmView.ts index 381bbc015..cbd2abd33 100644 --- a/packages/page-objects/src/components/sidebar/scm/ScmView.ts +++ b/packages/page-objects/src/components/sidebar/scm/ScmView.ts @@ -15,55 +15,54 @@ * limitations under the License. */ -import { SideBarView } from "../SideBarView"; -import { WebElement, Key, By, ChromiumWebDriver } from "selenium-webdriver"; -import { AbstractElement } from "../../AbstractElement"; -import { ContextMenu } from "../../.."; -import { ElementWithContexMenu } from "../../ElementWithContextMenu"; +import { SideBarView } from '../SideBarView'; +import { WebElement, Key, By, ChromiumWebDriver } from 'selenium-webdriver'; +import { AbstractElement } from '../../AbstractElement'; +import { ContextMenu } from '../../..'; +import { ElementWithContexMenu } from '../../ElementWithContextMenu'; /** * Page object representing the Source Control view */ export class ScmView extends SideBarView { + /** + * Get SCM provider (repository) by title + * @param title name of the repository + * @returns promise resolving to ScmProvider object + */ + async getProvider(title?: string): Promise { + const providers = await this.getProviders(); + if (!title || providers.length === 1) { + return providers[0]; + } + const names = await Promise.all(providers.map(async (item) => await item.getTitle())); + const index = names.findIndex((name) => name === title); - /** - * Get SCM provider (repository) by title - * @param title name of the repository - * @returns promise resolving to ScmProvider object - */ - async getProvider(title?: string): Promise { - const providers = await this.getProviders(); - if (!title || providers.length === 1) { - return providers[0]; - } - const names = await Promise.all(providers.map(async item => await item.getTitle())); - const index = names.findIndex(name => name === title); + return index > -1 ? providers[index] : undefined; + } - return index > -1 ? providers[index] : undefined; - } + /** + * Get all SCM providers + * @returns promise resolving to ScmProvider array + */ + async getProviders(): Promise { + const headers = await this.findElements(ScmView.locators.ScmView.providerHeader); + const sections = await Promise.all(headers.map(async (header) => await header.findElement(ScmView.locators.ScmView.providerRelative))); + return await Promise.all(sections.map(async (section) => new ScmProvider(section, this))); + } - /** - * Get all SCM providers - * @returns promise resolving to ScmProvider array - */ - async getProviders(): Promise { - const headers = await this.findElements(ScmView.locators.ScmView.providerHeader); - const sections = await Promise.all(headers.map(async header => await header.findElement(ScmView.locators.ScmView.providerRelative))); - return await Promise.all(sections.map(async section => new ScmProvider(section, this))); - } - - /** - * Initialize repository in the current folder if no SCM provider is found - * @returns true if the action was completed successfully, false if a provider already exists - */ - async initializeRepository(): Promise { - const buttons = await this.findElements(ScmView.locators.ScmView.initButton); - if (buttons.length > 0) { - await buttons[0].click(); - return true; - } - return false; - } + /** + * Initialize repository in the current folder if no SCM provider is found + * @returns true if the action was completed successfully, false if a provider already exists + */ + async initializeRepository(): Promise { + const buttons = await this.findElements(ScmView.locators.ScmView.initButton); + if (buttons.length > 0) { + await buttons[0].click(); + return true; + } + return false; + } } /** @@ -71,238 +70,237 @@ export class ScmView extends SideBarView { * Maps roughly to a view section of the source control view */ export class ScmProvider extends AbstractElement { - constructor(element: WebElement, view: ScmView) { - super(element, view); - } + constructor(element: WebElement, view: ScmView) { + super(element, view); + } - /** - * Get title of the scm provider - */ - async getTitle(): Promise { - return await this.findElement(ScmProvider.locators.ScmView.providerTitle).getAttribute('innerHTML'); - } + /** + * Get title of the scm provider + */ + async getTitle(): Promise { + return await this.findElement(ScmProvider.locators.ScmView.providerTitle).getAttribute('innerHTML'); + } - /** - * Get type of the scm provider (e.g. Git) - */ - async getType(): Promise { - return await this.findElement(ScmProvider.locators.ScmView.providerType).getAttribute('innerHTML'); - } + /** + * Get type of the scm provider (e.g. Git) + */ + async getType(): Promise { + return await this.findElement(ScmProvider.locators.ScmView.providerType).getAttribute('innerHTML'); + } - /** - * Find an action button for the SCM provider by title and click it. (e.g 'Commit') - * @param title Title of the action button to click - * @returns true if the given action could be performed, false if the button doesn't exist - */ - async takeAction(title: string): Promise { - const header = await this.findElement(ScmProvider.locators.ScmView.providerHeader); - let actions: WebElement[] = []; - if ((await header.getAttribute('class')).indexOf('hidden') > -1) { - const view = this.enclosingItem as ScmView; - actions = await view.getTitlePart().getActions(); - } else { - await this.getDriver().actions().move({origin: this}).perform(); - actions = await header.findElements(ScmProvider.locators.ScmView.action); - } - const names = await Promise.all(actions.map(async action => await action.getAttribute('title'))); - const index = names.findIndex(item => item === title); + /** + * Find an action button for the SCM provider by title and click it. (e.g 'Commit') + * @param title Title of the action button to click + * @returns true if the given action could be performed, false if the button doesn't exist + */ + async takeAction(title: string): Promise { + const header = await this.findElement(ScmProvider.locators.ScmView.providerHeader); + let actions: WebElement[] = []; + if ((await header.getAttribute('class')).indexOf('hidden') > -1) { + const view = this.enclosingItem as ScmView; + actions = await view.getTitlePart().getActions(); + } else { + await this.getDriver().actions().move({ origin: this }).perform(); + actions = await header.findElements(ScmProvider.locators.ScmView.action); + } + const names = await Promise.all(actions.map(async (action) => await action.getAttribute('title'))); + const index = names.findIndex((item) => item === title); - if (index > -1) { - await actions[index].click(); - return true; - } - return false; - } + if (index > -1) { + await actions[index].click(); + return true; + } + return false; + } - /** - * Open a context menu using the 'More Actions...' button - * @returns Promise resolving to a ContextMenu object - */ - async openMoreActions(): Promise { - const header = await this.findElement(ScmProvider.locators.ScmView.providerHeader); - if ((await header.getAttribute('class')).indexOf('hidden') > -1) { - return await new MoreAction(this.enclosingItem as ScmView).openContextMenu(); - } else { - await this.getDriver().actions().move({origin: this}).perform(); - return await new MoreAction(this).openContextMenu(); - } - } + /** + * Open a context menu using the 'More Actions...' button + * @returns Promise resolving to a ContextMenu object + */ + async openMoreActions(): Promise { + const header = await this.findElement(ScmProvider.locators.ScmView.providerHeader); + if ((await header.getAttribute('class')).indexOf('hidden') > -1) { + return await new MoreAction(this.enclosingItem as ScmView).openContextMenu(); + } else { + await this.getDriver().actions().move({ origin: this }).perform(); + return await new MoreAction(this).openContextMenu(); + } + } - /** - * Fill in the message field and send ctrl/cmd + enter to commit the changes - * @param message the commit message to use - * @returns promise resolving once the keypresses are sent - */ - async commitChanges(message: string): Promise { - const input = await this.findElement(ScmProvider.locators.ScmView.inputField); - await input.clear(); - await input.sendKeys(message); - await input.sendKeys(Key.chord(ScmProvider.ctlKey, Key.ENTER)); - } + /** + * Fill in the message field and send ctrl/cmd + enter to commit the changes + * @param message the commit message to use + * @returns promise resolving once the keypresses are sent + */ + async commitChanges(message: string): Promise { + const input = await this.findElement(ScmProvider.locators.ScmView.inputField); + await input.clear(); + await input.sendKeys(message); + await input.sendKeys(Key.chord(ScmProvider.ctlKey, Key.ENTER)); + } - /** - * Get page objects for all tree items representing individual changes - * @param staged when true, finds staged changes; otherwise finds unstaged changes - * @returns promise resolving to ScmChange object array - */ - async getChanges(staged: boolean = false): Promise { - const changes = await this.getChangeCount(staged); - const label = staged ? 'STAGED CHANGES' : 'CHANGES'; + /** + * Get page objects for all tree items representing individual changes + * @param staged when true, finds staged changes; otherwise finds unstaged changes + * @returns promise resolving to ScmChange object array + */ + async getChanges(staged: boolean = false): Promise { + const changes = await this.getChangeCount(staged); + const label = staged ? 'STAGED CHANGES' : 'CHANGES'; - let elements: WebElement[] = []; - if (changes > 0) { - let i = -1; - elements = await this.findElements(ScmProvider.locators.ScmView.changeItem); - for (const [index, item] of elements.entries()) { - const name = await item.findElement(ScmProvider.locators.ScmView.changeName); - if (await name.getText() === label) { - i = index + 1; - break; - } - } - if (i < 0) { - return []; - } - elements = elements.slice(i, i + changes); - } - return await Promise.all(elements.map(async element => new ScmChange(element, this).wait())); - } + let elements: WebElement[] = []; + if (changes > 0) { + let i = -1; + elements = await this.findElements(ScmProvider.locators.ScmView.changeItem); + for (const [index, item] of elements.entries()) { + const name = await item.findElement(ScmProvider.locators.ScmView.changeName); + if ((await name.getText()) === label) { + i = index + 1; + break; + } + } + if (i < 0) { + return []; + } + elements = elements.slice(i, i + changes); + } + return await Promise.all(elements.map(async (element) => new ScmChange(element, this).wait())); + } - /** - * Get the number of changes for a given section - * @param staged when true, counts the staged changes, unstaged otherwise - * @returns promise resolving to number of changes in the given subsection - */ - async getChangeCount(staged: boolean = false): Promise { - const locator = staged ? ScmProvider.locators.ScmView.stagedChanges : ScmProvider.locators.ScmView.changes; - const rows = await this.findElements(locator); + /** + * Get the number of changes for a given section + * @param staged when true, counts the staged changes, unstaged otherwise + * @returns promise resolving to number of changes in the given subsection + */ + async getChangeCount(staged: boolean = false): Promise { + const locator = staged ? ScmProvider.locators.ScmView.stagedChanges : ScmProvider.locators.ScmView.changes; + const rows = await this.findElements(locator); - if (rows.length < 1) { - return 0; - } - const count = await rows[0].findElement(ScmChange.locators.ScmView.changeCount); - return +await count.getText(); - } + if (rows.length < 1) { + return 0; + } + const count = await rows[0].findElement(ScmChange.locators.ScmView.changeCount); + return +(await count.getText()); + } } /** * Page object representing a SCM change tree item */ export class ScmChange extends ElementWithContexMenu { + constructor(row: WebElement, provider: ScmProvider) { + super(row, provider); + } - constructor(row: WebElement, provider: ScmProvider) { - super(row, provider); - } + /** + * Get label as a string + */ + async getLabel(): Promise { + const label = await this.findElement(ScmChange.locators.ScmView.changeLabel); + return await label.getText(); + } - /** - * Get label as a string - */ - async getLabel(): Promise { - const label = await this.findElement(ScmChange.locators.ScmView.changeLabel); - return await label.getText(); - } + /** + * Get description as a string + */ + async getDescription(): Promise { + const desc = await this.findElements(ScmChange.locators.ScmView.changeDesc); + if (desc.length < 1) { + return ''; + } + return await desc[0].getText(); + } - /** - * Get description as a string - */ - async getDescription(): Promise { - const desc = await this.findElements(ScmChange.locators.ScmView.changeDesc); - if (desc.length < 1) { - return ''; - } - return await desc[0].getText(); - } + /** + * Get the status string (e.g. 'Modified') + */ + async getStatus(): Promise { + const res = await this.findElement(ScmChange.locators.ScmView.resource); + const status = await res.getAttribute('data-tooltip'); - /** - * Get the status string (e.g. 'Modified') - */ - async getStatus(): Promise { - const res = await this.findElement(ScmChange.locators.ScmView.resource); - const status = await res.getAttribute('data-tooltip'); - - if (status && status.length > 0) { - return status; - } - return 'folder'; - } + if (status && status.length > 0) { + return status; + } + return 'folder'; + } - /** - * Find if the item is expanded - * @returns promise resolving to true if change is expanded, to false otherwise - */ - async isExpanded(): Promise { - const twisties = await this.findElements(ScmChange.locators.ScmView.expand); - if (twisties.length < 1) { - return true; - } - return (await twisties[0].getAttribute('class')).indexOf('collapsed') < 0; - } + /** + * Find if the item is expanded + * @returns promise resolving to true if change is expanded, to false otherwise + */ + async isExpanded(): Promise { + const twisties = await this.findElements(ScmChange.locators.ScmView.expand); + if (twisties.length < 1) { + return true; + } + return (await twisties[0].getAttribute('class')).indexOf('collapsed') < 0; + } - /** - * Expand or collapse a change item if possible, only works for folders in hierarchical view mode - * @param expand true to expand the item, false to collapse - * @returns promise resolving to true if the item changed state, to false otherwise - */ - async toggleExpand(expand: boolean): Promise { - if (await this.isExpanded() !== expand) { - await this.click(); - return true; - } - return false; - } + /** + * Expand or collapse a change item if possible, only works for folders in hierarchical view mode + * @param expand true to expand the item, false to collapse + * @returns promise resolving to true if the item changed state, to false otherwise + */ + async toggleExpand(expand: boolean): Promise { + if ((await this.isExpanded()) !== expand) { + await this.click(); + return true; + } + return false; + } - /** - * Find and click an action button available to a given change tree item - * @param title title of the action button (e.g 'Stage Changes') - * @returns promise resolving to true if the action was performed successfully, - * false if the given button does not exist - */ - async takeAction(title: string): Promise { - await this.getDriver().actions().move({origin: this}).perform(); - const actions = await this.findElements(ScmChange.locators.ScmView.action); - const names = await Promise.all(actions.map(async action => await action.getAttribute(ScmChange.locators.ScmView.actionLabel))); - const index = names.findIndex(item => item === title); + /** + * Find and click an action button available to a given change tree item + * @param title title of the action button (e.g 'Stage Changes') + * @returns promise resolving to true if the action was performed successfully, + * false if the given button does not exist + */ + async takeAction(title: string): Promise { + await this.getDriver().actions().move({ origin: this }).perform(); + const actions = await this.findElements(ScmChange.locators.ScmView.action); + const names = await Promise.all(actions.map(async (action) => await action.getAttribute(ScmChange.locators.ScmView.actionLabel))); + const index = names.findIndex((item) => item === title); - if (index > -1) { - await actions[index].click(); - return true; - } - return false; - } + if (index > -1) { + await actions[index].click(); + return true; + } + return false; + } } export class MoreAction extends ElementWithContexMenu { - constructor(scm: ScmProvider | ScmView) { - super(MoreAction.locators.ScmView.more,scm); - } + constructor(scm: ScmProvider | ScmView) { + super(MoreAction.locators.ScmView.more, scm); + } - async openContextMenu(): Promise { - await this.click(); - const shadowRootHost = await this.enclosingItem.findElements(By.className('shadow-root-host')); - const actions = this.getDriver().actions(); - await actions.clear(); - await actions.sendKeys(Key.ESCAPE).perform(); - const webdriverCapabilities = await (this.getDriver() as ChromiumWebDriver).getCapabilities(); - const chromiumVersion = webdriverCapabilities.getBrowserVersion(); - if (shadowRootHost.length > 0) { - if (await this.getAttribute('aria-expanded') !== 'true') { - await this.click(); - } - let shadowRoot; - const webdriverCapabilities = await (this.getDriver() as ChromiumWebDriver).getCapabilities(); - const chromiumVersion = webdriverCapabilities.getBrowserVersion(); - if (chromiumVersion && parseInt(chromiumVersion.split('.')[0]) >= 96) { - shadowRoot = await shadowRootHost[0].getShadowRoot(); - return new ContextMenu(await shadowRoot.findElement(By.className('monaco-menu-container'))).wait(); - } else { - shadowRoot = await this.getDriver().executeScript('return arguments[0].shadowRoot', shadowRootHost[0]) as WebElement; - return new ContextMenu(shadowRoot).wait(); - } - } else if (chromiumVersion && parseInt(chromiumVersion.split('.')[0]) >= 100) { - await this.click(); - const workbench = await this.getDriver().findElement(ElementWithContexMenu.locators.Workbench.constructor); - return new ContextMenu(workbench).wait(); - } - return await super.openContextMenu(); - } -} \ No newline at end of file + async openContextMenu(): Promise { + await this.click(); + const shadowRootHost = await this.enclosingItem.findElements(By.className('shadow-root-host')); + const actions = this.getDriver().actions(); + await actions.clear(); + await actions.sendKeys(Key.ESCAPE).perform(); + const webdriverCapabilities = await (this.getDriver() as ChromiumWebDriver).getCapabilities(); + const chromiumVersion = webdriverCapabilities.getBrowserVersion(); + if (shadowRootHost.length > 0) { + if ((await this.getAttribute('aria-expanded')) !== 'true') { + await this.click(); + } + let shadowRoot; + const webdriverCapabilities = await (this.getDriver() as ChromiumWebDriver).getCapabilities(); + const chromiumVersion = webdriverCapabilities.getBrowserVersion(); + if (chromiumVersion && parseInt(chromiumVersion.split('.')[0]) >= 96) { + shadowRoot = await shadowRootHost[0].getShadowRoot(); + return new ContextMenu(await shadowRoot.findElement(By.className('monaco-menu-container'))).wait(); + } else { + shadowRoot = (await this.getDriver().executeScript('return arguments[0].shadowRoot', shadowRootHost[0])) as WebElement; + return new ContextMenu(shadowRoot).wait(); + } + } else if (chromiumVersion && parseInt(chromiumVersion.split('.')[0]) >= 100) { + await this.click(); + const workbench = await this.getDriver().findElement(ElementWithContexMenu.locators.Workbench.constructor); + return new ContextMenu(workbench).wait(); + } + return await super.openContextMenu(); + } +} diff --git a/packages/page-objects/src/components/sidebar/tree/TreeSection.ts b/packages/page-objects/src/components/sidebar/tree/TreeSection.ts index b4918e442..77a073e64 100644 --- a/packages/page-objects/src/components/sidebar/tree/TreeSection.ts +++ b/packages/page-objects/src/components/sidebar/tree/TreeSection.ts @@ -15,61 +15,61 @@ * limitations under the License. */ -import { ViewSection } from "../ViewSection"; -import { TreeItem } from "../ViewItem"; -import { error } from "selenium-webdriver"; +import { ViewSection } from '../ViewSection'; +import { TreeItem } from '../ViewItem'; +import { error } from 'selenium-webdriver'; export class TreeItemNotFoundError extends error.NoSuchElementError { - constructor(msg?: string) { - super(msg); - this.name = 'TreeItemNotFoundError'; - } + constructor(msg?: string) { + super(msg); + this.name = 'TreeItemNotFoundError'; + } } /** * Abstract representation of a view section containing a tree */ export abstract class TreeSection extends ViewSection { - async openItem(...path: string[]): Promise { - let items: TreeItem[] = []; + async openItem(...path: string[]): Promise { + let items: TreeItem[] = []; - for (let i = 0; i < path.length; i++) { - const item = await this.findItem(path[i], i + 1); - if (await item?.hasChildren() && !await item?.isExpanded()) { - await item?.expand(); - } - } + for (let i = 0; i < path.length; i++) { + const item = await this.findItem(path[i], i + 1); + if ((await item?.hasChildren()) && !(await item?.isExpanded())) { + await item?.expand(); + } + } - let currentItem = await this.findItem(path[0], 1); - for (let i = 0; i < path.length; i++) { - if (!currentItem) { - if (i === 0) { - items = await this.getVisibleItems(); - } - let names = await Promise.all(items.map(item => item.getLabel())); - names = names.sort((a, b) => a > b ? 1 : (a < b ? -1 : 0)); - const message = names.length < 1 ? `Current directory is empty.` : `Available items in current directory: [${names.toString()}]`; + let currentItem = await this.findItem(path[0], 1); + for (let i = 0; i < path.length; i++) { + if (!currentItem) { + if (i === 0) { + items = await this.getVisibleItems(); + } + let names = await Promise.all(items.map((item) => item.getLabel())); + names = names.sort((a, b) => (a > b ? 1 : a < b ? -1 : 0)); + const message = names.length < 1 ? `Current directory is empty.` : `Available items in current directory: [${names.toString()}]`; - throw new TreeItemNotFoundError(`Item '${path[i]}' not found. ${message}`); - } - items = await currentItem.getChildren(); - if (items.length < 1) { - await currentItem.select(); - return items; - } - if (i + 1 < path.length) { - currentItem = undefined; - for (const item of items) { - if (await item.getLabel() === path[i + 1]) { - currentItem = item; - break; - } - } - } - } - return items; - } + throw new TreeItemNotFoundError(`Item '${path[i]}' not found. ${message}`); + } + items = await currentItem.getChildren(); + if (items.length < 1) { + await currentItem.select(); + return items; + } + if (i + 1 < path.length) { + currentItem = undefined; + for (const item of items) { + if ((await item.getLabel()) === path[i + 1]) { + currentItem = item; + break; + } + } + } + } + return items; + } - abstract findItem(label: string, maxLevel?: number): Promise; - abstract getVisibleItems(): Promise; + abstract findItem(label: string, maxLevel?: number): Promise; + abstract getVisibleItems(): Promise; } diff --git a/packages/page-objects/src/components/sidebar/tree/custom/CustomTreeItem.ts b/packages/page-objects/src/components/sidebar/tree/custom/CustomTreeItem.ts index 5e85fc46d..7acfd7abc 100644 --- a/packages/page-objects/src/components/sidebar/tree/custom/CustomTreeItem.ts +++ b/packages/page-objects/src/components/sidebar/tree/custom/CustomTreeItem.ts @@ -15,43 +15,43 @@ * limitations under the License. */ -import { TreeItem } from "../../ViewItem"; -import { TreeSection } from "../TreeSection"; -import { WebElement } from "selenium-webdriver"; +import { TreeItem } from '../../ViewItem'; +import { TreeSection } from '../TreeSection'; +import { WebElement } from 'selenium-webdriver'; /** * View item in a custom-made content section (e.g. an extension tree view) */ export class CustomTreeItem extends TreeItem { - constructor(element: WebElement, viewPart: TreeSection) { - super(element, viewPart); - } - - async getLabel(): Promise { - return await this.findElement(CustomTreeItem.locators.CustomTreeSection.itemLabel).getText(); - } - - async getTooltip(): Promise { - return await this.getAttribute(CustomTreeItem.locators.CustomTreeItem.tooltipAttribute); - } - - async getDescription(): Promise { - return await this.findElement(CustomTreeItem.locators.CustomTreeItem.description).getText(); - } - - async isExpanded(): Promise { - const attr = await this.getAttribute(CustomTreeItem.locators.CustomTreeItem.expandedAttr); - return attr === CustomTreeItem.locators.CustomTreeItem.expandedValue; - } - - async getChildren(): Promise { - const rows = await this.getChildItems(CustomTreeItem.locators.DefaultTreeSection.itemRow); - const items = await Promise.all(rows.map(async row => new CustomTreeItem(row, this.enclosingItem as TreeSection).wait())); - return items; - } - - async isExpandable(): Promise { - const attr = await this.getAttribute(CustomTreeItem.locators.CustomTreeItem.expandedAttr); - return attr !== null; - } -} \ No newline at end of file + constructor(element: WebElement, viewPart: TreeSection) { + super(element, viewPart); + } + + async getLabel(): Promise { + return await this.findElement(CustomTreeItem.locators.CustomTreeSection.itemLabel).getText(); + } + + async getTooltip(): Promise { + return await this.getAttribute(CustomTreeItem.locators.CustomTreeItem.tooltipAttribute); + } + + async getDescription(): Promise { + return await this.findElement(CustomTreeItem.locators.CustomTreeItem.description).getText(); + } + + async isExpanded(): Promise { + const attr = await this.getAttribute(CustomTreeItem.locators.CustomTreeItem.expandedAttr); + return attr === CustomTreeItem.locators.CustomTreeItem.expandedValue; + } + + async getChildren(): Promise { + const rows = await this.getChildItems(CustomTreeItem.locators.DefaultTreeSection.itemRow); + const items = await Promise.all(rows.map(async (row) => new CustomTreeItem(row, this.enclosingItem as TreeSection).wait())); + return items; + } + + async isExpandable(): Promise { + const attr = await this.getAttribute(CustomTreeItem.locators.CustomTreeItem.expandedAttr); + return attr !== null; + } +} diff --git a/packages/page-objects/src/components/sidebar/tree/custom/CustomTreeSection.ts b/packages/page-objects/src/components/sidebar/tree/custom/CustomTreeSection.ts index 09964e761..d0367d0db 100644 --- a/packages/page-objects/src/components/sidebar/tree/custom/CustomTreeSection.ts +++ b/packages/page-objects/src/components/sidebar/tree/custom/CustomTreeSection.ts @@ -15,72 +15,78 @@ * limitations under the License. */ -import { TreeSection } from "../TreeSection"; -import { TreeItem } from "../../ViewItem"; -import { Key, until, WebElement } from "selenium-webdriver"; -import { CustomTreeItem, ViewContent } from "../../../.."; +import { TreeSection } from '../TreeSection'; +import { TreeItem } from '../../ViewItem'; +import { Key, until, WebElement } from 'selenium-webdriver'; +import { CustomTreeItem, ViewContent } from '../../../..'; -export type GenericCustomTreeItemConstructor = { new(rootElement: WebElement, tree: TreeSection): T }; +export type GenericCustomTreeItemConstructor = { + new (rootElement: WebElement, tree: TreeSection): T; +}; /** * Generic custom tree view, e.g. contributed by an extension */ export class GenericCustomTreeSection extends TreeSection { - private _itemConstructor: GenericCustomTreeItemConstructor; + private _itemConstructor: GenericCustomTreeItemConstructor; - constructor(panel: WebElement, private _viewContent: ViewContent, itemConstructor: GenericCustomTreeItemConstructor) { - super(panel, _viewContent); - this._itemConstructor = itemConstructor; - } + constructor( + panel: WebElement, + private _viewContent: ViewContent, + itemConstructor: GenericCustomTreeItemConstructor, + ) { + super(panel, _viewContent); + this._itemConstructor = itemConstructor; + } - private get viewContent() : ViewContent { - return this._viewContent; - } + private get viewContent(): ViewContent { + return this._viewContent; + } - private get itemConstructor() : GenericCustomTreeItemConstructor { - return this._itemConstructor; - } - - async getVisibleItems(): Promise { - const items: T[] = []; - const container = await this.getContainer(); - const elements = await container.findElements(CustomTreeSection.locators.CustomTreeSection.itemRow); - for (const element of elements) { - if (await element.isDisplayed()) { - items.push(new this.itemConstructor(element, this)); - } - } - return items; - } + private get itemConstructor(): GenericCustomTreeItemConstructor { + return this._itemConstructor; + } - async findItem(labelOrPredicate: string | ((el: T) => (PromiseLike | boolean)), maxLevel: number = 0): Promise { - const predicate = typeof labelOrPredicate === 'string' ? (async (el: T) => await el.getLabel() === labelOrPredicate) : (labelOrPredicate); - const elements = await this.getVisibleItems(); - for (const element of elements) { - if (await predicate(element)) { - const level = +await element.getAttribute(CustomTreeSection.locators.ViewSection.level); - if (maxLevel < 1 || level <= maxLevel) { - return element; - } - } - } - return undefined; - } + async getVisibleItems(): Promise { + const items: T[] = []; + const container = await this.getContainer(); + const elements = await container.findElements(CustomTreeSection.locators.CustomTreeSection.itemRow); + for (const element of elements) { + if (await element.isDisplayed()) { + items.push(new this.itemConstructor(element, this)); + } + } + return items; + } - private async getContainer(): Promise> { - await this.expand(); - await this.getDriver().wait(until.elementLocated(CustomTreeSection.locators.CustomTreeSection.rowContainer), 5000); - const container = await this.findElement(CustomTreeSection.locators.CustomTreeSection.rowContainer); - await container.sendKeys(Key.HOME); - return new GenericCustomTreeSection(container, this.viewContent, this.itemConstructor); - } + async findItem(labelOrPredicate: string | ((el: T) => PromiseLike | boolean), maxLevel: number = 0): Promise { + const predicate = typeof labelOrPredicate === 'string' ? async (el: T) => (await el.getLabel()) === labelOrPredicate : labelOrPredicate; + const elements = await this.getVisibleItems(); + for (const element of elements) { + if (await predicate(element)) { + const level = +(await element.getAttribute(CustomTreeSection.locators.ViewSection.level)); + if (maxLevel < 1 || level <= maxLevel) { + return element; + } + } + } + return undefined; + } + + private async getContainer(): Promise> { + await this.expand(); + await this.getDriver().wait(until.elementLocated(CustomTreeSection.locators.CustomTreeSection.rowContainer), 5000); + const container = await this.findElement(CustomTreeSection.locators.CustomTreeSection.rowContainer); + await container.sendKeys(Key.HOME); + return new GenericCustomTreeSection(container, this.viewContent, this.itemConstructor); + } } /** * Custom tree view, e.g. contributed by an extension */ export class CustomTreeSection extends GenericCustomTreeSection { - constructor(panel: WebElement, viewContent: ViewContent) { - super(panel, viewContent, CustomTreeItem); - } + constructor(panel: WebElement, viewContent: ViewContent) { + super(panel, viewContent, CustomTreeItem); + } } diff --git a/packages/page-objects/src/components/sidebar/tree/debug/BreakpointSectionItem.ts b/packages/page-objects/src/components/sidebar/tree/debug/BreakpointSectionItem.ts index a333a93a7..262d531f5 100644 --- a/packages/page-objects/src/components/sidebar/tree/debug/BreakpointSectionItem.ts +++ b/packages/page-objects/src/components/sidebar/tree/debug/BreakpointSectionItem.ts @@ -15,73 +15,70 @@ * limitations under the License. */ -import { WebElement } from "selenium-webdriver"; -import { TreeSection } from "../TreeSection"; -import { CustomTreeItem } from "../custom/CustomTreeItem"; -import { SectionBreakpoint } from "./SectionBreakpoint"; +import { WebElement } from 'selenium-webdriver'; +import { TreeSection } from '../TreeSection'; +import { CustomTreeItem } from '../custom/CustomTreeItem'; +import { SectionBreakpoint } from './SectionBreakpoint'; export class BreakpointSectionItem extends CustomTreeItem { - constructor(element: WebElement, viewPart: TreeSection) { - super(element, viewPart); - } + constructor(element: WebElement, viewPart: TreeSection) { + super(element, viewPart); + } - /** - * Get breakpoint element which has context menu. - * @returns SectionBreakpoint page object - */ - async getBreakpoint(): Promise { - return new SectionBreakpoint( - BreakpointSectionItem.locators.BreakpointSectionItem.breakpoint.constructor, - this - ); - } + /** + * Get breakpoint element which has context menu. + * @returns SectionBreakpoint page object + */ + async getBreakpoint(): Promise { + return new SectionBreakpoint(BreakpointSectionItem.locators.BreakpointSectionItem.breakpoint.constructor, this); + } - /** - * Get status of the breakpoint. - * @returns boolean indicating status - */ - async isBreakpointEnabled(): Promise { - const locator = BreakpointSectionItem.locators.BreakpointSectionItem.breakpointCheckbox; - const el = await this.findElement(locator.constructor); - return await locator.value(el); - } + /** + * Get status of the breakpoint. + * @returns boolean indicating status + */ + async isBreakpointEnabled(): Promise { + const locator = BreakpointSectionItem.locators.BreakpointSectionItem.breakpointCheckbox; + const el = await this.findElement(locator.constructor); + return await locator.value(el); + } - /** - * Change breakpoint status to desired state. - * @param value new state - */ - async setBreakpointEnabled(value: boolean): Promise { - if (await this.isBreakpointEnabled() === value) { - return; - } - const locator = BreakpointSectionItem.locators.BreakpointSectionItem.breakpointCheckbox; - const el = await this.findElement(locator.constructor); - await el.click(); - } + /** + * Change breakpoint status to desired state. + * @param value new state + */ + async setBreakpointEnabled(value: boolean): Promise { + if ((await this.isBreakpointEnabled()) === value) { + return; + } + const locator = BreakpointSectionItem.locators.BreakpointSectionItem.breakpointCheckbox; + const el = await this.findElement(locator.constructor); + await el.click(); + } - async getLabel(): Promise { - const locator = BreakpointSectionItem.locators.BreakpointSectionItem.label; - const el = await this.findElement(locator.constructor); - return await locator.value(el); - } + async getLabel(): Promise { + const locator = BreakpointSectionItem.locators.BreakpointSectionItem.label; + const el = await this.findElement(locator.constructor); + return await locator.value(el); + } - /** - * Get breakpoint file path. Empty string is returned if path is not specified. - * @returns file path of breakpoint or empty string - */ - async getBreakpointFilePath(): Promise { - const locator = BreakpointSectionItem.locators.BreakpointSectionItem.filePath; - const el = await this.findElement(locator.constructor); - return await locator.value(el); - } + /** + * Get breakpoint file path. Empty string is returned if path is not specified. + * @returns file path of breakpoint or empty string + */ + async getBreakpointFilePath(): Promise { + const locator = BreakpointSectionItem.locators.BreakpointSectionItem.filePath; + const el = await this.findElement(locator.constructor); + return await locator.value(el); + } - /** - * Get line number of the breakpoint. - * @returns number indicating line position in file - */ - async getBreakpointLine(): Promise { - const locator = BreakpointSectionItem.locators.BreakpointSectionItem.lineNumber; - const el = await this.findElement(locator.constructor); - return Number.parseInt(await locator.value(el)); - } + /** + * Get line number of the breakpoint. + * @returns number indicating line position in file + */ + async getBreakpointLine(): Promise { + const locator = BreakpointSectionItem.locators.BreakpointSectionItem.lineNumber; + const el = await this.findElement(locator.constructor); + return Number.parseInt(await locator.value(el)); + } } diff --git a/packages/page-objects/src/components/sidebar/tree/debug/DebugBreakpointSection.ts b/packages/page-objects/src/components/sidebar/tree/debug/DebugBreakpointSection.ts index ebb6f9225..ef23e0453 100644 --- a/packages/page-objects/src/components/sidebar/tree/debug/DebugBreakpointSection.ts +++ b/packages/page-objects/src/components/sidebar/tree/debug/DebugBreakpointSection.ts @@ -15,13 +15,13 @@ * limitations under the License. */ -import { WebElement } from "selenium-webdriver"; -import { ViewContent } from "../../ViewContent"; -import { GenericCustomTreeSection } from "../custom/CustomTreeSection"; -import { BreakpointSectionItem } from "./BreakpointSectionItem"; +import { WebElement } from 'selenium-webdriver'; +import { ViewContent } from '../../ViewContent'; +import { GenericCustomTreeSection } from '../custom/CustomTreeSection'; +import { BreakpointSectionItem } from './BreakpointSectionItem'; -export class DebugBreakpointSection extends GenericCustomTreeSection { - constructor(panel: WebElement, viewContent: ViewContent) { - super(panel, viewContent, BreakpointSectionItem); - } +export class DebugBreakpointSection extends GenericCustomTreeSection { + constructor(panel: WebElement, viewContent: ViewContent) { + super(panel, viewContent, BreakpointSectionItem); + } } diff --git a/packages/page-objects/src/components/sidebar/tree/debug/DebugVariablesSection.ts b/packages/page-objects/src/components/sidebar/tree/debug/DebugVariablesSection.ts index bdf272481..b12ab7199 100644 --- a/packages/page-objects/src/components/sidebar/tree/debug/DebugVariablesSection.ts +++ b/packages/page-objects/src/components/sidebar/tree/debug/DebugVariablesSection.ts @@ -15,13 +15,13 @@ * limitations under the License. */ -import { WebElement } from "selenium-webdriver"; -import { ViewContent } from "../../ViewContent"; -import { GenericCustomTreeSection } from "../custom/CustomTreeSection"; -import { VariableSectionItem } from "./VariableSectionItem"; +import { WebElement } from 'selenium-webdriver'; +import { ViewContent } from '../../ViewContent'; +import { GenericCustomTreeSection } from '../custom/CustomTreeSection'; +import { VariableSectionItem } from './VariableSectionItem'; export class DebugVariableSection extends GenericCustomTreeSection { - constructor(panel: WebElement, viewContent: ViewContent) { - super(panel, viewContent, VariableSectionItem); - } + constructor(panel: WebElement, viewContent: ViewContent) { + super(panel, viewContent, VariableSectionItem); + } } diff --git a/packages/page-objects/src/components/sidebar/tree/debug/SectionBreakpoint.ts b/packages/page-objects/src/components/sidebar/tree/debug/SectionBreakpoint.ts index e8688e8f0..9c8c06457 100644 --- a/packages/page-objects/src/components/sidebar/tree/debug/SectionBreakpoint.ts +++ b/packages/page-objects/src/components/sidebar/tree/debug/SectionBreakpoint.ts @@ -15,8 +15,6 @@ * limitations under the License. */ -import { ElementWithContexMenu } from "../../../ElementWithContextMenu"; +import { ElementWithContexMenu } from '../../../ElementWithContextMenu'; -export class SectionBreakpoint extends ElementWithContexMenu { - -} +export class SectionBreakpoint extends ElementWithContexMenu {} diff --git a/packages/page-objects/src/components/sidebar/tree/debug/VariableSectionItem.ts b/packages/page-objects/src/components/sidebar/tree/debug/VariableSectionItem.ts index 4be6face0..cff8afd97 100644 --- a/packages/page-objects/src/components/sidebar/tree/debug/VariableSectionItem.ts +++ b/packages/page-objects/src/components/sidebar/tree/debug/VariableSectionItem.ts @@ -15,75 +15,75 @@ * limitations under the License. */ -import { Key, WebElement } from "selenium-webdriver"; -import { TreeSection } from "../TreeSection"; -import { CustomTreeItem } from "../custom/CustomTreeItem"; +import { Key, WebElement } from 'selenium-webdriver'; +import { TreeSection } from '../TreeSection'; +import { CustomTreeItem } from '../custom/CustomTreeItem'; export class VariableSectionItem extends CustomTreeItem { - constructor(element: WebElement, viewPart: TreeSection) { - super(element, viewPart); - } + constructor(element: WebElement, viewPart: TreeSection) { + super(element, viewPart); + } - /** - * Get name of the variable. - * @returns a promise resolving to variable name string - */ - async getVariableName(): Promise { - const name = await this.findElement(VariableSectionItem.locators.VariableSectionItem.name.constructor); - return VariableSectionItem.locators.VariableSectionItem.name.value(name); - } + /** + * Get name of the variable. + * @returns a promise resolving to variable name string + */ + async getVariableName(): Promise { + const name = await this.findElement(VariableSectionItem.locators.VariableSectionItem.name.constructor); + return VariableSectionItem.locators.VariableSectionItem.name.value(name); + } - /** - * Get value of the variable. - * @returns a promise resolving to value string of the variable - */ - async getVariableValue(): Promise { - const value = await this.findElement(VariableSectionItem.locators.VariableSectionItem.value.constructor); - return VariableSectionItem.locators.VariableSectionItem.value.value(value); - } + /** + * Get value of the variable. + * @returns a promise resolving to value string of the variable + */ + async getVariableValue(): Promise { + const value = await this.findElement(VariableSectionItem.locators.VariableSectionItem.value.constructor); + return VariableSectionItem.locators.VariableSectionItem.value.value(value); + } - /** - * Get variable name tooltip. - * @returns a promise resolving to variable name tooltip string - * @deprecated For VS Code 1.88+ this method won't be working any more - */ - async getVariableNameTooltip(): Promise { - if(VariableSectionItem.versionInfo.version >= '1.88.0') { - throw Error(`DEPRECATED METHOD! For VS Code 1.88+ this method won't be working any more.`); - } - const name = await this.findElement(VariableSectionItem.locators.VariableSectionItem.name.constructor); - return VariableSectionItem.locators.VariableSectionItem.name.tooltip(name); - } + /** + * Get variable name tooltip. + * @returns a promise resolving to variable name tooltip string + * @deprecated For VS Code 1.88+ this method won't be working any more + */ + async getVariableNameTooltip(): Promise { + if (VariableSectionItem.versionInfo.version >= '1.88.0') { + throw Error(`DEPRECATED METHOD! For VS Code 1.88+ this method won't be working any more.`); + } + const name = await this.findElement(VariableSectionItem.locators.VariableSectionItem.name.constructor); + return VariableSectionItem.locators.VariableSectionItem.name.tooltip(name); + } - /** - * Get variable value tooltip. - * @returns a promise resolving to variable value tooltip string - * @deprecated For VS Code 1.89+ this method won't be working any more - */ - async getVariableValueTooltip(): Promise { - if(VariableSectionItem.versionInfo.version >= '1.89.0') { - throw Error(`DEPRECATED METHOD! For VS Code 1.89+ this method won't be working any more.`); - } - const value = await this.findElement(VariableSectionItem.locators.VariableSectionItem.value.constructor); - return VariableSectionItem.locators.VariableSectionItem.value.tooltip(value); - } + /** + * Get variable value tooltip. + * @returns a promise resolving to variable value tooltip string + * @deprecated For VS Code 1.89+ this method won't be working any more + */ + async getVariableValueTooltip(): Promise { + if (VariableSectionItem.versionInfo.version >= '1.89.0') { + throw Error(`DEPRECATED METHOD! For VS Code 1.89+ this method won't be working any more.`); + } + const value = await this.findElement(VariableSectionItem.locators.VariableSectionItem.value.constructor); + return VariableSectionItem.locators.VariableSectionItem.value.tooltip(value); + } - async getLabel(): Promise { - return VariableSectionItem.locators.VariableSectionItem.label(this); - } + async getLabel(): Promise { + return VariableSectionItem.locators.VariableSectionItem.label(this); + } - getTooltip(): Promise { - throw new Error('unsupported: use either getVariableNameTooltip or getVariableValueTooltip'); - } + getTooltip(): Promise { + throw new Error('unsupported: use either getVariableNameTooltip or getVariableValueTooltip'); + } - /** - * Assign new value to the variable. - * @param value new value - */ - async setVariableValue(value: string): Promise { - const valueRootElement = await this.findElement(VariableSectionItem.locators.VariableSectionItem.value.constructor); - const driver = this.getDriver(); - await driver.actions().clear(); - await driver.actions().doubleClick(valueRootElement).sendKeys(value, Key.ENTER).perform(); - } + /** + * Assign new value to the variable. + * @param value new value + */ + async setVariableValue(value: string): Promise { + const valueRootElement = await this.findElement(VariableSectionItem.locators.VariableSectionItem.value.constructor); + const driver = this.getDriver(); + await driver.actions().clear(); + await driver.actions().doubleClick(valueRootElement).sendKeys(value, Key.ENTER).perform(); + } } diff --git a/packages/page-objects/src/components/sidebar/tree/default/DefaultTreeItem.ts b/packages/page-objects/src/components/sidebar/tree/default/DefaultTreeItem.ts index 5a96976b1..71ec923e2 100644 --- a/packages/page-objects/src/components/sidebar/tree/default/DefaultTreeItem.ts +++ b/packages/page-objects/src/components/sidebar/tree/default/DefaultTreeItem.ts @@ -15,45 +15,45 @@ * limitations under the License. */ -import { TreeItem } from "../../ViewItem"; -import { TreeSection } from "../TreeSection"; -import { WebElement } from "selenium-webdriver"; -import { NullAttributeError } from "../../../../errors/NullAttributeError"; +import { TreeItem } from '../../ViewItem'; +import { TreeSection } from '../TreeSection'; +import { WebElement } from 'selenium-webdriver'; +import { NullAttributeError } from '../../../../errors/NullAttributeError'; /** * Default tree item base on the items in explorer view */ export class DefaultTreeItem extends TreeItem { - constructor(element: WebElement, viewPart: TreeSection) { - super(element, viewPart); - } + constructor(element: WebElement, viewPart: TreeSection) { + super(element, viewPart); + } - async getLabel(): Promise { - const value = await this.getAttribute(DefaultTreeItem.locators.DefaultTreeSection.itemLabel); - if (value === null) { - throw new NullAttributeError(`${this.constructor.name}.getLabel returned null`); - } - return value; - } + async getLabel(): Promise { + const value = await this.getAttribute(DefaultTreeItem.locators.DefaultTreeSection.itemLabel); + if (value === null) { + throw new NullAttributeError(`${this.constructor.name}.getLabel returned null`); + } + return value; + } - async getTooltip(): Promise { - const tooltip = await this.findElement(DefaultTreeItem.locators.DefaultTreeItem.tooltip); - return await tooltip.getAttribute(DefaultTreeItem.locators.DefaultTreeItem.labelAttribute); - } + async getTooltip(): Promise { + const tooltip = await this.findElement(DefaultTreeItem.locators.DefaultTreeItem.tooltip); + return await tooltip.getAttribute(DefaultTreeItem.locators.DefaultTreeItem.labelAttribute); + } - async isExpanded(): Promise { - const twistieClass = await this.findElement(DefaultTreeItem.locators.DefaultTreeItem.twistie).getAttribute('class'); - return twistieClass.indexOf('collapsed') < 0; - } + async isExpanded(): Promise { + const twistieClass = await this.findElement(DefaultTreeItem.locators.DefaultTreeItem.twistie).getAttribute('class'); + return twistieClass.indexOf('collapsed') < 0; + } - async getChildren(): Promise { - const rows = await this.getChildItems(DefaultTreeItem.locators.DefaultTreeSection.itemRow); - const items = await Promise.all(rows.map(async row => new DefaultTreeItem(row, this.enclosingItem as TreeSection).wait())); - return items; - } + async getChildren(): Promise { + const rows = await this.getChildItems(DefaultTreeItem.locators.DefaultTreeSection.itemRow); + const items = await Promise.all(rows.map(async (row) => new DefaultTreeItem(row, this.enclosingItem as TreeSection).wait())); + return items; + } - async isExpandable(): Promise { - const twistieClass = await this.findElement(DefaultTreeItem.locators.DefaultTreeItem.twistie).getAttribute('class'); - return twistieClass.indexOf('collapsible') > -1; - } -} \ No newline at end of file + async isExpandable(): Promise { + const twistieClass = await this.findElement(DefaultTreeItem.locators.DefaultTreeItem.twistie).getAttribute('class'); + return twistieClass.indexOf('collapsible') > -1; + } +} diff --git a/packages/page-objects/src/components/sidebar/tree/default/DefaultTreeSection.ts b/packages/page-objects/src/components/sidebar/tree/default/DefaultTreeSection.ts index 462aaac8f..91f7b3928 100644 --- a/packages/page-objects/src/components/sidebar/tree/default/DefaultTreeSection.ts +++ b/packages/page-objects/src/components/sidebar/tree/default/DefaultTreeSection.ts @@ -15,46 +15,46 @@ * limitations under the License. */ -import { TreeSection } from "../TreeSection"; -import { TreeItem } from "../../../.."; +import { TreeSection } from '../TreeSection'; +import { TreeItem } from '../../../..'; import { Key } from 'selenium-webdriver'; -import { DefaultTreeItem } from "./DefaultTreeItem"; +import { DefaultTreeItem } from './DefaultTreeItem'; /** * Default view section */ export class DefaultTreeSection extends TreeSection { - async getVisibleItems(): Promise { - const items: TreeItem[] = []; - const elements = await this.findElements(DefaultTreeSection.locators.DefaultTreeSection.itemRow); - for (const element of elements) { - items.push(await new DefaultTreeItem(element, this).wait()); - } - return items; - } + async getVisibleItems(): Promise { + const items: TreeItem[] = []; + const elements = await this.findElements(DefaultTreeSection.locators.DefaultTreeSection.itemRow); + for (const element of elements) { + items.push(await new DefaultTreeItem(element, this).wait()); + } + return items; + } - async findItem(label: string, maxLevel: number = 0): Promise { - await this.expand(); - const container = await this.findElement(DefaultTreeSection.locators.DefaultTreeSection.rowContainer); - await container.sendKeys(Key.HOME); - let item: TreeItem | undefined = undefined; - do { - const temp = await container.findElements(DefaultTreeSection.locators.DefaultTreeItem.ctor(label)); - if (temp.length > 0) { - const level = +await temp[0].getAttribute(DefaultTreeSection.locators.ViewSection.level); - if (maxLevel < 1 || level <= maxLevel) { - item = await new DefaultTreeItem(temp[0], this).wait(); - } - } - if (!item) { - const lastrow = await container.findElements(DefaultTreeSection.locators.DefaultTreeSection.lastRow); - if (lastrow.length > 0) { - break; - } - await container.sendKeys(Key.PAGE_DOWN); - } - } while (!item); + async findItem(label: string, maxLevel: number = 0): Promise { + await this.expand(); + const container = await this.findElement(DefaultTreeSection.locators.DefaultTreeSection.rowContainer); + await container.sendKeys(Key.HOME); + let item: TreeItem | undefined = undefined; + do { + const temp = await container.findElements(DefaultTreeSection.locators.DefaultTreeItem.ctor(label)); + if (temp.length > 0) { + const level = +(await temp[0].getAttribute(DefaultTreeSection.locators.ViewSection.level)); + if (maxLevel < 1 || level <= maxLevel) { + item = await new DefaultTreeItem(temp[0], this).wait(); + } + } + if (!item) { + const lastrow = await container.findElements(DefaultTreeSection.locators.DefaultTreeSection.lastRow); + if (lastrow.length > 0) { + break; + } + await container.sendKeys(Key.PAGE_DOWN); + } + } while (!item); - return item; - } -} \ No newline at end of file + return item; + } +} diff --git a/packages/page-objects/src/components/statusBar/StatusBar.ts b/packages/page-objects/src/components/statusBar/StatusBar.ts index 226756864..5c427b968 100644 --- a/packages/page-objects/src/components/statusBar/StatusBar.ts +++ b/packages/page-objects/src/components/statusBar/StatusBar.ts @@ -15,172 +15,172 @@ * limitations under the License. */ -import { By, Locator, WebElement, error } from "selenium-webdriver"; -import { AbstractElement } from "../AbstractElement"; -import { NotificationsCenter } from "../workbench/NotificationsCenter"; +import { By, Locator, WebElement, error } from 'selenium-webdriver'; +import { AbstractElement } from '../AbstractElement'; +import { NotificationsCenter } from '../workbench/NotificationsCenter'; /** * Page object for the status bar at the bottom */ export class StatusBar extends AbstractElement { - constructor() { - super(StatusBar.locators.StatusBar.constructor, StatusBar.locators.Workbench.constructor); - } - - /** - * Retrieve all status bar items currently displayed - * @returns Promise resolving to an array of WebElement - */ - async getItems(): Promise { - return await this.findElements(StatusBar.locators.StatusBar.item); - } - - /** - * Find status bar item by title/visible label - * @param title title of the item - * @returns Promise resolving to a WebElement if item is found, to undefined otherwise - */ - async getItem(title: string): Promise { - const items = await this.getItems(); - for (const item of items) { - try { - if (await item.getAttribute(StatusBar.locators.StatusBar.itemTitle) === title) { - return item; - } - } catch (err) { - if (!(err instanceof error.StaleElementReferenceError)) { - throw err; - } - } - } - return undefined; - } - - /** - * Open the notifications center - * @returns Promise resolving to NotificationsCenter object - */ - async openNotificationsCenter(): Promise { - await this.toggleNotificationsCentre(true); - return new NotificationsCenter(); - } - - /** - * Close the notifications center - * @returns Promise resolving when the notifications center is closed - */ - async closeNotificationsCenter(): Promise { - await this.toggleNotificationsCentre(false); - } - - /** - * Open the language selection quick pick - * Only works with an open editor - * @returns Promise resolving when the language selection is opened - */ - async openLanguageSelection(): Promise { - await this.findElement(StatusBar.locators.StatusBar.language).click(); - } - - /** - * Get the current language label text - * Only works with an open editor - * @returns Promise resolving to string representation of current language - */ - async getCurrentLanguage(): Promise { - return await this.getPartText(StatusBar.locators.StatusBar.language); - } - - /** - * Open the quick pick for line endings selection - * Only works with an open editor - * @returns Promise resolving when the line ending selection is opened - */ - async openLineEndingSelection(): Promise { - await this.findElement(StatusBar.locators.StatusBar.lines).click(); - } - - /** - * Get the currently selected line ending as text - * Only works with an open editor - * @returns Promise resolving to string representation of current line ending - */ - async getCurrentLineEnding(): Promise { - return await this.getPartText(StatusBar.locators.StatusBar.lines); - } - - /** - * Open the encoding selection quick pick - * Only works with an open editor - * @returns Promise resolving when the encoding selection is opened - */ - async openEncodingSelection(): Promise { - await this.findElement(StatusBar.locators.StatusBar.encoding).click(); - } - - /** - * Get the name of the current encoding as text - * Only works with an open editor - * @returns Promise resolving to string representation of current encoding - */ - async getCurrentEncoding(): Promise { - return await this.getPartText(StatusBar.locators.StatusBar.encoding); - } - - /** - * Open the indentation selection quick pick - * Only works with an open editor - * @returns Promise resolving when the indentation selection is opened - */ - async openIndentationSelection(): Promise { - await this.findElement(StatusBar.locators.StatusBar.indent).click(); - } - - /** - * Get the current indentation option label as text - * Only works with an open editor - * @returns Promise resolving to string representation of current indentation - */ - async getCurrentIndentation(): Promise { - return await this.getPartText(StatusBar.locators.StatusBar.indent); - } - - /** - * Open the line selection input box - * Only works with an open editor - * @returns Promise resolving when the line selection is opened - */ - async openLineSelection(): Promise { - await this.findElement(StatusBar.locators.StatusBar.selection).click(); - } - - /** - * Get the current editor coordinates as text - * Only works with an open editor - * @returns Promise resolving to string representation of current position in the editor - */ - async getCurrentPosition(): Promise { - return await this.getPartText(StatusBar.locators.StatusBar.selection); - } - - /** - * Open/Close notification centre - * @param open true to open, false to close - */ - private async toggleNotificationsCentre(open: boolean): Promise { - let visible = false; - try { - const klass = await this.enclosingItem.findElement(StatusBar.locators.StatusBar.notifications).getAttribute('class'); - visible = klass.indexOf('visible') > -1; - } catch (err) { - // element doesn't exist until the button is first clicked - } - if (visible !== open) { - await this.findElement(StatusBar.locators.StatusBar.bell).click(); - } - } - - private async getPartText(locator: Locator): Promise { - return await this.findElement(locator).findElement(By.css('a')).getAttribute('innerHTML'); - } -} \ No newline at end of file + constructor() { + super(StatusBar.locators.StatusBar.constructor, StatusBar.locators.Workbench.constructor); + } + + /** + * Retrieve all status bar items currently displayed + * @returns Promise resolving to an array of WebElement + */ + async getItems(): Promise { + return await this.findElements(StatusBar.locators.StatusBar.item); + } + + /** + * Find status bar item by title/visible label + * @param title title of the item + * @returns Promise resolving to a WebElement if item is found, to undefined otherwise + */ + async getItem(title: string): Promise { + const items = await this.getItems(); + for (const item of items) { + try { + if ((await item.getAttribute(StatusBar.locators.StatusBar.itemTitle)) === title) { + return item; + } + } catch (err) { + if (!(err instanceof error.StaleElementReferenceError)) { + throw err; + } + } + } + return undefined; + } + + /** + * Open the notifications center + * @returns Promise resolving to NotificationsCenter object + */ + async openNotificationsCenter(): Promise { + await this.toggleNotificationsCentre(true); + return new NotificationsCenter(); + } + + /** + * Close the notifications center + * @returns Promise resolving when the notifications center is closed + */ + async closeNotificationsCenter(): Promise { + await this.toggleNotificationsCentre(false); + } + + /** + * Open the language selection quick pick + * Only works with an open editor + * @returns Promise resolving when the language selection is opened + */ + async openLanguageSelection(): Promise { + await this.findElement(StatusBar.locators.StatusBar.language).click(); + } + + /** + * Get the current language label text + * Only works with an open editor + * @returns Promise resolving to string representation of current language + */ + async getCurrentLanguage(): Promise { + return await this.getPartText(StatusBar.locators.StatusBar.language); + } + + /** + * Open the quick pick for line endings selection + * Only works with an open editor + * @returns Promise resolving when the line ending selection is opened + */ + async openLineEndingSelection(): Promise { + await this.findElement(StatusBar.locators.StatusBar.lines).click(); + } + + /** + * Get the currently selected line ending as text + * Only works with an open editor + * @returns Promise resolving to string representation of current line ending + */ + async getCurrentLineEnding(): Promise { + return await this.getPartText(StatusBar.locators.StatusBar.lines); + } + + /** + * Open the encoding selection quick pick + * Only works with an open editor + * @returns Promise resolving when the encoding selection is opened + */ + async openEncodingSelection(): Promise { + await this.findElement(StatusBar.locators.StatusBar.encoding).click(); + } + + /** + * Get the name of the current encoding as text + * Only works with an open editor + * @returns Promise resolving to string representation of current encoding + */ + async getCurrentEncoding(): Promise { + return await this.getPartText(StatusBar.locators.StatusBar.encoding); + } + + /** + * Open the indentation selection quick pick + * Only works with an open editor + * @returns Promise resolving when the indentation selection is opened + */ + async openIndentationSelection(): Promise { + await this.findElement(StatusBar.locators.StatusBar.indent).click(); + } + + /** + * Get the current indentation option label as text + * Only works with an open editor + * @returns Promise resolving to string representation of current indentation + */ + async getCurrentIndentation(): Promise { + return await this.getPartText(StatusBar.locators.StatusBar.indent); + } + + /** + * Open the line selection input box + * Only works with an open editor + * @returns Promise resolving when the line selection is opened + */ + async openLineSelection(): Promise { + await this.findElement(StatusBar.locators.StatusBar.selection).click(); + } + + /** + * Get the current editor coordinates as text + * Only works with an open editor + * @returns Promise resolving to string representation of current position in the editor + */ + async getCurrentPosition(): Promise { + return await this.getPartText(StatusBar.locators.StatusBar.selection); + } + + /** + * Open/Close notification centre + * @param open true to open, false to close + */ + private async toggleNotificationsCentre(open: boolean): Promise { + let visible = false; + try { + const klass = await this.enclosingItem.findElement(StatusBar.locators.StatusBar.notifications).getAttribute('class'); + visible = klass.indexOf('visible') > -1; + } catch (err) { + // element doesn't exist until the button is first clicked + } + if (visible !== open) { + await this.findElement(StatusBar.locators.StatusBar.bell).click(); + } + } + + private async getPartText(locator: Locator): Promise { + return await this.findElement(locator).findElement(By.css('a')).getAttribute('innerHTML'); + } +} diff --git a/packages/page-objects/src/components/workbench/DebugToolbar.ts b/packages/page-objects/src/components/workbench/DebugToolbar.ts index 051c1d552..3add266e8 100644 --- a/packages/page-objects/src/components/workbench/DebugToolbar.ts +++ b/packages/page-objects/src/components/workbench/DebugToolbar.ts @@ -15,102 +15,102 @@ * limitations under the License. */ -import { until, WebElement } from "selenium-webdriver"; -import { AbstractElement } from "../AbstractElement"; -import { Workbench } from "./Workbench"; +import { until, WebElement } from 'selenium-webdriver'; +import { AbstractElement } from '../AbstractElement'; +import { Workbench } from './Workbench'; /** * Page object for the Debugger Toolbar */ export class DebugToolbar extends AbstractElement { - constructor() { - super(DebugToolbar.locators.DebugToolbar.ctor, new Workbench()); - } + constructor() { + super(DebugToolbar.locators.DebugToolbar.ctor, new Workbench()); + } - /** - * Wait for the debug toolbar to appear and instantiate it. - * Assumes that debug session is already starting and it is just - * a matter of waiting for the toolbar to appear. - * - * @param timeout max time to wait in milliseconds, default 5000 - */ - static async create(timeout = 5000): Promise { - await DebugToolbar.driver.wait(until.elementLocated(DebugToolbar.locators.DebugToolbar.ctor), timeout); - return new DebugToolbar().wait(timeout); - } + /** + * Wait for the debug toolbar to appear and instantiate it. + * Assumes that debug session is already starting and it is just + * a matter of waiting for the toolbar to appear. + * + * @param timeout max time to wait in milliseconds, default 5000 + */ + static async create(timeout = 5000): Promise { + await DebugToolbar.driver.wait(until.elementLocated(DebugToolbar.locators.DebugToolbar.ctor), timeout); + return new DebugToolbar().wait(timeout); + } - /** - * Wait for the execution to pause at the next breakpoint - */ - async waitForBreakPoint(timeout = undefined): Promise { - let btn = await this.getDriver().wait(until.elementLocated(DebugToolbar.locators.DebugToolbar.button('continue'))); - await this.getDriver().wait(async () => { - try { - const enabled = await btn.isEnabled(); - return enabled; - } catch(err) { - btn = await this.findElement(DebugToolbar.locators.DebugToolbar.button('continue')); - } - }, timeout); - } + /** + * Wait for the execution to pause at the next breakpoint + */ + async waitForBreakPoint(timeout = undefined): Promise { + let btn = await this.getDriver().wait(until.elementLocated(DebugToolbar.locators.DebugToolbar.button('continue'))); + await this.getDriver().wait(async () => { + try { + const enabled = await btn.isEnabled(); + return enabled; + } catch (err) { + btn = await this.findElement(DebugToolbar.locators.DebugToolbar.button('continue')); + } + }, timeout); + } - /** - * Click Continue - */ - async continue(): Promise { - await (await this.getButton('continue')).click(); - } + /** + * Click Continue + */ + async continue(): Promise { + await (await this.getButton('continue')).click(); + } - /** - * Click Disconnect - */ - async disconnect(): Promise { - await (await this.getButton('disconnect')).click(); - } + /** + * Click Disconnect + */ + async disconnect(): Promise { + await (await this.getButton('disconnect')).click(); + } - /** - * Click Pause - */ - async pause(): Promise { - await (await this.getButton('pause')).click(); - } + /** + * Click Pause + */ + async pause(): Promise { + await (await this.getButton('pause')).click(); + } - /** - * Click Step Over - */ - async stepOver(): Promise { - await (await this.getButton('step-over')).click(); - } + /** + * Click Step Over + */ + async stepOver(): Promise { + await (await this.getButton('step-over')).click(); + } - /** - * Click Step Into - */ - async stepInto(): Promise { - await (await this.getButton('step-into')).click(); - } + /** + * Click Step Into + */ + async stepInto(): Promise { + await (await this.getButton('step-into')).click(); + } - /** - * Click Step Out - */ - async stepOut(): Promise { - await (await this.getButton('step-out')).click(); - } + /** + * Click Step Out + */ + async stepOut(): Promise { + await (await this.getButton('step-out')).click(); + } - /** - * Click Restart - */ - async restart(): Promise { - await (await this.getButton('restart')).click(); - } + /** + * Click Restart + */ + async restart(): Promise { + await (await this.getButton('restart')).click(); + } - /** - * Click Stop - */ - async stop(): Promise { - await (await this.getButton('stop')).click(); - } + /** + * Click Stop + */ + async stop(): Promise { + await (await this.getButton('stop')).click(); + } - private async getButton(name: string): Promise { - return await this.findElement(DebugToolbar.locators.DebugToolbar.button(name)); - } + private async getButton(name: string): Promise { + return await this.findElement(DebugToolbar.locators.DebugToolbar.button(name)); + } } diff --git a/packages/page-objects/src/components/workbench/Notification.ts b/packages/page-objects/src/components/workbench/Notification.ts index dfdaf1f85..18b8501d0 100644 --- a/packages/page-objects/src/components/workbench/Notification.ts +++ b/packages/page-objects/src/components/workbench/Notification.ts @@ -15,147 +15,144 @@ * limitations under the License. */ -import { ElementWithContexMenu } from "../ElementWithContextMenu"; -import { AbstractElement } from "../AbstractElement"; -import { By, until, WebElement } from "selenium-webdriver"; +import { ElementWithContexMenu } from '../ElementWithContextMenu'; +import { AbstractElement } from '../AbstractElement'; +import { By, until, WebElement } from 'selenium-webdriver'; /** * Available types of notifications */ export enum NotificationType { - Info = 'info', - Warning = 'warning', - Error = 'error', - Any = 'any' + Info = 'info', + Warning = 'warning', + Error = 'error', + Any = 'any', } /** * Abstract element representing a notification */ export abstract class Notification extends ElementWithContexMenu { - - /** - * Get the message of the notification - * @returns Promise resolving to notification message - */ - async getMessage(): Promise { - return await (await this.findElement(Notification.locators.Notification.message)).getText(); - } - - /** - * Get the type of the notification - * @returns Promise resolving to NotificationType - */ - async getType(): Promise { - const iconType = await (await this.findElement(Notification.locators.Notification.icon)).getAttribute('class'); - if (iconType.indexOf('icon-info') > -1) { - return NotificationType.Info; - } else if (iconType.indexOf('icon-warning') > -1) { - return NotificationType.Warning; - } else { - return NotificationType.Error; - } - } - - /** - * Get the source of the notification as text - * @returns Promise resolving to notification source - * @deprecated For VS Code 1.88+ this method won't be working any more - */ - async getSource(): Promise { - if(Notification.versionInfo.version >= '1.88.0') { - throw Error(`DEPRECATED METHOD! For VS Code 1.88+ this method won't be working any more.`); - } - await this.expand(); - return await (await this.findElement(Notification.locators.Notification.source)).getAttribute('title'); - } - - /** - * Find whether the notification has an active progress bar - * @returns Promise resolving to true/false - */ - async hasProgress(): Promise { - const klass = await (await this.findElement(Notification.locators.Notification.progress)).getAttribute('class'); - return klass.indexOf('done') < 0; - } - - /** - * Dismiss the notification - * @returns Promise resolving when notification is dismissed - */ - async dismiss(): Promise { - await this.getDriver().actions().move({origin: this}).perform(); - const btn = await this.findElement(Notification.locators.Notification.dismiss); - await this.getDriver().wait(until.elementIsVisible(btn), 2000); - await btn.click(); - } - - /** - * Get the action buttons of the notification as an array - * of NotificationButton objects - * @returns Promise resolving to array of NotificationButton objects - */ - async getActions(): Promise { - const buttons: NotificationButton[] = []; - const elements = await this.findElement(Notification.locators.Notification.actions) - .findElements(Notification.locators.Notification.action); - - for (const button of elements) { - const title = await Notification.locators.Notification.actionLabel.value(button); - buttons.push(await new NotificationButton(Notification.locators.Notification.buttonConstructor(title), this).wait()); - } - return buttons; - } - - /** - * Click on an action button with the given title - * @param title title of the action/button - * @returns Promise resolving when the select button is pressed - */ - async takeAction(title: string): Promise { - await new NotificationButton(Notification.locators.Notification.buttonConstructor(title), this).click(); - } - - /** - * Expand the notification if possible - */ - async expand(): Promise { - await this.getDriver().actions().move({origin: this}).perform(); - const exp = await this.findElements(Notification.locators.Notification.expand); - if (exp[0]) { - await exp[0].click(); - } - } + /** + * Get the message of the notification + * @returns Promise resolving to notification message + */ + async getMessage(): Promise { + return await (await this.findElement(Notification.locators.Notification.message)).getText(); + } + + /** + * Get the type of the notification + * @returns Promise resolving to NotificationType + */ + async getType(): Promise { + const iconType = await (await this.findElement(Notification.locators.Notification.icon)).getAttribute('class'); + if (iconType.indexOf('icon-info') > -1) { + return NotificationType.Info; + } else if (iconType.indexOf('icon-warning') > -1) { + return NotificationType.Warning; + } else { + return NotificationType.Error; + } + } + + /** + * Get the source of the notification as text + * @returns Promise resolving to notification source + * @deprecated For VS Code 1.88+ this method won't be working any more + */ + async getSource(): Promise { + if (Notification.versionInfo.version >= '1.88.0') { + throw Error(`DEPRECATED METHOD! For VS Code 1.88+ this method won't be working any more.`); + } + await this.expand(); + return await (await this.findElement(Notification.locators.Notification.source)).getAttribute('title'); + } + + /** + * Find whether the notification has an active progress bar + * @returns Promise resolving to true/false + */ + async hasProgress(): Promise { + const klass = await (await this.findElement(Notification.locators.Notification.progress)).getAttribute('class'); + return klass.indexOf('done') < 0; + } + + /** + * Dismiss the notification + * @returns Promise resolving when notification is dismissed + */ + async dismiss(): Promise { + await this.getDriver().actions().move({ origin: this }).perform(); + const btn = await this.findElement(Notification.locators.Notification.dismiss); + await this.getDriver().wait(until.elementIsVisible(btn), 2000); + await btn.click(); + } + + /** + * Get the action buttons of the notification as an array + * of NotificationButton objects + * @returns Promise resolving to array of NotificationButton objects + */ + async getActions(): Promise { + const buttons: NotificationButton[] = []; + const elements = await this.findElement(Notification.locators.Notification.actions).findElements(Notification.locators.Notification.action); + + for (const button of elements) { + const title = await Notification.locators.Notification.actionLabel.value(button); + buttons.push(await new NotificationButton(Notification.locators.Notification.buttonConstructor(title), this).wait()); + } + return buttons; + } + + /** + * Click on an action button with the given title + * @param title title of the action/button + * @returns Promise resolving when the select button is pressed + */ + async takeAction(title: string): Promise { + await new NotificationButton(Notification.locators.Notification.buttonConstructor(title), this).click(); + } + + /** + * Expand the notification if possible + */ + async expand(): Promise { + await this.getDriver().actions().move({ origin: this }).perform(); + const exp = await this.findElements(Notification.locators.Notification.expand); + if (exp[0]) { + await exp[0].click(); + } + } } /** * Notification displayed on its own in the notifications-toasts container */ export class StandaloneNotification extends Notification { - constructor(notification: WebElement) { - super(notification, StandaloneNotification.locators.Notification.standaloneContainer); - } + constructor(notification: WebElement) { + super(notification, StandaloneNotification.locators.Notification.standaloneContainer); + } } /** * Notification displayed within the notifications center */ export class CenterNotification extends Notification { - constructor(notification: WebElement) { - super(notification, CenterNotification.locators.NotificationsCenter.constructor); - } + constructor(notification: WebElement) { + super(notification, CenterNotification.locators.NotificationsCenter.constructor); + } } /** * Notification button */ class NotificationButton extends AbstractElement { + constructor(buttonConstructor: By, notification: Notification) { + super(buttonConstructor, notification); + } - constructor(buttonConstructor: By, notification: Notification) { - super(buttonConstructor, notification); - } - - async getTitle(): Promise { - return await Notification.locators.Notification.actionLabel.value(this); - } -} \ No newline at end of file + async getTitle(): Promise { + return await Notification.locators.Notification.actionLabel.value(this); + } +} diff --git a/packages/page-objects/src/components/workbench/NotificationsCenter.ts b/packages/page-objects/src/components/workbench/NotificationsCenter.ts index 3056d1626..115dad693 100644 --- a/packages/page-objects/src/components/workbench/NotificationsCenter.ts +++ b/packages/page-objects/src/components/workbench/NotificationsCenter.ts @@ -15,59 +15,59 @@ * limitations under the License. */ -import { Key } from "selenium-webdriver"; -import { AbstractElement } from "../AbstractElement"; -import { Notification, CenterNotification, NotificationType } from "./Notification"; +import { Key } from 'selenium-webdriver'; +import { AbstractElement } from '../AbstractElement'; +import { Notification, CenterNotification, NotificationType } from './Notification'; /** * Notifications center page object */ export class NotificationsCenter extends AbstractElement { - constructor() { - super(NotificationsCenter.locators.NotificationsCenter.constructor, NotificationsCenter.locators.Workbench.constructor); - } + constructor() { + super(NotificationsCenter.locators.NotificationsCenter.constructor, NotificationsCenter.locators.Workbench.constructor); + } - /** - * Close the notifications center - * @returns Promise resolving when the center is closed - */ - async close(): Promise { - if (await this.isDisplayed()) { - try { - await this.findElement(NotificationsCenter.locators.NotificationsCenter.close).click(); - } catch (error) { - await this.click(); - await this.getDriver().actions().sendKeys(Key.ESCAPE).perform(); - } - } - } + /** + * Close the notifications center + * @returns Promise resolving when the center is closed + */ + async close(): Promise { + if (await this.isDisplayed()) { + try { + await this.findElement(NotificationsCenter.locators.NotificationsCenter.close).click(); + } catch (error) { + await this.click(); + await this.getDriver().actions().sendKeys(Key.ESCAPE).perform(); + } + } + } - /** - * Clear all notifications in the notifications center - * Note that this will also hide the notifications center - * @returns Promise resolving when the clear all button is pressed - */ - async clearAllNotifications(): Promise { - await this.findElement(NotificationsCenter.locators.NotificationsCenter.clear).click(); - } + /** + * Clear all notifications in the notifications center + * Note that this will also hide the notifications center + * @returns Promise resolving when the clear all button is pressed + */ + async clearAllNotifications(): Promise { + await this.findElement(NotificationsCenter.locators.NotificationsCenter.clear).click(); + } - /** - * Get all notifications of a given type - * @param type type of the notifications to look for, - * NotificationType.Any will retrieve all notifications - * - * @returns Promise resolving to array of Notification objects - */ - async getNotifications(type: NotificationType): Promise { - const notifications: Notification[] = []; - const elements = await this.findElements(NotificationsCenter.locators.NotificationsCenter.row); + /** + * Get all notifications of a given type + * @param type type of the notifications to look for, + * NotificationType.Any will retrieve all notifications + * + * @returns Promise resolving to array of Notification objects + */ + async getNotifications(type: NotificationType): Promise { + const notifications: Notification[] = []; + const elements = await this.findElements(NotificationsCenter.locators.NotificationsCenter.row); - for (const element of elements) { - const not = new CenterNotification(element); - if (type === NotificationType.Any || await not.getType() === type) { - notifications.push(await not.wait()); - } - } - return notifications; - } -} \ No newline at end of file + for (const element of elements) { + const not = new CenterNotification(element); + if (type === NotificationType.Any || (await not.getType()) === type) { + notifications.push(await not.wait()); + } + } + return notifications; + } +} diff --git a/packages/page-objects/src/components/workbench/Workbench.ts b/packages/page-objects/src/components/workbench/Workbench.ts index 29ca4b984..f9b3c8a9c 100644 --- a/packages/page-objects/src/components/workbench/Workbench.ts +++ b/packages/page-objects/src/components/workbench/Workbench.ts @@ -15,146 +15,146 @@ * limitations under the License. */ -import { AbstractElement } from "../AbstractElement"; -import { WebElement, Key, until } from "selenium-webdriver"; -import { TitleBar } from "../menu/TitleBar"; -import { SideBarView } from "../sidebar/SideBarView"; -import { ActivityBar } from "../activityBar/ActivityBar"; -import { StatusBar } from "../statusBar/StatusBar"; -import { EditorView } from "../editor/EditorView"; -import { BottomBarPanel } from "../bottomBar/BottomBarPanel"; -import { Notification, StandaloneNotification } from "./Notification"; -import { NotificationsCenter } from "./NotificationsCenter"; -import { QuickOpenBox } from "./input/QuickOpenBox"; -import { SettingsEditor } from "../editor/SettingsEditor"; -import { InputBox } from "./input/InputBox"; +import { AbstractElement } from '../AbstractElement'; +import { WebElement, Key, until } from 'selenium-webdriver'; +import { TitleBar } from '../menu/TitleBar'; +import { SideBarView } from '../sidebar/SideBarView'; +import { ActivityBar } from '../activityBar/ActivityBar'; +import { StatusBar } from '../statusBar/StatusBar'; +import { EditorView } from '../editor/EditorView'; +import { BottomBarPanel } from '../bottomBar/BottomBarPanel'; +import { Notification, StandaloneNotification } from './Notification'; +import { NotificationsCenter } from './NotificationsCenter'; +import { QuickOpenBox } from './input/QuickOpenBox'; +import { SettingsEditor } from '../editor/SettingsEditor'; +import { InputBox } from './input/InputBox'; /** * Handler for general workbench related actions */ export class Workbench extends AbstractElement { - constructor() { - super(Workbench.locators.Workbench.constructor); - } + constructor() { + super(Workbench.locators.Workbench.constructor); + } - /** - * Get a title bar handle - */ - getTitleBar(): TitleBar { - return new TitleBar(); - } + /** + * Get a title bar handle + */ + getTitleBar(): TitleBar { + return new TitleBar(); + } - /** - * Get a side bar handle - */ - getSideBar(): SideBarView { - return new SideBarView(); - } + /** + * Get a side bar handle + */ + getSideBar(): SideBarView { + return new SideBarView(); + } - /** - * Get an activity bar handle - */ - getActivityBar(): ActivityBar { - return new ActivityBar(); - } + /** + * Get an activity bar handle + */ + getActivityBar(): ActivityBar { + return new ActivityBar(); + } - /** - * Get a status bar handle - */ - getStatusBar(): StatusBar { - return new StatusBar(); - } + /** + * Get a status bar handle + */ + getStatusBar(): StatusBar { + return new StatusBar(); + } - /** - * Get a bottom bar handle - */ - getBottomBar(): BottomBarPanel { - return new BottomBarPanel(); - } + /** + * Get a bottom bar handle + */ + getBottomBar(): BottomBarPanel { + return new BottomBarPanel(); + } - /** - * Get a handle for the editor view - */ - getEditorView(): EditorView { - return new EditorView(); - } + /** + * Get a handle for the editor view + */ + getEditorView(): EditorView { + return new EditorView(); + } - /** - * Get all standalone notifications (notifications outside the notifications center) - * @returns Promise resolving to array of Notification objects - */ - async getNotifications(): Promise { - const notifications: Notification[] = []; - let container: WebElement; - try { - container = await this.findElement(Workbench.locators.Workbench.notificationContainer); - } catch (err) { - return []; - } - const elements = await container.findElements(Workbench.locators.Workbench.notificationItem); - - for (const element of elements) { - notifications.push(await new StandaloneNotification(element).wait()); - } - return notifications; - } + /** + * Get all standalone notifications (notifications outside the notifications center) + * @returns Promise resolving to array of Notification objects + */ + async getNotifications(): Promise { + const notifications: Notification[] = []; + let container: WebElement; + try { + container = await this.findElement(Workbench.locators.Workbench.notificationContainer); + } catch (err) { + return []; + } + const elements = await container.findElements(Workbench.locators.Workbench.notificationItem); - /** - * Opens the notifications center - * @returns Promise resolving to NotificationsCenter object - */ - async openNotificationsCenter(): Promise { - return await new StatusBar().openNotificationsCenter(); - } - - /** - * Opens the settings editor - * - * @returns promise that resolves to a SettingsEditor instance - */ - async openSettings(): Promise { - await this.executeCommand('open user settings'); - await new EditorView().openEditor('Settings'); - await Workbench.driver.wait(until.elementLocated(Workbench.locators.Editor.constructor)); - await new Promise((res) => setTimeout(res, 500)); - return new SettingsEditor(); - } + for (const element of elements) { + notifications.push(await new StandaloneNotification(element).wait()); + } + return notifications; + } - /** - * Open the VS Code command line prompt - * @returns Promise resolving to InputBox (vscode 1.44+) or QuickOpenBox (vscode up to 1.43) object - */ - async openCommandPrompt(): Promise { - const webview = await new EditorView().findElements(EditorView.locators.EditorView.webView); - if (webview.length > 0) { - const tab = await new EditorView().getActiveTab(); - if (tab) { - await tab.sendKeys(Key.F1); - return await InputBox.create(); - } - } - const driver = this.getDriver(); - await driver.actions().keyDown(Workbench.ctlKey).keyDown(Key.SHIFT).sendKeys('p').perform(); + /** + * Opens the notifications center + * @returns Promise resolving to NotificationsCenter object + */ + async openNotificationsCenter(): Promise { + return await new StatusBar().openNotificationsCenter(); + } - if (Workbench.versionInfo.version >= '1.44.0') { - return await InputBox.create(); - } - return await QuickOpenBox.create(); - } + /** + * Opens the settings editor + * + * @returns promise that resolves to a SettingsEditor instance + */ + async openSettings(): Promise { + await this.executeCommand('open user settings'); + await new EditorView().openEditor('Settings'); + await Workbench.driver.wait(until.elementLocated(Workbench.locators.Editor.constructor)); + await new Promise((res) => setTimeout(res, 500)); + return new SettingsEditor(); + } - /** - * Open the command prompt, type in a command and execute - * @param command text of the command to be executed - * @returns Promise resolving when the command prompt is confirmed - */ - async executeCommand(command: string): Promise { - const prompt = await this.openCommandPrompt(); - await prompt.setText(`>${command}`); - const quickPicks = await Promise.all((await prompt.getQuickPicks()).map(item => item.getLabel())); - if(quickPicks.includes(command)) { - await prompt.selectQuickPick(command); - } else { - await prompt.confirm(); - } - } + /** + * Open the VS Code command line prompt + * @returns Promise resolving to InputBox (vscode 1.44+) or QuickOpenBox (vscode up to 1.43) object + */ + async openCommandPrompt(): Promise { + const webview = await new EditorView().findElements(EditorView.locators.EditorView.webView); + if (webview.length > 0) { + const tab = await new EditorView().getActiveTab(); + if (tab) { + await tab.sendKeys(Key.F1); + return await InputBox.create(); + } + } + const driver = this.getDriver(); + await driver.actions().keyDown(Workbench.ctlKey).keyDown(Key.SHIFT).sendKeys('p').perform(); + + if (Workbench.versionInfo.version >= '1.44.0') { + return await InputBox.create(); + } + return await QuickOpenBox.create(); + } + + /** + * Open the command prompt, type in a command and execute + * @param command text of the command to be executed + * @returns Promise resolving when the command prompt is confirmed + */ + async executeCommand(command: string): Promise { + const prompt = await this.openCommandPrompt(); + await prompt.setText(`>${command}`); + const quickPicks = await Promise.all((await prompt.getQuickPicks()).map((item) => item.getLabel())); + if (quickPicks.includes(command)) { + await prompt.selectQuickPick(command); + } else { + await prompt.confirm(); + } + } } diff --git a/packages/page-objects/src/components/workbench/input/Input.ts b/packages/page-objects/src/components/workbench/input/Input.ts index 8745b9257..a5eeb1d22 100644 --- a/packages/page-objects/src/components/workbench/input/Input.ts +++ b/packages/page-objects/src/components/workbench/input/Input.ts @@ -15,277 +15,269 @@ * limitations under the License. */ -import { AbstractElement } from "../../AbstractElement"; -import { Key } from "selenium-webdriver"; -import { QuickOpenBox } from "../../.."; +import { AbstractElement } from '../../AbstractElement'; +import { Key } from 'selenium-webdriver'; +import { QuickOpenBox } from '../../..'; /** * Abstract page object for input fields */ export abstract class Input extends AbstractElement { + /** + * Get current text of the input field + * @returns Promise resolving to text of the input field + */ + async getText(): Promise { + const input = await this.findElement(Input.locators.Input.inputBox).findElement(Input.locators.Input.input); + return await input.getAttribute('value'); + } - /** - * Get current text of the input field - * @returns Promise resolving to text of the input field - */ - async getText(): Promise { - const input = await this.findElement(Input.locators.Input.inputBox) - .findElement(Input.locators.Input.input); - return await input.getAttribute('value'); - } + /** + * Set (by selecting all and typing) text in the input field + * @param text text to set into the input field + * @returns Promise resolving when the text is typed in + */ + async setText(text: string): Promise { + const clipboard = (await import('clipboardy')).default; + let originalClipboard = ''; + try { + originalClipboard = clipboard.readSync(); + } catch (error) { + // workaround issue https://github.com/redhat-developer/vscode-extension-tester/issues/835 + // do not fail if clipboard is empty + } + const input = await this.findElement(Input.locators.Input.inputBox).findElement(Input.locators.Input.input); + await this.clear(); + await new Promise((res) => setTimeout(res, 200)); + if ((await this.getText()).length > 0) { + await input.sendKeys(Key.END, Key.chord(Key.SHIFT, Key.HOME)); + } + await input.sendKeys(text); - /** - * Set (by selecting all and typing) text in the input field - * @param text text to set into the input field - * @returns Promise resolving when the text is typed in - */ - async setText(text: string): Promise { - const clipboard = (await import('clipboardy')).default; - let originalClipboard = ''; - try { - originalClipboard = clipboard.readSync(); - } catch (error) { - // workaround issue https://github.com/redhat-developer/vscode-extension-tester/issues/835 - // do not fail if clipboard is empty - } - const input = await this.findElement(Input.locators.Input.inputBox) - .findElement(Input.locators.Input.input); - await this.clear(); - await new Promise(res => setTimeout(res, 200)); - if ((await this.getText()).length > 0) { - await input.sendKeys(Key.END, Key.chord(Key.SHIFT, Key.HOME)); - } - await input.sendKeys(text); + // fallback to clipboard if the text gets malformed + if ((await this.getText()) !== text) { + await clipboard.write(text); + await input.sendKeys(Key.END, Key.chord(Key.SHIFT, Key.HOME)); + await input.sendKeys(Key.chord(Input.ctlKey, 'v')); + if (originalClipboard.length > 0) { + clipboard.writeSync(originalClipboard); + } + } + } - // fallback to clipboard if the text gets malformed - if ((await this.getText()) !== text) { - await clipboard.write(text); - await input.sendKeys(Key.END, Key.chord(Key.SHIFT, Key.HOME)); - await input.sendKeys(Key.chord(Input.ctlKey, 'v')); - if(originalClipboard.length > 0) { - clipboard.writeSync(originalClipboard); - } - } - } + /** + * Get the placeholder text for the input field + * @returns Promise resolving to input placeholder + */ + async getPlaceHolder(): Promise { + return await this.findElement(Input.locators.Input.inputBox).findElement(Input.locators.Input.input).getAttribute('placeholder'); + } - /** - * Get the placeholder text for the input field - * @returns Promise resolving to input placeholder - */ - async getPlaceHolder(): Promise { - return await this.findElement(Input.locators.Input.inputBox) - .findElement(Input.locators.Input.input).getAttribute('placeholder'); - } + /** + * Confirm the input field by pressing Enter + * @returns Promise resolving when the input is confirmed + */ + async confirm(): Promise { + const input = await this.findElement(Input.locators.Input.inputBox).findElement(Input.locators.Input.input); + await input.click(); + await input.sendKeys(Key.ENTER); + } - /** - * Confirm the input field by pressing Enter - * @returns Promise resolving when the input is confirmed - */ - async confirm(): Promise { - const input = await this.findElement(Input.locators.Input.inputBox) - .findElement(Input.locators.Input.input); - await input.click(); - await input.sendKeys(Key.ENTER); - } + /** + * Cancel the input field by pressing Escape + * @returns Promise resolving when the input is cancelled + */ + async cancel(): Promise { + const input = await this.findElement(Input.locators.Input.inputBox).findElement(Input.locators.Input.input); + await input.sendKeys(Key.ESCAPE); + } - /** - * Cancel the input field by pressing Escape - * @returns Promise resolving when the input is cancelled - */ - async cancel(): Promise { - const input = await this.findElement(Input.locators.Input.inputBox) - .findElement(Input.locators.Input.input); - await input.sendKeys(Key.ESCAPE); - } + /** + * Clear the inpur field + * @returns Promise resolving when the field is cleared + */ + async clear(): Promise { + const input = await this.findElement(Input.locators.Input.inputBox).findElement(Input.locators.Input.input); + // VS Code 1.40 breaks the default clear method, use select all + back space instead + await input.sendKeys(Key.END, Key.chord(Key.SHIFT, Key.HOME), Key.BACK_SPACE); + if ((await input.getAttribute('value')).length > 0) { + await input.sendKeys(Key.END, Key.chord(Key.SHIFT, Key.HOME), Key.BACK_SPACE); + } + } - /** - * Clear the inpur field - * @returns Promise resolving when the field is cleared - */ - async clear(): Promise { - const input = await this.findElement(Input.locators.Input.inputBox) - .findElement(Input.locators.Input.input); - // VS Code 1.40 breaks the default clear method, use select all + back space instead - await input.sendKeys(Key.END, Key.chord(Key.SHIFT, Key.HOME), Key.BACK_SPACE); - if ((await input.getAttribute('value')).length > 0) { - await input.sendKeys(Key.END, Key.chord(Key.SHIFT, Key.HOME), Key.BACK_SPACE); - } - } + /** + * Select (click) a quick pick option. Will scroll through the quick picks to find the item. + * Search for the item can be done by its text, or index in the quick pick menu. + * Note that scrolling does not affect the item's index, but it will + * replace some items in the DOM (thus they become unreachable) + * + * @param indexOrText index (number) or text (string) of the item to search by + * @returns Promise resolving when the given quick pick is selected + */ + async selectQuickPick(indexOrText: string | number): Promise { + const pick = await this.findQuickPick(indexOrText); + if (pick) { + await pick.select(); + } else { + await this.resetPosition(); + } + } - /** - * Select (click) a quick pick option. Will scroll through the quick picks to find the item. - * Search for the item can be done by its text, or index in the quick pick menu. - * Note that scrolling does not affect the item's index, but it will - * replace some items in the DOM (thus they become unreachable) - * - * @param indexOrText index (number) or text (string) of the item to search by - * @returns Promise resolving when the given quick pick is selected - */ - async selectQuickPick(indexOrText: string | number): Promise { - const pick = await this.findQuickPick(indexOrText); - if (pick) { - await pick.select(); - } else { - await this.resetPosition(); - } - } + /** + * Select/Deselect all quick picks using the 'select all' checkbox + * If multiple selection is disabled on the input box, no action is performed + * + * @param state true to select all, false to deselect all + * @returns Promise resolving when all quick picks have been toggled to desired state + */ + async toggleAllQuickPicks(state: boolean): Promise { + const checkboxes = await this.findElements(Input.locators.Input.quickPickSelectAll); + if (!checkboxes) { + return; + } + if (!(await checkboxes[0].isSelected())) { + await checkboxes[0].click(); + } + if (state === false) { + await checkboxes[0].click(); + } + } - /** - * Select/Deselect all quick picks using the 'select all' checkbox - * If multiple selection is disabled on the input box, no action is performed - * - * @param state true to select all, false to deselect all - * @returns Promise resolving when all quick picks have been toggled to desired state - */ - async toggleAllQuickPicks(state: boolean): Promise { - const checkboxes = await this.findElements(Input.locators.Input.quickPickSelectAll); - if (!checkboxes) { - return; - } - if (!await checkboxes[0].isSelected()) { - await checkboxes[0].click(); - } - if (state === false) { - await checkboxes[0].click(); - } - } + /** + * Scroll through the quick picks to find an item by the name or index + * @param indexOrText index (number) or text (string) of the item to search by + * @returns Promise resolvnig to QuickPickItem if found, to undefined otherwise + */ + async findQuickPick(indexOrText: string | number): Promise { + const input = await this.findElement(Input.locators.Input.inputBox).findElement(Input.locators.Input.input); + const first = await this.findElements(Input.locators.Input.quickPickPosition(1)); + if (first.length < 1) { + await this.resetPosition(); + } + let endReached = false; - /** - * Scroll through the quick picks to find an item by the name or index - * @param indexOrText index (number) or text (string) of the item to search by - * @returns Promise resolvnig to QuickPickItem if found, to undefined otherwise - */ - async findQuickPick(indexOrText: string | number): Promise { - const input = await this.findElement(Input.locators.Input.inputBox) - .findElement(Input.locators.Input.input); - const first = await this.findElements(Input.locators.Input.quickPickPosition(1)); - if (first.length < 1) { - await this.resetPosition(); - } - let endReached = false; + while (!endReached) { + const picks = await this.getQuickPicks(); + for (const pick of picks) { + const lastRow = await this.findElements(Input.locators.DefaultTreeSection.lastRow); + if (lastRow.length > 0) { + endReached = true; + } else if ((await pick.getAttribute('aria-posinset')) === (await pick.getAttribute('aria-setsize'))) { + endReached = true; + } + if (typeof indexOrText === 'string') { + const text = await pick.getLabel(); + if (text.indexOf(indexOrText) > -1) { + return pick; + } + } else if (indexOrText === pick.getIndex()) { + return pick; + } + } + if (!endReached) { + await input.sendKeys(Key.PAGE_DOWN); + } + } + return undefined; + } - while(!endReached) { - const picks = await this.getQuickPicks(); - for (const pick of picks) { - const lastRow = await this.findElements(Input.locators.DefaultTreeSection.lastRow); - if (lastRow.length > 0) { - endReached = true; - } else if (await pick.getAttribute('aria-posinset') === await pick.getAttribute('aria-setsize')) { - endReached = true; - } - if (typeof indexOrText === 'string') { - const text = await pick.getLabel(); - if (text.indexOf(indexOrText) > -1) { - return pick; - } - } else if (indexOrText === pick.getIndex()){ - return pick; - } - } - if (!endReached) { - await input.sendKeys(Key.PAGE_DOWN); - } - } - return undefined; - } + /** + * Retrieve the title of an input box if it has one + * @returns Promise resolving to title if it exists, to undefined otherwise + */ + async getTitle(): Promise { + const titleBar = await this.findElements(Input.locators.Input.titleBar); + if (titleBar.length > 0 && (await titleBar[0].isDisplayed())) { + return (await titleBar[0].findElement(Input.locators.Input.title)).getText(); + } + } - /** - * Retrieve the title of an input box if it has one - * @returns Promise resolving to title if it exists, to undefined otherwise - */ - async getTitle(): Promise { - const titleBar = await this.findElements(Input.locators.Input.titleBar); - if (titleBar.length > 0 && await titleBar[0].isDisplayed()) { - return (await titleBar[0].findElement(Input.locators.Input.title)).getText(); - } - } + /** + * Click on the back button if it exists + * @returns Promise resolving to true if a button was clicked, to false otherwise + */ + async back(): Promise { + const titleBar = await this.findElements(Input.locators.Input.titleBar); + if (titleBar.length > 0 && (await titleBar[0].isDisplayed())) { + const backBtn = await titleBar[0].findElements(Input.locators.Input.backButton); + if (backBtn.length > 0 && (await backBtn[0].isEnabled())) { + await backBtn[0].click(); + return true; + } + } + return false; + } - /** - * Click on the back button if it exists - * @returns Promise resolving to true if a button was clicked, to false otherwise - */ - async back(): Promise { - const titleBar = await this.findElements(Input.locators.Input.titleBar); - if (titleBar.length > 0 && await titleBar[0].isDisplayed()) { - const backBtn = await titleBar[0].findElements(Input.locators.Input.backButton); - if (backBtn.length > 0 && await backBtn[0].isEnabled()) { - await backBtn[0].click(); - return true; - } - } - return false; - } + /** + * Find whether the input box has an active progress bar + * @returns Promise resolving to true/false + */ + abstract hasProgress(): Promise; - /** - * Find whether the input box has an active progress bar - * @returns Promise resolving to true/false - */ - abstract hasProgress(): Promise; + /** + * Retrieve the quick pick items currently available in the DOM + * (visible in the quick pick menu) + * @returns Promise resolving to array of QuickPickItem objects + */ + abstract getQuickPicks(): Promise; - /** - * Retrieve the quick pick items currently available in the DOM - * (visible in the quick pick menu) - * @returns Promise resolving to array of QuickPickItem objects - */ - abstract getQuickPicks(): Promise; - - private async resetPosition(): Promise { - const text = await this.getText(); - await this.clear(); - await this.setText(text); - } + private async resetPosition(): Promise { + const text = await this.getText(); + await this.clear(); + await this.setText(text); + } } /** * Page object representing a quick pick option in the input box */ export class QuickPickItem extends AbstractElement { - private index: number; + private index: number; - constructor(index: number, input: Input, isMultiSelect = false) { - let locator = Input.locators.Input.quickPickIndex(index); - if (input instanceof QuickOpenBox) { - locator = Input.locators.Input.quickPickPosition(index); - } + constructor(index: number, input: Input, isMultiSelect = false) { + let locator = Input.locators.Input.quickPickIndex(index); + if (input instanceof QuickOpenBox) { + locator = Input.locators.Input.quickPickPosition(index); + } - if (isMultiSelect) { - locator = Input.locators.Input.multiSelectIndex(index); - } + if (isMultiSelect) { + locator = Input.locators.Input.multiSelectIndex(index); + } - super(locator, input); - this.index = index; - } + super(locator, input); + this.index = index; + } - /** - * Get the label of the quick pick item - */ - async getLabel(): Promise { - return await this.findElement(Input.locators.Input.quickPickLabel).getText(); - } + /** + * Get the label of the quick pick item + */ + async getLabel(): Promise { + return await this.findElement(Input.locators.Input.quickPickLabel).getText(); + } - /** - * Get the description of the quick pick item - */ - async getDescription(): Promise { - try { - return await this.findElement(Input.locators.Input.quickPickDescription).getText(); - } catch (err) { - return undefined; - } - } + /** + * Get the description of the quick pick item + */ + async getDescription(): Promise { + try { + return await this.findElement(Input.locators.Input.quickPickDescription).getText(); + } catch (err) { + return undefined; + } + } - /** - * Get the index of the quick pick item - */ - getIndex(): number { - return this.index; - } + /** + * Get the index of the quick pick item + */ + getIndex(): number { + return this.index; + } - /** - * Select (click) the quick pick item - * @returns Promise resolving when the item has been clicked - */ - async select(): Promise { - await this.click(); - } -} \ No newline at end of file + /** + * Select (click) the quick pick item + * @returns Promise resolving when the item has been clicked + */ + async select(): Promise { + await this.click(); + } +} diff --git a/packages/page-objects/src/components/workbench/input/InputBox.ts b/packages/page-objects/src/components/workbench/input/InputBox.ts index b9847627d..5f0c4f1b6 100644 --- a/packages/page-objects/src/components/workbench/input/InputBox.ts +++ b/packages/page-objects/src/components/workbench/input/InputBox.ts @@ -15,84 +15,83 @@ * limitations under the License. */ -import { Input, QuickPickItem } from "../../.."; -import { until } from "selenium-webdriver"; +import { Input, QuickPickItem } from '../../..'; +import { until } from 'selenium-webdriver'; /** * Plain input box variation of the input page object */ export class InputBox extends Input { - constructor() { - super(InputBox.locators.InputBox.constructor, InputBox.locators.Workbench.constructor); - } + constructor() { + super(InputBox.locators.InputBox.constructor, InputBox.locators.Workbench.constructor); + } - /** - * Construct a new InputBox instance after waiting for its underlying element to exist - * Use when an input box is scheduled to appear. - * @param timeout max time to wait in milliseconds, default 5000 - */ - static async create(timeout: number = 5000): Promise { - await InputBox.driver.wait(until.elementLocated(InputBox.locators.InputBox.constructor), timeout); - return new InputBox().wait(); - } + /** + * Construct a new InputBox instance after waiting for its underlying element to exist + * Use when an input box is scheduled to appear. + * @param timeout max time to wait in milliseconds, default 5000 + */ + static async create(timeout: number = 5000): Promise { + await InputBox.driver.wait(until.elementLocated(InputBox.locators.InputBox.constructor), timeout); + return new InputBox().wait(); + } - /** - * Get the message below the input field - */ - async getMessage(): Promise { - return await this.findElement(InputBox.locators.InputBox.message).getText(); - } + /** + * Get the message below the input field + */ + async getMessage(): Promise { + return await this.findElement(InputBox.locators.InputBox.message).getText(); + } - async hasProgress(): Promise { - const klass = await this.findElement(InputBox.locators.InputBox.progress) - .getAttribute('class'); - return klass.indexOf('done') < 0; - } + async hasProgress(): Promise { + const klass = await this.findElement(InputBox.locators.InputBox.progress).getAttribute('class'); + return klass.indexOf('done') < 0; + } - async getQuickPicks(): Promise { - const picks: QuickPickItem[] = []; - const elements = await this.findElement(InputBox.locators.InputBox.quickList) - .findElement(InputBox.locators.InputBox.rows) - .findElements(InputBox.locators.InputBox.row); - - for (const element of elements) { - if (await element.isDisplayed()) { - picks.push(await new QuickPickItem(+await element.getAttribute('data-index'), this).wait()); - } - } - return picks; - } + async getQuickPicks(): Promise { + const picks: QuickPickItem[] = []; + const elements = await this.findElement(InputBox.locators.InputBox.quickList) + .findElement(InputBox.locators.InputBox.rows) + .findElements(InputBox.locators.InputBox.row); - async getCheckboxes(): Promise { - const picks: QuickPickItem[] = []; - const elements = await this.findElement(InputBox.locators.InputBox.quickList) - .findElement(InputBox.locators.InputBox.rows) - .findElements(InputBox.locators.InputBox.row); + for (const element of elements) { + if (await element.isDisplayed()) { + picks.push(await new QuickPickItem(+(await element.getAttribute('data-index')), this).wait()); + } + } + return picks; + } - for (const element of elements) { - if (await element.isDisplayed()) { - picks.push(await new QuickPickItem(+await element.getAttribute('data-index'), this, true).wait()); - } - } + async getCheckboxes(): Promise { + const picks: QuickPickItem[] = []; + const elements = await this.findElement(InputBox.locators.InputBox.quickList) + .findElement(InputBox.locators.InputBox.rows) + .findElements(InputBox.locators.InputBox.row); - return picks; - } + for (const element of elements) { + if (await element.isDisplayed()) { + picks.push(await new QuickPickItem(+(await element.getAttribute('data-index')), this, true).wait()); + } + } - /** - * Find whether the input is showing an error - * @returns Promise resolving to notification message - */ - async hasError(): Promise { - const klass = await this.findElement(InputBox.locators.Input.inputBox).getAttribute('class'); - return klass.indexOf('error') > -1; - } + return picks; + } - /** - * Check if the input field is masked (input type password) - * @returns Promise resolving to notification message - */ - async isPassword(): Promise { - const input = await this.findElement(InputBox.locators.Input.input); - return await input.getAttribute('type') === 'password'; - } -} \ No newline at end of file + /** + * Find whether the input is showing an error + * @returns Promise resolving to notification message + */ + async hasError(): Promise { + const klass = await this.findElement(InputBox.locators.Input.inputBox).getAttribute('class'); + return klass.indexOf('error') > -1; + } + + /** + * Check if the input field is masked (input type password) + * @returns Promise resolving to notification message + */ + async isPassword(): Promise { + const input = await this.findElement(InputBox.locators.Input.input); + return (await input.getAttribute('type')) === 'password'; + } +} diff --git a/packages/page-objects/src/components/workbench/input/QuickOpenBox.ts b/packages/page-objects/src/components/workbench/input/QuickOpenBox.ts index c3ae09f17..ad26d07fd 100644 --- a/packages/page-objects/src/components/workbench/input/QuickOpenBox.ts +++ b/packages/page-objects/src/components/workbench/input/QuickOpenBox.ts @@ -15,43 +15,42 @@ * limitations under the License. */ -import { Input, QuickPickItem } from "../../.."; -import { until } from "selenium-webdriver"; +import { Input, QuickPickItem } from '../../..'; +import { until } from 'selenium-webdriver'; /** * @deprecated as of VS Code 1.44.0, quick open box has been replaced with input box * The quick open box variation of the input */ export class QuickOpenBox extends Input { - constructor() { - super(QuickOpenBox.locators.QuickOpenBox.constructor, QuickOpenBox.locators.Workbench.constructor); - } + constructor() { + super(QuickOpenBox.locators.QuickOpenBox.constructor, QuickOpenBox.locators.Workbench.constructor); + } - /** - * Construct a new QuickOpenBox instance after waiting for its underlying element to exist - * Use when a quick open box is scheduled to appear. - */ - static async create(): Promise { - await QuickOpenBox.driver.wait(until.elementLocated(QuickOpenBox.locators.QuickOpenBox.constructor)); - return new QuickOpenBox().wait(); - } + /** + * Construct a new QuickOpenBox instance after waiting for its underlying element to exist + * Use when a quick open box is scheduled to appear. + */ + static async create(): Promise { + await QuickOpenBox.driver.wait(until.elementLocated(QuickOpenBox.locators.QuickOpenBox.constructor)); + return new QuickOpenBox().wait(); + } - async hasProgress(): Promise { - const klass = await this.findElement(QuickOpenBox.locators.QuickOpenBox.progress) - .getAttribute('class'); - return klass.indexOf('done') < 0; - } + async hasProgress(): Promise { + const klass = await this.findElement(QuickOpenBox.locators.QuickOpenBox.progress).getAttribute('class'); + return klass.indexOf('done') < 0; + } - async getQuickPicks(): Promise { - const picks: QuickPickItem[] = []; - const tree = await this.getDriver().wait(until.elementLocated(QuickOpenBox.locators.QuickOpenBox.quickList), 1000); - const elements = await tree.findElements(QuickOpenBox.locators.QuickOpenBox.row); - for (const element of elements) { - const index = +await element.getAttribute('aria-posinset'); - if (await element.isDisplayed()) { - picks.push(await new QuickPickItem(index, this).wait()); - } - } - return picks; - } -} \ No newline at end of file + async getQuickPicks(): Promise { + const picks: QuickPickItem[] = []; + const tree = await this.getDriver().wait(until.elementLocated(QuickOpenBox.locators.QuickOpenBox.quickList), 1000); + const elements = await tree.findElements(QuickOpenBox.locators.QuickOpenBox.row); + for (const element of elements) { + const index = +(await element.getAttribute('aria-posinset')); + if (await element.isDisplayed()) { + picks.push(await new QuickPickItem(index, this).wait()); + } + } + return picks; + } +} diff --git a/packages/page-objects/src/conditions/WaitForAttribute.ts b/packages/page-objects/src/conditions/WaitForAttribute.ts index fa484009c..3012de3d8 100644 --- a/packages/page-objects/src/conditions/WaitForAttribute.ts +++ b/packages/page-objects/src/conditions/WaitForAttribute.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { WebElement } from "selenium-webdriver"; +import { WebElement } from 'selenium-webdriver'; /** * Condition to wait until an element's attribute has a specified value @@ -24,8 +24,8 @@ import { WebElement } from "selenium-webdriver"; * @param value value to wait for the attribute to have */ export function waitForAttributeValue(element: WebElement, attribute: string, value: string) { - return async () => { - const result = await element.getAttribute(attribute); - return result === value; - }; -} \ No newline at end of file + return async () => { + const result = await element.getAttribute(attribute); + return result === value; + }; +} diff --git a/packages/page-objects/src/errors/NullAttributeError.ts b/packages/page-objects/src/errors/NullAttributeError.ts index 9656eec69..2765d4f67 100644 --- a/packages/page-objects/src/errors/NullAttributeError.ts +++ b/packages/page-objects/src/errors/NullAttributeError.ts @@ -16,8 +16,8 @@ */ export class NullAttributeError extends Error { - constructor(message?: string) { - super(message); - this.name = 'NullAttributeError'; - } + constructor(message?: string) { + super(message); + this.name = 'NullAttributeError'; + } } diff --git a/packages/page-objects/src/index.ts b/packages/page-objects/src/index.ts index bc856781c..b7eaa3216 100644 --- a/packages/page-objects/src/index.ts +++ b/packages/page-objects/src/index.ts @@ -95,7 +95,7 @@ export * from './conditions/WaitForAttribute'; * @param browserID identifier/name of the browser (i.e. vscode) */ export function initPageObjects(currentVersion: string, baseVersion: string, locatorFolder: string, driver: WebDriver, browserID: string) { - const locators = new LocatorLoader(currentVersion, baseVersion, locatorFolder).loadLocators(); + const locators = new LocatorLoader(currentVersion, baseVersion, locatorFolder).loadLocators(); - AbstractElement.init(locators, driver, browserID, currentVersion); -} \ No newline at end of file + AbstractElement.init(locators, driver, browserID, currentVersion); +} diff --git a/packages/page-objects/src/locators/loader.ts b/packages/page-objects/src/locators/loader.ts index a784b1c24..4e9036976 100644 --- a/packages/page-objects/src/locators/loader.ts +++ b/packages/page-objects/src/locators/loader.ts @@ -26,83 +26,81 @@ import clone from 'clone-deep'; * Utility for loading locators for a given vscode version */ export class LocatorLoader { - private baseVersion: string; - private baseFolder: string; - private version: string; - private locators: Locators; + private baseVersion: string; + private baseFolder: string; + private version: string; + private locators: Locators; - /** - * Construct new loader for a given vscode version - * @param version select version of vscode - */ - constructor(version: string, baseVersion: string, baseFolder: string) { - this.version = version; - if (version.endsWith('-insider')) { - this.version = version.substring(0, version.indexOf('-insider')); - } - this.baseVersion = baseVersion; - this.baseFolder = path.resolve(baseFolder); - const temp = require(path.resolve(baseFolder, baseVersion)); - this.locators = temp.locators as Locators; - } + /** + * Construct new loader for a given vscode version + * @param version select version of vscode + */ + constructor(version: string, baseVersion: string, baseFolder: string) { + this.version = version; + if (version.endsWith('-insider')) { + this.version = version.substring(0, version.indexOf('-insider')); + } + this.baseVersion = baseVersion; + this.baseFolder = path.resolve(baseFolder); + const temp = require(path.resolve(baseFolder, baseVersion)); + this.locators = temp.locators as Locators; + } - /** - * Loads locators for the selected vscode version - * @returns object containing all locators - */ - loadLocators(): Locators { - let versions = fs.readdirSync(this.baseFolder) - .filter((file) => file.endsWith('.js')) - .map((file) => path.basename(file, '.js')); - - if (compareVersions(this.baseVersion, this.version) === 0) { - return this.locators; - } + /** + * Loads locators for the selected vscode version + * @returns object containing all locators + */ + loadLocators(): Locators { + let versions = fs + .readdirSync(this.baseFolder) + .filter((file) => file.endsWith('.js')) + .map((file) => path.basename(file, '.js')); - if (compareVersions(this.baseVersion, this.version) < 0) { - versions = versions.filter((ver) => - compareVersions(this.baseVersion, ver) < 0 && - compareVersions(ver, this.version) <= 0) - .sort(compareVersions); - } else { - versions = versions.filter((ver) => - compareVersions(this.baseVersion, ver) > 0 && - compareVersions(ver, this.version) >= 0) - .sort(compareVersions).reverse(); - } + if (compareVersions(this.baseVersion, this.version) === 0) { + return this.locators; + } - for (const version of versions) { - const diff = require(path.join(this.baseFolder, version)).diff as LocatorDiff; + if (compareVersions(this.baseVersion, this.version) < 0) { + versions = versions.filter((ver) => compareVersions(this.baseVersion, ver) < 0 && compareVersions(ver, this.version) <= 0).sort(compareVersions); + } else { + versions = versions + .filter((ver) => compareVersions(this.baseVersion, ver) > 0 && compareVersions(ver, this.version) >= 0) + .sort(compareVersions) + .reverse(); + } - const newLocators: Merge> = mergeLocators(this.locators, diff); - this.locators = newLocators as RequiredDeep>>; - } - return this.locators; - } + for (const version of versions) { + const diff = require(path.join(this.baseFolder, version)).diff as LocatorDiff; + + const newLocators: Merge> = mergeLocators(this.locators, diff); + this.locators = newLocators as RequiredDeep>>; + } + return this.locators; + } } function mergeLocators(original: Locators, diff: LocatorDiff): Locators { - const target = clone(original); - const targetDiff = diff.locators; + const target = clone(original); + const targetDiff = diff.locators; - merge(target, targetDiff) as Locators; - return target; + merge(target, targetDiff) as Locators; + return target; } function merge(target: any, obj: any) { - for (const key in obj) { - if (key === '__proto__' || !Object.prototype.hasOwnProperty.call(obj, key)) { - continue; - } + for (const key in obj) { + if (key === '__proto__' || !Object.prototype.hasOwnProperty.call(obj, key)) { + continue; + } - const oldVal = obj[key]; - const newVal = target[key]; + const oldVal = obj[key]; + const newVal = target[key]; - if (typeof(newVal) === 'object' && typeof(oldVal) === 'object') { - target[key] = merge(newVal, oldVal); - } else { - target[key] = clone(oldVal); - } - } - return target; -} \ No newline at end of file + if (typeof newVal === 'object' && typeof oldVal === 'object') { + target[key] = merge(newVal, oldVal); + } else { + target[key] = clone(oldVal); + } + } + return target; +} diff --git a/packages/page-objects/src/locators/locators.ts b/packages/page-objects/src/locators/locators.ts index 797f661f6..966df2348 100644 --- a/packages/page-objects/src/locators/locators.ts +++ b/packages/page-objects/src/locators/locators.ts @@ -15,9 +15,9 @@ * limitations under the License. */ -import { By, WebElement } from "selenium-webdriver"; +import { By, WebElement } from 'selenium-webdriver'; import { PartialDeep } from 'type-fest'; -import { ViewSection } from "../components/sidebar/ViewSection"; +import { ViewSection } from '../components/sidebar/ViewSection'; type WebElementFunction = (element: E) => T | PromiseLike; type LocatorAwareWebElementFunction = (element: WebElement, locator: Locators) => T | PromiseLike; @@ -26,529 +26,529 @@ type LocatorAwareWebElementFunction = (element: WebElement, locator: Locators * Type definitions for all used locators */ export interface Locators { - // AbstractElement properties - AbstractElement: { - enabled: WebElementFunction; - selected: WebElementFunction; - }; + // AbstractElement properties + AbstractElement: { + enabled: WebElementFunction; + selected: WebElementFunction; + }; - // Activity Bar - ActivityBar: { - constructor: By; - viewContainer: By; - label: string; - actionsContainer: By; - actionItem: By; - }; - ViewControl: { - attribute: string; - klass: string; - scmId: By; - debugId: By; - badge: By; - }; + // Activity Bar + ActivityBar: { + constructor: By; + viewContainer: By; + label: string; + actionsContainer: By; + actionItem: By; + }; + ViewControl: { + attribute: string; + klass: string; + scmId: By; + debugId: By; + badge: By; + }; - // Bottom Bar - BottomBarPanel: { - constructor: By; - problemsTab: string; - outputTab: string; - debugTab: string; - terminalTab: string; - maximize: string; - restore: string; - close: string; - tabContainer: By; - tab: (title: string) => By; - actions: By; - globalActions: By; - action: (label: string) => By; - closeAction: By; - }; - BottomBarViews: { - actionsContainer: (label: string) => By; - channelOption: By; - channelCombo: By; - channelText: By; - channelRow: By; - textArea: By; - clearText: By; - }; - ProblemsView: { - constructor: By; - markersFilter: By; - input: By; - collapseAll: By; - markerRow: By; - rowLabel: string; - label: By; - markerTwistie: By; - changeCount: By; - }; - TerminalView: { - constructor: By; - actionsLabel: string; - textArea: By; - killTerminal: By; - newTerminal: By; - tabList: By; - singleTab: By; - selectedRow: By; - row: By; - newCommand: string; - }; - DebugConsoleView: { - constructor: By; - }; - OutputView: { - constructor: By; - actionsLabel: string; - optionByName: (name: string) => By; - }; - WebviewView: { - iframe: By; - }; + // Bottom Bar + BottomBarPanel: { + constructor: By; + problemsTab: string; + outputTab: string; + debugTab: string; + terminalTab: string; + maximize: string; + restore: string; + close: string; + tabContainer: By; + tab: (title: string) => By; + actions: By; + globalActions: By; + action: (label: string) => By; + closeAction: By; + }; + BottomBarViews: { + actionsContainer: (label: string) => By; + channelOption: By; + channelCombo: By; + channelText: By; + channelRow: By; + textArea: By; + clearText: By; + }; + ProblemsView: { + constructor: By; + markersFilter: By; + input: By; + collapseAll: By; + markerRow: By; + rowLabel: string; + label: By; + markerTwistie: By; + changeCount: By; + }; + TerminalView: { + constructor: By; + actionsLabel: string; + textArea: By; + killTerminal: By; + newTerminal: By; + tabList: By; + singleTab: By; + selectedRow: By; + row: By; + newCommand: string; + }; + DebugConsoleView: { + constructor: By; + }; + OutputView: { + constructor: By; + actionsLabel: string; + optionByName: (name: string) => By; + }; + WebviewView: { + iframe: By; + }; - // Editors - EditorView: { - constructor: By; - editorGroup: By; - settingsEditor: By; - webView: By; - diffEditor: By; - tab: By; - closeTab: By; - tabTitle: string; - tabSeparator: string; - tabLabel: string; - actionContainer: By; - actionItem: By; - attribute: string; - }; - Editor: { - constructor: By; - inputArea: By; - title: By; - }; - TextEditor: { - activeTab: By; - breakpoint: { - pauseSelector: By; - generalSelector: By; - properties: { - enabled: WebElementFunction; - line: { - selector: By; - number: WebElementFunction; - }; - paused: WebElementFunction; - }; - }; - editorContainer: By; - dataUri: string; - formatDoc: string; - marginArea: By; - lineNumber: (line: number) => By; - lineOverlay: (line: number) => By; - debugHint: By; - selection: By; - findWidget: By; - }; - FindWidget: { - toggleReplace: By; - replacePart: By; - findPart: By; - matchCount: By; - input: By; - content: By; - button: (title: string) => By; - checkbox: (title: string) => By; - }; - ContentAssist: { - constructor: By; - message: By; - itemRows: By; - itemRow: By; - itemLabel: By; - itemText: By; - itemList: By; - firstItem: By; - }; - SettingsEditor: { - title: string; - itemRow: By; - header: By; - tabs: By; - actions: By; - action: (label: string) => By; - settingConstructor: (title: string, category: string) => By; - settingDescription: By; - settingLabel: By; - settingCategory: By; - comboSetting: By; - comboOption: By; - comboValue: string; - textSetting: By; - checkboxSetting: By; - checkboxChecked: string; - linkButton: By; - itemCount: By; - arraySetting: By; - arrayRoot: By; - arrayRow: By; - arrayRowValue: By; - arrayNewRow: By; - arrayEditRow: By; - arrayBtnConstructor: (label: string) => By; - arraySettingItem: { - btnConstructor: (label: string) => By; - }; - }; - DiffEditor: { - originalEditor: By; - modifiedEditor: By; - }; - WebView: { - iframe: By; - activeFrame: By; - container: (id: string) => By; - attribute: string; - }; + // Editors + EditorView: { + constructor: By; + editorGroup: By; + settingsEditor: By; + webView: By; + diffEditor: By; + tab: By; + closeTab: By; + tabTitle: string; + tabSeparator: string; + tabLabel: string; + actionContainer: By; + actionItem: By; + attribute: string; + }; + Editor: { + constructor: By; + inputArea: By; + title: By; + }; + TextEditor: { + activeTab: By; + breakpoint: { + pauseSelector: By; + generalSelector: By; + properties: { + enabled: WebElementFunction; + line: { + selector: By; + number: WebElementFunction; + }; + paused: WebElementFunction; + }; + }; + editorContainer: By; + dataUri: string; + formatDoc: string; + marginArea: By; + lineNumber: (line: number) => By; + lineOverlay: (line: number) => By; + debugHint: By; + selection: By; + findWidget: By; + }; + FindWidget: { + toggleReplace: By; + replacePart: By; + findPart: By; + matchCount: By; + input: By; + content: By; + button: (title: string) => By; + checkbox: (title: string) => By; + }; + ContentAssist: { + constructor: By; + message: By; + itemRows: By; + itemRow: By; + itemLabel: By; + itemText: By; + itemList: By; + firstItem: By; + }; + SettingsEditor: { + title: string; + itemRow: By; + header: By; + tabs: By; + actions: By; + action: (label: string) => By; + settingConstructor: (title: string, category: string) => By; + settingDescription: By; + settingLabel: By; + settingCategory: By; + comboSetting: By; + comboOption: By; + comboValue: string; + textSetting: By; + checkboxSetting: By; + checkboxChecked: string; + linkButton: By; + itemCount: By; + arraySetting: By; + arrayRoot: By; + arrayRow: By; + arrayRowValue: By; + arrayNewRow: By; + arrayEditRow: By; + arrayBtnConstructor: (label: string) => By; + arraySettingItem: { + btnConstructor: (label: string) => By; + }; + }; + DiffEditor: { + originalEditor: By; + modifiedEditor: By; + }; + WebView: { + iframe: By; + activeFrame: By; + container: (id: string) => By; + attribute: string; + }; - // Menus - ContextMenu: { - contextView: By; - constructor: By; - itemConstructor: (label: string) => By; - itemElement: By; - itemLabel: By; - itemText: string; - itemNesting: By; - viewBlock: By; - }; - TitleBar: { - constructor: By; - itemConstructor: (label: string) => By; - itemElement: By; - itemLabel: string; - title: By; - }; - WindowControls: { - constructor: By; - minimize: By; - maximize: By; - restore: By; - close: By; - }; + // Menus + ContextMenu: { + contextView: By; + constructor: By; + itemConstructor: (label: string) => By; + itemElement: By; + itemLabel: By; + itemText: string; + itemNesting: By; + viewBlock: By; + }; + TitleBar: { + constructor: By; + itemConstructor: (label: string) => By; + itemElement: By; + itemLabel: string; + title: By; + }; + WindowControls: { + constructor: By; + minimize: By; + maximize: By; + restore: By; + close: By; + }; - // Side Bar - SideBarView: { - constructor: By; - }; - ViewTitlePart: { - constructor: By; - title: By; - action: By; - actionLabel: string; - actionConstructor: (title: string) => By; - }; - ViewContent: { - constructor: By; - progress: By; - section: By; - defaultView: By; - extensionsView: By; - }; - ViewSection: { - title: By; - titleText: string; - header: By; - headerExpanded: string; - actions: By; - actionConstructor: (label: string) => By; - button: By; - buttonLabel: string; - level: string; - index: string; - welcomeContent: By; - }; - TreeItem: { - actions: By; - actionLabel: By; - actionTitle: string; - twistie: By; - }; - DefaultTreeSection: { - itemRow: By; - itemLabel: string; - rowContainer: By; - rowWithLabel: (label: string) => By; - lastRow: By; - type: { - default: LocatorAwareWebElementFunction; - marketplace: { - extension: LocatorAwareWebElementFunction; - }; - }; - }; - DefaultTreeItem: { - ctor: (label: string) => By; - twistie: By; - tooltip: By; - labelAttribute: string; - }; - CustomTreeSection: { - itemRow: By; - itemLabel: By; - rowContainer: By; - rowWithLabel: (label: string) => By; - }; - CustomTreeItem: { - constructor: (label: string) => By; - expandedAttr: string; - expandedValue: string; - tooltipAttribute: string; - description: By; - }; - DebugBreakpointSection: { - predicate: WebElementFunction; - }; - BreakpointSectionItem: { - breakpoint: { - constructor: By; - }; - breakpointCheckbox: { - constructor: By; - value: WebElementFunction; - }; - label: { - constructor: By; - value: WebElementFunction; - }; - filePath: { - constructor: By; - value: WebElementFunction; - }; - lineNumber: { - constructor: By; - value: WebElementFunction; - }; - }; - DebugVariableSection: { - predicate: WebElementFunction; - }; - VariableSectionItem: { - label: WebElementFunction; - name: { - constructor: By; - value: WebElementFunction; - tooltip: WebElementFunction; - }; - value: { - constructor: By; - value: WebElementFunction; - tooltip: WebElementFunction; - }; - }; - ExtensionsViewSection: { - items: By; - itemRow: By; - itemTitle: By; - searchBox: By; - textContainer: By; - textField: By; - }; - ExtensionsViewItem: { - version: By; - author: By; - description: By; - install: By; - manage: By; - }; - ScmView: { - providerHeader: By; - providerRelative: By; - initButton: By; - providerTitle: By; - providerType: By; - action: By; - actionConstructor: (title: string) => By; - actionLabel: string; - inputField: By; - changeItem: By; - changeName: By; - changeCount: By; - changeLabel: By; - changeDesc: By; - resource: By; - changes: By; - stagedChanges: By; - expand: By; - more: By; - multiMore: By; - multiScmProvider: By; - singleScmProvider: By; - multiProviderItem: By; - itemLevel: (level: number) => By; - itemIndex: (index: number) => By; - }; - DebugView: { - launchCombo: By; - launchSelect: By; - launchOption: By; - optionByName: (name: string) => By; - startButton: By; - }; - DebugToolbar: { - ctor: By; - button: (title: string) => By; - }; + // Side Bar + SideBarView: { + constructor: By; + }; + ViewTitlePart: { + constructor: By; + title: By; + action: By; + actionLabel: string; + actionConstructor: (title: string) => By; + }; + ViewContent: { + constructor: By; + progress: By; + section: By; + defaultView: By; + extensionsView: By; + }; + ViewSection: { + title: By; + titleText: string; + header: By; + headerExpanded: string; + actions: By; + actionConstructor: (label: string) => By; + button: By; + buttonLabel: string; + level: string; + index: string; + welcomeContent: By; + }; + TreeItem: { + actions: By; + actionLabel: By; + actionTitle: string; + twistie: By; + }; + DefaultTreeSection: { + itemRow: By; + itemLabel: string; + rowContainer: By; + rowWithLabel: (label: string) => By; + lastRow: By; + type: { + default: LocatorAwareWebElementFunction; + marketplace: { + extension: LocatorAwareWebElementFunction; + }; + }; + }; + DefaultTreeItem: { + ctor: (label: string) => By; + twistie: By; + tooltip: By; + labelAttribute: string; + }; + CustomTreeSection: { + itemRow: By; + itemLabel: By; + rowContainer: By; + rowWithLabel: (label: string) => By; + }; + CustomTreeItem: { + constructor: (label: string) => By; + expandedAttr: string; + expandedValue: string; + tooltipAttribute: string; + description: By; + }; + DebugBreakpointSection: { + predicate: WebElementFunction; + }; + BreakpointSectionItem: { + breakpoint: { + constructor: By; + }; + breakpointCheckbox: { + constructor: By; + value: WebElementFunction; + }; + label: { + constructor: By; + value: WebElementFunction; + }; + filePath: { + constructor: By; + value: WebElementFunction; + }; + lineNumber: { + constructor: By; + value: WebElementFunction; + }; + }; + DebugVariableSection: { + predicate: WebElementFunction; + }; + VariableSectionItem: { + label: WebElementFunction; + name: { + constructor: By; + value: WebElementFunction; + tooltip: WebElementFunction; + }; + value: { + constructor: By; + value: WebElementFunction; + tooltip: WebElementFunction; + }; + }; + ExtensionsViewSection: { + items: By; + itemRow: By; + itemTitle: By; + searchBox: By; + textContainer: By; + textField: By; + }; + ExtensionsViewItem: { + version: By; + author: By; + description: By; + install: By; + manage: By; + }; + ScmView: { + providerHeader: By; + providerRelative: By; + initButton: By; + providerTitle: By; + providerType: By; + action: By; + actionConstructor: (title: string) => By; + actionLabel: string; + inputField: By; + changeItem: By; + changeName: By; + changeCount: By; + changeLabel: By; + changeDesc: By; + resource: By; + changes: By; + stagedChanges: By; + expand: By; + more: By; + multiMore: By; + multiScmProvider: By; + singleScmProvider: By; + multiProviderItem: By; + itemLevel: (level: number) => By; + itemIndex: (index: number) => By; + }; + DebugView: { + launchCombo: By; + launchSelect: By; + launchOption: By; + optionByName: (name: string) => By; + startButton: By; + }; + DebugToolbar: { + ctor: By; + button: (title: string) => By; + }; - // Status Bar - StatusBar: { - constructor: By; - language: By; - lines: By; - encoding: By; - indent: By; - selection: By; - notifications: By; - bell: By; - item: By; - itemTitle: string; - }; + // Status Bar + StatusBar: { + constructor: By; + language: By; + lines: By; + encoding: By; + indent: By; + selection: By; + notifications: By; + bell: By; + item: By; + itemTitle: string; + }; - // Workbench - Workbench: { - constructor: By; - notificationContainer: By; - notificationItem: By; - }; - Notification: { - message: By; - icon: By; - source: By; - progress: By; - dismiss: By; - expand: By; - actions: By; - action: By; - actionLabel: { - value: WebElementFunction; - }; - standalone: (id: string) => By; - standaloneContainer: By; - center: (index: number) => By; - buttonConstructor: (title: string) => By; - }; - NotificationsCenter: { - constructor: By; - close: By; - clear: By; - row: By; - }; + // Workbench + Workbench: { + constructor: By; + notificationContainer: By; + notificationItem: By; + }; + Notification: { + message: By; + icon: By; + source: By; + progress: By; + dismiss: By; + expand: By; + actions: By; + action: By; + actionLabel: { + value: WebElementFunction; + }; + standalone: (id: string) => By; + standaloneContainer: By; + center: (index: number) => By; + buttonConstructor: (title: string) => By; + }; + NotificationsCenter: { + constructor: By; + close: By; + clear: By; + row: By; + }; - // Inputs - Input: { - inputBox: By; - input: By; - quickPickIndex: (index: number) => By; - quickPickPosition: (index: number) => By; - quickPickLabel: By; - quickPickDescription: By; - quickPickSelectAll: By; - titleBar: By; - title: By; - backButton: By; - multiSelectIndex: (index: number) => By; - }; - InputBox: { - constructor: By; - message: By; - progress: By; - quickList: By; - rows: By; - row: By; - }; - QuickOpenBox: { - constructor: By; - progress: By; - quickList: By; - row: By; - }; + // Inputs + Input: { + inputBox: By; + input: By; + quickPickIndex: (index: number) => By; + quickPickPosition: (index: number) => By; + quickPickLabel: By; + quickPickDescription: By; + quickPickSelectAll: By; + titleBar: By; + title: By; + backButton: By; + multiSelectIndex: (index: number) => By; + }; + InputBox: { + constructor: By; + message: By; + progress: By; + quickList: By; + rows: By; + row: By; + }; + QuickOpenBox: { + constructor: By; + progress: By; + quickList: By; + row: By; + }; - // Dialogs - Dialog: { - constructor: By; - message: By; - details: By; - buttonContainer: By; - button: By; - closeButton: By; - buttonLabel: { - value: WebElementFunction; - }; - }; + // Dialogs + Dialog: { + constructor: By; + message: By; + details: By; + buttonContainer: By; + button: By; + closeButton: By; + buttonLabel: { + value: WebElementFunction; + }; + }; - WelcomeContent: { - button: By; - text: By; - buttonOrText: By; - }; + WelcomeContent: { + button: By; + text: By; + buttonOrText: By; + }; } /** * Definition for locator diff object */ export interface LocatorDiff { - locators: PartialDeep; - extras?: object; + locators: PartialDeep; + extras?: object; } -export function hasAttribute(attr: string, value?: string, locator?: By): ((el: WebElement) => Promise) { - return async (el: WebElement) => { - el = locator ? el.findElement(locator) : el; - const attrValue = await el.getAttribute(attr); - if (value === undefined) { - return attrValue !== null; - } - return attrValue === value; - }; +export function hasAttribute(attr: string, value?: string, locator?: By): (el: WebElement) => Promise { + return async (el: WebElement) => { + el = locator ? el.findElement(locator) : el; + const attrValue = await el.getAttribute(attr); + if (value === undefined) { + return attrValue !== null; + } + return attrValue === value; + }; } -export function hasClass(classOrPredicate: string | ((klass: string) => boolean), locator?: By): ((el: WebElement) => Promise) { - return async (el: WebElement) => { - el = locator ? el.findElement(locator) : el; - const klasses = await el.getAttribute('class'); - const segments = klasses?.split(/\s+/g); - const predicate = typeof classOrPredicate === 'string' ? ((klass: string) => klass === classOrPredicate) : (classOrPredicate); - return segments.find(predicate) !== undefined; - }; +export function hasClass(classOrPredicate: string | ((klass: string) => boolean), locator?: By): (el: WebElement) => Promise { + return async (el: WebElement) => { + el = locator ? el.findElement(locator) : el; + const klasses = await el.getAttribute('class'); + const segments = klasses?.split(/\s+/g); + const predicate = typeof classOrPredicate === 'string' ? (klass: string) => klass === classOrPredicate : classOrPredicate; + return segments.find(predicate) !== undefined; + }; } -export function hasNotClass(klass: string, locator?: By): ((el: WebElement) => Promise) { - return async (el: WebElement) => { - el = locator ? el.findElement(locator) : el; - return !(await hasClass(klass).call(undefined, el)); - }; +export function hasNotClass(klass: string, locator?: By): (el: WebElement) => Promise { + return async (el: WebElement) => { + el = locator ? el.findElement(locator) : el; + return !(await hasClass(klass).call(undefined, el)); + }; } -export function hasElement(locatorSelector: ((l: Locators) => By)): ((el: WebElement, locators: Locators) => Promise) { - return async (el: WebElement, locators: Locators) => { - return (await el.findElements(locatorSelector(locators))).length > 0; - }; +export function hasElement(locatorSelector: (l: Locators) => By): (el: WebElement, locators: Locators) => Promise { + return async (el: WebElement, locators: Locators) => { + return (await el.findElements(locatorSelector(locators))).length > 0; + }; } -export function fromAttribute(attribute: string, locator?: By): ((el: WebElement) => Promise) { - return async (el: WebElement) => { - el = locator ? el.findElement(locator) : el; - return el.getAttribute(attribute); - }; +export function fromAttribute(attribute: string, locator?: By): (el: WebElement) => Promise { + return async (el: WebElement) => { + el = locator ? el.findElement(locator) : el; + return el.getAttribute(attribute); + }; } -export function fromText(locator?: By): ((el: WebElement) => Promise) { - return async (el: WebElement) => { - el = locator ? el.findElement(locator) : el; - return el.getText(); - }; +export function fromText(locator?: By): (el: WebElement) => Promise { + return async (el: WebElement) => { + el = locator ? el.findElement(locator) : el; + return el.getText(); + }; } diff --git a/packages/page-objects/tsconfig.json b/packages/page-objects/tsconfig.json index b0677386b..42aa12949 100644 --- a/packages/page-objects/tsconfig.json +++ b/packages/page-objects/tsconfig.json @@ -4,7 +4,5 @@ "outDir": "out", "rootDir": "src" }, - "include": [ - "src" - ] -} \ No newline at end of file + "include": ["src"] +} diff --git a/tests/test-project/.mocharc-debug.js b/tests/test-project/.mocharc-debug.js index 54d8019ac..28792d56c 100644 --- a/tests/test-project/.mocharc-debug.js +++ b/tests/test-project/.mocharc-debug.js @@ -1,3 +1,3 @@ module.exports = { - timeout: 100000 -} \ No newline at end of file + timeout: 100000, +}; diff --git a/tests/test-project/.mocharc.js b/tests/test-project/.mocharc.js index 756b1d3c2..079f1662f 100644 --- a/tests/test-project/.mocharc.js +++ b/tests/test-project/.mocharc.js @@ -1,3 +1,3 @@ module.exports = { - timeout: 10000 -} \ No newline at end of file + timeout: 10000, +}; diff --git a/tests/test-project/.vscode/launch.json b/tests/test-project/.vscode/launch.json index 297614305..f55775ff7 100644 --- a/tests/test-project/.vscode/launch.json +++ b/tests/test-project/.vscode/launch.json @@ -3,38 +3,32 @@ // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 { - "version": "0.2.0", - "configurations": [ - { - "name": "Debug UI Tests", - "type": "node", - "request": "launch", - "program": "${workspaceFolder}/node_modules/.bin/extest", - "args": [ - "setup-and-run", - "${workspaceFolder}/out/src/test/**/scm*-test.js", - "-u", - "-s", - "~/test", - "--mocha_config", - "${workspaceFolder}/.mocharc-debug.js" - ], - "console": "integratedTerminal", - "internalConsoleOptions": "neverOpen", - }, - { - "name": "Run Extension", - "type": "extensionHost", - "request": "launch", - "args": [ - "--extensionDevelopmentPath=${workspaceFolder}" - ], - "outFiles": [ - "${workspaceFolder}/out/**/*.js" - ], - "preLaunchTask": "npm: watch" - } - ] - } - - \ No newline at end of file + "version": "0.2.0", + "configurations": [ + { + "name": "Debug UI Tests", + "type": "node", + "request": "launch", + "program": "${workspaceFolder}/node_modules/.bin/extest", + "args": [ + "setup-and-run", + "${workspaceFolder}/out/src/test/**/scm*-test.js", + "-u", + "-s", + "~/test", + "--mocha_config", + "${workspaceFolder}/.mocharc-debug.js" + ], + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen" + }, + { + "name": "Run Extension", + "type": "extensionHost", + "request": "launch", + "args": ["--extensionDevelopmentPath=${workspaceFolder}"], + "outFiles": ["${workspaceFolder}/out/**/*.js"], + "preLaunchTask": "npm: watch" + } + ] +} diff --git a/tests/test-project/.vscode/settings.json b/tests/test-project/.vscode/settings.json index 4c5dc4bc5..2cf486489 100644 --- a/tests/test-project/.vscode/settings.json +++ b/tests/test-project/.vscode/settings.json @@ -1,3 +1,3 @@ { - "window.title": "${activeEditorShort}${separator}folder: ${rootPath}" -} \ No newline at end of file + "window.title": "${activeEditorShort}${separator}folder: ${rootPath}" +} diff --git a/tests/test-project/.vscode/tasks.json b/tests/test-project/.vscode/tasks.json index 3b17e53b6..078ff7e01 100644 --- a/tests/test-project/.vscode/tasks.json +++ b/tests/test-project/.vscode/tasks.json @@ -1,20 +1,20 @@ // See https://go.microsoft.com/fwlink/?LinkId=733558 // for the documentation about the tasks.json format { - "version": "2.0.0", - "tasks": [ - { - "type": "npm", - "script": "watch", - "problemMatcher": "$tsc-watch", - "isBackground": true, - "presentation": { - "reveal": "never" - }, - "group": { - "kind": "build", - "isDefault": true - } - } - ] + "version": "2.0.0", + "tasks": [ + { + "type": "npm", + "script": "watch", + "problemMatcher": "$tsc-watch", + "isBackground": true, + "presentation": { + "reveal": "never" + }, + "group": { + "kind": "build", + "isDefault": true + } + } + ] } diff --git a/tests/test-project/media/catScratch.js b/tests/test-project/media/catScratch.js index 8830ba64d..7962e5f0c 100644 --- a/tests/test-project/media/catScratch.js +++ b/tests/test-project/media/catScratch.js @@ -1,100 +1,98 @@ -// @ts-check - -// Script run within the webview itself. -(function () { - - // Get a reference to the VS Code webview api. - // We use this API to post messages back to our extension. - - // @ts-ignore - const vscode = acquireVsCodeApi(); - - - const notesContainer = /** @type {HTMLElement} */ (document.querySelector('.notes')); - - const addButtonContainer = document.querySelector('.add-button'); - addButtonContainer.querySelector('button').addEventListener('click', () => { - vscode.postMessage({ - type: 'add' - }); - }) - - const errorContainer = document.createElement('div'); - document.body.appendChild(errorContainer); - errorContainer.className = 'error' - errorContainer.style.display = 'none' - - /** - * Render the document in the webview. - */ - function updateContent(/** @type {string} */ text) { - let json; - try { - if (!text) { - text = '{}'; - } - json = JSON.parse(text); - } catch { - notesContainer.style.display = 'none'; - errorContainer.innerText = 'Error: Document is not valid json'; - errorContainer.style.display = ''; - return; - } - notesContainer.style.display = ''; - errorContainer.style.display = 'none'; - - // Render the scratches - notesContainer.innerHTML = ''; - for (const note of json.scratches || []) { - const element = document.createElement('div'); - element.className = 'note'; - notesContainer.appendChild(element); - - const text = document.createElement('div'); - text.className = 'text'; - const textContent = document.createElement('span'); - textContent.innerText = note.text; - text.appendChild(textContent); - element.appendChild(text); - - const created = document.createElement('div'); - created.className = 'created'; - created.innerText = new Date(note.created).toUTCString(); - element.appendChild(created); - - const deleteButton = document.createElement('button'); - deleteButton.className = 'delete-button'; - deleteButton.addEventListener('click', () => { - vscode.postMessage({ type: 'delete', id: note.id, }); - }); - element.appendChild(deleteButton); - } - - notesContainer.appendChild(addButtonContainer); - } - - // Handle messages sent from the extension to the webview - window.addEventListener('message', event => { - const message = event.data; // The json data that the extension sent - switch (message.type) { - case 'update': - const text = message.text; - - // Update our webview's content - updateContent(text); - - // Then persist state information. - // This state is returned in the call to `vscode.getState` below when a webview is reloaded. - vscode.setState({ text }); - - return; - } - }); - - // Webviews are normally torn down when not visible and re-created when they become visible again. - // State lets us save information across these re-loads - const state = vscode.getState(); - if (state) { - updateContent(state.text); - } -}()); +// @ts-check + +// Script run within the webview itself. +(function () { + // Get a reference to the VS Code webview api. + // We use this API to post messages back to our extension. + + // @ts-ignore + const vscode = acquireVsCodeApi(); + + const notesContainer = /** @type {HTMLElement} */ (document.querySelector('.notes')); + + const addButtonContainer = document.querySelector('.add-button'); + addButtonContainer.querySelector('button').addEventListener('click', () => { + vscode.postMessage({ + type: 'add', + }); + }); + + const errorContainer = document.createElement('div'); + document.body.appendChild(errorContainer); + errorContainer.className = 'error'; + errorContainer.style.display = 'none'; + + /** + * Render the document in the webview. + */ + function updateContent(/** @type {string} */ text) { + let json; + try { + if (!text) { + text = '{}'; + } + json = JSON.parse(text); + } catch { + notesContainer.style.display = 'none'; + errorContainer.innerText = 'Error: Document is not valid json'; + errorContainer.style.display = ''; + return; + } + notesContainer.style.display = ''; + errorContainer.style.display = 'none'; + + // Render the scratches + notesContainer.innerHTML = ''; + for (const note of json.scratches || []) { + const element = document.createElement('div'); + element.className = 'note'; + notesContainer.appendChild(element); + + const text = document.createElement('div'); + text.className = 'text'; + const textContent = document.createElement('span'); + textContent.innerText = note.text; + text.appendChild(textContent); + element.appendChild(text); + + const created = document.createElement('div'); + created.className = 'created'; + created.innerText = new Date(note.created).toUTCString(); + element.appendChild(created); + + const deleteButton = document.createElement('button'); + deleteButton.className = 'delete-button'; + deleteButton.addEventListener('click', () => { + vscode.postMessage({ type: 'delete', id: note.id }); + }); + element.appendChild(deleteButton); + } + + notesContainer.appendChild(addButtonContainer); + } + + // Handle messages sent from the extension to the webview + window.addEventListener('message', (event) => { + const message = event.data; // The json data that the extension sent + switch (message.type) { + case 'update': + const text = message.text; + + // Update our webview's content + updateContent(text); + + // Then persist state information. + // This state is returned in the call to `vscode.getState` below when a webview is reloaded. + vscode.setState({ text }); + + return; + } + }); + + // Webviews are normally torn down when not visible and re-created when they become visible again. + // State lets us save information across these re-loads + const state = vscode.getState(); + if (state) { + updateContent(state.text); + } +})(); diff --git a/tests/test-project/media/pawDraw.css b/tests/test-project/media/pawDraw.css index 12af14ca4..5a8497ab7 100644 --- a/tests/test-project/media/pawDraw.css +++ b/tests/test-project/media/pawDraw.css @@ -1,4 +1,5 @@ -html, body { +html, +body { height: 100%; } @@ -54,12 +55,12 @@ html, body { } .drawing-controls button:before { - -webkit-mask: url("./paw-color.svg") no-repeat 50% 50%; + -webkit-mask: url('./paw-color.svg') no-repeat 50% 50%; } .drawing-controls button:after { background-color: #111; - -webkit-mask: url("./paw-outline.svg") no-repeat 50% 50%; + -webkit-mask: url('./paw-outline.svg') no-repeat 50% 50%; } .drawing-controls button.black:before { @@ -80,4 +81,4 @@ html, body { .drawing-controls button.blue:before { background-color: blue; -} \ No newline at end of file +} diff --git a/tests/test-project/media/pawDraw.js b/tests/test-project/media/pawDraw.js index 1977b119d..6843bbdff 100644 --- a/tests/test-project/media/pawDraw.js +++ b/tests/test-project/media/pawDraw.js @@ -16,16 +16,16 @@ } addPoint(/** @type {number} */ x, /** @type {number} */ y) { - this.stroke.push([x, y]) + this.stroke.push([x, y]); } } /** - * @param {Uint8Array} initialContent + * @param {Uint8Array} initialContent * @return {Promise} */ async function loadImageFromData(initialContent) { - const blob = new Blob([initialContent], { 'type': 'image/png' }); + const blob = new Blob([initialContent], { type: 'image/png' }); const url = URL.createObjectURL(blob); try { const img = document.createElement('img'); @@ -42,7 +42,7 @@ } class PawDrawEditor { - constructor( /** @type {HTMLElement} */ parent) { + constructor(/** @type {HTMLElement} */ parent) { this.ready = false; this.editable = false; @@ -60,7 +60,7 @@ addPoint(/** @type {number} */ x, /** @type {number} */ y) { if (this.currentStroke) { - this.currentStroke.addPoint(x, y) + this.currentStroke.addPoint(x, y); } } @@ -86,9 +86,9 @@ _initElements(/** @type {HTMLElement} */ parent) { const colorButtons = /** @type {NodeListOf} */ (document.querySelectorAll('.drawing-controls button')); for (const colorButton of colorButtons) { - colorButton.addEventListener('click', e => { + colorButton.addEventListener('click', (e) => { e.stopPropagation(); - colorButtons.forEach(button => button.classList.remove('active')); + colorButtons.forEach((button) => button.classList.remove('active')); colorButton.classList.add('active'); this.drawingColor = colorButton.dataset['color']; }); @@ -144,7 +144,7 @@ } }); - parent.addEventListener('mousemove', e => { + parent.addEventListener('mousemove', (e) => { if (!isDrawing || !this.ready || !this.editable) { return; } @@ -171,8 +171,8 @@ } /** - * @param {Uint8Array | undefined} data - * @param {Array | undefined} strokes + * @param {Uint8Array | undefined} data + * @param {Array | undefined} strokes */ async reset(data, strokes = []) { if (data) { @@ -188,7 +188,7 @@ } /** - * @param {Array | undefined} strokes + * @param {Array | undefined} strokes */ async resetUntitled(strokes = []) { const size = 100; @@ -218,8 +218,8 @@ outCtx.drawImage(this.initialCanvas, 0, 0); outCtx.drawImage(this.drawingCanvas, 0, 0); - const blob = await new Promise(resolve => { - outCanvas.toBlob(resolve, 'image/png') + const blob = await new Promise((resolve) => { + outCanvas.toBlob(resolve, 'image/png'); }); return new Uint8Array(await blob.arrayBuffer()); @@ -229,40 +229,41 @@ const editor = new PawDrawEditor(document.querySelector('.drawing-canvas')); // Handle messages from the extension - window.addEventListener('message', async e => { + window.addEventListener('message', async (e) => { const { type, body, requestId } = e.data; switch (type) { - case 'init': - { - editor.setEditable(body.editable); - if (body.untitled) { - await editor.resetUntitled(); - return; - } else { - // Load the initial image into the canvas. - const data = new Uint8Array(body.value.data); - await editor.reset(data); - return; - } - } - case 'update': - { - const data = body.content ? new Uint8Array(body.content.data) : undefined; - const strokes = body.edits.map(edit => new Stroke(edit.color, edit.stroke)); - await editor.reset(data, strokes) + case 'init': { + editor.setEditable(body.editable); + if (body.untitled) { + await editor.resetUntitled(); return; - } - case 'getFileData': - { - // Get the image data for the canvas and post it back to the extension. - editor.getImageData().then(data => { - vscode.postMessage({ type: 'response', requestId, body: Array.from(data) }); - }); + } else { + // Load the initial image into the canvas. + const data = new Uint8Array(body.value.data); + await editor.reset(data); return; } + } + case 'update': { + const data = body.content ? new Uint8Array(body.content.data) : undefined; + const strokes = body.edits.map((edit) => new Stroke(edit.color, edit.stroke)); + await editor.reset(data, strokes); + return; + } + case 'getFileData': { + // Get the image data for the canvas and post it back to the extension. + editor.getImageData().then((data) => { + vscode.postMessage({ + type: 'response', + requestId, + body: Array.from(data), + }); + }); + return; + } } }); // Signal to VS Code that the webview is initialized. vscode.postMessage({ type: 'ready' }); -}()); +})(); diff --git a/tests/test-project/package.json b/tests/test-project/package.json index 3e79081c8..e4a7215aa 100644 --- a/tests/test-project/package.json +++ b/tests/test-project/package.json @@ -1,207 +1,207 @@ { - "name": "extester-test", - "displayName": "Test Project", - "description": "Extension dedicated to self-testing the ExTester package", - "icon": "icons/logo.png", - "version": "0.1.0", - "preview": true, - "private": true, - "publisher": "ExTester", - "author": "Red Hat", - "license": "Apache-2.0", - "repository": { - "type": "git", - "url": "https://github.com/redhat-developer/vscode-extension-tester.git", - "directory": "tests/test-project" - }, - "engines": { - "vscode": "^1.85.0" - }, - "categories": [ - "Other", - "Testing" - ], - "activationEvents": [ - "onView:fileExplorer" - ], - "main": "./out/extension.js", - "contributes": { - "customEditors": [ - { - "viewType": "catCustoms.catScratch", - "displayName": "Cat Scratch", - "selector": [ - { - "filenamePattern": "*.cscratch" - } - ] - } - ], - "commands": [ - { - "command": "extension.helloWorld", - "title": "Hello World", - "icon": "$(rocket)" - }, - { - "command": "extension.helloWorld2", - "title": "Hello a World", - "icon": "$(rocket)" - }, - { - "command": "extension.warningMsg", - "title": "Warning Message" - }, - { - "command": "extension.errorMsg", - "title": "Error Message" - }, - { - "command": "extension.openFile", - "title": "Open Test File" - }, - { - "command": "extension.openFolder", - "title": "Open Test Folder" - }, - { - "command": "extension.closeFolder", - "title": "Close Test Folder" - }, - { - "command": "extension.test", - "title": "Extension Test Command" - }, - { - "command": "extension.webview", - "title": "Webview Test" - }, - { - "command": "extension.notification", - "title": "Test Notification" - }, - { - "command": "extension.quickpick", - "title": "Test Quickpicks" - }, - { - "command": "extension.populateTestView", - "title": "Populate Test View" - }, - { - "command": "extension.enableCodeLens", - "title": "Enable CodeLens" - }, - { - "command": "extension.disableCodeLens", - "title": "Disable Codelens" - }, - { - "command": "extension.treeItemAction", - "title": "Tree Item Action" - } - ], - "viewsContainers": { - "panel": [ - { - "icon": "./media/paw-outline.svg", - "id": "myPanel", - "title": "My Panel" - } - ] - }, - "views": { - "explorer": [ - { - "id": "testView", - "name": "Test View" - }, - { - "id": "emptyView", - "name": "Empty View" - }, - { - "id": "mySidePanelView", - "name": "My Side Panel View", - "type": "webview" - } - ], - "myPanel": [ - { - "id": "myPanelView", - "name": "My Panel View", - "type": "webview" - } - ] - }, - "viewsWelcome": [ - { - "view": "emptyView", - "contents": "This is the first line\n[Add stuff into this View](command:extension.populateTestView)\nThis is the second line\nAnd yet another line." - } - ], - "configuration": { - "title": "ExTester Tests", - "properties": { - "testProject.general.helloWorld": { - "type": "boolean", - "default": false, - "description": "Hello World!" - }, - "testProject.enableCodeLens": { - "type": "boolean", - "default": false - }, - "testProject.general.helloWorldArray": { - "type": "array", - "uniqueItems": true, - "items": { - "type": "string" - }, - "additionalProperties": false, - "markdownDescription": "This is an example array of strings", - "default": [ - "Hello World", - "Hello ExTester" - ] - } - } - }, - "menus": { - "editor/title": [ - { - "command": "extension.helloWorld", - "group": "navigation", - "when": "resourceFilename =~ /Untitled.*$/" - } - ], - "view/item/context": [ - { - "command": "extension.treeItemAction", - "when": "viewItem =~ /ExtensionTreeItem/", - "group": "inline" - } - ] - } - }, - "scripts": { - "vscode:prepublish": "npm run build", - "build": "npm run clean && npm run compile", - "clean": "rimraf out", - "watch": "tsc -watch -p ./", - "compile": "tsc -p ./ && npm run lint", - "lint": "eslint src --ext .ts", - "cb-init": "echo hello_ExTester | clipboard", - "ui-test": "npm run cb-init && extest setup-and-run './out/test/cli/order-3.test.js' './out/test/cli/order-2.test.js' './out/test/cli/order-1.test.js' './out/test/**/*.test.js' './out/test/system/clipboard.test.js' -u -i -r . -e ./test-extensions", - "ui-test:coverage": "MOCHA_GREP='order|clipboard|ExtensionsView|TitleBar' MOCHA_INVERT=true extest setup-and-run './out/test/**/*.test.js' -i -r . -e ./test-extensions --coverage" - }, - "devDependencies": { - "@types/chai": "^4.3.14", - "@types/vscode": "^1.85.0", - "chai": "^4.4.1", - "clipboard-cli": "^4.0.0", - "clipboardy": "^4.0.0", - "mocha": "^10.4.0", - "vscode-extension-tester": "*" - } + "name": "extester-test", + "displayName": "Test Project", + "description": "Extension dedicated to self-testing the ExTester package", + "icon": "icons/logo.png", + "version": "0.1.0", + "preview": true, + "private": true, + "publisher": "ExTester", + "author": "Red Hat", + "license": "Apache-2.0", + "repository": { + "type": "git", + "url": "https://github.com/redhat-developer/vscode-extension-tester.git", + "directory": "tests/test-project" + }, + "engines": { + "vscode": "^1.85.0" + }, + "categories": [ + "Other", + "Testing" + ], + "activationEvents": [ + "onView:fileExplorer" + ], + "main": "./out/extension.js", + "contributes": { + "customEditors": [ + { + "viewType": "catCustoms.catScratch", + "displayName": "Cat Scratch", + "selector": [ + { + "filenamePattern": "*.cscratch" + } + ] + } + ], + "commands": [ + { + "command": "extension.helloWorld", + "title": "Hello World", + "icon": "$(rocket)" + }, + { + "command": "extension.helloWorld2", + "title": "Hello a World", + "icon": "$(rocket)" + }, + { + "command": "extension.warningMsg", + "title": "Warning Message" + }, + { + "command": "extension.errorMsg", + "title": "Error Message" + }, + { + "command": "extension.openFile", + "title": "Open Test File" + }, + { + "command": "extension.openFolder", + "title": "Open Test Folder" + }, + { + "command": "extension.closeFolder", + "title": "Close Test Folder" + }, + { + "command": "extension.test", + "title": "Extension Test Command" + }, + { + "command": "extension.webview", + "title": "Webview Test" + }, + { + "command": "extension.notification", + "title": "Test Notification" + }, + { + "command": "extension.quickpick", + "title": "Test Quickpicks" + }, + { + "command": "extension.populateTestView", + "title": "Populate Test View" + }, + { + "command": "extension.enableCodeLens", + "title": "Enable CodeLens" + }, + { + "command": "extension.disableCodeLens", + "title": "Disable Codelens" + }, + { + "command": "extension.treeItemAction", + "title": "Tree Item Action" + } + ], + "viewsContainers": { + "panel": [ + { + "icon": "./media/paw-outline.svg", + "id": "myPanel", + "title": "My Panel" + } + ] + }, + "views": { + "explorer": [ + { + "id": "testView", + "name": "Test View" + }, + { + "id": "emptyView", + "name": "Empty View" + }, + { + "id": "mySidePanelView", + "name": "My Side Panel View", + "type": "webview" + } + ], + "myPanel": [ + { + "id": "myPanelView", + "name": "My Panel View", + "type": "webview" + } + ] + }, + "viewsWelcome": [ + { + "view": "emptyView", + "contents": "This is the first line\n[Add stuff into this View](command:extension.populateTestView)\nThis is the second line\nAnd yet another line." + } + ], + "configuration": { + "title": "ExTester Tests", + "properties": { + "testProject.general.helloWorld": { + "type": "boolean", + "default": false, + "description": "Hello World!" + }, + "testProject.enableCodeLens": { + "type": "boolean", + "default": false + }, + "testProject.general.helloWorldArray": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "string" + }, + "additionalProperties": false, + "markdownDescription": "This is an example array of strings", + "default": [ + "Hello World", + "Hello ExTester" + ] + } + } + }, + "menus": { + "editor/title": [ + { + "command": "extension.helloWorld", + "group": "navigation", + "when": "resourceFilename =~ /Untitled.*$/" + } + ], + "view/item/context": [ + { + "command": "extension.treeItemAction", + "when": "viewItem =~ /ExtensionTreeItem/", + "group": "inline" + } + ] + } + }, + "scripts": { + "vscode:prepublish": "npm run build", + "build": "npm run clean && npm run compile", + "clean": "rimraf out", + "watch": "tsc -watch -p ./", + "compile": "tsc -p ./ && npm run lint", + "lint": "eslint src --ext .ts", + "cb-init": "echo hello_ExTester | clipboard", + "ui-test": "npm run cb-init && extest setup-and-run './out/test/cli/order-3.test.js' './out/test/cli/order-2.test.js' './out/test/cli/order-1.test.js' './out/test/**/*.test.js' './out/test/system/clipboard.test.js' -u -i -r . -e ./test-extensions", + "ui-test:coverage": "MOCHA_GREP='order|clipboard|ExtensionsView|TitleBar' MOCHA_INVERT=true extest setup-and-run './out/test/**/*.test.js' -i -r . -e ./test-extensions --coverage" + }, + "devDependencies": { + "@types/chai": "^4.3.14", + "@types/vscode": "^1.85.0", + "chai": "^4.4.1", + "clipboard-cli": "^4.0.0", + "clipboardy": "^4.0.0", + "mocha": "^10.4.0", + "vscode-extension-tester": "*" + } } diff --git a/tests/test-project/src/catScratchEditor.ts b/tests/test-project/src/catScratchEditor.ts index 5d16c71f5..2e38d14d7 100644 --- a/tests/test-project/src/catScratchEditor.ts +++ b/tests/test-project/src/catScratchEditor.ts @@ -8,18 +8,17 @@ import * as vscode from 'vscode'; /** * Provider for cat scratch editors. - * + * * Cat scratch editors are used for `.cscratch` files, which are just json files. * To get started, run this extension and open an empty `.cscratch` file in VS Code. - * + * * This provider demonstrates: - * + * * - Setting up the initial webview for a custom editor. * - Loading scripts and styles in a custom editor. * - Synchronizing changes between a text document and a custom editor. */ export class CatScratchEditorProvider implements vscode.CustomTextEditorProvider { - public static register(context: vscode.ExtensionContext): vscode.Disposable { const provider = new CatScratchEditorProvider(context); const providerRegistration = vscode.window.registerCustomEditorProvider(CatScratchEditorProvider.viewType, provider); @@ -30,20 +29,14 @@ export class CatScratchEditorProvider implements vscode.CustomTextEditorProvider private static readonly scratchCharacters = ['😸', '😹', 'đŸ˜ē', 'đŸ˜ģ', 'đŸ˜ŧ', 'đŸ˜Ŋ', '😾', '🙀', 'đŸ˜ŋ', '🐱']; - constructor( - private readonly context: vscode.ExtensionContext - ) { } + constructor(private readonly context: vscode.ExtensionContext) {} /** * Called when our custom editor is opened. - * - * + * + * */ - public async resolveCustomTextEditor( - document: vscode.TextDocument, - webviewPanel: vscode.WebviewPanel, - _token: vscode.CancellationToken - ): Promise { + public async resolveCustomTextEditor(document: vscode.TextDocument, webviewPanel: vscode.WebviewPanel, _token: vscode.CancellationToken): Promise { // Setup initial content for the webview webviewPanel.webview.options = { enableScripts: true, @@ -61,11 +54,11 @@ export class CatScratchEditorProvider implements vscode.CustomTextEditorProvider // // The text document acts as our model, so we have to sync change in the document to our // editor and sync changes in the editor back to the document. - // + // // Remember that a single text document can also be shared between multiple custom // editors (this happens for example when you split a custom editor) - const changeDocumentSubscription = vscode.workspace.onDidChangeTextDocument(e => { + const changeDocumentSubscription = vscode.workspace.onDidChangeTextDocument((e) => { if (e.document.uri.toString() === document.uri.toString()) { updateWebview(); } @@ -77,7 +70,7 @@ export class CatScratchEditorProvider implements vscode.CustomTextEditorProvider }); // Receive message from the webview. - webviewPanel.webview.onDidReceiveMessage(e => { + webviewPanel.webview.onDidReceiveMessage((e) => { switch (e.type) { case 'add': this.addNewScratch(document); @@ -97,22 +90,18 @@ export class CatScratchEditorProvider implements vscode.CustomTextEditorProvider */ private getHtmlForWebview(webview: vscode.Webview): string { // Local path to script and css for the webview - const scriptUri = webview.asWebviewUri(vscode.Uri.joinPath( - this.context.extensionUri, 'media', 'catScratch.js')); + const scriptUri = webview.asWebviewUri(vscode.Uri.joinPath(this.context.extensionUri, 'media', 'catScratch.js')); - const styleResetUri = webview.asWebviewUri(vscode.Uri.joinPath( - this.context.extensionUri, 'media', 'reset.css')); + const styleResetUri = webview.asWebviewUri(vscode.Uri.joinPath(this.context.extensionUri, 'media', 'reset.css')); - const styleVSCodeUri = webview.asWebviewUri(vscode.Uri.joinPath( - this.context.extensionUri, 'media', 'vscode.css')); + const styleVSCodeUri = webview.asWebviewUri(vscode.Uri.joinPath(this.context.extensionUri, 'media', 'vscode.css')); - const styleMainUri = webview.asWebviewUri(vscode.Uri.joinPath( - this.context.extensionUri, 'media', 'catScratch.css')); + const styleMainUri = webview.asWebviewUri(vscode.Uri.joinPath(this.context.extensionUri, 'media', 'catScratch.css')); // Use a nonce to whitelist which scripts can be run const nonce = getNonce(); - return /* html */` + return /* html */ ` @@ -156,7 +145,7 @@ export class CatScratchEditorProvider implements vscode.CustomTextEditorProvider id: getNonce(), text: character, created: Date.now(), - } + }, ]; return this.updateTextDocument(document, json); @@ -200,10 +189,7 @@ export class CatScratchEditorProvider implements vscode.CustomTextEditorProvider // Just replace the entire document every time for this example extension. // A more complete extension should compute minimal edits instead. - edit.replace( - document.uri, - new vscode.Range(0, 0, document.lineCount, 0), - JSON.stringify(json, null, 2)); + edit.replace(document.uri, new vscode.Range(0, 0, document.lineCount, 0), JSON.stringify(json, null, 2)); return vscode.workspace.applyEdit(edit); } @@ -216,4 +202,4 @@ function getNonce() { text += possible.charAt(Math.floor(Math.random() * possible.length)); } return text; -} \ No newline at end of file +} diff --git a/tests/test-project/src/codelensProvider.ts b/tests/test-project/src/codelensProvider.ts index eb6cb1341..a76f207ee 100644 --- a/tests/test-project/src/codelensProvider.ts +++ b/tests/test-project/src/codelensProvider.ts @@ -9,51 +9,49 @@ import * as vscode from 'vscode'; * CodelensProvider */ export class CodelensProvider implements vscode.CodeLensProvider { - - private codeLenses: vscode.CodeLens[] = []; - private regex: RegExp; - private _onDidChangeCodeLenses: vscode.EventEmitter = new vscode.EventEmitter(); - public readonly onDidChangeCodeLenses: vscode.Event = this._onDidChangeCodeLenses.event; - - constructor() { - this.regex = /(\w+)/g; - - vscode.workspace.onDidChangeConfiguration(() => { - this._onDidChangeCodeLenses.fire(); - }); - } - - public provideCodeLenses(document: vscode.TextDocument): vscode.CodeLens[] | Thenable { - - if (vscode.workspace.getConfiguration("testProject").get("enableCodeLens", true)) { - this.codeLenses = []; - const regex = new RegExp(this.regex); - const text = document.getText(); - let matches: RegExpExecArray | null; - while ((matches = regex.exec(text)) !== null) { - const line = document.lineAt(document.positionAt(matches.index).line); - const indexOf = line.text.indexOf(matches[0]); - const position = new vscode.Position(line.lineNumber, indexOf); - const range = document.getWordRangeAtPosition(position, new RegExp(this.regex)); - if (range) { - this.codeLenses.push(new vscode.CodeLens(range)); - } - } - return this.codeLenses; - } - return []; - } - - public resolveCodeLens(codeLens: vscode.CodeLens) { - if (vscode.workspace.getConfiguration("testProject").get("enableCodeLens", true)) { - codeLens.command = { - title: "Codelens provided by sample extension", - tooltip: "Tooltip provided by sample extension", - command: "extension.codelensAction", - arguments: ["Argument 1", false] - }; - return codeLens; - } - return null; - } -} \ No newline at end of file + private codeLenses: vscode.CodeLens[] = []; + private regex: RegExp; + private _onDidChangeCodeLenses: vscode.EventEmitter = new vscode.EventEmitter(); + public readonly onDidChangeCodeLenses: vscode.Event = this._onDidChangeCodeLenses.event; + + constructor() { + this.regex = /(\w+)/g; + + vscode.workspace.onDidChangeConfiguration(() => { + this._onDidChangeCodeLenses.fire(); + }); + } + + public provideCodeLenses(document: vscode.TextDocument): vscode.CodeLens[] | Thenable { + if (vscode.workspace.getConfiguration('testProject').get('enableCodeLens', true)) { + this.codeLenses = []; + const regex = new RegExp(this.regex); + const text = document.getText(); + let matches: RegExpExecArray | null; + while ((matches = regex.exec(text)) !== null) { + const line = document.lineAt(document.positionAt(matches.index).line); + const indexOf = line.text.indexOf(matches[0]); + const position = new vscode.Position(line.lineNumber, indexOf); + const range = document.getWordRangeAtPosition(position, new RegExp(this.regex)); + if (range) { + this.codeLenses.push(new vscode.CodeLens(range)); + } + } + return this.codeLenses; + } + return []; + } + + public resolveCodeLens(codeLens: vscode.CodeLens) { + if (vscode.workspace.getConfiguration('testProject').get('enableCodeLens', true)) { + codeLens.command = { + title: 'Codelens provided by sample extension', + tooltip: 'Tooltip provided by sample extension', + command: 'extension.codelensAction', + arguments: ['Argument 1', false], + }; + return codeLens; + } + return null; + } +} diff --git a/tests/test-project/src/extension.ts b/tests/test-project/src/extension.ts index dd7f5a1c7..3798d2cc0 100644 --- a/tests/test-project/src/extension.ts +++ b/tests/test-project/src/extension.ts @@ -8,14 +8,14 @@ export const ERROR_MESSAGE_COMMAND = 'extension.errorMsg'; export function activate(context: vscode.ExtensionContext) { const openCommand = vscode.commands.registerCommand('extension.openFile', async () => { - const document = await vscode.workspace.openTextDocument(vscode.Uri.file( - path.resolve(__dirname, '..', '..', 'resources', 'test-file.ts'))); + const document = await vscode.workspace.openTextDocument(vscode.Uri.file(path.resolve(__dirname, '..', '..', 'resources', 'test-file.ts'))); await vscode.window.showTextDocument(document); }); const openFolder = vscode.commands.registerCommand('extension.openFolder', () => { const dirpath = path.resolve(__dirname, '..', '..', 'resources', 'test-folder'); - vscode.workspace.updateWorkspaceFolders(vscode.workspace.workspaceFolders ? vscode.workspace.workspaceFolders.length : 0, - null, { uri: vscode.Uri.file(dirpath) }); + vscode.workspace.updateWorkspaceFolders(vscode.workspace.workspaceFolders ? vscode.workspace.workspaceFolders.length : 0, null, { + uri: vscode.Uri.file(dirpath), + }); }); const closeFolder = vscode.commands.registerCommand('extension.closeFolder', () => { vscode.workspace.updateWorkspaceFolders(0, 1); @@ -30,18 +30,21 @@ export function activate(context: vscode.ExtensionContext) { await vscode.window.showInformationMessage('This is a notification', 'Yes', 'No'); }); const quickPickCommand = vscode.commands.registerCommand('extension.quickpick', async () => { - await vscode.window.showQuickPick(['test1', 'test2', 'test3'], { canPickMany: true, ignoreFocusOut: true }); + await vscode.window.showQuickPick(['test1', 'test2', 'test3'], { + canPickMany: true, + ignoreFocusOut: true, + }); }); context.subscriptions.push( vscode.commands.registerCommand('extension.helloWorld', async () => { await vscode.window.showInformationMessage('Hello World!'); - }) + }), ); context.subscriptions.push( vscode.commands.registerCommand('extension.helloWorld2', async () => { await vscode.window.showInformationMessage('Hello World, Test Project!'); - }) + }), ); context.subscriptions.push(openCommand); context.subscriptions.push(openFolder); @@ -50,52 +53,53 @@ export function activate(context: vscode.ExtensionContext) { context.subscriptions.push(webViewCommand); context.subscriptions.push(notificationCommand); context.subscriptions.push(quickPickCommand); - context.subscriptions.push(vscode.commands.registerCommand('extension.warningMsg', () => vscode.window.showWarningMessage("This is a warning!"))); - context.subscriptions.push(vscode.commands.registerCommand(ERROR_MESSAGE_COMMAND, () => vscode.window.showErrorMessage("This is an error!"))); + context.subscriptions.push(vscode.commands.registerCommand('extension.warningMsg', () => vscode.window.showWarningMessage('This is a warning!'))); + context.subscriptions.push(vscode.commands.registerCommand(ERROR_MESSAGE_COMMAND, () => vscode.window.showErrorMessage('This is an error!'))); context.subscriptions.push(CatScratchEditorProvider.register(context)); new TreeView(context); - context.subscriptions.push(vscode.window.createTreeView("emptyView", { - treeDataProvider: { - getChildren: () => emptyViewNoContent ? undefined : [{ key: "There is content!" }], - getTreeItem: (e) => new vscode.TreeItem(e.key), - onDidChangeTreeData: emitter.event - } - })); + context.subscriptions.push( + vscode.window.createTreeView('emptyView', { + treeDataProvider: { + getChildren: () => (emptyViewNoContent ? undefined : [{ key: 'There is content!' }]), + getTreeItem: (e) => new vscode.TreeItem(e.key), + onDidChangeTreeData: emitter.event, + }, + }), + ); - context.subscriptions.push(vscode.commands.registerCommand("extension.populateTestView", async () => { + context.subscriptions.push( + vscode.commands.registerCommand('extension.populateTestView', async () => { emptyViewNoContent = false; emitter.fire(undefined); - } - )); + }), + ); const codelensProvider = new CodelensProvider(); - context.subscriptions.push(vscode.languages.registerCodeLensProvider("*", codelensProvider)); + context.subscriptions.push(vscode.languages.registerCodeLensProvider('*', codelensProvider)); context.subscriptions.push( - vscode.commands.registerCommand("extension.enableCodeLens", async () => { - await vscode.workspace.getConfiguration("testProject").update("enableCodeLens", true, true); - })); - context.subscriptions.push( - vscode.commands.registerCommand("extension.disableCodeLens", async () => { - await vscode.workspace.getConfiguration("testProject").update("enableCodeLens", false, true); - })); - context.subscriptions.push( - vscode.commands.registerCommand("extension.codelensAction", async (args: any) => { - await vscode.window.showInformationMessage(`CodeLens action clicked with args=${args}`); - })); + vscode.commands.registerCommand('extension.enableCodeLens', async () => { + await vscode.workspace.getConfiguration('testProject').update('enableCodeLens', true, true); + }), + ); context.subscriptions.push( - vscode.window.registerWebviewViewProvider("myPanelView", new MyPanelView()) + vscode.commands.registerCommand('extension.disableCodeLens', async () => { + await vscode.workspace.getConfiguration('testProject').update('enableCodeLens', false, true); + }), ); context.subscriptions.push( - vscode.window.registerWebviewViewProvider("mySidePanelView", new MyPanelView()) + vscode.commands.registerCommand('extension.codelensAction', async (args: any) => { + await vscode.window.showInformationMessage(`CodeLens action clicked with args=${args}`); + }), ); + context.subscriptions.push(vscode.window.registerWebviewViewProvider('myPanelView', new MyPanelView())); + context.subscriptions.push(vscode.window.registerWebviewViewProvider('mySidePanelView', new MyPanelView())); - vscode.commands.registerCommand('extension.treeItemAction', async () => { - }); + vscode.commands.registerCommand('extension.treeItemAction', async () => {}); } -export function deactivate() { } +export function deactivate() {} let emptyViewNoContent: boolean = true; const emitter = new vscode.EventEmitter(); @@ -107,23 +111,22 @@ class TestView { private _disposables: vscode.Disposable[] = []; constructor() { - const column = vscode.window.activeTextEditor - ? vscode.window.activeTextEditor.viewColumn - : undefined; + const column = vscode.window.activeTextEditor ? vscode.window.activeTextEditor.viewColumn : undefined; - const randomWebViewTitle = "Test WebView " + Math.floor(Math.random() * 100); + const randomWebViewTitle = 'Test WebView ' + Math.floor(Math.random() * 100); this._panel = vscode.window.createWebviewPanel(TestView.viewType, randomWebViewTitle, column || vscode.ViewColumn.One); this.update(randomWebViewTitle); this._panel.onDidDispose(() => this.dispose(), null, this._disposables); - this._panel.onDidChangeViewState(() => { + this._panel.onDidChangeViewState( + () => { if (this._panel.visible) { this.update(randomWebViewTitle); } }, null, - this._disposables + this._disposables, ); } @@ -156,6 +159,7 @@ class TestView { class MyPanelView implements vscode.WebviewViewProvider { resolveWebviewView(webviewView: vscode.WebviewView): void | Thenable { - webviewView.webview.html = "My Panel View

Shopping List

  • Apple
  • Banana
"; + webviewView.webview.html = + 'My Panel View

Shopping List

  • Apple
  • Banana
'; } -} \ No newline at end of file +} diff --git a/tests/test-project/src/test/01_general/resource.test.ts b/tests/test-project/src/test/01_general/resource.test.ts index 8b20b81dc..e3457ccaf 100644 --- a/tests/test-project/src/test/01_general/resource.test.ts +++ b/tests/test-project/src/test/01_general/resource.test.ts @@ -21,45 +21,48 @@ import * as os from 'os'; import * as path from 'path'; async function getTitle(): Promise<[string | undefined, Error | undefined]> { - try { - const titleBar = new TitleBar(); - const title = await titleBar.getTitle(); - return [title, undefined]; - } - catch (e) { - if (e instanceof error.InvalidSelectorError) { - throw e; - } - return [undefined, new Error((e as Error).message)]; - } + try { + const titleBar = new TitleBar(); + const title = await titleBar.getTitle(); + return [title, undefined]; + } catch (e) { + if (e instanceof error.InvalidSelectorError) { + throw e; + } + return [undefined, new Error((e as Error).message)]; + } } describe('Open resource test', function () { - this.timeout(30000); - - it('Single folder is open from CLI', async function() { - let lastError = new Error('Could not get title from TitleBar.'); - const prefix = 'folder: '; + this.timeout(30000); - await VSBrowser.instance.driver.wait(async () => { - const [title, error] = await getTitle(); - lastError = error ?? lastError; + it('Single folder is open from CLI', async function () { + let lastError = new Error('Could not get title from TitleBar.'); + const prefix = 'folder: '; - const index = title?.indexOf(prefix) ?? 0; + await VSBrowser.instance.driver.wait( + async () => { + const [title, error] = await getTitle(); + lastError = error ?? lastError; - if (index > 0) { - let openFolderPath = title?.slice(index + prefix.length); - if (openFolderPath) { - if (openFolderPath.startsWith('~/')) { - openFolderPath = path.join(os.homedir(), openFolderPath.slice(2)); - } + const index = title?.indexOf(prefix) ?? 0; - expect(openFolderPath.split(' ')[0]).equals(process.cwd()); - return true; - } - } + if (index > 0) { + let openFolderPath = title?.slice(index + prefix.length); + if (openFolderPath) { + if (openFolderPath.startsWith('~/')) { + openFolderPath = path.join(os.homedir(), openFolderPath.slice(2)); + } - return false; - }, this.timeout() - 2000, lastError.toString()); - }); -}); \ No newline at end of file + expect(openFolderPath.split(' ')[0]).equals(process.cwd()); + return true; + } + } + + return false; + }, + this.timeout() - 2000, + lastError.toString(), + ); + }); +}); diff --git a/tests/test-project/src/test/activityBar/actionsControl.test.ts b/tests/test-project/src/test/activityBar/actionsControl.test.ts index 2d6d08236..bc2303983 100644 --- a/tests/test-project/src/test/activityBar/actionsControl.test.ts +++ b/tests/test-project/src/test/activityBar/actionsControl.test.ts @@ -19,22 +19,22 @@ import { expect } from 'chai'; import { ActivityBar, ActionsControl } from 'vscode-extension-tester'; (process.platform === 'darwin' ? describe.skip : describe)('ActionsControl', () => { - let bar: ActivityBar; - let control: ActionsControl | undefined; + let bar: ActivityBar; + let control: ActionsControl | undefined; - before(async function () { - bar = new ActivityBar(); - control = await bar.getGlobalAction('Manage'); - }); + before(async function () { + bar = new ActivityBar(); + control = await bar.getGlobalAction('Manage'); + }); - it('openActionsMenu displays context menu', async () => { - const menu = await control?.openActionMenu(); - expect(await menu?.isDisplayed()).is.true; - await menu?.close(); - }); + it('openActionsMenu displays context menu', async () => { + const menu = await control?.openActionMenu(); + expect(await menu?.isDisplayed()).is.true; + await menu?.close(); + }); - it('getTitle returns the action container label', async () => { - const title = await control?.getTitle(); - expect(title).equals('Manage'); - }); + it('getTitle returns the action container label', async () => { + const title = await control?.getTitle(); + expect(title).equals('Manage'); + }); }); diff --git a/tests/test-project/src/test/activityBar/activityBar.test.ts b/tests/test-project/src/test/activityBar/activityBar.test.ts index 6f730f3fa..328d74fcc 100644 --- a/tests/test-project/src/test/activityBar/activityBar.test.ts +++ b/tests/test-project/src/test/activityBar/activityBar.test.ts @@ -19,45 +19,45 @@ import { expect } from 'chai'; import { ActivityBar } from 'vscode-extension-tester'; describe('ActivityBar', () => { - let bar: ActivityBar; - - before(() => { - bar = new ActivityBar(); - }); - - it('getViewControls finds view containers', async () => { - const controls = await bar.getViewControls(); - expect(controls.length).greaterThan(0); - }); - - it('getViewControl works with the correct label', async () => { - const explorer = await bar.getViewControl('Explorer'); - expect(explorer).not.undefined; - }); - - it('getViewControl returns undefined with an invalid label', async () => { - const item = await bar.getViewControl('whatever'); - expect(item).undefined; - }); - - it('getGlobalActions finds global action containers', async () => { - const actions = await bar.getGlobalActions(); - expect(actions.length).greaterThan(0); - }); - - it('getGlobalAction finds action container with the given label', async () => { - const action = await bar.getGlobalAction('Manage'); - expect(action).not.undefined; - }); - - it('getGlobalAction returns undefined with nonexistent label', async () => { - const action = await bar.getGlobalAction('whatever'); - expect(action).undefined; - }); - - (process.platform === 'darwin' ? it.skip : it)('openContextMenu shows context menu', async () => { - const menu = await bar.openContextMenu(); - expect(await menu.isDisplayed()).is.true; - await menu.close(); - }); + let bar: ActivityBar; + + before(() => { + bar = new ActivityBar(); + }); + + it('getViewControls finds view containers', async () => { + const controls = await bar.getViewControls(); + expect(controls.length).greaterThan(0); + }); + + it('getViewControl works with the correct label', async () => { + const explorer = await bar.getViewControl('Explorer'); + expect(explorer).not.undefined; + }); + + it('getViewControl returns undefined with an invalid label', async () => { + const item = await bar.getViewControl('whatever'); + expect(item).undefined; + }); + + it('getGlobalActions finds global action containers', async () => { + const actions = await bar.getGlobalActions(); + expect(actions.length).greaterThan(0); + }); + + it('getGlobalAction finds action container with the given label', async () => { + const action = await bar.getGlobalAction('Manage'); + expect(action).not.undefined; + }); + + it('getGlobalAction returns undefined with nonexistent label', async () => { + const action = await bar.getGlobalAction('whatever'); + expect(action).undefined; + }); + + (process.platform === 'darwin' ? it.skip : it)('openContextMenu shows context menu', async () => { + const menu = await bar.openContextMenu(); + expect(await menu.isDisplayed()).is.true; + await menu.close(); + }); }); diff --git a/tests/test-project/src/test/activityBar/viewControl.test.ts b/tests/test-project/src/test/activityBar/viewControl.test.ts index 7b09dad7d..ffcef89b1 100644 --- a/tests/test-project/src/test/activityBar/viewControl.test.ts +++ b/tests/test-project/src/test/activityBar/viewControl.test.ts @@ -19,41 +19,41 @@ import { expect } from 'chai'; import { ActivityBar, ViewControl } from 'vscode-extension-tester'; describe('ViewControl', () => { - let bar: ActivityBar; - let control: ViewControl | undefined; - - before(async () => { - bar = new ActivityBar(); - control = await bar.getViewControl('Explorer'); - }); - - it('openView opens the underlying view', async () => { - const view = await control?.openView(); - const klass = await control?.getAttribute('class'); - - expect(klass?.indexOf('checked')).greaterThan(-1); - expect(await view?.isDisplayed()).is.true; - - const title = await view?.getTitlePart().getTitle(); - expect(title?.toLowerCase()).equals('explorer'); - }); - - it('closeView closes the side bar view', async () => { - await control?.openView(); - await control?.closeView(); - - const klass = await control?.getAttribute('class'); - expect(klass?.indexOf('checked')).lessThan(0); - }); - - it('getTitle returns container label', async () => { - const title = await control?.getTitle(); - expect(title).has.string('Explorer'); - }); - - (process.platform === 'darwin' ? it.skip : it)('openContextMenu shows context menu', async () => { - const menu = await control?.openContextMenu(); - expect(await menu?.isDisplayed()).is.true; - await menu?.close(); - }); + let bar: ActivityBar; + let control: ViewControl | undefined; + + before(async () => { + bar = new ActivityBar(); + control = await bar.getViewControl('Explorer'); + }); + + it('openView opens the underlying view', async () => { + const view = await control?.openView(); + const klass = await control?.getAttribute('class'); + + expect(klass?.indexOf('checked')).greaterThan(-1); + expect(await view?.isDisplayed()).is.true; + + const title = await view?.getTitlePart().getTitle(); + expect(title?.toLowerCase()).equals('explorer'); + }); + + it('closeView closes the side bar view', async () => { + await control?.openView(); + await control?.closeView(); + + const klass = await control?.getAttribute('class'); + expect(klass?.indexOf('checked')).lessThan(0); + }); + + it('getTitle returns container label', async () => { + const title = await control?.getTitle(); + expect(title).has.string('Explorer'); + }); + + (process.platform === 'darwin' ? it.skip : it)('openContextMenu shows context menu', async () => { + const menu = await control?.openContextMenu(); + expect(await menu?.isDisplayed()).is.true; + await menu?.close(); + }); }); diff --git a/tests/test-project/src/test/bottomBar/bottomBarPanel.test.ts b/tests/test-project/src/test/bottomBar/bottomBarPanel.test.ts index b14bdaaf7..6915e8a3a 100644 --- a/tests/test-project/src/test/bottomBar/bottomBarPanel.test.ts +++ b/tests/test-project/src/test/bottomBar/bottomBarPanel.test.ts @@ -19,66 +19,66 @@ import { expect } from 'chai'; import { BottomBarPanel, WebElement, Workbench, ViewControl, ActivityBar } from 'vscode-extension-tester'; describe('BottomBarPanel', function () { - let panel: BottomBarPanel; + let panel: BottomBarPanel; - before(async function () { - panel = new BottomBarPanel(); - await (await new Workbench().openNotificationsCenter()).clearAllNotifications(); - await (await new ActivityBar().getViewControl('Explorer') as ViewControl).openView(); - }); + before(async function () { + panel = new BottomBarPanel(); + await (await new Workbench().openNotificationsCenter()).clearAllNotifications(); + await ((await new ActivityBar().getViewControl('Explorer')) as ViewControl).openView(); + }); - after(async function () { - await panel.toggle(false); - }); + after(async function () { + await panel.toggle(false); + }); - it('can be toggled open', async function () { - await panel.toggle(true); - expect(await panel.isDisplayed()).is.true; - }); + it('can be toggled open', async function () { + await panel.toggle(true); + expect(await panel.isDisplayed()).is.true; + }); - it('can be toggled closed', async function () { - await panel.toggle(true); - await panel.toggle(false); - expect(await panel.isDisplayed()).is.false; - }); + it('can be toggled closed', async function () { + await panel.toggle(true); + await panel.toggle(false); + expect(await panel.isDisplayed()).is.false; + }); - it('can be maximized and restored', async function () { - this.timeout(20000); - await panel.toggle(true); - const initHeight = await getHeight(panel); + it('can be maximized and restored', async function () { + this.timeout(20000); + await panel.toggle(true); + const initHeight = await getHeight(panel); - await panel.maximize(); - const maxHeight = await getHeight(panel); - expect(maxHeight).greaterThan(initHeight); - await new Promise(res => setTimeout(res, 1000)); + await panel.maximize(); + const maxHeight = await getHeight(panel); + expect(maxHeight).greaterThan(initHeight); + await new Promise((res) => setTimeout(res, 1000)); - await panel.restore(); - const restoredHeight = await getHeight(panel); - expect(initHeight).equals(restoredHeight); - }); + await panel.restore(); + const restoredHeight = await getHeight(panel); + expect(initHeight).equals(restoredHeight); + }); - it('can open problems view', async function () { - const view = await panel.openProblemsView(); - expect(await view.isDisplayed()).is.true; - }); + it('can open problems view', async function () { + const view = await panel.openProblemsView(); + expect(await view.isDisplayed()).is.true; + }); - it('can open output view', async function () { - const view = await panel.openOutputView(); - expect(await view.isDisplayed()).is.true; - }); + it('can open output view', async function () { + const view = await panel.openOutputView(); + expect(await view.isDisplayed()).is.true; + }); - it('can open debug console view', async function () { - const view = await panel.openDebugConsoleView(); - expect(await view.isDisplayed()).is.true; - }); + it('can open debug console view', async function () { + const view = await panel.openDebugConsoleView(); + expect(await view.isDisplayed()).is.true; + }); - it('can open terminal view', async function () { - const view = await panel.openTerminalView(); - expect(await view.isDisplayed()).is.true; - }); + it('can open terminal view', async function () { + const view = await panel.openTerminalView(); + expect(await view.isDisplayed()).is.true; + }); }); async function getHeight(element: WebElement): Promise { - const size = await element.getRect(); - return size.height; -} \ No newline at end of file + const size = await element.getRect(); + return size.height; +} diff --git a/tests/test-project/src/test/bottomBar/problemsView.test.ts b/tests/test-project/src/test/bottomBar/problemsView.test.ts index 0d4a02826..cd75ffaa9 100644 --- a/tests/test-project/src/test/bottomBar/problemsView.test.ts +++ b/tests/test-project/src/test/bottomBar/problemsView.test.ts @@ -19,97 +19,99 @@ import * as path from 'path'; import { TextEditor, EditorView, ProblemsView, BottomBarPanel, MarkerType, VSBrowser } from 'vscode-extension-tester'; import { expect } from 'chai'; - describe('ProblemsView', function () { - let editor: TextEditor; - let view: ProblemsView; - let bar: BottomBarPanel; - - before(async function () { - this.timeout(25000); - await VSBrowser.instance.openResources(path.resolve(__dirname, '..', '..', '..', 'resources', 'test-file.ts')); - - bar = new BottomBarPanel(); - await bar.toggle(true); - view = await bar.openProblemsView(); - - editor = await new EditorView().openEditor('test-file.ts') as TextEditor; - await editor.setText('aaaa'); - await view.getDriver().wait(() => { return problemsExist(view); }, 15000); - }); - - after(async function () { - await view.clearFilter(); - await editor.clearText(); - if (await editor.isDirty()) { - await editor.save(); - } - await new EditorView().closeAllEditors(); - await bar.toggle(false); - }); - - it('get all markers works', async function () { - const markers = await view.getAllVisibleMarkers(MarkerType.Any); - expect(markers.length).greaterThan(1); - }); - - it('get warnings works', async function () { - const markers = await view.getAllVisibleMarkers(MarkerType.Warning); - expect(markers).empty; - }); - - it('get errors works', async function () { - const markers = await view.getAllVisibleMarkers(MarkerType.Error); - expect(markers.length).equals(1); - }); - - it('get files works', async function () { - const markers = await view.getAllVisibleMarkers(MarkerType.File); - expect(markers.length).equals(1); - }); - - it('filtering works', async function () { - await view.setFilter('aaaa'); - await view.getDriver().sleep(500); - const markers = await view.getAllVisibleMarkers(MarkerType.Any); - expect(markers.length).equals(2); - }); - - describe('Marker', function () { - it('getType works', async function () { - const markers = await view.getAllVisibleMarkers(MarkerType.Error); - expect(await markers[0].getType()).equals(MarkerType.Error); - }); - - it('getText works', async function () { - const markers = await view.getAllVisibleMarkers(MarkerType.File); - expect(await markers[0].getText()).has.string('test-file.ts'); - }); - - it('toggleExpand works', async function () { - const marker = (await view.getAllVisibleMarkers(MarkerType.File))[0]; - await marker.toggleExpand(false); - let markers = await view.getAllVisibleMarkers(MarkerType.Any); - expect(markers.length).equals(1); - - await marker.toggleExpand(true); - markers = await view.getAllVisibleMarkers(MarkerType.Any); - expect(markers.length).equals(2); - }); - - it('click works', async function () { - const markers = await view.getAllVisibleMarkers(MarkerType.File); - for (const marker of markers) { - if(await marker.getLabel() === 'test-file.ts') { - await marker.click(); - } - } - const anyMarkers = await view.getAllVisibleMarkers(MarkerType.Any); - expect(anyMarkers.length).equals(1); - }); - }); +describe('ProblemsView', function () { + let editor: TextEditor; + let view: ProblemsView; + let bar: BottomBarPanel; + + before(async function () { + this.timeout(25000); + await VSBrowser.instance.openResources(path.resolve(__dirname, '..', '..', '..', 'resources', 'test-file.ts')); + + bar = new BottomBarPanel(); + await bar.toggle(true); + view = await bar.openProblemsView(); + + editor = (await new EditorView().openEditor('test-file.ts')) as TextEditor; + await editor.setText('aaaa'); + await view.getDriver().wait(() => { + return problemsExist(view); + }, 15000); + }); + + after(async function () { + await view.clearFilter(); + await editor.clearText(); + if (await editor.isDirty()) { + await editor.save(); + } + await new EditorView().closeAllEditors(); + await bar.toggle(false); + }); + + it('get all markers works', async function () { + const markers = await view.getAllVisibleMarkers(MarkerType.Any); + expect(markers.length).greaterThan(1); + }); + + it('get warnings works', async function () { + const markers = await view.getAllVisibleMarkers(MarkerType.Warning); + expect(markers).empty; + }); + + it('get errors works', async function () { + const markers = await view.getAllVisibleMarkers(MarkerType.Error); + expect(markers.length).equals(1); + }); + + it('get files works', async function () { + const markers = await view.getAllVisibleMarkers(MarkerType.File); + expect(markers.length).equals(1); + }); + + it('filtering works', async function () { + await view.setFilter('aaaa'); + await view.getDriver().sleep(500); + const markers = await view.getAllVisibleMarkers(MarkerType.Any); + expect(markers.length).equals(2); + }); + + describe('Marker', function () { + it('getType works', async function () { + const markers = await view.getAllVisibleMarkers(MarkerType.Error); + expect(await markers[0].getType()).equals(MarkerType.Error); + }); + + it('getText works', async function () { + const markers = await view.getAllVisibleMarkers(MarkerType.File); + expect(await markers[0].getText()).has.string('test-file.ts'); + }); + + it('toggleExpand works', async function () { + const marker = (await view.getAllVisibleMarkers(MarkerType.File))[0]; + await marker.toggleExpand(false); + let markers = await view.getAllVisibleMarkers(MarkerType.Any); + expect(markers.length).equals(1); + + await marker.toggleExpand(true); + markers = await view.getAllVisibleMarkers(MarkerType.Any); + expect(markers.length).equals(2); + }); + + it('click works', async function () { + const markers = await view.getAllVisibleMarkers(MarkerType.File); + for (const marker of markers) { + if ((await marker.getLabel()) === 'test-file.ts') { + await marker.click(); + } + } + const anyMarkers = await view.getAllVisibleMarkers(MarkerType.Any); + expect(anyMarkers.length).equals(1); + }); + }); }); async function problemsExist(view: ProblemsView) { - const markers = await view.getAllVisibleMarkers(MarkerType.Any); - return markers.length > 0; -} \ No newline at end of file + const markers = await view.getAllVisibleMarkers(MarkerType.Any); + return markers.length > 0; +} diff --git a/tests/test-project/src/test/bottomBar/views.test.ts b/tests/test-project/src/test/bottomBar/views.test.ts index afa1f7bea..2af028aff 100644 --- a/tests/test-project/src/test/bottomBar/views.test.ts +++ b/tests/test-project/src/test/bottomBar/views.test.ts @@ -20,102 +20,102 @@ import * as path from 'path'; import { BottomBarPanel, OutputView, TerminalView, VSBrowser, Workbench } from 'vscode-extension-tester'; describe('Output View/Text Views', function () { - let panel: BottomBarPanel; - let view: OutputView; - const channelName = (VSBrowser.instance.version > '1.72.2' && VSBrowser.instance.version < '1.74.0') ? 'Log (Git)' : 'Git'; + let panel: BottomBarPanel; + let view: OutputView; + const channelName = VSBrowser.instance.version > '1.72.2' && VSBrowser.instance.version < '1.74.0' ? 'Log (Git)' : 'Git'; - before(async function () { - this.timeout(25000); - await VSBrowser.instance.openResources(path.resolve(__dirname, '..', '..', '..', 'resources')); - await VSBrowser.instance.waitForWorkbench(); - }); + before(async function () { + this.timeout(25000); + await VSBrowser.instance.openResources(path.resolve(__dirname, '..', '..', '..', 'resources')); + await VSBrowser.instance.waitForWorkbench(); + }); - before(async function () { - this.timeout(15000); - const center = await new Workbench().openNotificationsCenter(); - await center.clearAllNotifications(); - await center.close(); - panel = new BottomBarPanel(); - await panel.toggle(true); - await panel.maximize(); - view = await panel.openOutputView(); - await new Promise(resolve => setTimeout(resolve, 2000)); - }); + before(async function () { + this.timeout(15000); + const center = await new Workbench().openNotificationsCenter(); + await center.clearAllNotifications(); + await center.close(); + panel = new BottomBarPanel(); + await panel.toggle(true); + await panel.maximize(); + view = await panel.openOutputView(); + await new Promise((resolve) => setTimeout(resolve, 2000)); + }); - after(async function () { - await panel.restore(); - await new Promise(resolve => setTimeout(resolve, 500)); - await panel.toggle(false); - }); + after(async function () { + await panel.restore(); + await new Promise((resolve) => setTimeout(resolve, 500)); + await panel.toggle(false); + }); - it('getChannelNames returns list of channels', async function () { - const channels = await view.getChannelNames(); - expect(channels).not.empty; - }); + it('getChannelNames returns list of channels', async function () { + const channels = await view.getChannelNames(); + expect(channels).not.empty; + }); - it('getCurrentChannel returns the selected channel name', async function () { - if(process.platform !== 'darwin' && VSBrowser.instance.version >= '1.87.0') { - this.skip(); - } - const channel = await view.getCurrentChannel(); - expect(channel).not.empty; - }); + it('getCurrentChannel returns the selected channel name', async function () { + if (process.platform !== 'darwin' && VSBrowser.instance.version >= '1.87.0') { + this.skip(); + } + const channel = await view.getCurrentChannel(); + expect(channel).not.empty; + }); - it('selectChannel works', async function () { - this.timeout(10000); - if(process.platform !== 'darwin' && VSBrowser.instance.version >= '1.87.0') { - this.skip(); - } - await view.selectChannel('Tasks'); - const final = await view.getCurrentChannel(); - expect('Tasks').equals(final); - }); + it('selectChannel works', async function () { + this.timeout(10000); + if (process.platform !== 'darwin' && VSBrowser.instance.version >= '1.87.0') { + this.skip(); + } + await view.selectChannel('Tasks'); + const final = await view.getCurrentChannel(); + expect('Tasks').equals(final); + }); - it('getText returns all current text', async function () { - await view.selectChannel(channelName); - await new Promise(resolve => setTimeout(resolve, 2000)); - const text = await view.getText(); - expect(text).not.empty; - }); + it('getText returns all current text', async function () { + await view.selectChannel(channelName); + await new Promise((resolve) => setTimeout(resolve, 2000)); + const text = await view.getText(); + expect(text).not.empty; + }); - it('clearText clears the text view', async function () { - await view.selectChannel(channelName); - const text = await view.getText(); - await view.clearText(); - const cleared = await view.getText(); - expect(cleared).not.has.string(text); - }); + it('clearText clears the text view', async function () { + await view.selectChannel(channelName); + const text = await view.getText(); + await view.clearText(); + const cleared = await view.getText(); + expect(cleared).not.has.string(text); + }); - describe('Terminal View', function () { - let terminal: TerminalView; - let terminalName = process.platform === 'win32' ? (VSBrowser.instance.version >= '1.53.0' ? 'pwsh' : 'powershell') : 'bash'; + describe('Terminal View', function () { + let terminal: TerminalView; + let terminalName = process.platform === 'win32' ? (VSBrowser.instance.version >= '1.53.0' ? 'pwsh' : 'powershell') : 'bash'; - before(async () => { - terminal = await panel.openTerminalView(); - await new Promise(res => setTimeout(res, 2000)); - }); + before(async () => { + terminal = await panel.openTerminalView(); + await new Promise((res) => setTimeout(res, 2000)); + }); - it('getText returns all current text', async function () { - try { - await terminal.selectChannel(`1: ${terminalName}`); - } catch (err) { - terminalName = 'sh'; - await terminal.selectChannel(`1: ${terminalName}`); - } - const text = await terminal.getText(); - expect(text).not.empty; - }); + it('getText returns all current text', async function () { + try { + await terminal.selectChannel(`1: ${terminalName}`); + } catch (err) { + terminalName = 'sh'; + await terminal.selectChannel(`1: ${terminalName}`); + } + const text = await terminal.getText(); + expect(text).not.empty; + }); - it('executeCommand works', async function () { - const command = `${process.platform === 'win32' ? 'start-sleep -s' : 'sleep'} 2`; - await terminal.executeCommand(command, 5000); - expect(await terminal.getText()).to.have.string("sleep"); - }); + it('executeCommand works', async function () { + const command = `${process.platform === 'win32' ? 'start-sleep -s' : 'sleep'} 2`; + await terminal.executeCommand(command, 5000); + expect(await terminal.getText()).to.have.string('sleep'); + }); - it('newTerminal opens a new term channel', async function () { - await terminal.newTerminal(); - const channel = await terminal.getCurrentChannel(); - expect(channel).equals(`2: ${terminalName}`); - }); - }); + it('newTerminal opens a new term channel', async function () { + await terminal.newTerminal(); + const channel = await terminal.getCurrentChannel(); + expect(channel).equals(`2: ${terminalName}`); + }); + }); }); diff --git a/tests/test-project/src/test/cli/order-1.test.ts b/tests/test-project/src/test/cli/order-1.test.ts index 9bfca4339..2629825e5 100644 --- a/tests/test-project/src/test/cli/order-1.test.ts +++ b/tests/test-project/src/test/cli/order-1.test.ts @@ -15,11 +15,11 @@ * limitations under the License. */ -import { expect } from "chai"; +import { expect } from 'chai'; -describe('CLI blob order test - 1', function() { - it('Executed as last test', function() { - expect(process.env['CLI_ORDER'], 'this test should executed after order-2.test.ts').equal('2'); - process.env['CLI_ORDER'] = '1'; - }); +describe('CLI blob order test - 1', function () { + it('Executed as last test', function () { + expect(process.env['CLI_ORDER'], 'this test should executed after order-2.test.ts').equal('2'); + process.env['CLI_ORDER'] = '1'; + }); }); diff --git a/tests/test-project/src/test/cli/order-2.test.ts b/tests/test-project/src/test/cli/order-2.test.ts index ef3f32527..6876bee3f 100644 --- a/tests/test-project/src/test/cli/order-2.test.ts +++ b/tests/test-project/src/test/cli/order-2.test.ts @@ -15,11 +15,11 @@ * limitations under the License. */ -import { expect } from "chai"; +import { expect } from 'chai'; -describe('CLI blob order test - 2', function() { - it('Executed as middle test', function() { - expect(process.env['CLI_ORDER'], 'this test should executed after order-3.test.ts').equal('3'); - process.env['CLI_ORDER'] = '2'; - }); +describe('CLI blob order test - 2', function () { + it('Executed as middle test', function () { + expect(process.env['CLI_ORDER'], 'this test should executed after order-3.test.ts').equal('3'); + process.env['CLI_ORDER'] = '2'; + }); }); diff --git a/tests/test-project/src/test/cli/order-3.test.ts b/tests/test-project/src/test/cli/order-3.test.ts index de594fb23..5692b7715 100644 --- a/tests/test-project/src/test/cli/order-3.test.ts +++ b/tests/test-project/src/test/cli/order-3.test.ts @@ -15,11 +15,11 @@ * limitations under the License. */ -import { expect } from "chai"; +import { expect } from 'chai'; -describe('CLI blob order test - 3', function() { - it('Executed as first test', function() { - expect(process.env['CLI_ORDER'], `other test was executed first`).to.be.undefined; - process.env['CLI_ORDER'] = '3'; - }); +describe('CLI blob order test - 3', function () { + it('Executed as first test', function () { + expect(process.env['CLI_ORDER'], `other test was executed first`).to.be.undefined; + process.env['CLI_ORDER'] = '3'; + }); }); diff --git a/tests/test-project/src/test/debug/debug.test.ts b/tests/test-project/src/test/debug/debug.test.ts index 7f82452dc..79b0f9988 100644 --- a/tests/test-project/src/test/debug/debug.test.ts +++ b/tests/test-project/src/test/debug/debug.test.ts @@ -15,342 +15,371 @@ * limitations under the License. */ -import { ActivityBar, BottomBarPanel, Breakpoint, BreakpointSectionItem, DebugConsoleView, DebugToolbar, DebugView, EditorView, error, Key, TextEditor, until, VSBrowser, WebDriver, Workbench } from 'vscode-extension-tester'; +import { + ActivityBar, + BottomBarPanel, + Breakpoint, + BreakpointSectionItem, + DebugConsoleView, + DebugToolbar, + DebugView, + EditorView, + error, + Key, + TextEditor, + until, + VSBrowser, + WebDriver, + Workbench, +} from 'vscode-extension-tester'; import * as path from 'path'; -import { expect } from "chai"; +import { expect } from 'chai'; const line = 7; describe('Debugging', function () { - process.env.NODE = process.execPath; - const folder = path.resolve(__dirname, '..', '..', '..', 'resources', 'debug-project'); - let view: DebugView; - - before(async function () { - this.timeout(30000); - const browser = VSBrowser.instance; - await browser.openResources(folder); - await browser.driver.sleep(5000); - await browser.openResources(path.join(folder, 'test.js')); - await browser.driver.sleep(5000); - view = (await (await new ActivityBar().getViewControl('Run'))?.openView()) as DebugView; - - // clear notifications center which causes flaky tests from VS Code version 1.75.x - await (await new Workbench().openNotificationsCenter()).clearAllNotifications(); - }); - - after('After cleanup', async function () { - this.timeout(15000); - await new EditorView().closeAllEditors(); - await (await new ActivityBar().getViewControl('Run and Debug'))?.closeView(); - await new BottomBarPanel().toggle(false); - - await new Workbench().executeCommand('Workspaces: Close Workspace'); - await new Promise(res => setTimeout(res, 5000)); - }); - - describe('Debug View', () => { - it('getLaunchConfiguration works', async function () { - if(process.platform !== 'darwin' && VSBrowser.instance.version >= '1.87.0') { - this.skip(); - } - const config = await view.getLaunchConfiguration(); - expect(config).equals('Test Launch'); - }); - - it('getLaunchConfigurations works', async function () { - const configs = await view.getLaunchConfigurations(); - expect(configs).contains('Test Launch'); - expect(configs).contains('Test Launch2'); - }); - - it('selectLaunchConfiguration works', async function () { - if(process.platform !== 'darwin' && VSBrowser.instance.version >= '1.87.0') { - this.skip(); - } - await view.selectLaunchConfiguration('Test Launch2'); - const config = await view.getLaunchConfiguration(); - expect(config).equals('Test Launch2'); - }); - }); - - describe('Debug Session', function () { - let editor: TextEditor; - let debugBar: DebugToolbar; - let driver: WebDriver; - let breakpoint!: Breakpoint; - - before(async function () { - editor = await new EditorView().openEditor('test.js') as TextEditor; - driver = editor.getDriver(); - }); - - after(async function () { - this.timeout(15000); - if (await debugBar.isDisplayed()) { - await debugBar.stop(); - } - }); - - it('set first breakpoint', async function () { - const result = await editor.toggleBreakpoint(line + 1); - expect(result).to.be.true; - }); - - it('set second breakpoint', async function () { - const result = await editor.toggleBreakpoint(line); - expect(result).to.be.true; - }); - - it('start the debug session', async function () { - await view.start(); - debugBar = await DebugToolbar.create(); - await debugBar.waitForBreakPoint(); - }); - - it('TextEditor: getPausedBreakpoint works', async function () { - breakpoint = await driver.wait(async () => await editor.getPausedBreakpoint(), 10000, 'could not find paused breakpoint') as Breakpoint; - }); - - it('Breakpoint: getLineNumber works', async function () { - expect(await breakpoint.getLineNumber()).equals(line); - }); - - it('Breakpoint: isPaused works', async function () { - expect(await breakpoint.isPaused()).to.be.true; - }); - - it('BreakpointSectionItem.getBreakpoint', async function () { - const item = await getBreakpointItem(view, this.timeout() - 2000); - const breakpoint = await item?.getBreakpoint(); - expect(breakpoint).not.undefined; - }); - - it('BreakpointSectionItem.isBreakpointEnabled', async function () { - const item = await getBreakpointItem(view, this.timeout() - 2000); - expect(await item?.isBreakpointEnabled()).to.be.true; - }); - - it('BreakpointSectionItem.setBreakpointEnabled', async function () { - let item = await getBreakpointItem(view, this.timeout() - 2000); - if(item === undefined) { - throw Error('Breakpoint Item was not found!'); - } - const driver = item.getDriver(); - const status = await item.isBreakpointEnabled(); // true - - await item?.setBreakpointEnabled(status); // true --> true - await driver?.wait( - async () => { - item = await getBreakpointItem(view, this.timeout() - 2000); - return await item?.isBreakpointEnabled() === status; - }, - this.timeout() - 2000, - `could not set status from ${status} to ${status}` - ); - - await item?.setBreakpointEnabled(!status); // true --> false - await driver?.wait( - async () => { - item = await getBreakpointItem(view, this.timeout() - 2000); - return await item?.isBreakpointEnabled() === !status; - }, - this.timeout() - 2000, - `could not set status from ${status} to ${!status}` - ); - - await item?.setBreakpointEnabled(status); // false --> true - await driver?.wait( - async () => { - item = await getBreakpointItem(view, this.timeout() - 2000); - return await item?.isBreakpointEnabled() === status; - }, - this.timeout() - 2000, - `could not set status from ${!status} to ${status}` - ); - }); - - it('BreakpointSectionItem.getLabel', async function () { - const item = await getBreakpointItem(view, this.timeout() - 2000); - expect(await item?.getLabel()).equals('test.js'); - }); - - // Currently not supported - it.skip('BreakpointSectionItem.getBreakpointFilePath', async function () { - const item = await getBreakpointItem(view, this.timeout() - 2000); - expect(await item?.getBreakpointFilePath()).equals('test.js'); - }); - - it('BreakpointSectionItem.getBreakpointLine', async function () { - const item = await getBreakpointItem(view, this.timeout() - 2000); - expect(await item?.getBreakpointLine()).equals(line); - }); - - it('BreakpointSectionItem.getActionButtons', async function () { - await VSBrowser.instance.driver.wait(async () => { - try { - const item = await getBreakpointItem(view, this.timeout() - 2000); - const actionBtns = await item?.getActionButtons() ?? []; - return actionBtns.length > 0; - } - catch (e) { - if (e instanceof error.StaleElementReferenceError) { - return false; - } - throw e; - } - }, this.timeout() - 2000, 'actions are empty'); - }); - - it('VariableSectionItem.getVariableName', async function () { - const item = await getNumVariable(view, this.timeout() - 2000); - expect(await item?.getVariableName()).equals('num:'); - }); - - it('VariableSectionItem.getVariableValue', async function () { - const item = await getNumVariable(view, this.timeout() - 2000); - expect(await item?.getVariableValue()).equals('5'); - }); - - it('VariableSectionItem.getVariableNameTooltip', async function () { - if(VSBrowser.instance.version >= '1.88.0') { - this.skip(); - } - const item = await getNumVariable(view, this.timeout() - 2000); - expect(await item?.getVariableNameTooltip()).equals('number'); - }); - - it('VariableSectionItem.getVariableValueTooltip', async function () { - if(VSBrowser.instance.version >= '1.89.0') { - this.skip(); - } - const item = await getNumVariable(view, this.timeout() - 2000); - expect(await item?.getVariableValueTooltip()).equals('5'); - }); - - it('Variable view: setVariableValue', async function () { - let item = await getNumVariable(view, this.timeout() - 2000); - expect(await item?.getVariableValue()).equals('5'); - await item?.setVariableValue('42'); - item = await getNumVariable(view, this.timeout() - 2000); - expect(await item?.getVariableValue()).equals('42'); - }); - - it('evaluate an expression', async function () { - const debugConsole = new DebugConsoleView(); - await debugConsole.setExpression(`console.log('foo')`); - await debugConsole.evaluateExpression(); - - await debugConsole.evaluateExpression(`console.log('bar')`); - await new Promise(res => setTimeout(res, 1000)); - - const text = await debugConsole.getText(); - expect(text).to.have.string('foo'); - expect(text).to.have.string('bar'); - }); - - it('check content assist', async function () { - const debugConsole = new DebugConsoleView(); - await debugConsole.setExpression('i'); - await new Promise(res => setTimeout(res, 1000)); - let assist; - try { - assist = await debugConsole.getContentAssist(); - } catch (err) { - await VSBrowser.instance.driver.actions().keyDown(Key.CONTROL).sendKeys(Key.SPACE).perform(); - assist = await debugConsole.getContentAssist(); - } - const list = await assist.getItems(); - - expect(list).not.to.be.empty; - }); - - it('stop the debug session', async function () { - await debugBar.stop(); - await editor.getDriver().wait(until.elementIsNotVisible(debugBar)); - }); - - it('remove the second breakpoint', async function () { - const result = await editor.toggleBreakpoint(line + 1); - expect(result).to.be.false; - }); - - it('remove the first breakpoint', async function () { - const result = await editor.toggleBreakpoint(line); - expect(result).to.be.false; - }); - }); - - describe('Debug Console view', function () { - - it('can get text', async function () { - const view = await new BottomBarPanel().openDebugConsoleView(); - expect(await view.isDisplayed()).is.true; - - const text = await view.getText(); - expect(text).is.not.empty; - }); - }); - + process.env.NODE = process.execPath; + const folder = path.resolve(__dirname, '..', '..', '..', 'resources', 'debug-project'); + let view: DebugView; + + before(async function () { + this.timeout(30000); + const browser = VSBrowser.instance; + await browser.openResources(folder); + await browser.driver.sleep(5000); + await browser.openResources(path.join(folder, 'test.js')); + await browser.driver.sleep(5000); + view = (await (await new ActivityBar().getViewControl('Run'))?.openView()) as DebugView; + + // clear notifications center which causes flaky tests from VS Code version 1.75.x + await (await new Workbench().openNotificationsCenter()).clearAllNotifications(); + }); + + after('After cleanup', async function () { + this.timeout(15000); + await new EditorView().closeAllEditors(); + await (await new ActivityBar().getViewControl('Run and Debug'))?.closeView(); + await new BottomBarPanel().toggle(false); + + await new Workbench().executeCommand('Workspaces: Close Workspace'); + await new Promise((res) => setTimeout(res, 5000)); + }); + + describe('Debug View', () => { + it('getLaunchConfiguration works', async function () { + if (process.platform !== 'darwin' && VSBrowser.instance.version >= '1.87.0') { + this.skip(); + } + const config = await view.getLaunchConfiguration(); + expect(config).equals('Test Launch'); + }); + + it('getLaunchConfigurations works', async function () { + const configs = await view.getLaunchConfigurations(); + expect(configs).contains('Test Launch'); + expect(configs).contains('Test Launch2'); + }); + + it('selectLaunchConfiguration works', async function () { + if (process.platform !== 'darwin' && VSBrowser.instance.version >= '1.87.0') { + this.skip(); + } + await view.selectLaunchConfiguration('Test Launch2'); + const config = await view.getLaunchConfiguration(); + expect(config).equals('Test Launch2'); + }); + }); + + describe('Debug Session', function () { + let editor: TextEditor; + let debugBar: DebugToolbar; + let driver: WebDriver; + let breakpoint!: Breakpoint; + + before(async function () { + editor = (await new EditorView().openEditor('test.js')) as TextEditor; + driver = editor.getDriver(); + }); + + after(async function () { + this.timeout(15000); + if (await debugBar.isDisplayed()) { + await debugBar.stop(); + } + }); + + it('set first breakpoint', async function () { + const result = await editor.toggleBreakpoint(line + 1); + expect(result).to.be.true; + }); + + it('set second breakpoint', async function () { + const result = await editor.toggleBreakpoint(line); + expect(result).to.be.true; + }); + + it('start the debug session', async function () { + await view.start(); + debugBar = await DebugToolbar.create(); + await debugBar.waitForBreakPoint(); + }); + + it('TextEditor: getPausedBreakpoint works', async function () { + breakpoint = (await driver.wait( + async () => await editor.getPausedBreakpoint(), + 10000, + 'could not find paused breakpoint', + )) as Breakpoint; + }); + + it('Breakpoint: getLineNumber works', async function () { + expect(await breakpoint.getLineNumber()).equals(line); + }); + + it('Breakpoint: isPaused works', async function () { + expect(await breakpoint.isPaused()).to.be.true; + }); + + it('BreakpointSectionItem.getBreakpoint', async function () { + const item = await getBreakpointItem(view, this.timeout() - 2000); + const breakpoint = await item?.getBreakpoint(); + expect(breakpoint).not.undefined; + }); + + it('BreakpointSectionItem.isBreakpointEnabled', async function () { + const item = await getBreakpointItem(view, this.timeout() - 2000); + expect(await item?.isBreakpointEnabled()).to.be.true; + }); + + it('BreakpointSectionItem.setBreakpointEnabled', async function () { + let item = await getBreakpointItem(view, this.timeout() - 2000); + if (item === undefined) { + throw Error('Breakpoint Item was not found!'); + } + const driver = item.getDriver(); + const status = await item.isBreakpointEnabled(); // true + + await item?.setBreakpointEnabled(status); // true --> true + await driver?.wait( + async () => { + item = await getBreakpointItem(view, this.timeout() - 2000); + return (await item?.isBreakpointEnabled()) === status; + }, + this.timeout() - 2000, + `could not set status from ${status} to ${status}`, + ); + + await item?.setBreakpointEnabled(!status); // true --> false + await driver?.wait( + async () => { + item = await getBreakpointItem(view, this.timeout() - 2000); + return (await item?.isBreakpointEnabled()) === !status; + }, + this.timeout() - 2000, + `could not set status from ${status} to ${!status}`, + ); + + await item?.setBreakpointEnabled(status); // false --> true + await driver?.wait( + async () => { + item = await getBreakpointItem(view, this.timeout() - 2000); + return (await item?.isBreakpointEnabled()) === status; + }, + this.timeout() - 2000, + `could not set status from ${!status} to ${status}`, + ); + }); + + it('BreakpointSectionItem.getLabel', async function () { + const item = await getBreakpointItem(view, this.timeout() - 2000); + expect(await item?.getLabel()).equals('test.js'); + }); + + // Currently not supported + it.skip('BreakpointSectionItem.getBreakpointFilePath', async function () { + const item = await getBreakpointItem(view, this.timeout() - 2000); + expect(await item?.getBreakpointFilePath()).equals('test.js'); + }); + + it('BreakpointSectionItem.getBreakpointLine', async function () { + const item = await getBreakpointItem(view, this.timeout() - 2000); + expect(await item?.getBreakpointLine()).equals(line); + }); + + it('BreakpointSectionItem.getActionButtons', async function () { + await VSBrowser.instance.driver.wait( + async () => { + try { + const item = await getBreakpointItem(view, this.timeout() - 2000); + const actionBtns = (await item?.getActionButtons()) ?? []; + return actionBtns.length > 0; + } catch (e) { + if (e instanceof error.StaleElementReferenceError) { + return false; + } + throw e; + } + }, + this.timeout() - 2000, + 'actions are empty', + ); + }); + + it('VariableSectionItem.getVariableName', async function () { + const item = await getNumVariable(view, this.timeout() - 2000); + expect(await item?.getVariableName()).equals('num:'); + }); + + it('VariableSectionItem.getVariableValue', async function () { + const item = await getNumVariable(view, this.timeout() - 2000); + expect(await item?.getVariableValue()).equals('5'); + }); + + it('VariableSectionItem.getVariableNameTooltip', async function () { + if (VSBrowser.instance.version >= '1.88.0') { + this.skip(); + } + const item = await getNumVariable(view, this.timeout() - 2000); + expect(await item?.getVariableNameTooltip()).equals('number'); + }); + + it('VariableSectionItem.getVariableValueTooltip', async function () { + if (VSBrowser.instance.version >= '1.89.0') { + this.skip(); + } + const item = await getNumVariable(view, this.timeout() - 2000); + expect(await item?.getVariableValueTooltip()).equals('5'); + }); + + it('Variable view: setVariableValue', async function () { + let item = await getNumVariable(view, this.timeout() - 2000); + expect(await item?.getVariableValue()).equals('5'); + await item?.setVariableValue('42'); + item = await getNumVariable(view, this.timeout() - 2000); + expect(await item?.getVariableValue()).equals('42'); + }); + + it('evaluate an expression', async function () { + const debugConsole = new DebugConsoleView(); + await debugConsole.setExpression(`console.log('foo')`); + await debugConsole.evaluateExpression(); + + await debugConsole.evaluateExpression(`console.log('bar')`); + await new Promise((res) => setTimeout(res, 1000)); + + const text = await debugConsole.getText(); + expect(text).to.have.string('foo'); + expect(text).to.have.string('bar'); + }); + + it('check content assist', async function () { + const debugConsole = new DebugConsoleView(); + await debugConsole.setExpression('i'); + await new Promise((res) => setTimeout(res, 1000)); + let assist; + try { + assist = await debugConsole.getContentAssist(); + } catch (err) { + await VSBrowser.instance.driver.actions().keyDown(Key.CONTROL).sendKeys(Key.SPACE).perform(); + assist = await debugConsole.getContentAssist(); + } + const list = await assist.getItems(); + + expect(list).not.to.be.empty; + }); + + it('stop the debug session', async function () { + await debugBar.stop(); + await editor.getDriver().wait(until.elementIsNotVisible(debugBar)); + }); + + it('remove the second breakpoint', async function () { + const result = await editor.toggleBreakpoint(line + 1); + expect(result).to.be.false; + }); + + it('remove the first breakpoint', async function () { + const result = await editor.toggleBreakpoint(line); + expect(result).to.be.false; + }); + }); + + describe('Debug Console view', function () { + it('can get text', async function () { + const view = await new BottomBarPanel().openDebugConsoleView(); + expect(await view.isDisplayed()).is.true; + + const text = await view.getText(); + expect(text).is.not.empty; + }); + }); }); async function getNumVariable(view: DebugView, timeout: number) { - try { - return await view.getDriver().wait(async () => { - try { - const variablesSection = await view.getVariablesSection(); - await variablesSection?.openItem('Local'); - return await variablesSection.findItem('num:'); - } - catch (e) { - if (e instanceof error.StaleElementReferenceError || - e instanceof error.NoSuchElementError || - e instanceof error.ElementNotInteractableError) { - return undefined; - } - throw e; - } - }, timeout, 'could not find num variable'); - } - catch (e) { - if (e instanceof error.TimeoutError) { - console.log('items'); - const variablesSection = await view.getVariablesSection(); - const items = await variablesSection.getVisibleItems(); - for (const item of items) { - console.log(`Item: ${await item.getLabel().catch(() => '___error')}`); - } - } - throw e; - } + try { + return await view.getDriver().wait( + async () => { + try { + const variablesSection = await view.getVariablesSection(); + await variablesSection?.openItem('Local'); + return await variablesSection.findItem('num:'); + } catch (e) { + if ( + e instanceof error.StaleElementReferenceError || + e instanceof error.NoSuchElementError || + e instanceof error.ElementNotInteractableError + ) { + return undefined; + } + throw e; + } + }, + timeout, + 'could not find num variable', + ); + } catch (e) { + if (e instanceof error.TimeoutError) { + console.log('items'); + const variablesSection = await view.getVariablesSection(); + const items = await variablesSection.getVisibleItems(); + for (const item of items) { + console.log(`Item: ${await item.getLabel().catch(() => '___error')}`); + } + } + throw e; + } } async function getBreakpointItem(view: DebugView, timeout: number) { - try { - return await view.getDriver().wait(async function () { - try { - const breakpointSection = await view.getBreakpointSection(); - return await breakpointSection.findItem(async (item: BreakpointSectionItem) => await item.getBreakpointLine() === line); - } - catch (e) { - if (e instanceof error.StaleElementReferenceError || - e instanceof error.NoSuchElementError || - e instanceof error.ElementNotInteractableError) { - return undefined; - } - throw e; - } - }, timeout, 'could not find breakpoint item'); - } - catch (e) { - if (e instanceof error.TimeoutError) { - console.log('items'); - const variablesSection = await view.getBreakpointSection(); - const items = await variablesSection.getVisibleItems(); - for (const item of items) { - console.log(`Item: ${await item.getLabel().catch(() => '___error')}`); - } - } - throw e; - } + try { + return await view.getDriver().wait( + async function () { + try { + const breakpointSection = await view.getBreakpointSection(); + return await breakpointSection.findItem(async (item: BreakpointSectionItem) => (await item.getBreakpointLine()) === line); + } catch (e) { + if ( + e instanceof error.StaleElementReferenceError || + e instanceof error.NoSuchElementError || + e instanceof error.ElementNotInteractableError + ) { + return undefined; + } + throw e; + } + }, + timeout, + 'could not find breakpoint item', + ); + } catch (e) { + if (e instanceof error.TimeoutError) { + console.log('items'); + const variablesSection = await view.getBreakpointSection(); + const items = await variablesSection.getVisibleItems(); + for (const item of items) { + console.log(`Item: ${await item.getLabel().catch(() => '___error')}`); + } + } + throw e; + } } diff --git a/tests/test-project/src/test/dialog/dialog.test.ts b/tests/test-project/src/test/dialog/dialog.test.ts index 56769556a..65116e670 100644 --- a/tests/test-project/src/test/dialog/dialog.test.ts +++ b/tests/test-project/src/test/dialog/dialog.test.ts @@ -15,51 +15,51 @@ * limitations under the License. */ -import { expect } from "chai"; +import { expect } from 'chai'; import { By, EditorView, InputBox, ModalDialog, TextEditor, until, VSBrowser, after, before, Workbench } from 'vscode-extension-tester'; (VSBrowser.instance.version >= '1.50.0' && process.platform !== 'darwin' ? describe : describe.skip)('Modal Dialog', function () { - let dialog: ModalDialog; + let dialog: ModalDialog; - before(async () => { - this.timeout(30000); - await new Workbench().executeCommand('Create: New File...'); - await (await InputBox.create()).selectQuickPick('Text File'); - await new Promise(res => setTimeout(res, 1000)); - const editor = new TextEditor(); - await editor.typeTextAt(1, 1, 'text'); - await new Promise(res => setTimeout(res, 1000)); - await new EditorView().closeEditor(await editor.getTitle()); - await new Promise(res => setTimeout(res, 1000)); - dialog = new ModalDialog(); - await dialog.getDriver().wait(until.elementsLocated(By.className('monaco-dialog-box')), 5000); - }); + before(async () => { + this.timeout(30000); + await new Workbench().executeCommand('Create: New File...'); + await (await InputBox.create()).selectQuickPick('Text File'); + await new Promise((res) => setTimeout(res, 1000)); + const editor = new TextEditor(); + await editor.typeTextAt(1, 1, 'text'); + await new Promise((res) => setTimeout(res, 1000)); + await new EditorView().closeEditor(await editor.getTitle()); + await new Promise((res) => setTimeout(res, 1000)); + dialog = new ModalDialog(); + await dialog.getDriver().wait(until.elementsLocated(By.className('monaco-dialog-box')), 5000); + }); - after(async function () { - await new Promise(res => setTimeout(res, 1000)); - }); + after(async function () { + await new Promise((res) => setTimeout(res, 1000)); + }); - it('getMessage works', async function () { - this.timeout(10000); - const message = await dialog.getMessage(); - expect(message).has.string('Do you want to save the changes'); - }); + it('getMessage works', async function () { + this.timeout(10000); + const message = await dialog.getMessage(); + expect(message).has.string('Do you want to save the changes'); + }); - it('getDetails works', async function () { - this.timeout(10000); - const details = await dialog.getDetails(); - expect(details).has.string('Your changes will be lost'); - }); + it('getDetails works', async function () { + this.timeout(10000); + const details = await dialog.getDetails(); + expect(details).has.string('Your changes will be lost'); + }); - it('getButtons works', async function () { - this.timeout(10000); - const buttons = await dialog.getButtons(); - expect(buttons.length).equals(3); - }); + it('getButtons works', async function () { + this.timeout(10000); + const buttons = await dialog.getButtons(); + expect(buttons.length).equals(3); + }); - it('pushButton works', async function () { - this.timeout(10000); - await dialog.pushButton(`Don't Save`); - await dialog.getDriver().wait(until.stalenessOf(dialog), 2000); - }); + it('pushButton works', async function () { + this.timeout(10000); + await dialog.pushButton(`Don't Save`); + await dialog.getDriver().wait(until.stalenessOf(dialog), 2000); + }); }); diff --git a/tests/test-project/src/test/editor/customEditor.test.ts b/tests/test-project/src/test/editor/customEditor.test.ts index 812b2d412..f95fceba7 100644 --- a/tests/test-project/src/test/editor/customEditor.test.ts +++ b/tests/test-project/src/test/editor/customEditor.test.ts @@ -20,69 +20,69 @@ import path from 'path'; import { CustomEditor, EditorView, VSBrowser, By } from 'vscode-extension-tester'; describe('CustomEditor', () => { - let editor: CustomEditor; + let editor: CustomEditor; - const CUSTOM_TITLE: string = 'example.cscratch'; + const CUSTOM_TITLE: string = 'example.cscratch'; - before(async () => { - await VSBrowser.instance.openResources(path.resolve(__dirname, '..', '..', '..', 'resources', CUSTOM_TITLE)); - editor = new CustomEditor(); - }); + before(async () => { + await VSBrowser.instance.openResources(path.resolve(__dirname, '..', '..', '..', 'resources', CUSTOM_TITLE)); + editor = new CustomEditor(); + }); - after(async () => { - await new EditorView().closeAllEditors(); - }); + after(async () => { + await new EditorView().closeAllEditors(); + }); - it('webview is available', async () => { - const webview = editor.getWebView(); - await webview.switchToFrame(); - try { - const btn = await webview.findWebElement(By.className('add-button')); - await new Promise(res => setTimeout(res, 500)); - await btn.click(); - await new Promise(res => setTimeout(res, 1000)); - const notes = await webview.findWebElements(By.className('note')); - const note = notes[notes.length - 1]; - await webview.getDriver().actions().move({origin:note}).perform(); - await note.findElement(By.className('delete-button')).click(); - } catch(err) { - if (err instanceof Error) { - expect.fail(err.message); - } - } finally { - if(webview) { - await webview.switchBack(); - } - } - }); + it('webview is available', async () => { + const webview = editor.getWebView(); + await webview.switchToFrame(); + try { + const btn = await webview.findWebElement(By.className('add-button')); + await new Promise((res) => setTimeout(res, 500)); + await btn.click(); + await new Promise((res) => setTimeout(res, 1000)); + const notes = await webview.findWebElements(By.className('note')); + const note = notes[notes.length - 1]; + await webview.getDriver().actions().move({ origin: note }).perform(); + await note.findElement(By.className('delete-button')).click(); + } catch (err) { + if (err instanceof Error) { + expect.fail(err.message); + } + } finally { + if (webview) { + await webview.switchBack(); + } + } + }); - it('isDirty works', async () => { - await new EditorView().openEditor(CUSTOM_TITLE); - await new Promise(res => setTimeout(res, 500)); - expect(await editor.isDirty()).is.true; - }); + it('isDirty works', async () => { + await new EditorView().openEditor(CUSTOM_TITLE); + await new Promise((res) => setTimeout(res, 500)); + expect(await editor.isDirty()).is.true; + }); - it('save works', async () => { - await new EditorView().openEditor(CUSTOM_TITLE); - await new Promise(res => setTimeout(res, 500)); - await editor.save(); - await new Promise(res => setTimeout(res, 500)); - expect(await editor.isDirty()).is.false; - }); + it('save works', async () => { + await new EditorView().openEditor(CUSTOM_TITLE); + await new Promise((res) => setTimeout(res, 500)); + await editor.save(); + await new Promise((res) => setTimeout(res, 500)); + expect(await editor.isDirty()).is.false; + }); - it('save as works', async () => { - await new EditorView().openEditor(CUSTOM_TITLE); - await new Promise(res => setTimeout(res, 500)); - try { - const input = await editor.saveAs(); - expect(await input.isDisplayed()).is.true; - if (input && await input.isDisplayed()) { - await input.cancel(); - } - } catch (err) { - if (err instanceof Error) { - expect.fail(err.message); - } - } - }); -}); \ No newline at end of file + it('save as works', async () => { + await new EditorView().openEditor(CUSTOM_TITLE); + await new Promise((res) => setTimeout(res, 500)); + try { + const input = await editor.saveAs(); + expect(await input.isDisplayed()).is.true; + if (input && (await input.isDisplayed())) { + await input.cancel(); + } + } catch (err) { + if (err instanceof Error) { + expect.fail(err.message); + } + } + }); +}); diff --git a/tests/test-project/src/test/editor/diffEditor.test.ts b/tests/test-project/src/test/editor/diffEditor.test.ts index 636fd7ec2..4a1e08771 100644 --- a/tests/test-project/src/test/editor/diffEditor.test.ts +++ b/tests/test-project/src/test/editor/diffEditor.test.ts @@ -20,37 +20,41 @@ import { expect } from 'chai'; import { EditorView, Workbench, DiffEditor, QuickOpenBox, InputBox, VSBrowser } from 'vscode-extension-tester'; describe('DiffEditor', async () => { - let editor: DiffEditor; + let editor: DiffEditor; - before(async function() { - this.timeout(250000); - await VSBrowser.instance.openResources(path.resolve(__dirname, '..', '..', '..', 'resources', 'test-file-a.txt'), - path.resolve(__dirname, '..', '..', '..', 'resources', 'test-file-b.txt')); - await new EditorView().openEditor('test-file-b.txt'); - await new Workbench().executeCommand('File: Compare Active File With...'); - let quickOpen: QuickOpenBox | InputBox; - if (VSBrowser.instance.version >= '1.44.0') { - quickOpen = await InputBox.create(); - } else { - quickOpen = await QuickOpenBox.create(); - } - await quickOpen.setText('test-file-a.txt'); - await quickOpen.confirm(); - - editor = new DiffEditor(); - }); + before(async function () { + this.timeout(250000); + await VSBrowser.instance.openResources( + path.resolve(__dirname, '..', '..', '..', 'resources', 'test-file-a.txt'), + path.resolve(__dirname, '..', '..', '..', 'resources', 'test-file-b.txt'), + ); + await new EditorView().openEditor('test-file-b.txt'); + await new Workbench().executeCommand('File: Compare Active File With...'); + let quickOpen: QuickOpenBox | InputBox; + if (VSBrowser.instance.version >= '1.44.0') { + quickOpen = await InputBox.create(); + } else { + quickOpen = await QuickOpenBox.create(); + } + await quickOpen.setText('test-file-a.txt'); + await quickOpen.confirm(); - after(async () => { - await new Workbench().executeCommand('View: Close Editor'); - await new Promise((res) => { setTimeout(res, 500); }); - await new EditorView().closeAllEditors(); - }); + editor = new DiffEditor(); + }); - it('can get original and modified editors', async function() { - const originalEditor = await editor.getOriginalEditor(); - const modifiedEditor = await editor.getModifiedEditor(); + after(async () => { + await new Workbench().executeCommand('View: Close Editor'); + await new Promise((res) => { + setTimeout(res, 500); + }); + await new EditorView().closeAllEditors(); + }); - expect(await originalEditor.getText()).equals('b'); - expect(await modifiedEditor.getText()).equals('a'); - }); + it('can get original and modified editors', async function () { + const originalEditor = await editor.getOriginalEditor(); + const modifiedEditor = await editor.getModifiedEditor(); + + expect(await originalEditor.getText()).equals('b'); + expect(await modifiedEditor.getText()).equals('a'); + }); }); diff --git a/tests/test-project/src/test/editor/editorView.test.ts b/tests/test-project/src/test/editor/editorView.test.ts index 5686315fd..32948cb61 100644 --- a/tests/test-project/src/test/editor/editorView.test.ts +++ b/tests/test-project/src/test/editor/editorView.test.ts @@ -16,207 +16,227 @@ */ import { expect } from 'chai'; -import { EditorView, EditorTab, EditorTabNotFound, error, Workbench, TextEditor, SettingsEditor, WebView, QuickOpenBox, DiffEditor, InputBox, VSBrowser } from 'vscode-extension-tester'; +import { + EditorView, + EditorTab, + EditorTabNotFound, + error, + Workbench, + TextEditor, + SettingsEditor, + WebView, + QuickOpenBox, + DiffEditor, + InputBox, + VSBrowser, +} from 'vscode-extension-tester'; describe('EditorView', function () { - let view: EditorView; - - before(async function () { - this.timeout(60000); - view = new EditorView(); - await view.closeAllEditors(); - await newUntitledFile('Untitled-1'); - await newUntitledFile('Untitled-2'); - await new Workbench().executeCommand('Webview Test'); - await view.getDriver().sleep(2500); - await new Workbench().executeCommand('Open Settings UI'); - await view.getDriver().sleep(500); - }); - - after(async function () { - await view.closeAllEditors(); - }); - - it('openEditor works with text editor', async function () { - const editor = await view.openEditor('Untitled-1') as TextEditor; - expect(await editor.getTitle()).equals('Untitled-1'); - }); - - it('openEditor works with settings editor', async function () { - const editor = await view.openEditor('Settings') as SettingsEditor; - expect(editor.findSetting).not.undefined; - - await view.closeEditor(await editor.getTitle()); - }); - - it('openEditor works with webview editor', async function () { - let editorTitle: string = ''; - (await view.getOpenEditorTitles()).forEach(title => { - if (title.startsWith('Test WebView')) { - editorTitle = title; - } - }); - const editor = await view.openEditor(editorTitle) as WebView; - expect(editor.findWebElement).not.undefined; - - await view.closeEditor(editorTitle); - }); - - it('openEditor works with diff editor', async function () { - await view.openEditor('Untitled-2'); - - await new Workbench().executeCommand('File: Compare Active File With...'); - let quickOpen: QuickOpenBox | InputBox; - if (VSBrowser.instance.version >= '1.44.0') { - quickOpen = await InputBox.create(); - } else { - quickOpen = await QuickOpenBox.create(); - } - await quickOpen.setText('Untitled-1'); - await quickOpen.confirm(); - await quickOpen.getDriver().sleep(500); - - const diffEditor = await view.openEditor('Untitled-2 ↔ Untitled-1') as DiffEditor; - await diffEditor.getDriver().sleep(1000); - expect(await diffEditor.getOriginalEditor()).not.undefined; - expect(await diffEditor.getModifiedEditor()).not.undefined; - }); - - it('getTabByTitle works', async function () { - const tab = await view.getTabByTitle('Untitled-1'); - expect(tab).not.undefined; - }); - - it('getOpenEditorTitles works', async function () { - const tabs = await view.getOpenEditorTitles(); - expect(tabs).not.empty; - expect(tabs).contains('Untitled-1'); - expect(tabs).contains('Untitled-2'); - }); - - it('closeEditor works', async function () { - await view.closeEditor('Untitled-1'); - const tabs = await view.getOpenEditorTitles(); - expect(tabs).not.contains('Untitled-1'); - }); - - it('getActions works', async function () { - const actions = await view.getActions(); - expect(actions).not.empty; - }); - - it('getAction(title: string) works', async function () { - const action = await view.getAction('More Actions...'); - expect(await action?.getTitle()).equal('More Actions...'); - }); - - it('getAction(predicate: PredicateFunction) works', async function () { - const action = await view.getAction(async (action) => await action.getTitle() === 'More Actions...'); - expect(await action?.getTitle()).equal('More Actions...'); - }); - - it('Editor getAction works', async function () { - await new EditorView().openEditor('Untitled-2'); - const editorAction = await view.getAction('Hello World'); - expect(editorAction).not.undefined; - }); - - describe('Editor Tab', function () { - let tab2: EditorTab; - let tab3: EditorTab; - - before(async function () { - await newUntitledFile('Untitled-3'); - tab2 = await view.getTabByTitle('Untitled-2'); - tab3 = await view.getTabByTitle('Untitled-3'); - }); - - it('getTitle works', async function () { - expect(await tab2.getTitle()).equals('Untitled-2'); - }); - - it('isSelected works on active tab', async function () { - expect(await tab3.isSelected()).to.be.true; - }); - - it('isSelected works on inactive tab', async function () { - expect(await tab2.isSelected()).to.be.false; - }); - }); - - describe('Editor Groups', function () { - const testFile = 'Untitled-4'; - - before(async function () { - view = new EditorView(); - await newUntitledFile(testFile); - }); - - it('getEditorGroups works', async function () { - this.timeout(30000); - const driverActions = view.getDriver().actions(); - await driverActions.clear(); - await driverActions.keyDown(EditorView.ctlKey).sendKeys('\\').keyUp(EditorView.ctlKey).perform(); - await view.getDriver().wait(async function() { - return (await view.getEditorGroups()).length === 2; - }, 15000, 'could not get 2 editor groups'); - - const groups = await view.getEditorGroups(); - const group1 = await view.getEditorGroup(0); - const group2 = await view.getEditorGroup(1); - - expect(groups.length).equals(2); - expect((await group1.getRect()).x).equals((await groups[0].getRect()).x); - expect((await group2.getRect()).x).equals((await groups[1].getRect()).x); - }); - - it('openEditor works for different groups', async function () { - const editor1 = await view.openEditor(testFile, 0); - const editor2 = await view.openEditor(testFile, 1); - - expect((await editor1.getRect()).x < (await editor2.getRect()).x); - }); - - it('closeEditor works for different groups', async function () { - await view.getDriver().actions().keyDown(EditorView.ctlKey).sendKeys('\\').perform(); - await view.getDriver().sleep(500); - - await view.closeEditor(testFile, 2); - expect((await view.getEditorGroups()).length).equals(2); - }); - - it('getOpenEditorTitles works for different editor groups', async function () { - const titles = await view.getOpenEditorTitles(); - const titles1 = await view.getOpenEditorTitles(0); - const titles2 = await view.getOpenEditorTitles(1); - - const allTitles = [...titles1, ...titles2]; - expect(titles).deep.equals(allTitles); - }); - }); - - async function newUntitledFile(title?: string, group?: number, timeout: number = 10000): Promise { - await new Workbench().executeCommand('Create: New File...'); - await (await InputBox.create()).selectQuickPick('Text File'); - if (title === undefined) { - await view.getDriver().sleep(500); - } - else { - let view = new EditorView(); - await view.getDriver().wait(async () => { - try { - return await view.getTabByTitle(title, group); - } catch (e) { - if (e instanceof EditorTabNotFound) { - return undefined; - } - if (e instanceof error.StaleElementReferenceError) { - view = new EditorView(); - return undefined; - } - throw e; - } - }, timeout, `could not find tab with title '${title}' in group '${group}'`); - } - } + let view: EditorView; + + before(async function () { + this.timeout(60000); + view = new EditorView(); + await view.closeAllEditors(); + await newUntitledFile('Untitled-1'); + await newUntitledFile('Untitled-2'); + await new Workbench().executeCommand('Webview Test'); + await view.getDriver().sleep(2500); + await new Workbench().executeCommand('Open Settings UI'); + await view.getDriver().sleep(500); + }); + + after(async function () { + await view.closeAllEditors(); + }); + + it('openEditor works with text editor', async function () { + const editor = (await view.openEditor('Untitled-1')) as TextEditor; + expect(await editor.getTitle()).equals('Untitled-1'); + }); + + it('openEditor works with settings editor', async function () { + const editor = (await view.openEditor('Settings')) as SettingsEditor; + expect(editor.findSetting).not.undefined; + + await view.closeEditor(await editor.getTitle()); + }); + + it('openEditor works with webview editor', async function () { + let editorTitle: string = ''; + (await view.getOpenEditorTitles()).forEach((title) => { + if (title.startsWith('Test WebView')) { + editorTitle = title; + } + }); + const editor = (await view.openEditor(editorTitle)) as WebView; + expect(editor.findWebElement).not.undefined; + + await view.closeEditor(editorTitle); + }); + + it('openEditor works with diff editor', async function () { + await view.openEditor('Untitled-2'); + + await new Workbench().executeCommand('File: Compare Active File With...'); + let quickOpen: QuickOpenBox | InputBox; + if (VSBrowser.instance.version >= '1.44.0') { + quickOpen = await InputBox.create(); + } else { + quickOpen = await QuickOpenBox.create(); + } + await quickOpen.setText('Untitled-1'); + await quickOpen.confirm(); + await quickOpen.getDriver().sleep(500); + + const diffEditor = (await view.openEditor('Untitled-2 ↔ Untitled-1')) as DiffEditor; + await diffEditor.getDriver().sleep(1000); + expect(await diffEditor.getOriginalEditor()).not.undefined; + expect(await diffEditor.getModifiedEditor()).not.undefined; + }); + + it('getTabByTitle works', async function () { + const tab = await view.getTabByTitle('Untitled-1'); + expect(tab).not.undefined; + }); + + it('getOpenEditorTitles works', async function () { + const tabs = await view.getOpenEditorTitles(); + expect(tabs).not.empty; + expect(tabs).contains('Untitled-1'); + expect(tabs).contains('Untitled-2'); + }); + + it('closeEditor works', async function () { + await view.closeEditor('Untitled-1'); + const tabs = await view.getOpenEditorTitles(); + expect(tabs).not.contains('Untitled-1'); + }); + + it('getActions works', async function () { + const actions = await view.getActions(); + expect(actions).not.empty; + }); + + it('getAction(title: string) works', async function () { + const action = await view.getAction('More Actions...'); + expect(await action?.getTitle()).equal('More Actions...'); + }); + + it('getAction(predicate: PredicateFunction) works', async function () { + const action = await view.getAction(async (action) => (await action.getTitle()) === 'More Actions...'); + expect(await action?.getTitle()).equal('More Actions...'); + }); + + it('Editor getAction works', async function () { + await new EditorView().openEditor('Untitled-2'); + const editorAction = await view.getAction('Hello World'); + expect(editorAction).not.undefined; + }); + + describe('Editor Tab', function () { + let tab2: EditorTab; + let tab3: EditorTab; + + before(async function () { + await newUntitledFile('Untitled-3'); + tab2 = await view.getTabByTitle('Untitled-2'); + tab3 = await view.getTabByTitle('Untitled-3'); + }); + + it('getTitle works', async function () { + expect(await tab2.getTitle()).equals('Untitled-2'); + }); + + it('isSelected works on active tab', async function () { + expect(await tab3.isSelected()).to.be.true; + }); + + it('isSelected works on inactive tab', async function () { + expect(await tab2.isSelected()).to.be.false; + }); + }); + + describe('Editor Groups', function () { + const testFile = 'Untitled-4'; + + before(async function () { + view = new EditorView(); + await newUntitledFile(testFile); + }); + + it('getEditorGroups works', async function () { + this.timeout(30000); + const driverActions = view.getDriver().actions(); + await driverActions.clear(); + await driverActions.keyDown(EditorView.ctlKey).sendKeys('\\').keyUp(EditorView.ctlKey).perform(); + await view.getDriver().wait( + async function () { + return (await view.getEditorGroups()).length === 2; + }, + 15000, + 'could not get 2 editor groups', + ); + + const groups = await view.getEditorGroups(); + const group1 = await view.getEditorGroup(0); + const group2 = await view.getEditorGroup(1); + + expect(groups.length).equals(2); + expect((await group1.getRect()).x).equals((await groups[0].getRect()).x); + expect((await group2.getRect()).x).equals((await groups[1].getRect()).x); + }); + + it('openEditor works for different groups', async function () { + const editor1 = await view.openEditor(testFile, 0); + const editor2 = await view.openEditor(testFile, 1); + + expect((await editor1.getRect()).x < (await editor2.getRect()).x); + }); + + it('closeEditor works for different groups', async function () { + await view.getDriver().actions().keyDown(EditorView.ctlKey).sendKeys('\\').perform(); + await view.getDriver().sleep(500); + + await view.closeEditor(testFile, 2); + expect((await view.getEditorGroups()).length).equals(2); + }); + + it('getOpenEditorTitles works for different editor groups', async function () { + const titles = await view.getOpenEditorTitles(); + const titles1 = await view.getOpenEditorTitles(0); + const titles2 = await view.getOpenEditorTitles(1); + + const allTitles = [...titles1, ...titles2]; + expect(titles).deep.equals(allTitles); + }); + }); + + async function newUntitledFile(title?: string, group?: number, timeout: number = 10000): Promise { + await new Workbench().executeCommand('Create: New File...'); + await (await InputBox.create()).selectQuickPick('Text File'); + if (title === undefined) { + await view.getDriver().sleep(500); + } else { + let view = new EditorView(); + await view.getDriver().wait( + async () => { + try { + return await view.getTabByTitle(title, group); + } catch (e) { + if (e instanceof EditorTabNotFound) { + return undefined; + } + if (e instanceof error.StaleElementReferenceError) { + view = new EditorView(); + return undefined; + } + throw e; + } + }, + timeout, + `could not find tab with title '${title}' in group '${group}'`, + ); + } + } }); diff --git a/tests/test-project/src/test/editor/settingsEditor.test.ts b/tests/test-project/src/test/editor/settingsEditor.test.ts index ab7fd55be..08c11546d 100644 --- a/tests/test-project/src/test/editor/settingsEditor.test.ts +++ b/tests/test-project/src/test/editor/settingsEditor.test.ts @@ -19,236 +19,243 @@ import { expect } from 'chai'; import { SettingsEditor, Workbench, EditorView, ComboSetting, TextSetting, CheckboxSetting, ArraySetting, after, before } from 'vscode-extension-tester'; describe('Settings Editor', function () { - let editor: SettingsEditor; - - before(async () => { - this.timeout(30000); - editor = await new Workbench().openSettings(); - await new Promise(t => setTimeout(t, 5_000)); // wait to be sure settings editor is loaded properly - }); - - after(async function () { - await new EditorView().closeAllEditors(); - }); - - it('findSetting works', async function () { - this.timeout(15000); - const setting = await editor.findSetting('Title Bar Style', 'Window'); - expect(setting).not.undefined; - }); - - it('findSetting by ID works', async function () { - this.timeout(15000); - const setting = await editor.findSettingByID('workbench.editor.enablePreview'); - expect(setting).not.undefined; - }); - - it('findSetting works for nested configurations', async function () { - this.timeout(15000); - const setting = await editor.findSetting('Hello World', 'Test Project', 'General'); - expect(setting).not.undefined; - }); - - describe('combo setting', function () { - let setting: ComboSetting; - - before(async () => { - this.timeout(15000); - setting = await editor.findSetting('Title Bar Style', 'Window') as ComboSetting; - }); - - it('getTitle works', async function () { - const title = await setting.getTitle(); - expect(title).equals('Title Bar Style'); - }); - - it('getCategory works', async function () { - const cat = await setting.getCategory(); - expect(cat).equals('Window:'); - }); - - it('getValue works', async function () { - const value = await setting.getValue(); - expect(value).equals('custom'); - }); - - it('getValues works', async function () { - const values = await setting.getValues(); - expect(values).contains.members(['native', 'custom']); - }); - - it('setValue works', async function () { - const setting = await editor.findSetting('Custom Title Bar Visibility', 'Window') as ComboSetting; - - await setting.setValue('windowed'); - let value = await setting.getValue(); - expect(value).equals('windowed'); - - await setting.setValue('auto'); - value = await setting.getValue(); - expect(value).equals('auto'); - }); - - it('getDescription works', async function () { - setting = await editor.findSetting('Title Bar Style', 'Window') as ComboSetting; - const desc = await setting.getDescription(); - expect(desc).not.empty; - }); - }); - - describe('text setting', function () { - let setting: TextSetting; - - before(async () => { - this.timeout(15000); - setting = await editor.findSetting('Auto Save Delay', 'Files') as TextSetting; - }); - - it('getValue works', async function () { - const value = await setting.getValue(); - expect(+value).greaterThan(0); - }); - - it('setValue works', async function () { - const newVal = '1001'; - await setting.setValue(newVal); - expect(await setting.getValue()).equals(newVal); - }); - }); - - describe('checkbox setting', function () { - let setting: CheckboxSetting; - - before(async () => { - this.timeout(15000); - setting = await editor.findSetting('Code Lens', 'Editor') as CheckboxSetting; - }); - - it('getValue works', async function () { - const value = await setting.getValue(); - expect(value).is.true; - }); - - it('setValue works', async function () { - await setting.setValue(false); - expect(await setting.getValue()).is.false; - await setting.setValue(true); - }); - }); - - describe('array setting', function () { - let setting: ArraySetting; - - before(async () => { - this.timeout(15000); - setting = await editor.findSetting('Hello World Array', 'Test Project', 'General') as ArraySetting; - }); - - it('getItem works - using index', async function () { - const item = await setting.getItem(1); - expect(item).is.not.undefined; - const value = await item?.getValue(); - expect(value).is.equal('Hello ExTester'); - }); - - it('getItem works - using label', async function () { - const item = await setting.getItem('Hello World'); - expect(item).is.not.undefined; - const value = await item?.getValue(); - expect(value).is.equal('Hello World'); - }); - - it('getItems works', async function () { - const items = await setting.getItems(); - expect(items).is.not.empty; - expect(items.length).is.equal(2); - }); - - it('getValues works', async function () { - const values = await setting.getValues(); - expect(values).contains.members(['Hello World', 'Hello ExTester']); - }); - - it('addItem works', async function () { - this.timeout(45000); - const add1 = await setting.add(); - await add1.setValue('Add Item 1'); - await add1.ok(); - await waitUntilItemExists('Add Item 1'); - - const add2 = await setting.add(); - await add2.setValue('Add Item 2'); - await add2.ok(); - await waitUntilItemExists('Add Item 2'); - - const add3 = await setting.add(); - await add3.setValue('Add Item 3'); - await add3.ok(); - await waitUntilItemExists('Add Item 3'); - - const newValue = await setting.getItem('Add Item 1'); - expect(await newValue?.getValue()).is.equal('Add Item 1'); - }); - - it('removeItem works - using label', async function () { - this.timeout(15000); - const toRemove = await setting.getItem('Hello ExTester'); - await toRemove?.remove(); - await waitUntilItemNotExists('Hello ExTester'); - - const values = await setting.getValues(); - expect(values.length).is.lessThan(5); - expect(values).not.includes('Hello ExTester'); - }); - - it('removeItem works - using index', async function () { - this.timeout(15000); - const toRemove = await setting.getItem(1); - await toRemove?.remove(); - await waitUntilItemNotExists('Add Item 1'); - - const values = await setting.getValues(); - expect(values.length).is.lessThan(4); - expect(values).not.includes('Add Item 1'); - }); - - it('editItem works - using label', async function () { - this.timeout(15000); - const toEdit = await setting.edit('Hello World'); - await toEdit?.setValue('Edit Item Label'); - await toEdit?.ok(); - await waitUntilItemExists('Edit Item Label'); - - const values = await setting.getValues(); - expect(values).includes('Edit Item Label'); - }); - - it('editItem works - using index', async function () { - this.timeout(15000); - const toEdit = await setting.edit(1); - await toEdit?.setValue('Edit Item Index'); - await toEdit?.ok(); - await waitUntilItemExists('Edit Item Index'); - - const values = await setting.getValues(); - expect(values).includes('Edit Item Index'); - }); - - async function waitUntilItemExists(item: string, timeout: number = 10_000): Promise { - let values: string[] = []; - await setting.getDriver().wait(async function () { - values = await setting.getValues(); - return values.includes(item); - }, timeout, `Expected item - '${item}' was not found in list of: ${values}`); - } - - async function waitUntilItemNotExists(item: string, timeout: number = 10_000): Promise { - let values: string[] = []; - await setting.getDriver().wait(async function () { - values = await setting.getValues(); - return !values.includes(item); - }, timeout, `Expected item - '${item}' was found in list of: ${values}`); - } - }); - + let editor: SettingsEditor; + + before(async () => { + this.timeout(30000); + editor = await new Workbench().openSettings(); + await new Promise((t) => setTimeout(t, 5_000)); // wait to be sure settings editor is loaded properly + }); + + after(async function () { + await new EditorView().closeAllEditors(); + }); + + it('findSetting works', async function () { + this.timeout(15000); + const setting = await editor.findSetting('Title Bar Style', 'Window'); + expect(setting).not.undefined; + }); + + it('findSetting by ID works', async function () { + this.timeout(15000); + const setting = await editor.findSettingByID('workbench.editor.enablePreview'); + expect(setting).not.undefined; + }); + + it('findSetting works for nested configurations', async function () { + this.timeout(15000); + const setting = await editor.findSetting('Hello World', 'Test Project', 'General'); + expect(setting).not.undefined; + }); + + describe('combo setting', function () { + let setting: ComboSetting; + + before(async () => { + this.timeout(15000); + setting = (await editor.findSetting('Title Bar Style', 'Window')) as ComboSetting; + }); + + it('getTitle works', async function () { + const title = await setting.getTitle(); + expect(title).equals('Title Bar Style'); + }); + + it('getCategory works', async function () { + const cat = await setting.getCategory(); + expect(cat).equals('Window:'); + }); + + it('getValue works', async function () { + const value = await setting.getValue(); + expect(value).equals('custom'); + }); + + it('getValues works', async function () { + const values = await setting.getValues(); + expect(values).contains.members(['native', 'custom']); + }); + + it('setValue works', async function () { + const setting = (await editor.findSetting('Custom Title Bar Visibility', 'Window')) as ComboSetting; + + await setting.setValue('windowed'); + let value = await setting.getValue(); + expect(value).equals('windowed'); + + await setting.setValue('auto'); + value = await setting.getValue(); + expect(value).equals('auto'); + }); + + it('getDescription works', async function () { + setting = (await editor.findSetting('Title Bar Style', 'Window')) as ComboSetting; + const desc = await setting.getDescription(); + expect(desc).not.empty; + }); + }); + + describe('text setting', function () { + let setting: TextSetting; + + before(async () => { + this.timeout(15000); + setting = (await editor.findSetting('Auto Save Delay', 'Files')) as TextSetting; + }); + + it('getValue works', async function () { + const value = await setting.getValue(); + expect(+value).greaterThan(0); + }); + + it('setValue works', async function () { + const newVal = '1001'; + await setting.setValue(newVal); + expect(await setting.getValue()).equals(newVal); + }); + }); + + describe('checkbox setting', function () { + let setting: CheckboxSetting; + + before(async () => { + this.timeout(15000); + setting = (await editor.findSetting('Code Lens', 'Editor')) as CheckboxSetting; + }); + + it('getValue works', async function () { + const value = await setting.getValue(); + expect(value).is.true; + }); + + it('setValue works', async function () { + await setting.setValue(false); + expect(await setting.getValue()).is.false; + await setting.setValue(true); + }); + }); + + describe('array setting', function () { + let setting: ArraySetting; + + before(async () => { + this.timeout(15000); + setting = (await editor.findSetting('Hello World Array', 'Test Project', 'General')) as ArraySetting; + }); + + it('getItem works - using index', async function () { + const item = await setting.getItem(1); + expect(item).is.not.undefined; + const value = await item?.getValue(); + expect(value).is.equal('Hello ExTester'); + }); + + it('getItem works - using label', async function () { + const item = await setting.getItem('Hello World'); + expect(item).is.not.undefined; + const value = await item?.getValue(); + expect(value).is.equal('Hello World'); + }); + + it('getItems works', async function () { + const items = await setting.getItems(); + expect(items).is.not.empty; + expect(items.length).is.equal(2); + }); + + it('getValues works', async function () { + const values = await setting.getValues(); + expect(values).contains.members(['Hello World', 'Hello ExTester']); + }); + + it('addItem works', async function () { + this.timeout(45000); + const add1 = await setting.add(); + await add1.setValue('Add Item 1'); + await add1.ok(); + await waitUntilItemExists('Add Item 1'); + + const add2 = await setting.add(); + await add2.setValue('Add Item 2'); + await add2.ok(); + await waitUntilItemExists('Add Item 2'); + + const add3 = await setting.add(); + await add3.setValue('Add Item 3'); + await add3.ok(); + await waitUntilItemExists('Add Item 3'); + + const newValue = await setting.getItem('Add Item 1'); + expect(await newValue?.getValue()).is.equal('Add Item 1'); + }); + + it('removeItem works - using label', async function () { + this.timeout(15000); + const toRemove = await setting.getItem('Hello ExTester'); + await toRemove?.remove(); + await waitUntilItemNotExists('Hello ExTester'); + + const values = await setting.getValues(); + expect(values.length).is.lessThan(5); + expect(values).not.includes('Hello ExTester'); + }); + + it('removeItem works - using index', async function () { + this.timeout(15000); + const toRemove = await setting.getItem(1); + await toRemove?.remove(); + await waitUntilItemNotExists('Add Item 1'); + + const values = await setting.getValues(); + expect(values.length).is.lessThan(4); + expect(values).not.includes('Add Item 1'); + }); + + it('editItem works - using label', async function () { + this.timeout(15000); + const toEdit = await setting.edit('Hello World'); + await toEdit?.setValue('Edit Item Label'); + await toEdit?.ok(); + await waitUntilItemExists('Edit Item Label'); + + const values = await setting.getValues(); + expect(values).includes('Edit Item Label'); + }); + + it('editItem works - using index', async function () { + this.timeout(15000); + const toEdit = await setting.edit(1); + await toEdit?.setValue('Edit Item Index'); + await toEdit?.ok(); + await waitUntilItemExists('Edit Item Index'); + + const values = await setting.getValues(); + expect(values).includes('Edit Item Index'); + }); + + async function waitUntilItemExists(item: string, timeout: number = 10_000): Promise { + let values: string[] = []; + await setting.getDriver().wait( + async function () { + values = await setting.getValues(); + return values.includes(item); + }, + timeout, + `Expected item - '${item}' was not found in list of: ${values}`, + ); + } + + async function waitUntilItemNotExists(item: string, timeout: number = 10_000): Promise { + let values: string[] = []; + await setting.getDriver().wait( + async function () { + values = await setting.getValues(); + return !values.includes(item); + }, + timeout, + `Expected item - '${item}' was found in list of: ${values}`, + ); + } + }); }); diff --git a/tests/test-project/src/test/editor/textEditor.test.ts b/tests/test-project/src/test/editor/textEditor.test.ts index a5b6b8342..7f4448348 100644 --- a/tests/test-project/src/test/editor/textEditor.test.ts +++ b/tests/test-project/src/test/editor/textEditor.test.ts @@ -20,339 +20,342 @@ import { expect } from 'chai'; import { TextEditor, EditorView, StatusBar, InputBox, ContentAssist, Workbench, FindWidget, VSBrowser, after, before } from 'vscode-extension-tester'; describe('ContentAssist', async function () { - let assist: ContentAssist; - let editor: TextEditor; - - before(async () => { - this.timeout(30000); - await VSBrowser.instance.openResources(path.resolve(__dirname, '..', '..', '..', 'resources', 'test-file.ts')); - await VSBrowser.instance.waitForWorkbench(); - await new Promise(res => setTimeout(res, 2000)); - const ew = new EditorView(); - try { - await ew.closeEditor('Welcome'); - } catch (error) { - // continue - Welcome page is not displayed - } - editor = await ew.openEditor('test-file.ts') as TextEditor; - await editor.getDriver().wait(async function () { - const progress = await new StatusBar().getItem('Initializing JS/TS language features'); - if (progress) { - return false; - } else { - return true; - } - }, this.timeout() - 2000, 'Initializing JS/TS language features was not finished yet!'); - - }); - - beforeEach(async function () { - assist = await editor.toggleContentAssist(true) as ContentAssist; - await new Promise(res => setTimeout(res, 2000)); - }); - - after(async function () { - await new EditorView().closeAllEditors(); - }); - - afterEach(async function () { - await editor.toggleContentAssist(false); - await new Promise(res => setTimeout(res, 1000)); - }); - - it('getItems retrieves the suggestions', async function () { - const items = await assist.getItems(); - expect(items).not.empty; - }); - - it('getItem retrieves suggestion by text', async function () { - const item = await assist.getItem('AbortController'); - expect(await item?.getLabel()).equals('AbortController'); - }); - - it('getItem can find an item beyond visible range', async function () { - const item = await assist.getItem('Buffer'); - expect(item).not.undefined; - }).timeout(15000); - - it('hasItem finds items beyond visible range', async function () { - const exists = await assist.hasItem('CSSRule'); - expect(exists).is.true; - }).timeout(15000); + let assist: ContentAssist; + let editor: TextEditor; + + before(async () => { + this.timeout(30000); + await VSBrowser.instance.openResources(path.resolve(__dirname, '..', '..', '..', 'resources', 'test-file.ts')); + await VSBrowser.instance.waitForWorkbench(); + await new Promise((res) => setTimeout(res, 2000)); + const ew = new EditorView(); + try { + await ew.closeEditor('Welcome'); + } catch (error) { + // continue - Welcome page is not displayed + } + editor = (await ew.openEditor('test-file.ts')) as TextEditor; + await editor.getDriver().wait( + async function () { + const progress = await new StatusBar().getItem('Initializing JS/TS language features'); + if (progress) { + return false; + } else { + return true; + } + }, + this.timeout() - 2000, + 'Initializing JS/TS language features was not finished yet!', + ); + }); + + beforeEach(async function () { + assist = (await editor.toggleContentAssist(true)) as ContentAssist; + await new Promise((res) => setTimeout(res, 2000)); + }); + + after(async function () { + await new EditorView().closeAllEditors(); + }); + + afterEach(async function () { + await editor.toggleContentAssist(false); + await new Promise((res) => setTimeout(res, 1000)); + }); + + it('getItems retrieves the suggestions', async function () { + const items = await assist.getItems(); + expect(items).not.empty; + }); + + it('getItem retrieves suggestion by text', async function () { + const item = await assist.getItem('AbortController'); + expect(await item?.getLabel()).equals('AbortController'); + }); + + it('getItem can find an item beyond visible range', async function () { + const item = await assist.getItem('Buffer'); + expect(item).not.undefined; + }).timeout(15000); + + it('hasItem finds items beyond visible range', async function () { + const exists = await assist.hasItem('CSSRule'); + expect(exists).is.true; + }).timeout(15000); }); describe('TextEditor', function () { - let editor: TextEditor; - let view: EditorView; - - const testText = process.platform === 'win32' ? `line1\r\nline2\r\nline3` : `line1\nline2\nline3`; - - before(async () => { - this.timeout(8000); - await new Workbench().executeCommand('Create: New File...'); - await (await InputBox.create()).selectQuickPick('Text File'); - await new Promise((res) => { setTimeout(res, 1000); }); - view = new EditorView(); - editor = new TextEditor(view); - - await new StatusBar().openLanguageSelection(); - const input = await InputBox.create(); - await input.setText('typescript'); - await input.confirm(); - }); - - after(async function () { - await editor.clearText(); - await view.closeAllEditors(); - }); - - it('can get and set text', async function () { - await editor.setText(testText); - const text = await editor.getText(); - expect(text).equals(testText); - }); - - it('can get and set text at line', async function () { - await editor.setTextAtLine(2, 'line5'); - const line = await editor.getTextAtLine(2); - expect(line).has.string('line5'); - }); - - it('can type text at given coordinates', async function () { - this.timeout(5000); - await editor.typeTextAt(1, 6, '1'); - const line = await editor.getTextAtLine(1); - expect(line).has.string('line11'); - }); - - it('getCoordinates works', async function () { - this.timeout(15000); - - await editor.moveCursor(1, 1); - expect(await editor.getCoordinates()).to.deep.equal([1, 1]); - - const lineCount = await editor.getNumberOfLines(); - const lastLine = await editor.getTextAtLine(lineCount); - - await editor.moveCursor(lineCount, lastLine.length); - expect(await editor.getCoordinates()).to.deep.equal([lineCount, lastLine.length]); - }); - - it('getNumberOfLines works', async function () { - const lines = await editor.getNumberOfLines(); - expect(lines).equals(3); - }); - - it('toggleContentAssist works', async function () { - this.timeout(15000); - const assist = await editor.toggleContentAssist(true) as ContentAssist; - expect(await assist.isDisplayed()).is.true; - - await editor.toggleContentAssist(false); - }); - - it('getTab works', async function () { - const tab = await editor.getTab(); - expect(await tab.getTitle()).equals(await editor.getTitle()); - }); - - (process.platform === 'darwin' ? it.skip : it)('formatDocument works', async function () { - expect(await editor.formatDocument()).not.to.throw; - }); - - describe('searching', function () { - - before(async function () { - await editor.setText('aline\nbline\ncline\ndline\nnope\neline1 eline2\n'); - }); - - it('getLineOfText works', async function () { - const line = await editor.getLineOfText('line'); - expect(line).equals(1); - }); - - it('getLineOfText finds multiple occurrences', async function () { - const line = await editor.getLineOfText('line', 5); - expect(line).equals(6); - }); - - it('getLineOfText returns -1 on no line found', async function () { - const line = await editor.getLineOfText('wat'); - expect(line).equals(-1); - }); - - it('getLineOfText returns last known occurrence if there are fewer than specified', async function () { - const line = await editor.getLineOfText('line', 15); - expect(line).equals(6); - }); - - it('selected text can be get', async function () { - const text = 'bline'; - await editor.selectText(text); - expect(await editor.getSelectedText()).equals(text); - }); - - it('selectText errors if given text doesnt exist', async function () { - const text = 'wat'; - try { - await editor.selectText(text); - } catch (err) { - if(err instanceof Error) { - expect(err.message).has.string(`Text '${text}' not found`); - } else { - expect.fail(); - } - } - }); - - (process.platform === 'darwin' ? it.skip : it)('getSelection works', async function () { - await editor.selectText('cline'); - const selection = await editor.getSelection(); - - expect(selection).not.undefined; - - const menu = await selection?.openContextMenu(); - await menu?.close(); - }); - }); - - describe('find widget', function () { - let widget: FindWidget; - - before(async function () { - widget = await editor.openFindWidget(); - }); - - after(async function () { - await widget.close(); - }); - - it('toggleReplace works', async function () { - const height = (await widget.getRect()).height; - await widget.toggleReplace(true); - expect((await widget.getRect()).height).to.be.gt(height); - }); - - it('setSearchText works', async function () { - await widget.setSearchText('line'); - expect(await widget.getSearchText()).equals('line'); - }); - - it('setReplaceText works', async function () { - await widget.setReplaceText('line1'); - expect(await widget.getReplaceText()).equals('line1'); - }); - - it('getResultCount works', async function () { - const count = await widget.getResultCount(); - expect(count[0]).gte(1); - expect(count[1]).gt(1); - }); - - it('nextMatch works', async function () { - const count = (await widget.getResultCount())[0]; - await widget.nextMatch(); - expect((await widget.getResultCount())[0]).equals(count + 1); - }); - - it('previousMatch works', async function () { - const count = (await widget.getResultCount())[0]; - await widget.previousMatch(); - expect((await widget.getResultCount())[0]).equals(count - 1); - }); - - it('replace works', async function () { - await widget.replace(); - expect(await editor.getLineOfText('line1')).gt(0); - }); - - it('replace all works', async function () { - const original = await editor.getText(); - await widget.replaceAll(); - expect(await editor.getText()).not.equals(original); - }); - - it('toggleMatchCase works', async function () { - await widget.toggleMatchCase(true); - }); - - it('toggleMatchWholeWord works', async function () { - await widget.toggleMatchWholeWord(true); - }); - - it('toggleUseRegularExpression works', async function () { - await widget.toggleUseRegularExpression(true); - }); - - it('togglePreserveCase works', async function () { - await widget.togglePreserveCase(true); - }); - }); - - describe('CodeLens', function () { - - before(async function () { - await new Workbench().executeCommand('enable codelens'); - // older versions of vscode dont fire the update event immediately, give it some encouragement - // otherwise the lenses end up empty - await new Workbench().executeCommand('enable codelens'); - await new Promise(res => setTimeout(res, 1000)); - }); - - after(async function () { - await new Workbench().executeCommand('disable codelens'); - }); - - it('getCodeLenses works', async function () { - const lenses = await editor.getCodeLenses(); - expect(lenses.length).is.equal(7); - }); - - it('getCodeLens works with index', async function () { - const lens0 = await editor.getCodeLens(0); - const lens0Duplicate = await editor.getCodeLens(0); - const lens1 = await editor.getCodeLens(1); - - expect(await lens0?.getId()).not.equal(await lens1?.getId()); - expect(await lens0?.getId()).equal(await lens0Duplicate?.getId()); - }); - - it('getCodeLens works with partial text', async function () { - const lens = await editor.getCodeLens('Codelens provided'); - expect(await lens?.getText()).has.string('Codelens provided'); - expect(await lens?.getTooltip()).has.string('Tooltip provided'); - }); - - it('getCodeLenses works with second in the span', async function () { - const lens = await editor.getCodeLens(6); - expect(lens).is.not.undefined; - expect(await lens?.getText()).has.string('Codelens provided'); - expect(await lens?.getTooltip()).has.string('Tooltip provided'); - }); - - it('getCodeLens returns undefined when nothing is found', async function () { - const lens1 = await editor.getCodeLens('This does not exist'); - expect(lens1).is.undefined; - - const lens2 = await editor.getCodeLens(666); - expect(lens2).is.undefined; - }); - - it('clicking triggers the lens command', async function () { - this.timeout(20000); - const lens = await editor.getCodeLens(2); - await lens?.click(); - await lens?.getDriver().sleep(1000); - const notifications = await new Workbench().getNotifications(); - - let notification = undefined; - for (const not of notifications) { - if ((await not.getMessage()).startsWith('CodeLens action clicked')) { - notification = not; - break; - } - } - expect(notification).not.undefined; - }); - }); -}); \ No newline at end of file + let editor: TextEditor; + let view: EditorView; + + const testText = process.platform === 'win32' ? `line1\r\nline2\r\nline3` : `line1\nline2\nline3`; + + before(async () => { + this.timeout(8000); + await new Workbench().executeCommand('Create: New File...'); + await (await InputBox.create()).selectQuickPick('Text File'); + await new Promise((res) => { + setTimeout(res, 1000); + }); + view = new EditorView(); + editor = new TextEditor(view); + + await new StatusBar().openLanguageSelection(); + const input = await InputBox.create(); + await input.setText('typescript'); + await input.confirm(); + }); + + after(async function () { + await editor.clearText(); + await view.closeAllEditors(); + }); + + it('can get and set text', async function () { + await editor.setText(testText); + const text = await editor.getText(); + expect(text).equals(testText); + }); + + it('can get and set text at line', async function () { + await editor.setTextAtLine(2, 'line5'); + const line = await editor.getTextAtLine(2); + expect(line).has.string('line5'); + }); + + it('can type text at given coordinates', async function () { + this.timeout(5000); + await editor.typeTextAt(1, 6, '1'); + const line = await editor.getTextAtLine(1); + expect(line).has.string('line11'); + }); + + it('getCoordinates works', async function () { + this.timeout(15000); + + await editor.moveCursor(1, 1); + expect(await editor.getCoordinates()).to.deep.equal([1, 1]); + + const lineCount = await editor.getNumberOfLines(); + const lastLine = await editor.getTextAtLine(lineCount); + + await editor.moveCursor(lineCount, lastLine.length); + expect(await editor.getCoordinates()).to.deep.equal([lineCount, lastLine.length]); + }); + + it('getNumberOfLines works', async function () { + const lines = await editor.getNumberOfLines(); + expect(lines).equals(3); + }); + + it('toggleContentAssist works', async function () { + this.timeout(15000); + const assist = (await editor.toggleContentAssist(true)) as ContentAssist; + expect(await assist.isDisplayed()).is.true; + + await editor.toggleContentAssist(false); + }); + + it('getTab works', async function () { + const tab = await editor.getTab(); + expect(await tab.getTitle()).equals(await editor.getTitle()); + }); + + (process.platform === 'darwin' ? it.skip : it)('formatDocument works', async function () { + expect(await editor.formatDocument()).not.to.throw; + }); + + describe('searching', function () { + before(async function () { + await editor.setText('aline\nbline\ncline\ndline\nnope\neline1 eline2\n'); + }); + + it('getLineOfText works', async function () { + const line = await editor.getLineOfText('line'); + expect(line).equals(1); + }); + + it('getLineOfText finds multiple occurrences', async function () { + const line = await editor.getLineOfText('line', 5); + expect(line).equals(6); + }); + + it('getLineOfText returns -1 on no line found', async function () { + const line = await editor.getLineOfText('wat'); + expect(line).equals(-1); + }); + + it('getLineOfText returns last known occurrence if there are fewer than specified', async function () { + const line = await editor.getLineOfText('line', 15); + expect(line).equals(6); + }); + + it('selected text can be get', async function () { + const text = 'bline'; + await editor.selectText(text); + expect(await editor.getSelectedText()).equals(text); + }); + + it('selectText errors if given text doesnt exist', async function () { + const text = 'wat'; + try { + await editor.selectText(text); + } catch (err) { + if (err instanceof Error) { + expect(err.message).has.string(`Text '${text}' not found`); + } else { + expect.fail(); + } + } + }); + + (process.platform === 'darwin' ? it.skip : it)('getSelection works', async function () { + await editor.selectText('cline'); + const selection = await editor.getSelection(); + + expect(selection).not.undefined; + + const menu = await selection?.openContextMenu(); + await menu?.close(); + }); + }); + + describe('find widget', function () { + let widget: FindWidget; + + before(async function () { + widget = await editor.openFindWidget(); + }); + + after(async function () { + await widget.close(); + }); + + it('toggleReplace works', async function () { + const height = (await widget.getRect()).height; + await widget.toggleReplace(true); + expect((await widget.getRect()).height).to.be.gt(height); + }); + + it('setSearchText works', async function () { + await widget.setSearchText('line'); + expect(await widget.getSearchText()).equals('line'); + }); + + it('setReplaceText works', async function () { + await widget.setReplaceText('line1'); + expect(await widget.getReplaceText()).equals('line1'); + }); + + it('getResultCount works', async function () { + const count = await widget.getResultCount(); + expect(count[0]).gte(1); + expect(count[1]).gt(1); + }); + + it('nextMatch works', async function () { + const count = (await widget.getResultCount())[0]; + await widget.nextMatch(); + expect((await widget.getResultCount())[0]).equals(count + 1); + }); + + it('previousMatch works', async function () { + const count = (await widget.getResultCount())[0]; + await widget.previousMatch(); + expect((await widget.getResultCount())[0]).equals(count - 1); + }); + + it('replace works', async function () { + await widget.replace(); + expect(await editor.getLineOfText('line1')).gt(0); + }); + + it('replace all works', async function () { + const original = await editor.getText(); + await widget.replaceAll(); + expect(await editor.getText()).not.equals(original); + }); + + it('toggleMatchCase works', async function () { + await widget.toggleMatchCase(true); + }); + + it('toggleMatchWholeWord works', async function () { + await widget.toggleMatchWholeWord(true); + }); + + it('toggleUseRegularExpression works', async function () { + await widget.toggleUseRegularExpression(true); + }); + + it('togglePreserveCase works', async function () { + await widget.togglePreserveCase(true); + }); + }); + + describe('CodeLens', function () { + before(async function () { + await new Workbench().executeCommand('enable codelens'); + // older versions of vscode dont fire the update event immediately, give it some encouragement + // otherwise the lenses end up empty + await new Workbench().executeCommand('enable codelens'); + await new Promise((res) => setTimeout(res, 1000)); + }); + + after(async function () { + await new Workbench().executeCommand('disable codelens'); + }); + + it('getCodeLenses works', async function () { + const lenses = await editor.getCodeLenses(); + expect(lenses.length).is.equal(7); + }); + + it('getCodeLens works with index', async function () { + const lens0 = await editor.getCodeLens(0); + const lens0Duplicate = await editor.getCodeLens(0); + const lens1 = await editor.getCodeLens(1); + + expect(await lens0?.getId()).not.equal(await lens1?.getId()); + expect(await lens0?.getId()).equal(await lens0Duplicate?.getId()); + }); + + it('getCodeLens works with partial text', async function () { + const lens = await editor.getCodeLens('Codelens provided'); + expect(await lens?.getText()).has.string('Codelens provided'); + expect(await lens?.getTooltip()).has.string('Tooltip provided'); + }); + + it('getCodeLenses works with second in the span', async function () { + const lens = await editor.getCodeLens(6); + expect(lens).is.not.undefined; + expect(await lens?.getText()).has.string('Codelens provided'); + expect(await lens?.getTooltip()).has.string('Tooltip provided'); + }); + + it('getCodeLens returns undefined when nothing is found', async function () { + const lens1 = await editor.getCodeLens('This does not exist'); + expect(lens1).is.undefined; + + const lens2 = await editor.getCodeLens(666); + expect(lens2).is.undefined; + }); + + it('clicking triggers the lens command', async function () { + this.timeout(20000); + const lens = await editor.getCodeLens(2); + await lens?.click(); + await lens?.getDriver().sleep(1000); + const notifications = await new Workbench().getNotifications(); + + let notification = undefined; + for (const not of notifications) { + if ((await not.getMessage()).startsWith('CodeLens action clicked')) { + notification = not; + break; + } + } + expect(notification).not.undefined; + }); + }); +}); diff --git a/tests/test-project/src/test/menu/contextMenu.test.ts b/tests/test-project/src/test/menu/contextMenu.test.ts index c025b64f2..6f93fe0f7 100644 --- a/tests/test-project/src/test/menu/contextMenu.test.ts +++ b/tests/test-project/src/test/menu/contextMenu.test.ts @@ -20,31 +20,31 @@ import { expect } from 'chai'; import * as path from 'path'; (process.platform === 'darwin' ? describe.skip : describe)('ContextMenu', function () { - let bar: TitleBar; - let menu: ContextMenu; + let bar: TitleBar; + let menu: ContextMenu; - before(async () => { - this.timeout(30000); - await VSBrowser.instance.openResources(path.resolve(__dirname, '..', '..', '..', 'resources', 'test-folder')); - await VSBrowser.instance.driver.sleep(5000); - }); + before(async () => { + this.timeout(30000); + await VSBrowser.instance.openResources(path.resolve(__dirname, '..', '..', '..', 'resources', 'test-folder')); + await VSBrowser.instance.driver.sleep(5000); + }); - beforeEach(async function () { - bar = new TitleBar(); - menu = await bar.select('File') as ContextMenu; - }); + beforeEach(async function () { + bar = new TitleBar(); + menu = (await bar.select('File')) as ContextMenu; + }); - it('getItems finds all menu items', async function () { - this.timeout(5000); - const items = await menu.getItems(); - await menu.close(); - expect(items).not.empty; - }); + it('getItems finds all menu items', async function () { + this.timeout(5000); + const items = await menu.getItems(); + await menu.close(); + expect(items).not.empty; + }); - it('getItem finds an item with the given name', async function () { - this.timeout(5000); - const item = await menu.getItem('New File...'); - await menu.close(); - expect(item).not.undefined; - }); + it('getItem finds an item with the given name', async function () { + this.timeout(5000); + const item = await menu.getItem('New File...'); + await menu.close(); + expect(item).not.undefined; + }); }); diff --git a/tests/test-project/src/test/menu/macTitleBar.test.ts b/tests/test-project/src/test/menu/macTitleBar.test.ts index a7eac920e..1e7e8097e 100644 --- a/tests/test-project/src/test/menu/macTitleBar.test.ts +++ b/tests/test-project/src/test/menu/macTitleBar.test.ts @@ -19,38 +19,38 @@ import { EditorView, MacTitleBar, OutputView } from 'vscode-extension-tester'; import { AssertionError, expect } from 'chai'; describe.skip('MacTitleBar', () => { - beforeEach(async () => { - await new Promise(res => setTimeout(res, 2000)); - await new EditorView().closeAllEditors(); - }); + beforeEach(async () => { + await new Promise((res) => setTimeout(res, 2000)); + await new EditorView().closeAllEditors(); + }); - it('works with a single context menu', async () => { - MacTitleBar.select('File', 'New File'); - const view = new EditorView(); - expect(await view.getOpenEditorTitles()).to.include('Untitled-1'); - }); + it('works with a single context menu', async () => { + MacTitleBar.select('File', 'New File'); + const view = new EditorView(); + expect(await view.getOpenEditorTitles()).to.include('Untitled-1'); + }); - it('works with a different menu', async () => { - MacTitleBar.select('View', 'Output'); - const output = new OutputView(); - expect(await output.isDisplayed()).to.be.true; - }); + it('works with a different menu', async () => { + MacTitleBar.select('View', 'Output'); + const output = new OutputView(); + expect(await output.isDisplayed()).to.be.true; + }); - it('work with a nested submenu', async () => { - MacTitleBar.select('Code', 'Preferences', 'Settings'); - const view = new EditorView(); - expect(await view.getOpenEditorTitles()).to.include('Settings'); - }); + it('work with a nested submenu', async () => { + MacTitleBar.select('Code', 'Preferences', 'Settings'); + const view = new EditorView(); + expect(await view.getOpenEditorTitles()).to.include('Settings'); + }); - it('errors when an item does not exist', () => { - try { - MacTitleBar.select('File', 'This does not exist'); - expect.fail('no error was produced'); - } catch (err) { - if (err instanceof AssertionError) { - throw err; - } - expect(err).not.to.be.empty; - } - }); -}); \ No newline at end of file + it('errors when an item does not exist', () => { + try { + MacTitleBar.select('File', 'This does not exist'); + expect.fail('no error was produced'); + } catch (err) { + if (err instanceof AssertionError) { + throw err; + } + expect(err).not.to.be.empty; + } + }); +}); diff --git a/tests/test-project/src/test/menu/titleBar.test.ts b/tests/test-project/src/test/menu/titleBar.test.ts index 3675c5abd..22691c416 100644 --- a/tests/test-project/src/test/menu/titleBar.test.ts +++ b/tests/test-project/src/test/menu/titleBar.test.ts @@ -20,93 +20,94 @@ import * as path from 'path'; import { ActivityBar, TitleBar, ContextMenu, TitleBarItem, EditorView, VSBrowser } from 'vscode-extension-tester'; (process.platform === 'darwin' ? describe.skip : describe)('TitleBar', function () { - - let bar: TitleBar; - - before(async function () { - this.timeout(30000); - await VSBrowser.instance.openResources(path.resolve(__dirname, '..', '..', '..', 'resources', 'test-folder')); - await VSBrowser.instance.driver.sleep(5000); - bar = new TitleBar(); - - await VSBrowser.instance.openResources(path.resolve(__dirname, '..', '..', '..', 'resources', 'test-folder', 'foo')); - - // workspace cleanup before tests - await new EditorView().closeAllEditors(); - await (await new ActivityBar().getViewControl('Explorer'))?.closeView(); - }); - - after(async function () { - this.timeout(10000); - await new EditorView().closeAllEditors(); - }); - - it('getTitle returns the window title', async function() { - const title = await bar.getTitle(); - expect(title).not.empty; - expect(title).to.includes('Visual Studio Code'); - }); - - it('getWindowControls works', async function () { - const controls = bar.getWindowControls(); - expect(controls).not.undefined; - }); - - it('getItem returns an item with the given name', async function () { - const item = await bar.getItem('File'); - expect(await item?.getLabel()).equals('File'); - }); - - it('getItems returns all top menu items', async function () { - this.timeout(4000); - const items = (await bar.getItems()).map((item) => { return item.getLabel(); }); - expect(items.length).greaterThan(5); - expect(items).contains.members(['File', 'Edit', 'Selection', 'View', 'Go', 'Run']); - }); - - it('hasItem returns true if item exists', async function () { - const exists = await bar.hasItem('File'); - expect(exists).is.true; - }); - - it('hasItem returns false if item does not exist', async function () { - const exists = await bar.hasItem('whatever1234'); - expect(exists).is.false; - }); - - it('select opens a context menu if the item has children', async function () { - const menu = (await bar.select('File')) as ContextMenu; - expect(await menu.isDisplayed()).is.true; - await menu.close(); - }); - - it('select navigates a multi level path', async function () { - const menu = (await bar.select('File', 'Open Recent', 'Reopen Closed Editor')) as ContextMenu; - expect(menu).is.undefined; - }); - - describe('TitleBarItem', function () { - let item: TitleBarItem | undefined; - - before(async function () { - bar = new TitleBar(); - item = await bar.getItem('File'); - }); - - it('getParent returns the title bar', async function() { - const parent = item?.getParent(); - expect(parent).equals(bar); - }); - - it('getLabel returns the item label', async function () { - const label = await item?.getLabel(); - expect(label).equals('File'); - }); - - it('select open the item', async function () { - const menu = await item?.select(); - expect(await menu?.isDisplayed()).is.true; - await menu?.close(); - }); - }); -}); \ No newline at end of file + let bar: TitleBar; + + before(async function () { + this.timeout(30000); + await VSBrowser.instance.openResources(path.resolve(__dirname, '..', '..', '..', 'resources', 'test-folder')); + await VSBrowser.instance.driver.sleep(5000); + bar = new TitleBar(); + + await VSBrowser.instance.openResources(path.resolve(__dirname, '..', '..', '..', 'resources', 'test-folder', 'foo')); + + // workspace cleanup before tests + await new EditorView().closeAllEditors(); + await (await new ActivityBar().getViewControl('Explorer'))?.closeView(); + }); + + after(async function () { + this.timeout(10000); + await new EditorView().closeAllEditors(); + }); + + it('getTitle returns the window title', async function () { + const title = await bar.getTitle(); + expect(title).not.empty; + expect(title).to.includes('Visual Studio Code'); + }); + + it('getWindowControls works', async function () { + const controls = bar.getWindowControls(); + expect(controls).not.undefined; + }); + + it('getItem returns an item with the given name', async function () { + const item = await bar.getItem('File'); + expect(await item?.getLabel()).equals('File'); + }); + + it('getItems returns all top menu items', async function () { + this.timeout(4000); + const items = (await bar.getItems()).map((item) => { + return item.getLabel(); + }); + expect(items.length).greaterThan(5); + expect(items).contains.members(['File', 'Edit', 'Selection', 'View', 'Go', 'Run']); + }); + + it('hasItem returns true if item exists', async function () { + const exists = await bar.hasItem('File'); + expect(exists).is.true; + }); + + it('hasItem returns false if item does not exist', async function () { + const exists = await bar.hasItem('whatever1234'); + expect(exists).is.false; + }); + + it('select opens a context menu if the item has children', async function () { + const menu = (await bar.select('File')) as ContextMenu; + expect(await menu.isDisplayed()).is.true; + await menu.close(); + }); + + it('select navigates a multi level path', async function () { + const menu = (await bar.select('File', 'Open Recent', 'Reopen Closed Editor')) as ContextMenu; + expect(menu).is.undefined; + }); + + describe('TitleBarItem', function () { + let item: TitleBarItem | undefined; + + before(async function () { + bar = new TitleBar(); + item = await bar.getItem('File'); + }); + + it('getParent returns the title bar', async function () { + const parent = item?.getParent(); + expect(parent).equals(bar); + }); + + it('getLabel returns the item label', async function () { + const label = await item?.getLabel(); + expect(label).equals('File'); + }); + + it('select open the item', async function () { + const menu = await item?.select(); + expect(await menu?.isDisplayed()).is.true; + await menu?.close(); + }); + }); +}); diff --git a/tests/test-project/src/test/statusBar/statusBar.test.ts b/tests/test-project/src/test/statusBar/statusBar.test.ts index fb4a9f175..59c136862 100644 --- a/tests/test-project/src/test/statusBar/statusBar.test.ts +++ b/tests/test-project/src/test/statusBar/statusBar.test.ts @@ -19,99 +19,99 @@ import { expect } from 'chai'; import { StatusBar, EditorView, InputBox, QuickOpenBox, Workbench, VSBrowser } from 'vscode-extension-tester'; describe('StatusBar', () => { - let bar: StatusBar; - - before(async function() { - this.timeout(30000); - await new Workbench().executeCommand('Create: New File...'); - await (await InputBox.create()).selectQuickPick('Text File'); - bar = new StatusBar(); - }); - - after(async() => { - await new EditorView().closeAllEditors(); - }); - - it('can open and close the notification center', async () => { - const center = await bar.openNotificationsCenter(); - expect(await center.isDisplayed()).is.true; - - await bar.closeNotificationsCenter(); - expect(await center.isDisplayed()).is.false; - }); - - it('openLanguageSelection works', async () => { - await bar.openLanguageSelection(); - const input = await InputBox.create(); - expect(await input.getPlaceHolder()).equals('Select Language Mode'); - await input.cancel(); - }); - - it('getCurrentLanguage returns editor mode', async () => { - const mode = await bar.getCurrentLanguage(); - expect(mode.startsWith('Plain Text')).is.true; - }); - - it('openLineEndingSelection works', async () => { - await bar.openLineEndingSelection(); - const input = await InputBox.create(); - expect(await input.getPlaceHolder()).equals('Select End of Line Sequence'); - await input.cancel(); - }); - - it('getCurrentLineEnding returns current line ending', async () => { - const lf = await bar.getCurrentLineEnding(); - expect('CRLF').has.string(lf); - }); - - it('openEncodingSelection works', async () => { - await bar.openEncodingSelection(); - const input = await InputBox.create(); - expect(await input.getPlaceHolder()).equals('Select File Encoding to Save with'); - await input.cancel(); - }); - - it('getCurrentEncoing returns current encoding', async () => { - const encoding = await bar.getCurrentEncoding(); - expect(encoding).has.string('UTF-8'); - }); - - it('openIndentationSelection works', async () => { - await bar.openIndentationSelection(); - const input = await InputBox.create(); - expect(await input.getPlaceHolder()).equals('Select Action'); - await input.cancel(); - }); - - it('getCurrentIndentation returns current indent setting', async () => { - const encoding = await bar.getCurrentIndentation(); - expect(encoding).has.string('Spaces: 4'); - }); - - it('openLineSelection works', async () => { - await bar.openLineSelection(); - let input: QuickOpenBox | InputBox; - if (VSBrowser.instance.version >= '1.44.0') { - input = await InputBox.create(); - } else { - input = await QuickOpenBox.create(); - } - expect(await input.isDisplayed()).is.true; - await input.cancel(); - }); - - it('getCurrentPosition returns current editor coordinates', async () => { - const encoding = await bar.getCurrentPosition(); - expect(encoding).has.string('Ln 1, Col 1'); - }); - - it('getItems works', async () => { - const items = await bar.getItems(); - expect(items).not.empty; - }); - - it('getItem works', async () => { - const item = await bar.getItem('UTF-8'); - expect(item).not.undefined; - }); -}); \ No newline at end of file + let bar: StatusBar; + + before(async function () { + this.timeout(30000); + await new Workbench().executeCommand('Create: New File...'); + await (await InputBox.create()).selectQuickPick('Text File'); + bar = new StatusBar(); + }); + + after(async () => { + await new EditorView().closeAllEditors(); + }); + + it('can open and close the notification center', async () => { + const center = await bar.openNotificationsCenter(); + expect(await center.isDisplayed()).is.true; + + await bar.closeNotificationsCenter(); + expect(await center.isDisplayed()).is.false; + }); + + it('openLanguageSelection works', async () => { + await bar.openLanguageSelection(); + const input = await InputBox.create(); + expect(await input.getPlaceHolder()).equals('Select Language Mode'); + await input.cancel(); + }); + + it('getCurrentLanguage returns editor mode', async () => { + const mode = await bar.getCurrentLanguage(); + expect(mode.startsWith('Plain Text')).is.true; + }); + + it('openLineEndingSelection works', async () => { + await bar.openLineEndingSelection(); + const input = await InputBox.create(); + expect(await input.getPlaceHolder()).equals('Select End of Line Sequence'); + await input.cancel(); + }); + + it('getCurrentLineEnding returns current line ending', async () => { + const lf = await bar.getCurrentLineEnding(); + expect('CRLF').has.string(lf); + }); + + it('openEncodingSelection works', async () => { + await bar.openEncodingSelection(); + const input = await InputBox.create(); + expect(await input.getPlaceHolder()).equals('Select File Encoding to Save with'); + await input.cancel(); + }); + + it('getCurrentEncoing returns current encoding', async () => { + const encoding = await bar.getCurrentEncoding(); + expect(encoding).has.string('UTF-8'); + }); + + it('openIndentationSelection works', async () => { + await bar.openIndentationSelection(); + const input = await InputBox.create(); + expect(await input.getPlaceHolder()).equals('Select Action'); + await input.cancel(); + }); + + it('getCurrentIndentation returns current indent setting', async () => { + const encoding = await bar.getCurrentIndentation(); + expect(encoding).has.string('Spaces: 4'); + }); + + it('openLineSelection works', async () => { + await bar.openLineSelection(); + let input: QuickOpenBox | InputBox; + if (VSBrowser.instance.version >= '1.44.0') { + input = await InputBox.create(); + } else { + input = await QuickOpenBox.create(); + } + expect(await input.isDisplayed()).is.true; + await input.cancel(); + }); + + it('getCurrentPosition returns current editor coordinates', async () => { + const encoding = await bar.getCurrentPosition(); + expect(encoding).has.string('Ln 1, Col 1'); + }); + + it('getItems works', async () => { + const items = await bar.getItems(); + expect(items).not.empty; + }); + + it('getItem works', async () => { + const item = await bar.getItem('UTF-8'); + expect(item).not.undefined; + }); +}); diff --git a/tests/test-project/src/test/system/clipboard.test.ts b/tests/test-project/src/test/system/clipboard.test.ts index 5fa227f13..dd7da5dca 100644 --- a/tests/test-project/src/test/system/clipboard.test.ts +++ b/tests/test-project/src/test/system/clipboard.test.ts @@ -18,9 +18,9 @@ import { expect } from 'chai'; describe('System clipboard', function () { - it('Should contains same text as before tests were run', async function () { - const cb = await import('clipboardy'); - const clipboard = cb.default.readSync(); - expect(clipboard.startsWith('hello_ExTester'), `Fail, the clipboard is: '${clipboard}'`).to.be.true; - }); -}); \ No newline at end of file + it('Should contains same text as before tests were run', async function () { + const cb = await import('clipboardy'); + const clipboard = cb.default.readSync(); + expect(clipboard.startsWith('hello_ExTester'), `Fail, the clipboard is: '${clipboard}'`).to.be.true; + }); +}); diff --git a/tests/test-project/src/test/webview/webView.test.ts b/tests/test-project/src/test/webview/webView.test.ts index 89d7d86ac..5f94483e3 100644 --- a/tests/test-project/src/test/webview/webView.test.ts +++ b/tests/test-project/src/test/webview/webView.test.ts @@ -19,122 +19,123 @@ import { Workbench, EditorView, WebView, By } from 'vscode-extension-tester'; import { expect } from 'chai'; describe('WebViews', function () { - - describe('Single WebView', function () { - - let view: WebView; - - before(async function () { - this.timeout(8000); - await new Workbench().executeCommand('Webview Test'); - await new Promise((res) => { setTimeout(res, 500); }); - view = new WebView(); - await view.switchToFrame(); - }); - - after(async function () { - await view.switchBack(); - await new EditorView().closeAllEditors(); - }); - - it('findWebElement works', async function () { - const element = await view.findWebElement(By.css('h1')); - expect(await element.getText()).has.string('This is a web view'); - }); - - it('findWebElements works', async function () { - const elements = await view.findWebElements(By.css('h1')); - expect(elements.length).equals(1); - }); - }); - - describe('Several WebViews', function () { - - let view: WebView; - let tabs: string[]; - - before(async function () { - await new EditorView().closeAllEditors(); - }); - - before(async function () { - this.timeout(30000); - - const workbench = new Workbench(); - - await workbench.executeCommand('Webview Test'); - await new Promise((res) => { setTimeout(res, 500); }); - - await workbench.executeCommand('Webview Test'); - await new Promise((res) => { setTimeout(res, 500); }); - - await workbench.executeCommand('Webview Test'); - await new Promise((res) => { setTimeout(res, 500); }); - - tabs = await new EditorView().getOpenEditorTitles(); - }); - - after(async function () { - await new EditorView().closeAllEditors(); - }); - - describe('First WebView', function () { - - before(async function () { - await new EditorView().openEditor(tabs[0]); - }); - - void switchToFrame(); - void runTests(); - void clean(); - }); - - describe('Second WebView', async function () { - - before(async function () { - await new EditorView().openEditor(tabs[1]); - }); - - void switchToFrame(); - void runTests(); - void clean(); - }); - - describe('Third WebView', async function () { - - before(async function () { - await new EditorView().openEditor(tabs[2]); - }); - - void switchToFrame(); - void runTests(); - void clean(); - }); - - async function switchToFrame() { - before(async function () { - view = new WebView(); - await view.switchToFrame(); - }); - } - - async function runTests() { - it('findWebElement works', async function () { - const element = await view.findWebElement(By.css('h1')); - expect(await element.getText()).has.string('This is a web view'); - }); - - it('findWebElements works', async function () { - const elements = await view.findWebElements(By.css('h1')); - expect(elements.length).equals(1); - }); - } - - async function clean() { - after(async function () { - await view.switchBack(); - }); - } - - }); + describe('Single WebView', function () { + let view: WebView; + + before(async function () { + this.timeout(8000); + await new Workbench().executeCommand('Webview Test'); + await new Promise((res) => { + setTimeout(res, 500); + }); + view = new WebView(); + await view.switchToFrame(); + }); + + after(async function () { + await view.switchBack(); + await new EditorView().closeAllEditors(); + }); + + it('findWebElement works', async function () { + const element = await view.findWebElement(By.css('h1')); + expect(await element.getText()).has.string('This is a web view'); + }); + + it('findWebElements works', async function () { + const elements = await view.findWebElements(By.css('h1')); + expect(elements.length).equals(1); + }); + }); + + describe('Several WebViews', function () { + let view: WebView; + let tabs: string[]; + + before(async function () { + await new EditorView().closeAllEditors(); + }); + + before(async function () { + this.timeout(30000); + + const workbench = new Workbench(); + + await workbench.executeCommand('Webview Test'); + await new Promise((res) => { + setTimeout(res, 500); + }); + + await workbench.executeCommand('Webview Test'); + await new Promise((res) => { + setTimeout(res, 500); + }); + + await workbench.executeCommand('Webview Test'); + await new Promise((res) => { + setTimeout(res, 500); + }); + + tabs = await new EditorView().getOpenEditorTitles(); + }); + + after(async function () { + await new EditorView().closeAllEditors(); + }); + + describe('First WebView', function () { + before(async function () { + await new EditorView().openEditor(tabs[0]); + }); + + void switchToFrame(); + void runTests(); + void clean(); + }); + + describe('Second WebView', async function () { + before(async function () { + await new EditorView().openEditor(tabs[1]); + }); + + void switchToFrame(); + void runTests(); + void clean(); + }); + + describe('Third WebView', async function () { + before(async function () { + await new EditorView().openEditor(tabs[2]); + }); + + void switchToFrame(); + void runTests(); + void clean(); + }); + + async function switchToFrame() { + before(async function () { + view = new WebView(); + await view.switchToFrame(); + }); + } + + async function runTests() { + it('findWebElement works', async function () { + const element = await view.findWebElement(By.css('h1')); + expect(await element.getText()).has.string('This is a web view'); + }); + + it('findWebElements works', async function () { + const elements = await view.findWebElements(By.css('h1')); + expect(elements.length).equals(1); + }); + } + + async function clean() { + after(async function () { + await view.switchBack(); + }); + } + }); }); diff --git a/tests/test-project/src/test/webview/webviewView.test.ts b/tests/test-project/src/test/webview/webviewView.test.ts index 0e2670a2f..46aa3bf5b 100644 --- a/tests/test-project/src/test/webview/webviewView.test.ts +++ b/tests/test-project/src/test/webview/webviewView.test.ts @@ -19,56 +19,66 @@ import { expect } from 'chai'; import { BottomBarPanel, By, CustomTreeSection, SideBarView, WebviewView, Workbench } from 'vscode-extension-tester'; describe('WebviewViews', function () { + const params = [ + { + title: 'BottomBar', + command: 'My Panel: Focus on My Panel View View', + closePanel: true, + closeSection: false, + }, + { + title: 'SideBar', + command: 'Explorer: Focus on My Side Panel View View', + closePanel: false, + closeSection: true, + }, + ]; - const params = [ - { title: 'BottomBar', command: 'My Panel: Focus on My Panel View View', closePanel: true, closeSection: false }, - { title: 'SideBar', command: 'Explorer: Focus on My Side Panel View View', closePanel: false, closeSection: true } - ]; + params.forEach(function (param) { + describe(`${param.title} WebviewViews`, function () { + let webviewView: InstanceType; - params.forEach(function (param) { - describe(`${param.title} WebviewViews`, function () { + before(async function () { + await new Workbench().executeCommand(param.command); + webviewView = new WebviewView(); + await webviewView.switchToFrame(1000); + }); - let webviewView: InstanceType; + after(async function () { + if (webviewView) { + await webviewView.switchBack(); + } + if (param.closePanel) { + await new BottomBarPanel().toggle(false); + } + if (param.closeSection) { + const section = (await new SideBarView().getContent().getSection('My Side Panel View')) as CustomTreeSection; + await section.collapse(); + } + }); - before(async function () { - await new Workbench().executeCommand(param.command); - webviewView = new WebviewView(); - await webviewView.switchToFrame(1000); - }); + it('findWebElement works', async function () { + const element = await webviewView.findWebElement(By.css('h1')); + expect(await element.getText()).has.string('Shopping List'); + }); - after(async function () { - if (webviewView) { - await webviewView.switchBack(); - } - if(param.closePanel) { - await new BottomBarPanel().toggle(false); - } - if(param.closeSection) { - const section = await new SideBarView().getContent().getSection('My Side Panel View') as CustomTreeSection; - await section.collapse(); - } - }); + it('findWebElements works', async function () { + const elements = await webviewView.findWebElements(By.css('li')); + expect(elements.length).equals(2); + }); - it('findWebElement works', async function () { - const element = await webviewView.findWebElement(By.css('h1')); - expect(await element.getText()).has.string('Shopping List'); - }); - - it('findWebElements works', async function () { - const elements = await webviewView.findWebElements(By.css('li')); - expect(elements.length).equals(2); - }); - - it('contains Apple and Banana', async function () { - const elts = await webviewView.findWebElements(By.xpath('//div/ul/li')); - const listContent: string[] = []; - await Promise.all(elts.map(async elt => { - listContent.push(await elt.getText()); - })); - expect(listContent).to.have.length(2); - expect(listContent).to.contain('Apple'); - expect(listContent).to.contain('Banana'); - }); - }); - }); + it('contains Apple and Banana', async function () { + const elts = await webviewView.findWebElements(By.xpath('//div/ul/li')); + const listContent: string[] = []; + await Promise.all( + elts.map(async (elt) => { + listContent.push(await elt.getText()); + }), + ); + expect(listContent).to.have.length(2); + expect(listContent).to.contain('Apple'); + expect(listContent).to.contain('Banana'); + }); + }); + }); }); diff --git a/tests/test-project/src/test/workbench/input.test.ts b/tests/test-project/src/test/workbench/input.test.ts index 9b0ad9e03..8f0265ba1 100644 --- a/tests/test-project/src/test/workbench/input.test.ts +++ b/tests/test-project/src/test/workbench/input.test.ts @@ -19,196 +19,195 @@ import { expect } from 'chai'; import { QuickOpenBox, Workbench, QuickPickItem, InputBox, StatusBar, EditorView, VSBrowser, By } from 'vscode-extension-tester'; describe('QuickOpenBox', () => { - let input: QuickOpenBox; - - before(async () => { - input = await new Workbench().openCommandPrompt(); - }); - - after(async () => { - await input.cancel(); - }); - - it('selectQuickPick works', async function() { - this.timeout(5000); - await input.setText('>hello world'); - await input.selectQuickPick('Hello World'); - expect(await input.isDisplayed()).is.false; - input = await new Workbench().openCommandPrompt(); - }); - - it('can set and retrieve the text', async function() { - this.timeout(5000); - const testText = 'test-text'; - await input.setText(testText); - const text = await input.getText(); - expect(testText).has.string(text); - }); - - it('getPlaceholder returns placeholder text', async function() { - this.timeout(5000); - await input.setText(''); - const holder = await input.getPlaceHolder(); - - let searchString = `Type '?' to get help`; - if (VSBrowser.instance.version >= '1.44.0') { - searchString = 'Search files by name'; - } - expect(holder).has.string(searchString); - }); - - it('hasProgress checks for progress bar', async () => { - const prog = await input.hasProgress(); - expect(prog).is.false; - }); - - it('getQuickPicks finds quick pick options', async () => { - await input.setText('>hello world'); - const picks = await input.getQuickPicks(); - expect(picks).not.empty; - }); - - it('findQuickPick works when item exists', async function() { - this.timeout(150000); - await input.setText('>'); - const pick = await input.findQuickPick('Workspaces: Add Folder to Workspace...'); - expect(pick).not.undefined; - }); - - - it('findQuickPick works when item does not exist', async function() { - this.timeout(150000); - await input.setText('>'); - const pick = await input.findQuickPick('thisdoesnot exits definitely'); - expect(pick).undefined; - }); + let input: QuickOpenBox; + + before(async () => { + input = await new Workbench().openCommandPrompt(); + }); + + after(async () => { + await input.cancel(); + }); + + it('selectQuickPick works', async function () { + this.timeout(5000); + await input.setText('>hello world'); + await input.selectQuickPick('Hello World'); + expect(await input.isDisplayed()).is.false; + input = await new Workbench().openCommandPrompt(); + }); + + it('can set and retrieve the text', async function () { + this.timeout(5000); + const testText = 'test-text'; + await input.setText(testText); + const text = await input.getText(); + expect(testText).has.string(text); + }); + + it('getPlaceholder returns placeholder text', async function () { + this.timeout(5000); + await input.setText(''); + const holder = await input.getPlaceHolder(); + + let searchString = `Type '?' to get help`; + if (VSBrowser.instance.version >= '1.44.0') { + searchString = 'Search files by name'; + } + expect(holder).has.string(searchString); + }); + + it('hasProgress checks for progress bar', async () => { + const prog = await input.hasProgress(); + expect(prog).is.false; + }); + + it('getQuickPicks finds quick pick options', async () => { + await input.setText('>hello world'); + const picks = await input.getQuickPicks(); + expect(picks).not.empty; + }); + + it('findQuickPick works when item exists', async function () { + this.timeout(150000); + await input.setText('>'); + const pick = await input.findQuickPick('Workspaces: Add Folder to Workspace...'); + expect(pick).not.undefined; + }); + + it('findQuickPick works when item does not exist', async function () { + this.timeout(150000); + await input.setText('>'); + const pick = await input.findQuickPick('thisdoesnot exits definitely'); + expect(pick).undefined; + }); }); describe('QuickPickItem', () => { - let item: QuickPickItem; - let input: QuickOpenBox; - - before(async function() { - this.timeout(5000); - input = await new Workbench().openCommandPrompt(); - await input.setText('>hello world'); - const picks = await input.getQuickPicks(); - item = picks[0]; - }); - - it('getLabel returns label', async () => { - const text = await item.getLabel(); - expect(text).not.empty; - }); - - it('getIndex returns the index of the item', () => { - const index = item.getIndex(); - let expected = 0; - if (VSBrowser.instance.version < '1.44.0') { - expected = 1; - } - expect(index).equals(expected); - }); - - it('select works', async () => { - await item.select(); - expect(await input.isDisplayed()).is.false; - }); - - it('getDescription works', async function() { - this.timeout(8000); - await new Workbench().executeCommand('Extension Test Command'); - const inputbox = await InputBox.create(); - const pick = (await inputbox.getQuickPicks())[0]; - const desc = await pick.getDescription(); - expect(desc).has.string('Test Description'); - }); + let item: QuickPickItem; + let input: QuickOpenBox; + + before(async function () { + this.timeout(5000); + input = await new Workbench().openCommandPrompt(); + await input.setText('>hello world'); + const picks = await input.getQuickPicks(); + item = picks[0]; + }); + + it('getLabel returns label', async () => { + const text = await item.getLabel(); + expect(text).not.empty; + }); + + it('getIndex returns the index of the item', () => { + const index = item.getIndex(); + let expected = 0; + if (VSBrowser.instance.version < '1.44.0') { + expected = 1; + } + expect(index).equals(expected); + }); + + it('select works', async () => { + await item.select(); + expect(await input.isDisplayed()).is.false; + }); + + it('getDescription works', async function () { + this.timeout(8000); + await new Workbench().executeCommand('Extension Test Command'); + const inputbox = await InputBox.create(); + const pick = (await inputbox.getQuickPicks())[0]; + const desc = await pick.getDescription(); + expect(desc).has.string('Test Description'); + }); }); describe('InputBox', () => { - let input: InputBox; - - before(async function () { - this.timeout(6000); - await new Workbench().executeCommand('Create: New File...'); - await (await InputBox.create()).selectQuickPick('Text File'); - await new Promise(res => setTimeout(res, 500)); - await new StatusBar().openLanguageSelection(); - input = await InputBox.create(); - }); - - after(async() => { - await input.cancel(); - await new EditorView().closeAllEditors(); - }); - - it('text handling works', async function() { - this.timeout(5000); - const text = 'text'; - await input.setText(text); - expect(await input.getText()).equals(text); - - await input.clear(); - expect(await input.getText()).empty; - }); - - it('getMessage works', async () => { - const message = await input.getMessage(); - expect(message).empty; - }); - - it('hasProgress works', async () => { - const prog = await input.hasProgress(); - expect(prog).is.false; - }); - - it('getQuickPicks works', async function() { - this.timeout(4000); - const picks = await input.getQuickPicks(); - expect(picks).not.empty; - }); - - it('hasError works', async () => { - const err = await input.hasError(); - expect(err).is.false; - }); - - it('isPassword works', async () => { - const pass = await input.isPassword(); - expect(pass).is.false; - }); + let input: InputBox; + + before(async function () { + this.timeout(6000); + await new Workbench().executeCommand('Create: New File...'); + await (await InputBox.create()).selectQuickPick('Text File'); + await new Promise((res) => setTimeout(res, 500)); + await new StatusBar().openLanguageSelection(); + input = await InputBox.create(); + }); + + after(async () => { + await input.cancel(); + await new EditorView().closeAllEditors(); + }); + + it('text handling works', async function () { + this.timeout(5000); + const text = 'text'; + await input.setText(text); + expect(await input.getText()).equals(text); + + await input.clear(); + expect(await input.getText()).empty; + }); + + it('getMessage works', async () => { + const message = await input.getMessage(); + expect(message).empty; + }); + + it('hasProgress works', async () => { + const prog = await input.hasProgress(); + expect(prog).is.false; + }); + + it('getQuickPicks works', async function () { + this.timeout(4000); + const picks = await input.getQuickPicks(); + expect(picks).not.empty; + }); + + it('hasError works', async () => { + const err = await input.hasError(); + expect(err).is.false; + }); + + it('isPassword works', async () => { + const pass = await input.isPassword(); + expect(pass).is.false; + }); }); describe('Multiple selection input', () => { - let input: InputBox; - - before(async () => { - await new Workbench().executeCommand('Test Quickpicks'); - await new Promise(res => setTimeout(res, 500)); - input = await InputBox.create(); - }); - - after(async () => { - await input.confirm(); - }); - - it('Select all works', async () => { - await input.toggleAllQuickPicks(true); - const checkbox = await input.findElement(By.css('input')); - expect(await checkbox.isSelected()).is.true; - }); - - it('Deselect all works', async () => { - await input.toggleAllQuickPicks(false); - const checkbox = await input.findElement(By.css('input')); - expect(await checkbox.isSelected()).is.false; - }); - - it('allows retrieving quickpicks', async () => { - const [first] = await input.getCheckboxes(); - expect(await first.getText()).equals('test1'); - await first.select(); - const checkbox = await first.findElement(By.css('input')); - expect(await checkbox.isSelected()).is.true; - }); -}); \ No newline at end of file + let input: InputBox; + + before(async () => { + await new Workbench().executeCommand('Test Quickpicks'); + await new Promise((res) => setTimeout(res, 500)); + input = await InputBox.create(); + }); + + after(async () => { + await input.confirm(); + }); + + it('Select all works', async () => { + await input.toggleAllQuickPicks(true); + const checkbox = await input.findElement(By.css('input')); + expect(await checkbox.isSelected()).is.true; + }); + + it('Deselect all works', async () => { + await input.toggleAllQuickPicks(false); + const checkbox = await input.findElement(By.css('input')); + expect(await checkbox.isSelected()).is.false; + }); + + it('allows retrieving quickpicks', async () => { + const [first] = await input.getCheckboxes(); + expect(await first.getText()).equals('test1'); + await first.select(); + const checkbox = await first.findElement(By.css('input')); + expect(await checkbox.isSelected()).is.true; + }); +}); diff --git a/tests/test-project/src/test/workbench/notifications.test.ts b/tests/test-project/src/test/workbench/notifications.test.ts index 41ed213d5..524fdb52c 100644 --- a/tests/test-project/src/test/workbench/notifications.test.ts +++ b/tests/test-project/src/test/workbench/notifications.test.ts @@ -19,124 +19,128 @@ import { expect } from 'chai'; import { NotificationsCenter, Workbench, NotificationType, Notification, until, VSBrowser } from 'vscode-extension-tester'; describe('NotificationsCenter', () => { - let center: NotificationsCenter; - - before(async () => { - center = await new Workbench().openNotificationsCenter(); - }); - - after(async () => { - await center.close(); - }); - - it('getNotifications works', async function() { - this.timeout(4000); - await new Workbench().executeCommand('hello world'); - await center.getDriver().sleep(500); - center = await new Workbench().openNotificationsCenter(); - await center.getDriver().sleep(500); - const notifications = await center.getNotifications(NotificationType.Any); - expect(notifications).not.empty; - }); - - it('clearAllNotifications works', async function () { - this.timeout(8000); - await new Workbench().executeCommand('hello world'); - await center.getDriver().sleep(500); - center = await new Workbench().openNotificationsCenter(); - await center.getDriver().sleep(500); - const notifications = await center.getNotifications(NotificationType.Any); - expect(notifications).not.empty; - - await center.clearAllNotifications(); - await center.getDriver().sleep(1000); - expect(await center.isDisplayed()).is.false; - }); - - describe('Notification', () => { - let notification: Notification; - - before(async () => { - await new Workbench().executeCommand('test notification'); - await center.getDriver().sleep(200); - center = await new Workbench().openNotificationsCenter(); - notification = (await center.getNotifications(NotificationType.Any))[0]; - }); - - it('getMessage gets the text', async () => { - const message = await notification.getMessage(); - expect(message).has.string('This is a notification'); - }); - - it('getType returns notificationType', async () => { - const type = await notification.getType(); - expect(type).equals(NotificationType.Info); - }); - - it('hasProgress works', async () => { - const prog = await notification.hasProgress(); - expect(prog).is.false; - }); - - it('getActions looks for action buttons', async () => { - const actions = await notification.getActions(); - expect(await Promise.all(actions.map(async (item) => { - return await item.getTitle(); - }))).deep.equals(['Yes', 'No']); - }); - - it('getSource returns title of origin', async function () { - if(VSBrowser.instance.version >= '1.88.0') { - this.skip(); - } - const source = await notification.getSource(); - expect(source).has.string('Test Project'); - }); - - it('expand works', async () => { - await notification.expand(); - }); - - it('takeAction works', async function() { - this.timeout(8000); - const driver = notification.getDriver(); - await notification.takeAction('Yes'); - await driver.wait(until.stalenessOf(notification)); - }); - - it('dismiss works', async () => { - await new Workbench().executeCommand('test notification'); - await center.getDriver().sleep(200); - center = await new Workbench().openNotificationsCenter(); - notification = (await center.getNotifications(NotificationType.Any))[0]; - - const driver = notification.getDriver(); - await notification.dismiss(); - await driver.wait(until.stalenessOf(notification)); - }); - - it('get warning notification works', async () => { - await new Workbench().executeCommand('Warning Message'); - await center.getDriver().sleep(200); - center = await new Workbench().openNotificationsCenter(); - notification = (await center.getNotifications(NotificationType.Warning))[0]; - - expect(await notification.getMessage()).to.equal("This is a warning!"); - expect(await notification.getType()).to.equal(NotificationType.Warning); - await notification.dismiss(); - await center.getDriver().wait(until.stalenessOf(notification)); - }); - - it('get error notification works', async () => { - await new Workbench().executeCommand('Error Message'); - await center.getDriver().sleep(200); - center = await new Workbench().openNotificationsCenter(); - notification = (await center.getNotifications(NotificationType.Error))[0]; - - expect(await notification.getMessage()).to.equal("This is an error!"); - expect(await notification.getType()).to.equal(NotificationType.Error); - await notification.dismiss(); - await center.getDriver().wait(until.stalenessOf(notification)); - }); - }); -}); \ No newline at end of file + let center: NotificationsCenter; + + before(async () => { + center = await new Workbench().openNotificationsCenter(); + }); + + after(async () => { + await center.close(); + }); + + it('getNotifications works', async function () { + this.timeout(4000); + await new Workbench().executeCommand('hello world'); + await center.getDriver().sleep(500); + center = await new Workbench().openNotificationsCenter(); + await center.getDriver().sleep(500); + const notifications = await center.getNotifications(NotificationType.Any); + expect(notifications).not.empty; + }); + + it('clearAllNotifications works', async function () { + this.timeout(8000); + await new Workbench().executeCommand('hello world'); + await center.getDriver().sleep(500); + center = await new Workbench().openNotificationsCenter(); + await center.getDriver().sleep(500); + const notifications = await center.getNotifications(NotificationType.Any); + expect(notifications).not.empty; + + await center.clearAllNotifications(); + await center.getDriver().sleep(1000); + expect(await center.isDisplayed()).is.false; + }); + + describe('Notification', () => { + let notification: Notification; + + before(async () => { + await new Workbench().executeCommand('test notification'); + await center.getDriver().sleep(200); + center = await new Workbench().openNotificationsCenter(); + notification = (await center.getNotifications(NotificationType.Any))[0]; + }); + + it('getMessage gets the text', async () => { + const message = await notification.getMessage(); + expect(message).has.string('This is a notification'); + }); + + it('getType returns notificationType', async () => { + const type = await notification.getType(); + expect(type).equals(NotificationType.Info); + }); + + it('hasProgress works', async () => { + const prog = await notification.hasProgress(); + expect(prog).is.false; + }); + + it('getActions looks for action buttons', async () => { + const actions = await notification.getActions(); + expect( + await Promise.all( + actions.map(async (item) => { + return await item.getTitle(); + }), + ), + ).deep.equals(['Yes', 'No']); + }); + + it('getSource returns title of origin', async function () { + if (VSBrowser.instance.version >= '1.88.0') { + this.skip(); + } + const source = await notification.getSource(); + expect(source).has.string('Test Project'); + }); + + it('expand works', async () => { + await notification.expand(); + }); + + it('takeAction works', async function () { + this.timeout(8000); + const driver = notification.getDriver(); + await notification.takeAction('Yes'); + await driver.wait(until.stalenessOf(notification)); + }); + + it('dismiss works', async () => { + await new Workbench().executeCommand('test notification'); + await center.getDriver().sleep(200); + center = await new Workbench().openNotificationsCenter(); + notification = (await center.getNotifications(NotificationType.Any))[0]; + + const driver = notification.getDriver(); + await notification.dismiss(); + await driver.wait(until.stalenessOf(notification)); + }); + + it('get warning notification works', async () => { + await new Workbench().executeCommand('Warning Message'); + await center.getDriver().sleep(200); + center = await new Workbench().openNotificationsCenter(); + notification = (await center.getNotifications(NotificationType.Warning))[0]; + + expect(await notification.getMessage()).to.equal('This is a warning!'); + expect(await notification.getType()).to.equal(NotificationType.Warning); + await notification.dismiss(); + await center.getDriver().wait(until.stalenessOf(notification)); + }); + + it('get error notification works', async () => { + await new Workbench().executeCommand('Error Message'); + await center.getDriver().sleep(200); + center = await new Workbench().openNotificationsCenter(); + notification = (await center.getNotifications(NotificationType.Error))[0]; + + expect(await notification.getMessage()).to.equal('This is an error!'); + expect(await notification.getType()).to.equal(NotificationType.Error); + await notification.dismiss(); + await center.getDriver().wait(until.stalenessOf(notification)); + }); + }); +}); diff --git a/tests/test-project/src/test/workbench/open.test.ts b/tests/test-project/src/test/workbench/open.test.ts index 561eca7ea..3b971b86f 100644 --- a/tests/test-project/src/test/workbench/open.test.ts +++ b/tests/test-project/src/test/workbench/open.test.ts @@ -19,19 +19,18 @@ import { EditorView, Workbench } from 'vscode-extension-tester'; import * as path from 'path'; describe('Simple open file dialog', function () { + const filePath = path.resolve('.', 'package.json'); - const filePath = path.resolve('.', 'package.json'); + it('Opens a file', async function () { + this.timeout(30000); + const input = await new Workbench().openCommandPrompt(); + await input.setText('>File: Open File...'); + await input.selectQuickPick('File: Open File...'); + await new Promise((res) => setTimeout(res, 1000)); + await input.setText(filePath); + await input.confirm(); + await new Promise((res) => setTimeout(res, 1000)); - it('Opens a file', async function () { - this.timeout(30000); - const input = await new Workbench().openCommandPrompt(); - await input.setText('>File: Open File...'); - await input.selectQuickPick('File: Open File...'); - await new Promise(res => setTimeout(res, 1000)); - await input.setText(filePath); - await input.confirm(); - await new Promise(res => setTimeout(res, 1000)); - - await new EditorView().openEditor('package.json'); - }); -}); \ No newline at end of file + await new EditorView().openEditor('package.json'); + }); +}); diff --git a/tests/test-project/src/test/workbench/workbench.test.ts b/tests/test-project/src/test/workbench/workbench.test.ts index 342fa1c4e..b9548aad1 100644 --- a/tests/test-project/src/test/workbench/workbench.test.ts +++ b/tests/test-project/src/test/workbench/workbench.test.ts @@ -19,70 +19,74 @@ import { expect } from 'chai'; import { EditorView, Workbench } from 'vscode-extension-tester'; describe('Workbench', () => { - let bench: Workbench; + let bench: Workbench; - before(() => { - bench = new Workbench(); - }); + before(() => { + bench = new Workbench(); + }); - it('getTitleBar returns title bar reference', () => { - const bar = bench.getTitleBar(); - expect(bar).not.undefined; - }); + it('getTitleBar returns title bar reference', () => { + const bar = bench.getTitleBar(); + expect(bar).not.undefined; + }); - it('getSideBar returns side bar reference', () => { - const bar = bench.getSideBar(); - expect(bar).not.undefined; - }); + it('getSideBar returns side bar reference', () => { + const bar = bench.getSideBar(); + expect(bar).not.undefined; + }); - it('getActivityBar returns activity bar reference', () => { - const bar = bench.getActivityBar(); - expect(bar).not.undefined; - }); + it('getActivityBar returns activity bar reference', () => { + const bar = bench.getActivityBar(); + expect(bar).not.undefined; + }); - it('getStatusBar returns status bar reference', () => { - const bar = bench.getStatusBar(); - expect(bar).not.undefined; - }); + it('getStatusBar returns status bar reference', () => { + const bar = bench.getStatusBar(); + expect(bar).not.undefined; + }); - it('getBottomBar returns bottom bar reference', () => { - const bar = bench.getBottomBar(); - expect(bar).not.undefined; - }); + it('getBottomBar returns bottom bar reference', () => { + const bar = bench.getBottomBar(); + expect(bar).not.undefined; + }); - it('getEditorView returns editor view reference', () => { - const view = bench.getEditorView(); - expect(view).not.undefined; - }); + it('getEditorView returns editor view reference', () => { + const view = bench.getEditorView(); + expect(view).not.undefined; + }); - it('openNotificationsCenter works', async () => { - const center = await bench.openNotificationsCenter(); - await center.getDriver().wait(async () => { - return await center.isDisplayed(); - }, 5000, 'Notifications center was not displayed properly!'); - await center.close(); - }); + it('openNotificationsCenter works', async () => { + const center = await bench.openNotificationsCenter(); + await center.getDriver().wait( + async () => { + return await center.isDisplayed(); + }, + 5000, + 'Notifications center was not displayed properly!', + ); + await center.close(); + }); - it('openCommandPrompt works', async () => { - const prompt = await bench.openCommandPrompt(); - expect(await prompt.isDisplayed()).is.true; - await prompt.cancel(); - }); + it('openCommandPrompt works', async () => { + const prompt = await bench.openCommandPrompt(); + expect(await prompt.isDisplayed()).is.true; + await prompt.cancel(); + }); - it('executeCommand works', async () => { - await bench.executeCommand('Hello World'); - await bench.getDriver().sleep(500); - const notifications = await bench.getNotifications(); - expect(notifications).not.empty; + it('executeCommand works', async () => { + await bench.executeCommand('Hello World'); + await bench.getDriver().sleep(500); + const notifications = await bench.getNotifications(); + expect(notifications).not.empty; - const message = await notifications[0].getMessage(); - expect(message).is.equal('Hello World!'); - }); + const message = await notifications[0].getMessage(); + expect(message).is.equal('Hello World!'); + }); - it('openSettings opens the settings editor', async function() { - this.timeout(8000); - const editor = await bench.openSettings(); - expect(await editor.getTitle()).equals('Settings'); - await new EditorView().closeAllEditors(); - }); -}); \ No newline at end of file + it('openSettings opens the settings editor', async function () { + this.timeout(8000); + const editor = await bench.openSettings(); + expect(await editor.getTitle()).equals('Settings'); + await new EditorView().closeAllEditors(); + }); +}); diff --git a/tests/test-project/src/test/xsideBar/customView.test.ts b/tests/test-project/src/test/xsideBar/customView.test.ts index 512fc2f36..1da353665 100644 --- a/tests/test-project/src/test/xsideBar/customView.test.ts +++ b/tests/test-project/src/test/xsideBar/customView.test.ts @@ -16,240 +16,261 @@ */ import { expect } from 'chai'; -import { ActivityBar, CustomTreeItem, CustomTreeSection, NotificationType, TreeItem, VSBrowser, ViewContent, ViewControl, ViewItem, WelcomeContentButton, WelcomeContentSection, Workbench } from 'vscode-extension-tester'; +import { + ActivityBar, + CustomTreeItem, + CustomTreeSection, + NotificationType, + TreeItem, + VSBrowser, + ViewContent, + ViewControl, + ViewItem, + WelcomeContentButton, + WelcomeContentSection, + Workbench, +} from 'vscode-extension-tester'; describe('CustomTreeSection', () => { - let section: CustomTreeSection; - let emptyViewSection: CustomTreeSection; - let content: ViewContent; - - before(async function() { - this.timeout(5000); - const view = await (await new ActivityBar().getViewControl('Explorer') as ViewControl).openView(); - await new Promise((res) => { setTimeout(res, 1000); }); - content = view.getContent(); - section = await content.getSection('Test View'); - emptyViewSection = await content.getSection('Empty View'); - await emptyViewSection.expand(); - }); - - after(async () => { - await (await new ActivityBar().getViewControl('Explorer') as ViewControl).closeView(); - await new Promise((res) => { setTimeout(res, 1000); }); - }); - - it('getTitle works', async () => { - const title = await section.getTitle(); - expect(title).equals('Test View'); - }); - - it('collapse/expand works', async () => { - await section.collapse(); - expect(await section.isExpanded()).is.false; - - await new Promise(res => setTimeout(res, 500)); - await section.expand(); - expect(await section.isExpanded()).is.true; - }); - - it('getVisibleItems works', async function() { - this.timeout(10000); - await VSBrowser.instance.driver.wait(async () => { - const items = await section.getVisibleItems(); - return items.length === 4; - }, this.timeout() - 1000, `expected: 4; got: ${(await section.getVisibleItems().catch(() => [])).length}`); - }); - - it('findItem works', async () => { - const item = await section.findItem('b'); - expect(item).not.undefined; - - const item1 = await section.findItem('e'); - expect(item1).undefined; - }); - - it('openItem returns subitems', async () => { - const items = await section.openItem('a'); - expect(items.length).equals(2); - }); - - it('openItem returns empty array for leaves', async () => { - const items = await section.openItem('b', 'ba') as ViewItem[]; - expect(items).empty; - }); - - it('getActions works', async () => { - const actions = await section.getActions(); - expect(actions).not.empty; - }); - - it('getAction works', async () => { - const action = await section.getAction('Collapse All'); - expect(await action?.getLabel()).equals('Collapse All'); - }); - - it('findWelcomeContent returns undefined if no WelcomeContent is present', async () => { - expect(await section.findWelcomeContent()).to.equal(undefined); - expect(await emptyViewSection.findWelcomeContent()).to.not.equal(undefined); - }); - - it('findWelcomeContent returns the section', async () => { - const welcomeContent = await emptyViewSection.findWelcomeContent() as WelcomeContentSection; - const buttons = await welcomeContent.getButtons(); - const textSections = await welcomeContent.getTextSections(); - - expect(buttons).to.be.an("array").and.have.length(1); - expect(textSections).to.be.an("array").and.have.length(3); - - expect(textSections[0]).to.deep.equal("This is the first line"); - expect(textSections[1]).to.deep.equal("This is the second line"); - expect(textSections[2]).to.deep.equal("And yet another line."); - - expect(await buttons[0].getTitle()).to.deep.equal("Add stuff into this View"); - }); - - it('getContent returns the buttons and strings in an ordered array', async () => { - const welcomeContentEntries = await (await emptyViewSection.findWelcomeContent() as WelcomeContentSection).getContents(); - - expect(welcomeContentEntries).to.be.an("array").and.have.length(4); - - expect(welcomeContentEntries[0]).to.deep.equal("This is the first line"); - expect(welcomeContentEntries[1]).to.not.be.a("string"); - expect(await (welcomeContentEntries[1] as WelcomeContentButton).getText()).to.deep.equal("Add stuff into this View"); - expect(welcomeContentEntries[2]).to.deep.equal("This is the second line"); - expect(welcomeContentEntries[3]).to.deep.equal("And yet another line."); - }); - - describe('WelcomeContentButton', () => { - it('takeAction executes the command', async function() { - this.timeout(10000); - const buttons = await (await emptyViewSection.findWelcomeContent() as WelcomeContentSection).getButtons(); - await buttons[0].click(); - - await new Promise(res => setTimeout(res, 500)); - expect(await emptyViewSection.findWelcomeContent()).to.equal(undefined); - }); - }); - - describe('CustomViewItem', () => { - let item: CustomTreeItem | undefined; - - before(async () => { - await emptyViewSection.collapse(); - item = await section.findItem('a'); - }); - - it('getAction works', async() => { - const action = await item?.getActionButton("Tree Item Action"); - expect(action).not.undefined; - }); - - it('getLabel works', async () => { - const label = await item?.getLabel(); - expect(label).equals('a'); - }); - - it('getTooltip works', async () => { - const tooltip = await item?.getTooltip(); - expect(tooltip).equals('Tooltip for a'); - }); - - it('getDescription works', async () => { - const description = await item?.getDescription(); - expect(description).equals('Description for a'); - }); - - it('collapse works', async () => { - await item?.collapse(); - expect(await item?.isExpanded()).is.false; - }); - - it('selecting toggles expand state', async () => { - await item?.select(); - expect(await item?.isExpanded()).is.true; - await item?.collapse(); - expect(await item?.isExpanded()).is.false; - }); - - it('hasChildren works', async () => { - const children = await item?.hasChildren(); - expect(children).is.true; - }); - - it('hasChildren works for expandable elements without children', async () => { - const cItem = await section.findItem('c'); - expect(await cItem?.hasChildren()).is.false; - }); - - it('getChildren works', async () => { - const children = await item?.getChildren(); - expect(children?.length).equals(2); - }); - - it('findChildItem works', async () => { - const child = await item?.findChildItem('ab'); - expect(child).not.undefined; - }); - - it('expand works', async () => { - item = await section.findItem('a'); - await item?.collapse(); - expect(await item?.isExpanded()).to.equal(false); - await item?.expand(); - expect(await item?.isExpanded()).to.equal(true); - }); - - it('expand is idempotent', async () => { - const items = [1, 2]; - for(let i = 0; i < items.length; i++) { - await item?.expand(); - expect(await item?.isExpanded()).to.equal(true); - } - }); - - describe('tree item with a command', function () { - let dItem: TreeItem | undefined; - let bench: Workbench; - - before(async function () { - dItem = await section.findItem('d'); - bench = new Workbench(); - }); - - beforeEach(async function () { - try { - await (await bench.openNotificationsCenter()).clearAllNotifications(); - } catch (error) { - // do nothing - } - }); - - afterEach(async () => { - const notifications = await (await bench.openNotificationsCenter()).getNotifications(NotificationType.Error); - expect(notifications).to.have.length(0); - }); - - it('getChildren does not click on the tree item', async () => { - expect(await (dItem as CustomTreeItem).getChildren()).to.have.length(2); - await dItem?.collapse(); - }); - - it('findChildItem does not click on the tree item', async () => { - expect(await (dItem as CustomTreeItem).findChildItem("da")).to.not.equal(undefined); - await dItem?.collapse(); - }); - - it('findItem does not click on the tree item', async () => { - expect(await section.openItem('d', 'da')).to.not.equal(undefined); - }); - - it('clicking on the tree item with a command assigned, triggers the command', async () => { - await dItem?.click(); - const errorNotification = await (await bench.openNotificationsCenter()).getNotifications(NotificationType.Error); - expect(errorNotification).to.have.length(1); - expect(await errorNotification[0].getMessage()).to.equal("This is an error!"); - await errorNotification[0].dismiss(); - }); - }); - }); -}); \ No newline at end of file + let section: CustomTreeSection; + let emptyViewSection: CustomTreeSection; + let content: ViewContent; + + before(async function () { + this.timeout(5000); + const view = await ((await new ActivityBar().getViewControl('Explorer')) as ViewControl).openView(); + await new Promise((res) => { + setTimeout(res, 1000); + }); + content = view.getContent(); + section = await content.getSection('Test View'); + emptyViewSection = await content.getSection('Empty View'); + await emptyViewSection.expand(); + }); + + after(async () => { + await ((await new ActivityBar().getViewControl('Explorer')) as ViewControl).closeView(); + await new Promise((res) => { + setTimeout(res, 1000); + }); + }); + + it('getTitle works', async () => { + const title = await section.getTitle(); + expect(title).equals('Test View'); + }); + + it('collapse/expand works', async () => { + await section.collapse(); + expect(await section.isExpanded()).is.false; + + await new Promise((res) => setTimeout(res, 500)); + await section.expand(); + expect(await section.isExpanded()).is.true; + }); + + it('getVisibleItems works', async function () { + this.timeout(10000); + await VSBrowser.instance.driver.wait( + async () => { + const items = await section.getVisibleItems(); + return items.length === 4; + }, + this.timeout() - 1000, + `expected: 4; got: ${(await section.getVisibleItems().catch(() => [])).length}`, + ); + }); + + it('findItem works', async () => { + const item = await section.findItem('b'); + expect(item).not.undefined; + + const item1 = await section.findItem('e'); + expect(item1).undefined; + }); + + it('openItem returns subitems', async () => { + const items = await section.openItem('a'); + expect(items.length).equals(2); + }); + + it('openItem returns empty array for leaves', async () => { + const items = (await section.openItem('b', 'ba')) as ViewItem[]; + expect(items).empty; + }); + + it('getActions works', async () => { + const actions = await section.getActions(); + expect(actions).not.empty; + }); + + it('getAction works', async () => { + const action = await section.getAction('Collapse All'); + expect(await action?.getLabel()).equals('Collapse All'); + }); + + it('findWelcomeContent returns undefined if no WelcomeContent is present', async () => { + expect(await section.findWelcomeContent()).to.equal(undefined); + expect(await emptyViewSection.findWelcomeContent()).to.not.equal(undefined); + }); + + it('findWelcomeContent returns the section', async () => { + const welcomeContent = (await emptyViewSection.findWelcomeContent()) as WelcomeContentSection; + const buttons = await welcomeContent.getButtons(); + const textSections = await welcomeContent.getTextSections(); + + expect(buttons).to.be.an('array').and.have.length(1); + expect(textSections).to.be.an('array').and.have.length(3); + + expect(textSections[0]).to.deep.equal('This is the first line'); + expect(textSections[1]).to.deep.equal('This is the second line'); + expect(textSections[2]).to.deep.equal('And yet another line.'); + + expect(await buttons[0].getTitle()).to.deep.equal('Add stuff into this View'); + }); + + it('getContent returns the buttons and strings in an ordered array', async () => { + const welcomeContentEntries = await ((await emptyViewSection.findWelcomeContent()) as WelcomeContentSection).getContents(); + + expect(welcomeContentEntries).to.be.an('array').and.have.length(4); + + expect(welcomeContentEntries[0]).to.deep.equal('This is the first line'); + expect(welcomeContentEntries[1]).to.not.be.a('string'); + expect(await (welcomeContentEntries[1] as WelcomeContentButton).getText()).to.deep.equal('Add stuff into this View'); + expect(welcomeContentEntries[2]).to.deep.equal('This is the second line'); + expect(welcomeContentEntries[3]).to.deep.equal('And yet another line.'); + }); + + describe('WelcomeContentButton', () => { + it('takeAction executes the command', async function () { + this.timeout(10000); + const buttons = await ((await emptyViewSection.findWelcomeContent()) as WelcomeContentSection).getButtons(); + await buttons[0].click(); + + await new Promise((res) => setTimeout(res, 500)); + expect(await emptyViewSection.findWelcomeContent()).to.equal(undefined); + }); + }); + + describe('CustomViewItem', () => { + let item: CustomTreeItem | undefined; + + before(async () => { + await emptyViewSection.collapse(); + item = await section.findItem('a'); + }); + + it('getAction works', async () => { + const action = await item?.getActionButton('Tree Item Action'); + expect(action).not.undefined; + }); + + it('getLabel works', async () => { + const label = await item?.getLabel(); + expect(label).equals('a'); + }); + + it('getTooltip works', async () => { + const tooltip = await item?.getTooltip(); + expect(tooltip).equals('Tooltip for a'); + }); + + it('getDescription works', async () => { + const description = await item?.getDescription(); + expect(description).equals('Description for a'); + }); + + it('collapse works', async () => { + await item?.collapse(); + expect(await item?.isExpanded()).is.false; + }); + + it('selecting toggles expand state', async () => { + await item?.select(); + expect(await item?.isExpanded()).is.true; + await item?.collapse(); + expect(await item?.isExpanded()).is.false; + }); + + it('hasChildren works', async () => { + const children = await item?.hasChildren(); + expect(children).is.true; + }); + + it('hasChildren works for expandable elements without children', async () => { + const cItem = await section.findItem('c'); + expect(await cItem?.hasChildren()).is.false; + }); + + it('getChildren works', async () => { + const children = await item?.getChildren(); + expect(children?.length).equals(2); + }); + + it('findChildItem works', async () => { + const child = await item?.findChildItem('ab'); + expect(child).not.undefined; + }); + + it('expand works', async () => { + item = await section.findItem('a'); + await item?.collapse(); + expect(await item?.isExpanded()).to.equal(false); + await item?.expand(); + expect(await item?.isExpanded()).to.equal(true); + }); + + it('expand is idempotent', async () => { + const items = [1, 2]; + for (let i = 0; i < items.length; i++) { + await item?.expand(); + expect(await item?.isExpanded()).to.equal(true); + } + }); + + describe('tree item with a command', function () { + let dItem: TreeItem | undefined; + let bench: Workbench; + + before(async function () { + dItem = await section.findItem('d'); + bench = new Workbench(); + }); + + beforeEach(async function () { + try { + await (await bench.openNotificationsCenter()).clearAllNotifications(); + } catch (error) { + // do nothing + } + }); + + afterEach(async () => { + const notifications = await (await bench.openNotificationsCenter()).getNotifications(NotificationType.Error); + expect(notifications).to.have.length(0); + }); + + it('getChildren does not click on the tree item', async () => { + expect(await (dItem as CustomTreeItem).getChildren()).to.have.length(2); + await dItem?.collapse(); + }); + + it('findChildItem does not click on the tree item', async () => { + expect(await (dItem as CustomTreeItem).findChildItem('da')).to.not.equal(undefined); + await dItem?.collapse(); + }); + + it('findItem does not click on the tree item', async () => { + expect(await section.openItem('d', 'da')).to.not.equal(undefined); + }); + + it('clicking on the tree item with a command assigned, triggers the command', async () => { + await dItem?.click(); + const errorNotification = await (await bench.openNotificationsCenter()).getNotifications(NotificationType.Error); + expect(errorNotification).to.have.length(1); + expect(await errorNotification[0].getMessage()).to.equal('This is an error!'); + await errorNotification[0].dismiss(); + }); + }); + }); +}); diff --git a/tests/test-project/src/test/xsideBar/scmView.test.ts b/tests/test-project/src/test/xsideBar/scmView.test.ts index 9a51d0882..8ab14d540 100644 --- a/tests/test-project/src/test/xsideBar/scmView.test.ts +++ b/tests/test-project/src/test/xsideBar/scmView.test.ts @@ -21,117 +21,119 @@ import { expect } from 'chai'; import * as fs from 'fs-extra'; (VSBrowser.instance.version >= '1.38.0' ? describe : describe.skip)('SCM View', () => { - let view: ScmView; - - before(async function () { - this.timeout(15000); - fs.writeFileSync(path.resolve('.', 'testfile'), 'content'); - await VSBrowser.instance.openResources(path.resolve('..', '..')); - await VSBrowser.instance.waitForWorkbench(); - view = await (await new ActivityBar().getViewControl('Source Control') as ViewControl).openView() as ScmView; - await new Promise((res) => { setTimeout(res, 2000); }); - }); - - after(() => { - fs.unlinkSync(path.resolve('.', 'testfile')); - }); - - it('getProviders works', async () => { - const providers = await view.getProviders(); - expect(providers).not.empty; - }); - - it('getProvider works', async () => { - const provider = await view.getProvider('vscode-extension-tester'); - expect(provider).not.undefined; - }); - - describe('ScmProvider', () => { - let provider: ScmProvider; - - before(async () => { - provider = await view.getProvider('vscode-extension-tester') as ScmProvider; - }); - - it('getTitle works', async () => { - const title = await provider.getTitle(); - if (VSBrowser.instance.version >= '1.47.0') { - expect(title).equals(''); - } else { - expect(title).equals('vscode-extension-tester'); - } - }); - - it('getType works', async () => { - const type = await provider.getType(); - if (VSBrowser.instance.version >= '1.47.0') { - expect(type).equals(''); - } else { - expect(type).equals('Git'); - } - }); - - it('getChangeCount works', async () => { - const unCount = await provider.getChangeCount(false); - expect(unCount).gt(0); - const stCount = await provider.getChangeCount(true); - expect(stCount).gte(0); - }); - - it('takeAction works', async () => { - const action = await provider.takeAction('Refresh'); - expect(action).to.be.true; - }); - - (process.platform === 'darwin' ? it.skip : it)('openMoreActions works', async () => { - const menu = await provider.openMoreActions(); - expect(menu).not.undefined; - await menu.close(); - }); - - it('getChanges works', async () => { - const changes = await provider.getChanges(false); - expect(changes).not.empty; - }); - - describe('ScmChange', () => { - let change: ScmChange; - - before(async () => { - const changes = await provider.getChanges(false); - const titles = await Promise.all(changes.map(async item => item.getLabel())); - const index = titles.findIndex(item => item === 'testfile'); - change = changes[index]; - }); - - after(async () => { - await new EditorView().closeAllEditors(); - }); - - it('getLabel works', async () => { - const label = await change.getLabel(); - expect(label).has.string('testfile'); - }); - - it('getDescritption works', async () => { - const desc = await change.getDescription(); - expect(desc).has.string(''); - }); - - it('getStatus works', async () => { - expect(await change.getStatus()).has.string('Untracked'); - }); - - it('isExpanded works', async () => { - expect(await change.isExpanded()).to.be.true; - }); - - it('takeAction works', async () => { - const act = await change.takeAction('Open File'); - expect(act).to.be.true; - - expect(await new EditorView().getOpenEditorTitles()).contains('testfile'); - }); - }); - }); -}); \ No newline at end of file + let view: ScmView; + + before(async function () { + this.timeout(15000); + fs.writeFileSync(path.resolve('.', 'testfile'), 'content'); + await VSBrowser.instance.openResources(path.resolve('..', '..')); + await VSBrowser.instance.waitForWorkbench(); + view = (await ((await new ActivityBar().getViewControl('Source Control')) as ViewControl).openView()) as ScmView; + await new Promise((res) => { + setTimeout(res, 2000); + }); + }); + + after(() => { + fs.unlinkSync(path.resolve('.', 'testfile')); + }); + + it('getProviders works', async () => { + const providers = await view.getProviders(); + expect(providers).not.empty; + }); + + it('getProvider works', async () => { + const provider = await view.getProvider('vscode-extension-tester'); + expect(provider).not.undefined; + }); + + describe('ScmProvider', () => { + let provider: ScmProvider; + + before(async () => { + provider = (await view.getProvider('vscode-extension-tester')) as ScmProvider; + }); + + it('getTitle works', async () => { + const title = await provider.getTitle(); + if (VSBrowser.instance.version >= '1.47.0') { + expect(title).equals(''); + } else { + expect(title).equals('vscode-extension-tester'); + } + }); + + it('getType works', async () => { + const type = await provider.getType(); + if (VSBrowser.instance.version >= '1.47.0') { + expect(type).equals(''); + } else { + expect(type).equals('Git'); + } + }); + + it('getChangeCount works', async () => { + const unCount = await provider.getChangeCount(false); + expect(unCount).gt(0); + const stCount = await provider.getChangeCount(true); + expect(stCount).gte(0); + }); + + it('takeAction works', async () => { + const action = await provider.takeAction('Refresh'); + expect(action).to.be.true; + }); + + (process.platform === 'darwin' ? it.skip : it)('openMoreActions works', async () => { + const menu = await provider.openMoreActions(); + expect(menu).not.undefined; + await menu.close(); + }); + + it('getChanges works', async () => { + const changes = await provider.getChanges(false); + expect(changes).not.empty; + }); + + describe('ScmChange', () => { + let change: ScmChange; + + before(async () => { + const changes = await provider.getChanges(false); + const titles = await Promise.all(changes.map(async (item) => item.getLabel())); + const index = titles.findIndex((item) => item === 'testfile'); + change = changes[index]; + }); + + after(async () => { + await new EditorView().closeAllEditors(); + }); + + it('getLabel works', async () => { + const label = await change.getLabel(); + expect(label).has.string('testfile'); + }); + + it('getDescritption works', async () => { + const desc = await change.getDescription(); + expect(desc).has.string(''); + }); + + it('getStatus works', async () => { + expect(await change.getStatus()).has.string('Untracked'); + }); + + it('isExpanded works', async () => { + expect(await change.isExpanded()).to.be.true; + }); + + it('takeAction works', async () => { + const act = await change.takeAction('Open File'); + expect(act).to.be.true; + + expect(await new EditorView().getOpenEditorTitles()).contains('testfile'); + }); + }); + }); +}); diff --git a/tests/test-project/src/test/xsideBar/sideBarView.test.ts b/tests/test-project/src/test/xsideBar/sideBarView.test.ts index fb64dca72..b58b48da5 100644 --- a/tests/test-project/src/test/xsideBar/sideBarView.test.ts +++ b/tests/test-project/src/test/xsideBar/sideBarView.test.ts @@ -17,220 +17,239 @@ import * as path from 'path'; import { expect } from 'chai'; -import { SideBarView, ActivityBar, ViewTitlePart, Workbench, ViewItem, ViewContent, DefaultTreeSection, DefaultTreeItem, TextEditor, EditorView, VSBrowser, ViewControl, TreeItem, ViewPanelAction } from 'vscode-extension-tester'; +import { + SideBarView, + ActivityBar, + ViewTitlePart, + Workbench, + ViewItem, + ViewContent, + DefaultTreeSection, + DefaultTreeItem, + TextEditor, + EditorView, + VSBrowser, + ViewControl, + TreeItem, + ViewPanelAction, +} from 'vscode-extension-tester'; describe('SideBarView', () => { - let view: SideBarView; - - before(async () => { - view = await (await new ActivityBar().getViewControl('Explorer') as ViewControl).openView(); - }); - - after(async () => { - await (await new ActivityBar().getViewControl('Explorer') as ViewControl).closeView(); - }); - - it('getTitlePart works', async () => { - const titlePart = await view.getTitlePart().wait(); - expect(titlePart).not.undefined; - }); - - it('getContent works', async () => { - const content = await view.getContent().wait(); - expect(content).not.undefined; - }); - - describe('ViewTitlePart', async () => { - let part: ViewTitlePart; - - before(async () => { - part = view.getTitlePart(); - }); - - it('getTitle works', async () => { - const title = await part.getTitle(); - expect(title.toLowerCase()).equals('explorer'); - }); - - it('getActions works', async () => { - const actions = await part.getActions(); - if (VSBrowser.instance.version >= '1.47.0') { - expect(actions).not.empty; - } else { - expect(actions).empty; - } - }); - }); - - describe('ViewContent', async () => { - let content: ViewContent; - - before(async function() { - this.timeout(15000); - await VSBrowser.instance.openResources(path.resolve(__dirname, '..', '..', '..', 'resources', 'test-folder')); - view = await (await new ActivityBar().getViewControl('Explorer') as ViewControl).openView(); - await new Promise((res) => { setTimeout(res, 1000); }); - content = view.getContent(); - }); - - after(async function() { - this.timeout(15000); - await new Workbench().executeCommand('close test folder'); - await new Promise((res) => { setTimeout(res, 3000); }); - }); - - it('getSections works', async () => { - const sections = await content.getSections(); - expect(sections).not.empty; - }); - - it('getSection works', async () => { - const section = await content.getSection('Outline'); - expect(await section.getTitle()).equals('Outline'); - }); - - describe('DefaultTreeSection', async () => { - let section: DefaultTreeSection; - - before(async () => { - section = await content.getSection('test-folder') as DefaultTreeSection; - }); - - it('getTitle works', async () => { - const title = await section.getTitle(); - expect(title).equals('test-folder'); - }); - - it('collapse/expand works', async () => { - await section.collapse(); - expect(await section.isExpanded()).is.false; - - await section.expand(); - expect(await section.isExpanded()).is.true; - }); - - it('getVisibleItems works', async () => { - const items = await section.getVisibleItems(); - expect(items).not.empty; - }); - - it('findItem works', async () => { - const item = await section.findItem('foo'); - expect(item).not.undefined; - }); - - it('findItem returns undefined when item exists outside its level range', async () => { - await section.openItem('foolder'); - const item = await section.findItem('bar', 1); - await (await section.findItem('foolder') as TreeItem).collapse(); - expect(item).undefined; - }); - - it('openItem lists available items when part of the path does not exist', async () => { - const items = ['foo', 'foolder']; - try { - await section.openItem('x', 'y'); - } catch (err) { - if (err instanceof Error) { - expect(err.message).to.have.string(`Available items in current directory: [${items.toString()}]`); - } else { - expect.fail(); - } - } - }); - - it('openItem returns folders subitems', async () => { - const items = await section.openItem('foolder') as ViewItem[]; - expect(items.length).equals(1); - }); - - it('openItem returns empty array for files', async () => { - const items = await section.openItem('foolder', 'bar') as ViewItem[]; - await (await section.findItem('foolder') as DefaultTreeItem).collapse(); - expect(items).empty; - }); - - it('getActions works', async () => { - const actions = await section.getActions(); - expect(actions).not.empty; - }); - - it('getAction works', async () => { - const action = await section.getAction('Refresh Explorer') as ViewPanelAction; - expect(await action.getLabel()).equals('Refresh Explorer'); - }); - - (process.platform === 'darwin' ? it.skip : it)('moreActions works', async () => { - const outline = await content.getSection('Outline'); - await outline.expand(); - await outline.getDriver().actions().move({origin:outline}).perform(); - - const menu = await outline.moreActions(); - expect(menu).not.undefined; - - if (menu) { - await menu.close(); - } - await outline.collapse(); - }); - - describe('DefaultTreeItem', async () => { - let defaultSection: DefaultTreeSection; - let item: DefaultTreeItem; - - before(async () => { - defaultSection = section as DefaultTreeSection; - item = await defaultSection.findItem('foolder') as DefaultTreeItem; - }); - - after(async () => { - await new EditorView().closeAllEditors(); - }); - - it('getLabel works', async () => { - const label = await item.getLabel(); - expect(label).equals('foolder'); - }); - - it('getTooltip works', async () => { - const tooltip = await item.getTooltip(); - expect(tooltip).has.string('foolder'); - }); - - it('selecting folders toggles expand state', async () => { - expect(await item.isExpanded()).is.false; - await item.select(); - expect(await item.isExpanded()).is.true; - await item.collapse(); - expect(await item.isExpanded()).is.false; - }); - - it('hasChildren works', async () => { - const children = await item.hasChildren(); - expect(children).is.true; - }); - - it('getChildren works', async () => { - const children = await item.getChildren(); - expect(children.length).equals(1); - }); - - it('findChildItem works', async () => { - const child = await item.findChildItem('bar'); - expect(child).not.undefined; - }); - - it('select opens editor for a file', async () => { - const foo = await item.findChildItem('bar') as DefaultTreeItem; - await foo.select(); - - try { - await new TextEditor().wait(); - } catch (err) { - expect.fail('No editor was opened'); - } - }); - }); - }); - }); -}); \ No newline at end of file + let view: SideBarView; + + before(async () => { + view = await ((await new ActivityBar().getViewControl('Explorer')) as ViewControl).openView(); + }); + + after(async () => { + await ((await new ActivityBar().getViewControl('Explorer')) as ViewControl).closeView(); + }); + + it('getTitlePart works', async () => { + const titlePart = await view.getTitlePart().wait(); + expect(titlePart).not.undefined; + }); + + it('getContent works', async () => { + const content = await view.getContent().wait(); + expect(content).not.undefined; + }); + + describe('ViewTitlePart', async () => { + let part: ViewTitlePart; + + before(async () => { + part = view.getTitlePart(); + }); + + it('getTitle works', async () => { + const title = await part.getTitle(); + expect(title.toLowerCase()).equals('explorer'); + }); + + it('getActions works', async () => { + const actions = await part.getActions(); + if (VSBrowser.instance.version >= '1.47.0') { + expect(actions).not.empty; + } else { + expect(actions).empty; + } + }); + }); + + describe('ViewContent', async () => { + let content: ViewContent; + + before(async function () { + this.timeout(15000); + await VSBrowser.instance.openResources(path.resolve(__dirname, '..', '..', '..', 'resources', 'test-folder')); + view = await ((await new ActivityBar().getViewControl('Explorer')) as ViewControl).openView(); + await new Promise((res) => { + setTimeout(res, 1000); + }); + content = view.getContent(); + }); + + after(async function () { + this.timeout(15000); + await new Workbench().executeCommand('close test folder'); + await new Promise((res) => { + setTimeout(res, 3000); + }); + }); + + it('getSections works', async () => { + const sections = await content.getSections(); + expect(sections).not.empty; + }); + + it('getSection works', async () => { + const section = await content.getSection('Outline'); + expect(await section.getTitle()).equals('Outline'); + }); + + describe('DefaultTreeSection', async () => { + let section: DefaultTreeSection; + + before(async () => { + section = (await content.getSection('test-folder')) as DefaultTreeSection; + }); + + it('getTitle works', async () => { + const title = await section.getTitle(); + expect(title).equals('test-folder'); + }); + + it('collapse/expand works', async () => { + await section.collapse(); + expect(await section.isExpanded()).is.false; + + await section.expand(); + expect(await section.isExpanded()).is.true; + }); + + it('getVisibleItems works', async () => { + const items = await section.getVisibleItems(); + expect(items).not.empty; + }); + + it('findItem works', async () => { + const item = await section.findItem('foo'); + expect(item).not.undefined; + }); + + it('findItem returns undefined when item exists outside its level range', async () => { + await section.openItem('foolder'); + const item = await section.findItem('bar', 1); + await ((await section.findItem('foolder')) as TreeItem).collapse(); + expect(item).undefined; + }); + + it('openItem lists available items when part of the path does not exist', async () => { + const items = ['foo', 'foolder']; + try { + await section.openItem('x', 'y'); + } catch (err) { + if (err instanceof Error) { + expect(err.message).to.have.string(`Available items in current directory: [${items.toString()}]`); + } else { + expect.fail(); + } + } + }); + + it('openItem returns folders subitems', async () => { + const items = (await section.openItem('foolder')) as ViewItem[]; + expect(items.length).equals(1); + }); + + it('openItem returns empty array for files', async () => { + const items = (await section.openItem('foolder', 'bar')) as ViewItem[]; + await ((await section.findItem('foolder')) as DefaultTreeItem).collapse(); + expect(items).empty; + }); + + it('getActions works', async () => { + const actions = await section.getActions(); + expect(actions).not.empty; + }); + + it('getAction works', async () => { + const action = (await section.getAction('Refresh Explorer')) as ViewPanelAction; + expect(await action.getLabel()).equals('Refresh Explorer'); + }); + + (process.platform === 'darwin' ? it.skip : it)('moreActions works', async () => { + const outline = await content.getSection('Outline'); + await outline.expand(); + await outline.getDriver().actions().move({ origin: outline }).perform(); + + const menu = await outline.moreActions(); + expect(menu).not.undefined; + + if (menu) { + await menu.close(); + } + await outline.collapse(); + }); + + describe('DefaultTreeItem', async () => { + let defaultSection: DefaultTreeSection; + let item: DefaultTreeItem; + + before(async () => { + defaultSection = section as DefaultTreeSection; + item = (await defaultSection.findItem('foolder')) as DefaultTreeItem; + }); + + after(async () => { + await new EditorView().closeAllEditors(); + }); + + it('getLabel works', async () => { + const label = await item.getLabel(); + expect(label).equals('foolder'); + }); + + it('getTooltip works', async () => { + const tooltip = await item.getTooltip(); + expect(tooltip).has.string('foolder'); + }); + + it('selecting folders toggles expand state', async () => { + expect(await item.isExpanded()).is.false; + await item.select(); + expect(await item.isExpanded()).is.true; + await item.collapse(); + expect(await item.isExpanded()).is.false; + }); + + it('hasChildren works', async () => { + const children = await item.hasChildren(); + expect(children).is.true; + }); + + it('getChildren works', async () => { + const children = await item.getChildren(); + expect(children.length).equals(1); + }); + + it('findChildItem works', async () => { + const child = await item.findChildItem('bar'); + expect(child).not.undefined; + }); + + it('select opens editor for a file', async () => { + const foo = (await item.findChildItem('bar')) as DefaultTreeItem; + await foo.select(); + + try { + await new TextEditor().wait(); + } catch (err) { + expect.fail('No editor was opened'); + } + }); + }); + }); + }); +}); diff --git a/tests/test-project/src/test/xsideBar/xtensionsView.test.ts b/tests/test-project/src/test/xsideBar/xtensionsView.test.ts index 4229a87fb..6a82b781b 100644 --- a/tests/test-project/src/test/xsideBar/xtensionsView.test.ts +++ b/tests/test-project/src/test/xsideBar/xtensionsView.test.ts @@ -20,88 +20,87 @@ import { expect } from 'chai'; import pjson from '../../../package.json'; describe('ExtensionsView', () => { - let section: ExtensionsViewSection; - let item: ExtensionsViewItem; - - let sectionTitle = 'Enabled'; - if (VSBrowser.browserName === 'vscode' && VSBrowser.instance.version >= '1.48.0') { - sectionTitle = 'Installed'; - } - - before(async () => { - const view = await (await new ActivityBar().getViewControl('Extensions') as ViewControl).openView(); - await view.getDriver().wait(async function () { - return (await view.getContent().getSections()).length > 0; - }); - section = await view.getContent().getSection(sectionTitle) as ExtensionsViewSection; - }); - - after(async function() { - await (await new ActivityBar().getViewControl('Extensions') as ViewControl).closeView(); - await new EditorView().closeAllEditors(); - }); - - it('getTitle works', async () => { - const title = await section.getTitle(); - expect(title).equals(sectionTitle); - }); - - it('getVisibleItems works', async () => { - const items = await section.getVisibleItems(); - expect(items).not.undefined; - }); - - it('findItem works', async function() { - this.timeout(30000); - await section.getDriver().wait(async function () { - item = await section.findItem(`@installed ${pjson.displayName}`) as ExtensionsViewItem; - return item !== undefined; - }); - expect(item).not.undefined; - }); - - describe('ExtensionsViewItem', async () => { - - beforeEach(async function () { - await section.getDriver().wait(async function () { - item = await section.findItem(`@installed ${pjson.displayName}`) as ExtensionsViewItem; - return item !== undefined; - }); - }); - - after(async () => { - await section.clearSearch(); - }); - - it('getTitle works', async () => { - const title = await item.getTitle(); - expect(title).equals(pjson.displayName); - }); - - it('getVersion works', async () => { - const version = await item.getVersion(); - expect(version).equals(pjson.version); - }); - - it('getAuthor works', async () => { - const author = await item.getAuthor(); - expect(author).equals(pjson.publisher); - }); - - it('getDescription works', async () => { - const desc = await item.getDescription(); - expect(desc).equals(pjson.description); - }); - - it('isInstalled works', async () => { - const installed = await item.isInstalled(); - expect(installed).is.true; - }); - - (process.platform === 'darwin' ? it.skip : it)('manage works', async () => { - const menu = await item.manage(); - expect(menu).not.undefined; - await menu.close(); - }); - }); -}); \ No newline at end of file + let section: ExtensionsViewSection; + let item: ExtensionsViewItem; + + let sectionTitle = 'Enabled'; + if (VSBrowser.browserName === 'vscode' && VSBrowser.instance.version >= '1.48.0') { + sectionTitle = 'Installed'; + } + + before(async () => { + const view = await ((await new ActivityBar().getViewControl('Extensions')) as ViewControl).openView(); + await view.getDriver().wait(async function () { + return (await view.getContent().getSections()).length > 0; + }); + section = (await view.getContent().getSection(sectionTitle)) as ExtensionsViewSection; + }); + + after(async function () { + await ((await new ActivityBar().getViewControl('Extensions')) as ViewControl).closeView(); + await new EditorView().closeAllEditors(); + }); + + it('getTitle works', async () => { + const title = await section.getTitle(); + expect(title).equals(sectionTitle); + }); + + it('getVisibleItems works', async () => { + const items = await section.getVisibleItems(); + expect(items).not.undefined; + }); + + it('findItem works', async function () { + this.timeout(30000); + await section.getDriver().wait(async function () { + item = (await section.findItem(`@installed ${pjson.displayName}`)) as ExtensionsViewItem; + return item !== undefined; + }); + expect(item).not.undefined; + }); + + describe('ExtensionsViewItem', async () => { + beforeEach(async function () { + await section.getDriver().wait(async function () { + item = (await section.findItem(`@installed ${pjson.displayName}`)) as ExtensionsViewItem; + return item !== undefined; + }); + }); + + after(async () => { + await section.clearSearch(); + }); + + it('getTitle works', async () => { + const title = await item.getTitle(); + expect(title).equals(pjson.displayName); + }); + + it('getVersion works', async () => { + const version = await item.getVersion(); + expect(version).equals(pjson.version); + }); + + it('getAuthor works', async () => { + const author = await item.getAuthor(); + expect(author).equals(pjson.publisher); + }); + + it('getDescription works', async () => { + const desc = await item.getDescription(); + expect(desc).equals(pjson.description); + }); + + it('isInstalled works', async () => { + const installed = await item.isInstalled(); + expect(installed).is.true; + }); + + (process.platform === 'darwin' ? it.skip : it)('manage works', async () => { + const menu = await item.manage(); + expect(menu).not.undefined; + await menu.close(); + }); + }); +}); diff --git a/tests/test-project/src/treeView.ts b/tests/test-project/src/treeView.ts index 3a5baea92..61f40ca50 100644 --- a/tests/test-project/src/treeView.ts +++ b/tests/test-project/src/treeView.ts @@ -6,10 +6,13 @@ import { ERROR_MESSAGE_COMMAND } from './extension'; * https://github.com/microsoft/vscode-extension-samples/tree/master/tree-view-sample */ export class TreeView { - constructor(context: vscode.ExtensionContext) { - const view = vscode.window.createTreeView('testView', { treeDataProvider: dataProvider(), showCollapseAll: true }); - context.subscriptions.push(view); - } + constructor(context: vscode.ExtensionContext) { + const view = vscode.window.createTreeView('testView', { + treeDataProvider: dataProvider(), + showCollapseAll: true, + }); + context.subscriptions.push(view); + } } type Tree = { [key: string]: Tree | undefined | null }; @@ -18,47 +21,50 @@ type Tree = { [key: string]: Tree | undefined | null }; // leafs are keys that are undefined or null. If it is undefined, then its collapsibleState is None, if it null, then it is Collapsed // The tree element with the key 'd' is special: it has a command attached to it. It thus cannot be expanded by simply clicking on it, as that just triggers the command. const tree: Tree = { - 'a': { - 'aa': { - 'aaa': { - 'aaaa': { - 'aaaaa': { - 'aaaaaa': undefined, - } - } - } + a: { + aa: { + aaa: { + aaaa: { + aaaaa: { + aaaaaa: undefined, + }, + }, + }, }, - 'ab': undefined, + ab: undefined, }, - 'b': { - 'ba': undefined, - 'bb': undefined + b: { + ba: undefined, + bb: undefined, + }, + c: null, + d: { + da: undefined, + db: undefined, }, - 'c': null, - 'd': { - 'da': undefined, - 'db': undefined - } }; const nodes: any = {}; function dataProvider(): vscode.TreeDataProvider<{ key: string }> { - return { + return { getChildren: (element?: { key: string }): { key: string }[] => { return getChildren(element?.key).map((key: string) => getNode(key)); }, getTreeItem: (element: { key: string }): vscode.TreeItem => { const treeItem = getTreeItem(element.key); treeItem.id = element.key; - if (element.key === "d") { - treeItem.command = { title: "show an error", command: ERROR_MESSAGE_COMMAND }; + if (element.key === 'd') { + treeItem.command = { + title: 'show an error', + command: ERROR_MESSAGE_COMMAND, + }; } return treeItem; }, getParent: ({ key }: { key: string }): { key: string } => { const parentKey = key.substring(0, key.length - 1); return new Key(parentKey); - } + }, }; } @@ -77,16 +83,16 @@ function getTreeItem(key: string): vscode.TreeItem { const treeElement = getTreeElement(key); let collapsibleState: vscode.TreeItemCollapsibleState; if (treeElement === undefined) { - collapsibleState = vscode.TreeItemCollapsibleState.None; - } else { - collapsibleState = vscode.TreeItemCollapsibleState.Collapsed; - } + collapsibleState = vscode.TreeItemCollapsibleState.None; + } else { + collapsibleState = vscode.TreeItemCollapsibleState.Collapsed; + } return { label: key, tooltip: `Tooltip for ${key}`, description: `Description for ${key}`, collapsibleState, - contextValue: "ExtensionTreeItem" + contextValue: 'ExtensionTreeItem', }; } @@ -109,5 +115,5 @@ function getNode(key: string): { key: string } { } class Key { - constructor(readonly key: string) { } + constructor(readonly key: string) {} } diff --git a/tests/test-project/tsconfig.json b/tests/test-project/tsconfig.json index 9fb603571..b92f69d33 100644 --- a/tests/test-project/tsconfig.json +++ b/tests/test-project/tsconfig.json @@ -1,12 +1,10 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "out", - "rootDir": "src", - "forceConsistentCasingInFileNames": true, - "strict": true - }, - "include": [ - "src" - ] -} \ No newline at end of file + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "out", + "rootDir": "src", + "forceConsistentCasingInFileNames": true, + "strict": true + }, + "include": ["src"] +} diff --git a/tsconfig.json b/tsconfig.json index 09c323f0b..7bad068f3 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,9 +2,7 @@ "compilerOptions": { "module": "Node16", "target": "ES2022", - "lib": [ - "ES2022" - ], + "lib": ["ES2022"], "sourceMap": true, "strict": true, "noUnusedLocals": true, @@ -13,7 +11,5 @@ "resolveJsonModule": true, "declaration": true }, - "exclude": [ - "node_modules" - ] -} \ No newline at end of file + "exclude": ["node_modules"] +}