diff --git a/.github/workflows/apexE2E.yml b/.github/workflows/apexE2E.yml index 6744919d22..0329da1b03 100644 --- a/.github/workflows/apexE2E.yml +++ b/.github/workflows/apexE2E.yml @@ -3,12 +3,7 @@ name: Apex End to End Tests on: workflow_dispatch: inputs: - automationBranch: - description: 'Set the branch to use for automation tests' - required: false - default: 'main' - type: string - createOASDoc: + createOasDoc: description: 'Create OAS Doc' required: false default: true @@ -50,12 +45,7 @@ on: workflow_call: inputs: - automationBranch: - description: 'Set the branch to use for automation tests' - required: false - default: 'main' - type: string - createOASDoc: + createOasDoc: description: 'Create OAS Doc' required: false default: true @@ -96,14 +86,12 @@ on: type: string jobs: - createOASDoc: - if: ${{ inputs.createOASDoc }} + createOasDoc: + if: ${{ inputs.createOasDoc }} uses: ./.github/workflows/runE2ETest.yml secrets: inherit with: - automationBranch: ${{ inputs.automationBranch }} - automationRepo: 'salesforcedx-vscode-automation-tests-redhat' - testToRun: 'createOASDoc.e2e.js' + testToRun: 'createOasDoc.e2e.js' vscodeVersion: ${{ inputs.vscodeVersion }} runId: ${{ inputs.runId }} @@ -112,8 +100,6 @@ jobs: uses: ./.github/workflows/runE2ETest.yml secrets: inherit with: - automationBranch: ${{ inputs.automationBranch }} - automationRepo: 'salesforcedx-vscode-automation-tests-redhat' testToRun: 'apexLsp.e2e.js' vscodeVersion: ${{ inputs.vscodeVersion }} runId: ${{ inputs.runId }} @@ -123,8 +109,6 @@ jobs: uses: ./.github/workflows/runE2ETest.yml secrets: inherit with: - automationBranch: ${{ inputs.automationBranch }} - automationRepo: 'salesforcedx-vscode-automation-tests-redhat' testToRun: 'apexReplayDebugger.e2e.js' vscodeVersion: ${{ inputs.vscodeVersion }} runId: ${{ inputs.runId }} @@ -135,8 +119,6 @@ jobs: uses: ./.github/workflows/runE2ETest.yml secrets: inherit with: - automationBranch: ${{ inputs.automationBranch }} - automationRepo: 'salesforcedx-vscode-automation-tests-redhat' testToRun: 'debugApexTests.e2e.js' vscodeVersion: ${{ inputs.vscodeVersion }} runId: ${{ inputs.runId }} @@ -146,8 +128,6 @@ jobs: uses: ./.github/workflows/runE2ETest.yml secrets: inherit with: - automationBranch: ${{ inputs.automationBranch }} - automationRepo: 'salesforcedx-vscode-automation-tests-redhat' testToRun: 'runApexTests.e2e.js' vscodeVersion: ${{ inputs.vscodeVersion }} runId: ${{ inputs.runId }} @@ -157,50 +137,48 @@ jobs: uses: ./.github/workflows/runE2ETest.yml secrets: inherit with: - automationBranch: ${{ inputs.automationBranch }} - automationRepo: 'salesforcedx-vscode-automation-tests-redhat' testToRun: 'trailApexReplayDebugger.e2e.js' vscodeVersion: ${{ inputs.vscodeVersion }} runId: ${{ inputs.runId }} slack_success_notification: if: ${{ success() }} - needs: [createOASDoc, apexLSP, apexReplayDebugger, debugApexTests, runApexTests, trailApexReplayDebugger] + needs: [createOasDoc, apexLSP, apexReplayDebugger, debugApexTests, runApexTests, trailApexReplayDebugger] uses: ./.github/workflows/slackNotification.yml secrets: inherit with: title: 'Apex E2E Tests' vscodeVersion: ${{ inputs.vscodeVersion }} - testsBranch: ${{ inputs.automationBranch }} - summary: '\n-Create OAS Doc: ${{ needs.createOASDoc.result }}\n- Apex LSP: ${{ needs.apexLSP.result }}\n- Apex Replay Debugger: ${{ needs.apexReplayDebugger.result }}\n- Debug Apex Tests: ${{ needs.debugApexTests.result }}\n- Run Apex Tests: ${{ needs.runApexTests.result }}\n- Trail Apex Replay Debugger: ${{ needs.trailApexReplayDebugger.result }}' + testsBranch: ${{ github.ref }} + summary: '\n-Create OAS Doc: ${{ needs.createOasDoc.result }}\n- Apex LSP: ${{ needs.apexLSP.result }}\n- Apex Replay Debugger: ${{ needs.apexReplayDebugger.result }}\n- Debug Apex Tests: ${{ needs.debugApexTests.result }}\n- Run Apex Tests: ${{ needs.runApexTests.result }}\n- Trail Apex Replay Debugger: ${{ needs.trailApexReplayDebugger.result }}' result: 'All the tests passed.' workflow: 'actions/runs/${{ github.run_id }}' type: 'e2e' slack_failure_notification: if: ${{ failure() }} - needs: [createOASDoc, apexLSP, apexReplayDebugger, debugApexTests, runApexTests, trailApexReplayDebugger] + needs: [createOasDoc, apexLSP, apexReplayDebugger, debugApexTests, runApexTests, trailApexReplayDebugger] uses: ./.github/workflows/slackNotification.yml secrets: inherit with: title: 'Apex E2E Tests' vscodeVersion: ${{ inputs.vscodeVersion }} - testsBranch: ${{ inputs.automationBranch }} - summary: '\n-Create OAS Doc: ${{ needs.createOASDoc.result }}\n- Apex LSP: ${{ needs.apexLSP.result }}\n- Apex Replay Debugger: ${{ needs.apexReplayDebugger.result }}\n- Debug Apex Tests: ${{ needs.debugApexTests.result }}\n- Run Apex Tests: ${{ needs.runApexTests.result }}\n- Trail Apex Replay Debugger: ${{ needs.trailApexReplayDebugger.result }}' + testsBranch: ${{ github.ref }} + summary: '\n-Create OAS Doc: ${{ needs.createOasDoc.result }}\n- Apex LSP: ${{ needs.apexLSP.result }}\n- Apex Replay Debugger: ${{ needs.apexReplayDebugger.result }}\n- Debug Apex Tests: ${{ needs.debugApexTests.result }}\n- Run Apex Tests: ${{ needs.runApexTests.result }}\n- Trail Apex Replay Debugger: ${{ needs.trailApexReplayDebugger.result }}' result: 'Not all the tests passed.' workflow: 'actions/runs/${{ github.run_id }}' type: 'e2e' slack_cancelled_notification: if: ${{ cancelled() }} - needs: [createOASDoc, apexLSP, apexReplayDebugger, debugApexTests, runApexTests, trailApexReplayDebugger] + needs: [createOasDoc, apexLSP, apexReplayDebugger, debugApexTests, runApexTests, trailApexReplayDebugger] uses: ./.github/workflows/slackNotification.yml secrets: inherit with: title: 'Apex E2E Tests' vscodeVersion: ${{ inputs.vscodeVersion }} - testsBranch: ${{ inputs.automationBranch }} - summary: '\n-Create OAS Doc: ${{ needs.createOASDoc.result }}\n- Apex LSP: ${{ needs.apexLSP.result }}\n- Apex Replay Debugger: ${{ needs.apexReplayDebugger.result }}\n- Debug Apex Tests: ${{ needs.debugApexTests.result }}\n- Run Apex Tests: ${{ needs.runApexTests.result }}\n- Trail Apex Replay Debugger: ${{ needs.trailApexReplayDebugger.result }}' + testsBranch: ${{ github.ref }} + summary: '\n-Create OAS Doc: ${{ needs.createOasDoc.result }}\n- Apex LSP: ${{ needs.apexLSP.result }}\n- Apex Replay Debugger: ${{ needs.apexReplayDebugger.result }}\n- Debug Apex Tests: ${{ needs.debugApexTests.result }}\n- Run Apex Tests: ${{ needs.runApexTests.result }}\n- Trail Apex Replay Debugger: ${{ needs.trailApexReplayDebugger.result }}' result: 'The workflow was cancelled.' workflow: 'actions/runs/${{ github.run_id }}' type: 'e2e' diff --git a/.github/workflows/coreE2E.yml b/.github/workflows/coreE2E.yml index 97eedc480c..fcfe5f73fd 100644 --- a/.github/workflows/coreE2E.yml +++ b/.github/workflows/coreE2E.yml @@ -3,11 +3,6 @@ name: Core End to End Tests on: workflow_dispatch: inputs: - automationBranch: - description: 'Set the branch to use for automation tests' - required: false - default: 'main' - type: string anInitialSuite: description: 'Verify Extensions' required: false @@ -55,11 +50,6 @@ on: workflow_call: inputs: - automationBranch: - description: 'Set the branch to use for automation tests' - required: false - default: 'main' - type: string anInitialSuite: description: 'Verify Extensions' required: false @@ -111,8 +101,6 @@ jobs: uses: ./.github/workflows/runE2ETest.yml secrets: inherit with: - automationBranch: ${{ inputs.automationBranch }} - automationRepo: 'salesforcedx-vscode-automation-tests-redhat' testToRun: 'anInitialSuite.e2e.js' vscodeVersion: ${{ inputs.vscodeVersion }} runId: ${{ inputs.runId }} @@ -122,8 +110,6 @@ jobs: uses: ./.github/workflows/runE2ETest.yml secrets: inherit with: - automationBranch: ${{ inputs.automationBranch }} - automationRepo: 'salesforcedx-vscode-automation-tests-redhat' testToRun: 'authentication.e2e.js' vscodeVersion: ${{ inputs.vscodeVersion }} runId: ${{ inputs.runId }} @@ -133,8 +119,6 @@ jobs: uses: ./.github/workflows/runE2ETest.yml secrets: inherit with: - automationBranch: ${{ inputs.automationBranch }} - automationRepo: 'salesforcedx-vscode-automation-tests-redhat' testToRun: 'createProject.e2e.js' vscodeVersion: ${{ inputs.vscodeVersion }} runId: ${{ inputs.runId }} @@ -144,8 +128,6 @@ jobs: uses: ./.github/workflows/runE2ETest.yml secrets: inherit with: - automationBranch: ${{ inputs.automationBranch }} - automationRepo: 'salesforcedx-vscode-automation-tests-redhat' testToRun: 'miscellaneous.e2e.js' vscodeVersion: ${{ inputs.vscodeVersion }} runId: ${{ inputs.runId }} @@ -155,8 +137,6 @@ jobs: uses: ./.github/workflows/runE2ETest.yml secrets: inherit with: - automationBranch: ${{ inputs.automationBranch }} - automationRepo: 'salesforcedx-vscode-automation-tests-redhat' testToRun: 'sObjectsDefinitions.e2e.js' vscodeVersion: ${{ inputs.vscodeVersion }} runId: ${{ inputs.runId }} @@ -166,8 +146,6 @@ jobs: uses: ./.github/workflows/runE2ETest.yml secrets: inherit with: - automationBranch: ${{ inputs.automationBranch }} - automationRepo: 'salesforcedx-vscode-automation-tests-redhat' testToRun: 'templates.e2e.js' vscodeVersion: ${{ inputs.vscodeVersion }} runId: ${{ inputs.runId }} @@ -177,8 +155,6 @@ jobs: uses: ./.github/workflows/runE2ETest.yml secrets: inherit with: - automationBranch: ${{ inputs.automationBranch }} - automationRepo: 'salesforcedx-vscode-automation-tests-redhat' testToRun: 'sfdxProjectJson.e2e.js' vscodeVersion: ${{ inputs.vscodeVersion }} runId: ${{ inputs.runId }} @@ -192,7 +168,7 @@ jobs: with: title: 'Core E2E Tests' vscodeVersion: ${{ inputs.vscodeVersion }} - testsBranch: ${{ inputs.automationBranch }} + testsBranch: ${{ github.ref }} summary: '\n- An Initial Suite: ${{ needs.anInitialSuite.result }}\n- Authentication: ${{ needs.authentication.result }}\n- Create Project: ${{ needs.createProject.result }}\n- Miscellaneous: ${{ needs.miscellaneous.result }}\n- SObjects Definitions: ${{ needs.sObjectsDefinitions.result }}\n- Templates: ${{ needs.templates.result }}\n- SFDX Project Json: ${{ needs.sfdxProjectJson.result}}' result: 'All the tests passed.' workflow: 'actions/runs/${{ github.run_id }}' @@ -207,7 +183,7 @@ jobs: with: title: 'Core E2E Tests' vscodeVersion: ${{ inputs.vscodeVersion }} - testsBranch: ${{ inputs.automationBranch }} + testsBranch: ${{ github.ref }} summary: '\n- An Initial Suite: ${{ needs.anInitialSuite.result }}\n- Authentication: ${{ needs.authentication.result }}\n- Create Project: ${{ needs.createProject.result }}\n- Miscellaneous: ${{ needs.miscellaneous.result }}\n- SObjects Definitions: ${{ needs.sObjectsDefinitions.result }}\n- Templates: ${{ needs.templates.result }}\n- SFDX Project Json: ${{ needs.sfdxProjectJson.result}}' result: 'Not all the tests passed.' workflow: 'actions/runs/${{ github.run_id }}' @@ -222,7 +198,7 @@ jobs: with: title: 'Core E2E Tests' vscodeVersion: ${{ inputs.vscodeVersion }} - testsBranch: ${{ inputs.automationBranch }} + testsBranch: ${{ github.ref }} summary: '\n- An Initial Suite: ${{ needs.anInitialSuite.result }}\n- Authentication: ${{ needs.authentication.result }}\n- Create Project: ${{ needs.createProject.result }}\n- Miscellaneous: ${{ needs.miscellaneous.result }}\n- SObjects Definitions: ${{ needs.sObjectsDefinitions.result }}\n- Templates: ${{ needs.templates.result }}\n- SFDX Project Json: ${{ needs.sfdxProjectJson.result}}' result: 'The workflow was cancelled.' workflow: 'actions/runs/${{ github.run_id }}' diff --git a/.github/workflows/deployRetrieveE2E.yml b/.github/workflows/deployRetrieveE2E.yml index 04c9e7b434..caa748fd35 100644 --- a/.github/workflows/deployRetrieveE2E.yml +++ b/.github/workflows/deployRetrieveE2E.yml @@ -3,11 +3,6 @@ name: Deploy and Retrieve End to End Tests on: workflow_dispatch: inputs: - automationBranch: - description: 'Set the branch to use for automation tests' - required: false - default: 'main' - type: string deployAndRetrieve: description: 'Deploy and Retrieve' required: false @@ -45,11 +40,6 @@ on: workflow_call: inputs: - automationBranch: - description: 'Set the branch to use for automation tests' - required: false - default: 'main' - type: string deployAndRetrieve: description: 'Deploy and Retrieve' required: false @@ -91,8 +81,6 @@ jobs: uses: ./.github/workflows/runE2ETest.yml secrets: inherit with: - automationBranch: ${{ inputs.automationBranch }} - automationRepo: 'salesforcedx-vscode-automation-tests-redhat' testToRun: 'deployAndRetrieve.e2e.js' vscodeVersion: ${{ inputs.vscodeVersion }} runId: ${{ inputs.runId }} @@ -102,8 +90,6 @@ jobs: uses: ./.github/workflows/runE2ETest.yml secrets: inherit with: - automationBranch: ${{ inputs.automationBranch }} - automationRepo: 'salesforcedx-vscode-automation-tests-redhat' testToRun: 'manifestBuilder.e2e.js' vscodeVersion: ${{ inputs.vscodeVersion }} runId: ${{ inputs.runId }} @@ -113,8 +99,6 @@ jobs: uses: ./.github/workflows/runE2ETest.yml secrets: inherit with: - automationBranch: ${{ inputs.automationBranch }} - automationRepo: 'salesforcedx-vscode-automation-tests-redhat' testToRun: 'orgBrowser.e2e.js' vscodeVersion: ${{ inputs.vscodeVersion }} runId: ${{ inputs.runId }} @@ -124,8 +108,6 @@ jobs: uses: ./.github/workflows/runE2ETest.yml secrets: inherit with: - automationBranch: ${{ inputs.automationBranch }} - automationRepo: 'salesforcedx-vscode-automation-tests-redhat' testToRun: 'pushAndPull.e2e.js' vscodeVersion: ${{ inputs.vscodeVersion }} runId: ${{ inputs.runId }} @@ -135,8 +117,6 @@ jobs: uses: ./.github/workflows/runE2ETest.yml secrets: inherit with: - automationBranch: ${{ inputs.automationBranch }} - automationRepo: 'salesforcedx-vscode-automation-tests-redhat' testToRun: 'metadataDeployRetrieve.e2e.js' vscodeVersion: ${{ inputs.vscodeVersion }} runId: ${{ inputs.runId }} @@ -149,7 +129,7 @@ jobs: with: title: 'Deploy and Retrieve E2E Tests' vscodeVersion: ${{ inputs.vscodeVersion }} - testsBranch: ${{ inputs.automationBranch }} + testsBranch: ${{ github.ref }} summary: '\n- Deploy and Retrieve: ${{ needs.deployAndRetrieve.result }}\n- Manifest Builder: ${{ needs.manifestBuilder.result }}\n- Org Browser: ${{ needs.orgBrowser.result }}\n- Push and Pull: ${{ needs.pushAndPull.result }}\n- MD Deploy and Retrieve: ${{needs.mdDeployRetrieve.result}} ' result: 'All the tests passed.' workflow: 'actions/runs/${{ github.run_id }}' @@ -163,7 +143,7 @@ jobs: with: title: 'Deploy and Retrieve E2E Tests' vscodeVersion: ${{ inputs.vscodeVersion }} - testsBranch: ${{ inputs.automationBranch }} + testsBranch: ${{ github.ref }} summary: '\n- Deploy and Retrieve: ${{ needs.deployAndRetrieve.result }}\n- Manifest Builder: ${{ needs.manifestBuilder.result }}\n- Org Browser: ${{ needs.orgBrowser.result }}\n- Push and Pull: ${{ needs.pushAndPull.result }}\n- MD Deploy and Retrieve: ${{needs.mdDeployRetrieve.result}} ' result: 'Not all the tests passed.' workflow: 'actions/runs/${{ github.run_id }}' @@ -177,7 +157,7 @@ jobs: with: title: 'Deploy and Retrieve E2E Tests' vscodeVersion: ${{ inputs.vscodeVersion }} - testsBranch: ${{ inputs.automationBranch }} + testsBranch: ${{ github.ref }} summary: '\n- Deploy and Retrieve: ${{ needs.deployAndRetrieve.result }}\n- Manifest Builder: ${{ needs.manifestBuilder.result }}\n- Org Browser: ${{ needs.orgBrowser.result }}\n- Push and Pull: ${{ needs.pushAndPull.result }}\n- MD Deploy and Retrieve: ${{needs.mdDeployRetrieve.result}} ' result: 'The workflow was cancelled.' workflow: 'actions/runs/${{ github.run_id }}' diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index d3c481c68e..8943b92ded 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -8,13 +8,50 @@ on: types: - completed - workflow_dispatch: + workflow_call: inputs: - automationBranch: - description: 'Set the branch to use for automation tests' + apexE2ETests: + description: 'Apex E2E Tests' required: false - default: 'main' + default: true + type: boolean + coreE2ETests: + description: 'Core E2E Tests' + required: false + default: true + type: boolean + deployAndRetrieveE2ETests: + description: 'Deploy and Retrieve E2E Tests' + required: false + default: true + type: boolean + lspE2ETests: + description: 'LSP E2E Tests' + required: false + default: true + type: boolean + lwcE2ETests: + description: 'LWC E2E Tests' + required: false + default: true + type: boolean + soqlE2ETests: + description: 'SOQL E2E Tests' + required: false + default: true + type: boolean + vscodeVersion: + description: 'VSCode Version' + required: false + default: 'latest' + type: string + runId: + description: 'Run ID of the workflow run that created the vsixes' + required: true type: string + + workflow_dispatch: + inputs: apexE2ETests: description: 'Apex E2E Tests' required: false @@ -61,7 +98,6 @@ jobs: uses: ./.github/workflows/apexE2E.yml secrets: inherit with: - automationBranch: ${{ inputs.automationBranch || 'main' }} vscodeVersion: ${{ inputs.vscodeVersion || 'latest' }} runId: ${{ inputs.runId || github.event.workflow_run.id }} @@ -70,7 +106,6 @@ jobs: uses: ./.github/workflows/coreE2E.yml secrets: inherit with: - automationBranch: ${{ inputs.automationBranch || 'main' }} vscodeVersion: ${{ inputs.vscodeVersion || 'latest' }} runId: ${{ inputs.runId || github.event.workflow_run.id }} @@ -79,7 +114,6 @@ jobs: uses: ./.github/workflows/deployRetrieveE2E.yml secrets: inherit with: - automationBranch: ${{ inputs.automationBranch || 'main' }} vscodeVersion: ${{ inputs.vscodeVersion || 'latest' }} runId: ${{ inputs.runId || github.event.workflow_run.id }} @@ -88,7 +122,6 @@ jobs: uses: ./.github/workflows/lspE2E.yml secrets: inherit with: - automationBranch: ${{ inputs.automationBranch || 'main' }} vscodeVersion: ${{ inputs.vscodeVersion || 'latest' }} runId: ${{ inputs.runId || github.event.workflow_run.id }} @@ -97,7 +130,6 @@ jobs: uses: ./.github/workflows/lwcE2E.yml secrets: inherit with: - automationBranch: ${{ inputs.automationBranch || 'main' }} vscodeVersion: ${{ inputs.vscodeVersion || 'latest' }} runId: ${{ inputs.runId || github.event.workflow_run.id }} @@ -106,7 +138,6 @@ jobs: uses: ./.github/workflows/soqlE2E.yml secrets: inherit with: - automationBranch: ${{ inputs.automationBranch || 'main' }} vscodeVersion: ${{ inputs.vscodeVersion || 'latest' }} runId: ${{ inputs.runId || github.event.workflow_run.id }} @@ -115,7 +146,6 @@ jobs: uses: ./.github/workflows/apexE2E.yml secrets: inherit with: - automationBranch: 'main' vscodeVersion: '1.90.0' runId: ${{ github.event.workflow_run.id }} @@ -124,7 +154,6 @@ jobs: uses: ./.github/workflows/coreE2E.yml secrets: inherit with: - automationBranch: 'main' vscodeVersion: '1.90.0' runId: ${{ github.event.workflow_run.id }} @@ -133,7 +162,6 @@ jobs: uses: ./.github/workflows/deployRetrieveE2E.yml secrets: inherit with: - automationBranch: 'main' vscodeVersion: '1.90.0' runId: ${{ github.event.workflow_run.id }} @@ -142,7 +170,6 @@ jobs: uses: ./.github/workflows/lspE2E.yml secrets: inherit with: - automationBranch: 'main' vscodeVersion: '1.90.0' runId: ${{ github.event.workflow_run.id }} @@ -151,7 +178,6 @@ jobs: uses: ./.github/workflows/lwcE2E.yml secrets: inherit with: - automationBranch: 'main' vscodeVersion: '1.90.0' runId: ${{ github.event.workflow_run.id }} @@ -160,6 +186,5 @@ jobs: uses: ./.github/workflows/soqlE2E.yml secrets: inherit with: - automationBranch: 'main' vscodeVersion: '1.90.0' runId: ${{ github.event.workflow_run.id }} diff --git a/.github/workflows/lspE2E.yml b/.github/workflows/lspE2E.yml index 19ae81e374..78993b9279 100644 --- a/.github/workflows/lspE2E.yml +++ b/.github/workflows/lspE2E.yml @@ -3,11 +3,6 @@ name: LSP End to End Tests on: workflow_dispatch: inputs: - automationBranch: - description: 'Set the branch to use for automation tests' - required: false - default: 'main' - type: string auraLsp: description: 'Aura LSP' required: false @@ -35,11 +30,6 @@ on: workflow_call: inputs: - automationBranch: - description: 'Set the branch to use for automation tests' - required: false - default: 'main' - type: string auraLsp: description: 'Aura LSP' required: false @@ -71,8 +61,6 @@ jobs: uses: ./.github/workflows/runE2ETest.yml secrets: inherit with: - automationBranch: ${{ inputs.automationBranch }} - automationRepo: 'salesforcedx-vscode-automation-tests-redhat' testToRun: 'auraLsp.e2e.js' vscodeVersion: ${{ inputs.vscodeVersion }} runId: ${{ inputs.runId }} @@ -82,8 +70,6 @@ jobs: uses: ./.github/workflows/runE2ETest.yml secrets: inherit with: - automationBranch: ${{ inputs.automationBranch }} - automationRepo: 'salesforcedx-vscode-automation-tests-redhat' testToRun: 'lwcLsp.e2e.js' vscodeVersion: ${{ inputs.vscodeVersion }} runId: ${{ inputs.runId }} @@ -93,8 +79,6 @@ jobs: uses: ./.github/workflows/runE2ETest.yml secrets: inherit with: - automationBranch: ${{ inputs.automationBranch }} - automationRepo: 'salesforcedx-vscode-automation-tests-redhat' testToRun: 'visualforceLsp.e2e.js' vscodeVersion: ${{ inputs.vscodeVersion }} runId: ${{ inputs.runId }} @@ -108,7 +92,7 @@ jobs: with: title: 'LSP E2E Tests' vscodeVersion: ${{ inputs.vscodeVersion }} - testsBranch: ${{ inputs.automationBranch }} + testsBranch: ${{ github.ref }} summary: '\n- Aura LSP: ${{ needs.auraLSP.result }}\n- LWC LSP: ${{ needs.lwcLSP.result }}\n- Visualforce LSP: ${{ needs.visualforceLSP.result }}' result: 'All the tests passed.' workflow: 'actions/runs/${{ github.run_id }}' @@ -122,7 +106,7 @@ jobs: with: title: 'LSP E2E Tests' vscodeVersion: ${{ inputs.vscodeVersion }} - testsBranch: ${{ inputs.automationBranch }} + testsBranch: ${{ github.ref }} summary: '\n- Aura LSP: ${{ needs.auraLSP.result }}\n- LWC LSP: ${{ needs.lwcLSP.result }}\n- Visualforce LSP: ${{ needs.visualforceLSP.result }}' result: 'Not all the tests passed.' workflow: 'actions/runs/${{ github.run_id }}' @@ -136,7 +120,7 @@ jobs: with: title: 'LSP E2E Tests' vscodeVersion: ${{ inputs.vscodeVersion }} - testsBranch: ${{ inputs.automationBranch }} + testsBranch: ${{ github.ref }} summary: '\n- Aura LSP: ${{ needs.auraLSP.result }}\n- LWC LSP: ${{ needs.lwcLSP.result }}\n- Visualforce LSP: ${{ needs.visualforceLSP.result }}' result: 'The workflow was cancelled.' workflow: 'actions/runs/${{ github.run_id }}' diff --git a/.github/workflows/lwcE2E.yml b/.github/workflows/lwcE2E.yml index 09db2d0d3e..bcfaeecc8e 100644 --- a/.github/workflows/lwcE2E.yml +++ b/.github/workflows/lwcE2E.yml @@ -3,11 +3,6 @@ name: LWC End to End Tests on: workflow_dispatch: inputs: - automationBranch: - description: 'Set the branch to use for automation tests' - required: false - default: 'main' - type: string debugLwcTests: description: 'Debug LWC Tests' required: false @@ -30,11 +25,6 @@ on: workflow_call: inputs: - automationBranch: - description: 'Set the branch to use for automation tests' - required: false - default: 'develop' - type: string debugLwcTests: description: 'Debug LWC Tests' required: false @@ -61,8 +51,6 @@ jobs: uses: ./.github/workflows/runE2ETest.yml secrets: inherit with: - automationBranch: ${{ inputs.automationBranch }} - automationRepo: 'salesforcedx-vscode-automation-tests-redhat' testToRun: 'debugLwcTests.e2e.js' vscodeVersion: ${{ inputs.vscodeVersion }} runId: ${{ inputs.runId }} @@ -72,8 +60,6 @@ jobs: uses: ./.github/workflows/runE2ETest.yml secrets: inherit with: - automationBranch: ${{ inputs.automationBranch }} - automationRepo: 'salesforcedx-vscode-automation-tests-redhat' testToRun: 'runLwcTests.e2e.js' vscodeVersion: ${{ inputs.vscodeVersion }} runId: ${{ inputs.runId }} @@ -86,7 +72,7 @@ jobs: with: title: 'LWC E2E Tests' vscodeVersion: ${{ inputs.vscodeVersion }} - testsBranch: ${{ inputs.automationBranch }} + testsBranch: ${{ github.ref }} summary: '\n- Debug LWC Tests: ${{ needs.debugLwcTests.result }}\n- Run LWC Tests: ${{ needs.runLwcTests.result }}' result: 'All the tests passed.' workflow: 'actions/runs/${{ github.run_id }}' @@ -100,7 +86,7 @@ jobs: with: title: 'LWC E2E Tests' vscodeVersion: ${{ inputs.vscodeVersion }} - testsBranch: ${{ inputs.automationBranch }} + testsBranch: ${{ github.ref }} summary: '\n- Debug LWC Tests: ${{ needs.debugLwcTests.result }}\n- Run LWC Tests: ${{ needs.runLwcTests.result }}' result: 'Not all the tests passed.' workflow: 'actions/runs/${{ github.run_id }}' @@ -114,7 +100,7 @@ jobs: with: title: 'LWC E2E Tests' vscodeVersion: ${{ inputs.vscodeVersion }} - testsBranch: ${{ inputs.automationBranch }} + testsBranch: ${{ github.ref }} summary: '\n- Debug LWC Tests: ${{ needs.debugLwcTests.result }}\n- Run LWC Tests: ${{ needs.runLwcTests.result }}' result: 'The workflow was cancelled.' workflow: 'actions/runs/${{ github.run_id }}' diff --git a/.github/workflows/runE2ETest.yml b/.github/workflows/runE2ETest.yml index 96562b6e5f..55a4983455 100644 --- a/.github/workflows/runE2ETest.yml +++ b/.github/workflows/runE2ETest.yml @@ -2,16 +2,6 @@ name: Run E2E Test on: workflow_call: inputs: - automationBranch: - description: 'Set the branch to use for automation tests' - required: false - default: 'main' - type: string - automationRepo: - description: 'Set the repo to use for automation tests' - required: false - default: 'salesforcedx-vscode-automation-tests-redhat' - type: string testToRun: description: 'Run this E2E test' required: false @@ -39,7 +29,7 @@ jobs: matrix: os: ${{ fromJson(inputs.os) }} nodeVersion: - - 20.17.0 + - 20.18.1 vscodeVersion: - ${{ inputs.vscodeVersion }} steps: @@ -48,17 +38,6 @@ jobs: with: path: ./salesforcedx-vscode ref: ${{ github.event.ref }} - - name: Download extension vsixes - run: | - mkdir ./extensions - pwd - gh run download ${{ inputs.runId }} -D ./extensions - mv ./extensions/*/* ./extensions/ - working-directory: salesforcedx-vscode - env: - GITHUB_TOKEN: ${{ secrets.IDEE_GH_TOKEN }} - - name: Display downloaded vsix files - run: ls -R ./salesforcedx-vscode/extensions - name: Setup node uses: actions/setup-node@v4 with: @@ -66,78 +45,332 @@ jobs: cache: npm cache-dependency-path: | salesforcedx-vscode/package-lock.json - ${{ inputs.automationRepo }}/package-lock.json - name: Setup java uses: actions/setup-java@v4 with: distribution: 'zulu' java-version: '11' - - name: Clone automation tests - uses: actions/checkout@v4 + + - name: Clean VS Code cache and setup + shell: bash + run: | + # Clean up any corrupted VS Code downloads/cache + echo "Cleaning VS Code cache and temp files..." + + # Remove vscode-extension-tester cache + rm -rf ~/.vscode-test 2>/dev/null || true + rm -rf ~/.cache/vscode-extension-tester 2>/dev/null || true + + # Remove any partial downloads + if [ "$RUNNER_OS" = "Windows" ]; then + rm -rf "$TEMP"/vscode-* 2>/dev/null || true + rm -rf "$USERPROFILE"/.vscode-test 2>/dev/null || true + # Clean VS Code user extensions directory + rm -rf "$USERPROFILE"/.vscode/extensions 2>/dev/null || true + # Clean VS Code insiders extensions directory + rm -rf "$USERPROFILE"/.vscode-insiders/extensions 2>/dev/null || true + # Force kill any VS Code processes that might be locking files + taskkill //F //IM Code.exe 2>/dev/null || true + taskkill //F //IM Code-insiders.exe 2>/dev/null || true + taskkill //F //IM "Microsoft VS Code.exe" 2>/dev/null || true + # Wait a bit for processes to fully terminate + sleep 5 + else + rm -rf /tmp/vscode-* 2>/dev/null || true + fi + + # Clean npm cache + npm cache clean --force + + echo "VS Code cache cleanup completed" + + - name: Create VS Code download script + shell: bash + run: | + # Create a script to download VS Code with fallback methods + cat > download-vscode.sh << 'EOF' + #!/bin/bash + set -e + + VSCODE_VERSION="${1:-latest}" + echo "Downloading VS Code version: $VSCODE_VERSION" + + # Function to download with curl as fallback + download_with_curl() { + local version="$1" + local platform + + case "$RUNNER_OS" in + "Linux") + platform="linux-x64" + ;; + "Windows") + platform="win32-x64" + ;; + "macOS") + platform="darwin" + ;; + *) + echo "Unsupported platform: $RUNNER_OS" + return 1 + ;; + esac + + local url="https://update.code.visualstudio.com/${version}/${platform}/stable" + local vscode_dir="$HOME/.vscode-test" + mkdir -p "$vscode_dir" + + echo "Downloading VS Code using curl from: $url" + + # Use curl with built-in retry capability + curl -L --retry 3 --retry-delay 10 --retry-max-time 120 \ + --connect-timeout 30 --max-time 300 \ + --create-dirs -o "$vscode_dir/vscode-${version}-${platform}.tar.gz" \ + "$url" + + echo "VS Code downloaded successfully with curl" + return 0 + } + + # Try to download VS Code using the extension tester's download mechanism first + echo "Attempting VS Code download with @vscode/test-electron..." + if npx @vscode/test-electron download --version "$VSCODE_VERSION" 2>&1; then + echo "VS Code download successful with @vscode/test-electron" + exit 0 + else + echo "Primary download method failed, trying curl fallback..." + # Clean up any partial downloads + rm -rf ~/.vscode-test 2>/dev/null || true + rm -rf ~/.cache/vscode-extension-tester 2>/dev/null || true + + # Try curl as fallback + if download_with_curl "$VSCODE_VERSION"; then + echo "VS Code download successful with curl fallback" + exit 0 + else + echo "All download methods failed" + exit 1 + fi + fi + EOF + + chmod +x download-vscode.sh + + - name: Pre-download VS Code with retry + uses: salesforcecli/github-workflows/.github/actions/retry@main with: - repository: forcedotcom/${{ inputs.automationRepo }} - path: ${{ inputs.automationRepo }} - ref: ${{ inputs.automationBranch }} - - name: Install Test Dependencies + max_attempts: 5 + command: bash download-vscode.sh "${{ matrix.vscodeVersion }}" + retry_wait_seconds: 30 + + - name: Windows-specific setup + if: runner.os == 'Windows' + shell: powershell run: | - npm install - working-directory: ${{ inputs.automationRepo }} - - name: Install the Salesforce CLI - run: npm install -g @salesforce/cli - - name: Verify CLI + Write-Host "Setting up Windows-specific configurations..." + + # Disable Windows Defender real-time protection for the workspace + # This helps prevent file locking issues during extension operations + try { + Set-MpPreference -DisableRealtimeMonitoring $true -ErrorAction SilentlyContinue + Write-Host "Windows Defender real-time protection disabled" + } catch { + Write-Host "Could not disable Windows Defender (non-critical)" + } + + # Set environment variables for better file handling + [Environment]::SetEnvironmentVariable("VSCODE_SKIP_GETTING_STARTED", "true", "Process") + [Environment]::SetEnvironmentVariable("VSCODE_DISABLE_CRASH_REPORTER", "true", "Process") + [Environment]::SetEnvironmentVariable("VSCODE_EXTENSIONS_AUTOCHECK_UPDATES", "false", "Process") + + # Create extensions directory with proper permissions + $extensionsDir = "$env:USERPROFILE\.vscode\extensions" + if (Test-Path $extensionsDir) { + Remove-Item $extensionsDir -Recurse -Force -ErrorAction SilentlyContinue + } + New-Item -ItemType Directory -Path $extensionsDir -Force + + Write-Host "Windows-specific setup completed" + + - name: Setup project directories shell: bash + env: + GH_TOKEN: ${{ secrets.IDEE_GH_TOKEN }} run: | set -e + export MSYS_NO_PATHCONV=1 + cd salesforcedx-vscode + + # Clean project directories + rm -rf ./extensions 2>/dev/null || true + rm -rf ./e2e-temp 2>/dev/null || true + + # Create extensions directory + mkdir -p ./extensions + + # Download extension vsixes + pwd + gh run download ${{ inputs.runId }} -D ./extensions + + # Move downloaded files to extensions root + find ./extensions -mindepth 2 -type f -exec mv {} ./extensions/ \; 2>/dev/null || true + find ./extensions -mindepth 1 -type d -exec rm -rf {} + 2>/dev/null || true + + - name: Create retry scripts + shell: bash + run: | + # Create install dependencies script + cat > install-dependencies.sh << 'EOF' + #!/bin/bash + set -e + echo "Installing npm dependencies..." + cd salesforcedx-vscode + npm install + echo "Dependencies installed successfully" + EOF + chmod +x install-dependencies.sh + + # Create install Salesforce CLI script + cat > install-salesforce-cli.sh << 'EOF' + #!/bin/bash + set -e + echo "Installing Salesforce CLI..." + npm install -g @salesforce/cli + echo "Salesforce CLI installed successfully" + EOF + chmod +x install-salesforce-cli.sh + + # Create E2E test script with Windows-specific handling (retry handled by GitHub action) + cat > run-e2e-test.sh << 'EOF' + #!/bin/bash + set -e + if [ $# -eq 0 ]; then + echo "Usage: $0 " + echo "Example: $0 packages/salesforcedx-vscode-automation-tests/lib/test/specs/mytest.e2e.js" + exit 1 + fi + SPEC_PATH="$1" + echo "Running E2E test for spec: $SPEC_PATH" + cd salesforcedx-vscode + + # Windows-specific pre-test setup + if [ "$RUNNER_OS" = "Windows" ]; then + echo "Setting up Windows-specific environment..." + export VSCODE_SKIP_GETTING_STARTED=true + export VSCODE_DISABLE_CRASH_REPORTER=true + export VSCODE_EXTENSIONS_AUTOCHECK_UPDATES=false + # Add retry delay for Windows + export RETRY_DELAY=2000 + fi + + # Set network timeout and retry environment variables + export VSCODE_EXTENSION_TESTER_NETWORK_TIMEOUT=60000 + export VSCODE_EXTENSION_TESTER_DOWNLOAD_RETRY=5 + export VSCODE_EXTENSION_TESTER_DOWNLOAD_DELAY=10000 + + # Clean up any existing VS Code cache on failure + rm -rf ~/.vscode-test 2>/dev/null || true + rm -rf ~/.cache/vscode-extension-tester 2>/dev/null || true + + npm run compile + npm run gha-automation-tests --spec "$SPEC_PATH" + echo "E2E test completed successfully" + EOF + chmod +x run-e2e-test.sh + + - name: Install dependencies with retry + uses: salesforcecli/github-workflows/.github/actions/retry@main + with: + max_attempts: 3 + command: bash install-dependencies.sh + retry_wait_seconds: 30 + + - name: Install Salesforce CLI with retry + uses: salesforcecli/github-workflows/.github/actions/retry@main + with: + max_attempts: 3 + command: bash install-salesforce-cli.sh + retry_wait_seconds: 30 + + - name: Verify CLI and setup environment + shell: bash + run: | + cd salesforcedx-vscode + + # Verify CLI and set environment variables sf version SF_CLI_VERSION=$(sf version) - if [[ ((`echo $SF_CLI_VERSION | grep -c "@salesforce/cli/"` > 0))]] - then - echo "@salesforce/cli installed -" $SF_CLI_VERSION + echo "SF_CLI_VERSION: $SF_CLI_VERSION" + echo "SF_CLI_VERSION=$SF_CLI_VERSION" >> $GITHUB_ENV + + if echo "$SF_CLI_VERSION" | grep -q "@salesforce/cli/"; then + echo "@salesforce/cli installed - $SF_CLI_VERSION" else - echo "The @salesforce/cli installation could not be verified" + echo "The @salesforce/cli installation cannot be verified" exit 1 fi - - name: Set THROTTLE_FACTOR for macOS and Linux - if: matrix.os != 'windows-latest' - run: echo "THROTTLE_FACTOR=1" >> $GITHUB_ENV - - name: Set THROTTLE_FACTOR for Windows - if: matrix.os == 'windows-latest' - run: echo "THROTTLE_FACTOR=5" >> $GITHUB_ENV - - name: Run test setup script - if: inputs.automationRepo == 'salesforcedx-vscode-automation-tests' - uses: coactions/setup-xvfb@b6b4fcfb9f5a895edadc3bc76318fae0ac17c8b3 - with: - run: | - npm run setup - working-directory: 'salesforcedx-vscode-automation-tests' - env: - VSCODE_VERSION: ${{ matrix.vscodeVersion }} - SFDX_AUTH_URL: ${{ secrets.SFDX_AUTH_URL_E2E }} - ORG_ID: ${{ secrets.ORG_ID_E2E }} - - name: Run headless test - uses: coactions/setup-xvfb@b6b4fcfb9f5a895edadc3bc76318fae0ac17c8b3 + + if [ "$RUNNER_OS" = "Windows" ]; then + echo "THROTTLE_FACTOR=1" >> $GITHUB_ENV + # Create temporary directories for VS Code extension tester + mkdir -p "$RUNNER_TEMP/extest-settings" "$RUNNER_TEMP/extest-storage" + # Set permissions for Windows + chmod 755 "$RUNNER_TEMP/extest-settings" "$RUNNER_TEMP/extest-storage" 2>/dev/null || true + else + echo "THROTTLE_FACTOR=1" >> $GITHUB_ENV + fi + + - name: Run E2E test with retry + uses: salesforcecli/github-workflows/.github/actions/retry@main with: - run: | - npm run automation-tests - working-directory: ${{ inputs.automationRepo }} + max_attempts: 3 + command: bash run-e2e-test.sh ${{ format('packages/salesforcedx-vscode-automation-tests/lib/test/specs/{0}', inputs.testToRun) }} + retry_wait_seconds: 30 env: + GITHUB_TOKEN: ${{ secrets.IDEE_GH_TOKEN }} + GH_TOKEN: ${{ secrets.IDEE_GH_TOKEN }} VSCODE_VERSION: ${{ matrix.vscodeVersion }} - SPEC_FILES: ${{ inputs.testToRun }} + SPEC_FILES: ${{ format('packages/salesforcedx-vscode-automation-tests/lib/test/specs/{0}', inputs.testToRun) }} SFDX_AUTH_URL: ${{ secrets.SFDX_AUTH_URL_E2E }} ORG_ID: ${{ secrets.ORG_ID_E2E }} THROTTLE_FACTOR: ${{ env.THROTTLE_FACTOR }} + VSIX_TO_INSTALL: ./extensions + DEV_HUB_ALIAS_NAME: vscodeOrg + DEV_HUB_USER_NAME: svcideebot@salesforce.com + BASH_SILENCE_DEPRECATION_WARNING: 1 + # Windows-specific environment variables to prevent file locking issues + VSCODE_SKIP_GETTING_STARTED: true + VSCODE_DISABLE_CRASH_REPORTER: true + VSCODE_EXTENSIONS_AUTOCHECK_UPDATES: false + VSCODE_SKIP_RELEASE_NOTES: true + VSCODE_SKIP_WELCOME: true + # Extension tester specific settings + EXTEST_SETTINGS_DIR: ${{ runner.temp }}/extest-settings + EXTEST_STORAGE_DIR: ${{ runner.temp }}/extest-storage + # Network timeout and retry settings + VSCODE_EXTENSION_TESTER_NETWORK_TIMEOUT: 60000 + VSCODE_EXTENSION_TESTER_DOWNLOAD_RETRY: 5 + VSCODE_EXTENSION_TESTER_DOWNLOAD_DELAY: 10000 + + - name: Clean up VS Code cache on failure + if: failure() + shell: bash + run: | + echo "Cleaning up VS Code cache after failure..." + rm -rf ~/.vscode-test 2>/dev/null || true + rm -rf ~/.cache/vscode-extension-tester 2>/dev/null || true + if [ "$RUNNER_OS" = "Windows" ]; then + rm -rf "$USERPROFILE"/.vscode-test 2>/dev/null || true + rm -rf "$USERPROFILE"/.cache/vscode-extension-tester 2>/dev/null || true + fi + echo "VS Code cache cleanup completed" - uses: actions/upload-artifact@v4 - if: failure() && inputs.automationRepo == 'salesforcedx-vscode-automation-tests' - with: - name: screenshots-${{ inputs.testToRun }}-${{ matrix.os }}-${{ inputs.vscodeVersion }} - path: ./${{ inputs.automationRepo }}/screenshots - - uses: actions/upload-artifact@v4 - if: failure() && inputs.automationRepo == 'salesforcedx-vscode-automation-tests-redhat' + if: failure() with: name: screenshots-${{ inputs.testToRun }}-${{ matrix.os }}-${{ inputs.vscodeVersion }} - path: ./salesforcedx-vscode/extensions/screenshots + path: salesforcedx-vscode/test-resources/screenshots - uses: actions/upload-artifact@v4 - if: ${{ inputs.testToRun }} == 'createOASDoc' + if: ${{ inputs.testToRun == 'createOasDoc.e2e.js' }} with: name: llm-logs-${{ matrix.os }}-${{ inputs.vscodeVersion }} - path: ./${{ inputs.automationRepo }}/e2e-temp/TempProject-CreateOASDoc/llm-logs + path: salesforcedx-vscode/e2e-temp/TempProject-CreateOASDoc/llm-logs diff --git a/.github/workflows/soqlE2E.yml b/.github/workflows/soqlE2E.yml index dbb183127a..7db0382bf4 100644 --- a/.github/workflows/soqlE2E.yml +++ b/.github/workflows/soqlE2E.yml @@ -3,11 +3,6 @@ name: SOQL End to End Tests on: workflow_dispatch: inputs: - automationBranch: - description: 'Set the branch to use for automation tests' - required: false - default: 'main' - type: string soqlBuilder: description: 'SOQL Builder' required: false @@ -25,11 +20,6 @@ on: workflow_call: inputs: - automationBranch: - description: 'Set the branch to use for automation tests' - required: false - default: 'main' - type: string soqlBuilder: description: 'SOQL Builder' required: false @@ -51,8 +41,6 @@ jobs: uses: ./.github/workflows/runE2ETest.yml secrets: inherit with: - automationBranch: ${{ inputs.automationBranch }} - automationRepo: 'salesforcedx-vscode-automation-tests-redhat' testToRun: 'soql.e2e.js' vscodeVersion: ${{ inputs.vscodeVersion }} runId: ${{ inputs.runId }} @@ -65,7 +53,7 @@ jobs: with: title: 'SOQL E2E Tests' vscodeVersion: ${{ inputs.vscodeVersion }} - testsBranch: ${{ inputs.automationBranch }} + testsBranch: ${{ github.ref }} summary: '\n- SOQL Builder: ${{ needs.soqlBuilder.result }}' result: 'All the tests passed.' workflow: 'actions/runs/${{ github.run_id }}' @@ -79,7 +67,7 @@ jobs: with: title: 'SOQL E2E Tests' vscodeVersion: ${{ inputs.vscodeVersion }} - testsBranch: ${{ inputs.automationBranch }} + testsBranch: ${{ github.ref }} summary: '\n- SOQL Builder: ${{ needs.soqlBuilder.result }}' result: 'Not all the tests passed.' workflow: 'actions/runs/${{ github.run_id }}' @@ -93,7 +81,7 @@ jobs: with: title: 'SOQL E2E Tests' vscodeVersion: ${{ inputs.vscodeVersion }} - testsBranch: ${{ inputs.automationBranch }} + testsBranch: ${{ github.ref }} summary: '\n- SOQL Builder: ${{ needs.soqlBuilder.result }}' result: 'The workflow was cancelled.' workflow: 'actions/runs/${{ github.run_id }}' diff --git a/.github/workflows/testCommitExceptMain.yml b/.github/workflows/testCommitExceptMain.yml index fb0f6706dd..fd28edeb34 100644 --- a/.github/workflows/testCommitExceptMain.yml +++ b/.github/workflows/testCommitExceptMain.yml @@ -5,4 +5,11 @@ on: jobs: build-and-test: - uses: ./.github/workflows/buildAndTest.yml \ No newline at end of file + uses: ./.github/workflows/buildAndTest.yml + e2e-tests: + needs: build-and-test + uses: ./.github/workflows/triggerE2EForCommit.yml + secrets: inherit + with: + vscodeVersion: 'latest' + runId: ${{ github.run_id }} diff --git a/.github/workflows/triggerE2EForCommit.yml b/.github/workflows/triggerE2EForCommit.yml new file mode 100644 index 0000000000..83416ef238 --- /dev/null +++ b/.github/workflows/triggerE2EForCommit.yml @@ -0,0 +1,217 @@ +name: Direct E2E Tests +on: + workflow_call: + inputs: + vscodeVersion: + description: 'VSCode Version' + required: false + default: 'latest' + type: string + runId: + description: 'Run ID of the workflow run that created the vsixes' + required: true + type: string + +jobs: + # 🚀 Setup & Initialization + setup-initial-suite: + uses: ./.github/workflows/runE2ETest.yml + secrets: inherit + with: + testToRun: 'anInitialSuite.e2e.js' + vscodeVersion: ${{ inputs.vscodeVersion }} + runId: ${{ inputs.runId }} + + # 🔧 Core Features + core-authentication: + uses: ./.github/workflows/runE2ETest.yml + secrets: inherit + with: + testToRun: 'authentication.e2e.js' + vscodeVersion: ${{ inputs.vscodeVersion }} + runId: ${{ inputs.runId }} + + core-create-project: + uses: ./.github/workflows/runE2ETest.yml + secrets: inherit + with: + testToRun: 'createProject.e2e.js' + vscodeVersion: ${{ inputs.vscodeVersion }} + runId: ${{ inputs.runId }} + + core-sfdx-project-json: + uses: ./.github/workflows/runE2ETest.yml + secrets: inherit + with: + testToRun: 'sfdxProjectJson.e2e.js' + vscodeVersion: ${{ inputs.vscodeVersion }} + runId: ${{ inputs.runId }} + + core-sobjects-definitions: + uses: ./.github/workflows/runE2ETest.yml + secrets: inherit + with: + testToRun: 'sObjectsDefinitions.e2e.js' + vscodeVersion: ${{ inputs.vscodeVersion }} + runId: ${{ inputs.runId }} + + core-org-browser: + uses: ./.github/workflows/runE2ETest.yml + secrets: inherit + with: + testToRun: 'orgBrowser.e2e.js' + vscodeVersion: ${{ inputs.vscodeVersion }} + runId: ${{ inputs.runId }} + + core-miscellaneous: + uses: ./.github/workflows/runE2ETest.yml + secrets: inherit + with: + testToRun: 'miscellaneous.e2e.js' + vscodeVersion: ${{ inputs.vscodeVersion }} + runId: ${{ inputs.runId }} + + # 📦 Deploy & Retrieve + deploy-and-retrieve: + uses: ./.github/workflows/runE2ETest.yml + secrets: inherit + with: + testToRun: 'deployAndRetrieve.e2e.js' + vscodeVersion: ${{ inputs.vscodeVersion }} + runId: ${{ inputs.runId }} + + deploy-metadata-deploy-retrieve: + uses: ./.github/workflows/runE2ETest.yml + secrets: inherit + with: + testToRun: 'metadataDeployRetrieve.e2e.js' + vscodeVersion: ${{ inputs.vscodeVersion }} + runId: ${{ inputs.runId }} + + deploy-push-and-pull: + uses: ./.github/workflows/runE2ETest.yml + secrets: inherit + with: + testToRun: 'pushAndPull.e2e.js' + vscodeVersion: ${{ inputs.vscodeVersion }} + runId: ${{ inputs.runId }} + + deploy-manifest-builder: + uses: ./.github/workflows/runE2ETest.yml + secrets: inherit + with: + testToRun: 'manifestBuilder.e2e.js' + vscodeVersion: ${{ inputs.vscodeVersion }} + runId: ${{ inputs.runId }} + + # 🐛 Apex Features + apex-lsp: + uses: ./.github/workflows/runE2ETest.yml + secrets: inherit + with: + testToRun: 'apexLsp.e2e.js' + vscodeVersion: ${{ inputs.vscodeVersion }} + runId: ${{ inputs.runId }} + + apex-run-tests: + uses: ./.github/workflows/runE2ETest.yml + secrets: inherit + with: + testToRun: 'runApexTests.e2e.js' + vscodeVersion: ${{ inputs.vscodeVersion }} + runId: ${{ inputs.runId }} + + apex-debug-tests: + uses: ./.github/workflows/runE2ETest.yml + secrets: inherit + with: + testToRun: 'debugApexTests.e2e.js' + vscodeVersion: ${{ inputs.vscodeVersion }} + runId: ${{ inputs.runId }} + + apex-replay-debugger: + uses: ./.github/workflows/runE2ETest.yml + secrets: inherit + with: + testToRun: 'apexReplayDebugger.e2e.js' + vscodeVersion: ${{ inputs.vscodeVersion }} + runId: ${{ inputs.runId }} + os: '["macos-latest"]' + + apex-trail-replay-debugger: + uses: ./.github/workflows/runE2ETest.yml + secrets: inherit + with: + testToRun: 'trailApexReplayDebugger.e2e.js' + vscodeVersion: ${{ inputs.vscodeVersion }} + runId: ${{ inputs.runId }} + + # ⚡ LWC Features + lwc-lsp: + uses: ./.github/workflows/runE2ETest.yml + secrets: inherit + with: + testToRun: 'lwcLsp.e2e.js' + vscodeVersion: ${{ inputs.vscodeVersion }} + runId: ${{ inputs.runId }} + + lwc-run-tests: + uses: ./.github/workflows/runE2ETest.yml + secrets: inherit + with: + testToRun: 'runLwcTests.e2e.js' + vscodeVersion: ${{ inputs.vscodeVersion }} + runId: ${{ inputs.runId }} + + lwc-debug-tests: + uses: ./.github/workflows/runE2ETest.yml + secrets: inherit + with: + testToRun: 'debugLwcTests.e2e.js' + vscodeVersion: ${{ inputs.vscodeVersion }} + runId: ${{ inputs.runId }} + + # 🔥 Aura Features + aura-lsp: + uses: ./.github/workflows/runE2ETest.yml + secrets: inherit + with: + testToRun: 'auraLsp.e2e.js' + vscodeVersion: ${{ inputs.vscodeVersion }} + runId: ${{ inputs.runId }} + + # 📄 Visualforce Features + visualforce-lsp: + uses: ./.github/workflows/runE2ETest.yml + secrets: inherit + with: + testToRun: 'visualforceLsp.e2e.js' + vscodeVersion: ${{ inputs.vscodeVersion }} + runId: ${{ inputs.runId }} + + # 🔍 SOQL Features + soql-tests: + uses: ./.github/workflows/runE2ETest.yml + secrets: inherit + with: + testToRun: 'soql.e2e.js' + vscodeVersion: ${{ inputs.vscodeVersion }} + runId: ${{ inputs.runId }} + + # 📋 Templates + templates-tests: + uses: ./.github/workflows/runE2ETest.yml + secrets: inherit + with: + testToRun: 'templates.e2e.js' + vscodeVersion: ${{ inputs.vscodeVersion }} + runId: ${{ inputs.runId }} + + # 📚 OpenAPI Documentation + docs-create-oas-doc: + uses: ./.github/workflows/runE2ETest.yml + secrets: inherit + with: + testToRun: 'createOasDoc.e2e.js' + vscodeVersion: ${{ inputs.vscodeVersion }} + runId: ${{ inputs.runId }} diff --git a/.gitignore b/.gitignore index f5b9f86c5d..7f255d2879 100644 --- a/.gitignore +++ b/.gitignore @@ -120,5 +120,6 @@ tsconfig.tsbuildinfo extensions/ /test-results /.codegenie - +e2e-temp/ +test-resources/ .nx/cache diff --git a/.mocharc.json b/.mocharc.json new file mode 100644 index 0000000000..25310404ca --- /dev/null +++ b/.mocharc.json @@ -0,0 +1,12 @@ +{ + "timeout": 1800000, + "require": [ + "ts-node/register" + ], + "extension": [ + "ts" + ], + "slow": 75, + "exit": true, + "reporter": "./packages/salesforcedx-vscode-automation-tests/lib/test/customSummaryReporter.js" +} diff --git a/.vscode/settings.json b/.vscode/settings.json index 3bbb4d96d4..fc536991b0 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -25,8 +25,9 @@ "eslint.experimental.useFlatConfig": true, "eslint.validate": ["typescript", "javascript"], "editor.codeActionsOnSave": { - "source.fixAll.eslint": "always" + "source.fixAll.eslint": "explicit" }, + "eslint.validate": ["javascript", "typescript"], "prettier.singleQuote": true, "prettier.trailingComma": "none", "prettier.tabWidth": 2, diff --git a/package-lock.json b/package-lock.json index 5db82c1f62..5c4c2507b9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "dependencies": { "@actions/core": "^1.11.0", "@actions/github": "^6.0.0", + "a": "3.0.1", "node": "^20.17.0", "npm": "^10", "semver": "^7.5.4", @@ -25,11 +26,14 @@ "@octokit/core": "^5", "@octokit/webhooks-types": "7.6.1", "@salesforce/dev-config": "^3.1.0", + "@salesforce/salesforcedx-vscode-test-tools": "*", "@stylistic/eslint-plugin-ts": "2.13.0", "@tony.ganchev/eslint-plugin-header": "3.1.2", "@tsconfig/node20": "20.1.4", + "@types/chai": "^4.3.17", "@types/cross-spawn": "6.0.6", "@types/jest": "^29.5.5", + "@types/mocha": "^10.0.10", "@types/semver": "7.5.8", "@types/vscode": "1.90.0", "@typescript-eslint/eslint-plugin": "8.33.0", @@ -59,7 +63,9 @@ "junit-report-merger": "7.0.0", "lerna": "8.2.2", "markdown-link-check": "^3.9.3", + "mocha": "^11.7.1", "mocha-multi-reporters": "^1.1.7", + "ncp": "^2.0.0", "nyc": "15.1.0", "ovsx": "0.10.2", "prettier": "3.3.3", @@ -68,6 +74,7 @@ "ts-loader": "^9.3.0", "ts-node": "10.9.2", "typescript": "^5.8.3", + "vscode-extension-tester": "^8.16.0", "yaml": "2.6.0" }, "engines": { @@ -1065,6 +1072,13 @@ "node": ">=6.9.0" } }, + "node_modules/@bazel/runfiles": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@bazel/runfiles/-/runfiles-6.3.1.tgz", + "integrity": "sha512-1uLNT5NZsUVIGS4syuHwTzZ8HycMPyr6POA3FCE4GbMtc4rhoJk8aZKtNIRthJYfL+iioppi+rTfH3olMPr9nA==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/@bcoe/v8-coverage": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", @@ -5531,6 +5545,48 @@ "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", "license": "BSD-3-Clause" }, + "node_modules/@redhat-developer/locators": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@redhat-developer/locators/-/locators-1.14.0.tgz", + "integrity": "sha512-zr3K4z/iSR+SGYgIrKfAAmP13ZID0L/SDLTJsM5WoRPuzKOdaz8zI9RsUvQ7u2UiH7WHjJg7xtpe2qDigbqk3Q==", + "dev": true, + "license": "Apache-2.0", + "peerDependencies": { + "@redhat-developer/page-objects": ">=1.0.0", + "selenium-webdriver": ">=4.6.1" + } + }, + "node_modules/@redhat-developer/page-objects": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@redhat-developer/page-objects/-/page-objects-1.14.0.tgz", + "integrity": "sha512-1dEtWEB5icFrXakG7UaoJBI8LqCRTNmIWZOqZrZk9jYkpw8K5XI5JTpnUdND83AoTvTJgDFI8z8+zQVTvSFJSw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "clipboardy": "^4.0.0", + "clone-deep": "^4.0.1", + "compare-versions": "^6.1.1", + "fs-extra": "^11.3.0", + "type-fest": "^4.41.0" + }, + "peerDependencies": { + "selenium-webdriver": ">=4.6.1", + "typescript": ">=4.6.2" + } + }, + "node_modules/@redhat-developer/page-objects/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@rtsao/scc": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", @@ -6063,6 +6119,87 @@ "resolved": "packages/salesforcedx-visualforce-markup-language-server", "link": true }, + "node_modules/@salesforce/salesforcedx-vscode-test-tools": { + "version": "1.0.1-dev.118", + "resolved": "https://registry.npmjs.org/@salesforce/salesforcedx-vscode-test-tools/-/salesforcedx-vscode-test-tools-1.0.1-dev.118.tgz", + "integrity": "sha512-wYM6+maOO1HA58mM1HCNgewsE7rSBl1W0zgNwoyBhiHpw3KsQ5Sz5pDg/vnIT8E019f0lzyUnfNeG8hpuwHNjw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@salesforce/kit": "^3.2.3", + "chai": "^4.5.0", + "cross-spawn": "^7.0.3", + "fast-glob": "^3.3.2", + "mocha": "^11.7.1", + "mocha-steps": "^1.3.0", + "vscode-extension-tester": "^8.15.0", + "yargs": "^17.7.2" + } + }, + "node_modules/@salesforce/salesforcedx-vscode-test-tools/node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@salesforce/salesforcedx-vscode-test-tools/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, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@salesforce/salesforcedx-vscode-test-tools/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, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@salesforce/salesforcedx-vscode-test-tools/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, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@salesforce/salesforcedx-vscode-test-tools/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, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/@salesforce/schema": { "version": "0.0.21", "resolved": "https://registry.npmjs.org/@salesforce/schema/-/schema-0.0.21.tgz", @@ -6338,6 +6475,13 @@ "node": ">=18.18.2" } }, + "node_modules/@sec-ant/readable-stream": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", + "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", + "dev": true, + "license": "MIT" + }, "node_modules/@secretlint/config-creator": { "version": "9.3.4", "resolved": "https://registry.npmjs.org/@secretlint/config-creator/-/config-creator-9.3.4.tgz", @@ -7340,12 +7484,22 @@ } }, "node_modules/@types/chai": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.3.tgz", - "integrity": "sha512-hC7OMnszpxhZPduX+m+nrx+uFoLkWOMiR4oa/AZF3MuSETYTZmFfJAHqZEM8MVlvfG7BEUcgvtwoCTxBp6hm3g==", + "version": "4.3.20", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.20.tgz", + "integrity": "sha512-/pC9HAB5I/xMlc5FP77qjCnI16ChlJfW0tGa0IUcFn38VJrTV6DeZ60NU5KZBtaOZqjdpwTWohz5HU1RrhiYxQ==", "dev": true, "license": "MIT" }, + "node_modules/@types/conventional-commits-parser": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@types/conventional-commits-parser/-/conventional-commits-parser-5.0.1.tgz", + "integrity": "sha512-7uz5EHdzz2TqoMfV7ee61Egf5y6NkcO4FB/1iCCQnbeiI1F3xzv3vK5dBCXUCLQgGYS+mUeigK1iKQzvED+QnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/cross-spawn": { "version": "6.0.6", "resolved": "https://registry.npmjs.org/@types/cross-spawn/-/cross-spawn-6.0.6.tgz", @@ -7559,6 +7713,17 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/selenium-webdriver": { + "version": "4.1.28", + "resolved": "https://registry.npmjs.org/@types/selenium-webdriver/-/selenium-webdriver-4.1.28.tgz", + "integrity": "sha512-Au7CXegiS7oapbB16zxPToY4Cjzi9UQQMf3W2ZZM8PigMLTGR3iUAHjPUTddyE5g1SBjT/qpmvlsAQLBfNAdKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/ws": "*" + } + }, "node_modules/@types/semver": { "version": "7.5.8", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", @@ -7606,6 +7771,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/yargs": { "version": "17.0.33", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", @@ -8764,6 +8939,20 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/a": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/a/-/a-3.0.1.tgz", + "integrity": "sha512-7keco9nAKQNb1+EFWVpzHrPfrFEcpzDoeYoKBktoUq/TQO0cpGDQ63KBQ3486a5OV5OGRzKC0Xu/g9IQb205qA==", + "license": "MIT", + "dependencies": { + "a_mock": "1.0.5" + } + }, + "node_modules/a_mock": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/a_mock/-/a_mock-1.0.5.tgz", + "integrity": "sha512-HnjqBn1raagaPlpT8+4A9ZP3ntlh3fuW7kpdoeiCapxtYYz8VPTOkyv7TdQ18b4kMHqMNfwqlWSurex8dXWMkw==" + }, "node_modules/abbrev": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", @@ -9816,6 +10005,24 @@ "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, + "license": "MIT", + "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, + "license": "MIT" + }, "node_modules/buffer-crc32": { "version": "0.2.13", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", @@ -9832,6 +10039,13 @@ "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", "license": "BSD-3-Clause" }, + "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, + "license": "MIT" + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -9878,6 +10092,220 @@ "node": ">=12.17" } }, + "node_modules/c8": { + "version": "10.1.3", + "resolved": "https://registry.npmjs.org/c8/-/c8-10.1.3.tgz", + "integrity": "sha512-LvcyrOAaOnrrlMpW22n690PUvxiq4Uf9WMhQwNJ9vgagkL/ph1+D4uvjvDA5XCbykrc0sx+ay6pVi9YZ1GnhyA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@bcoe/v8-coverage": "^1.0.1", + "@istanbuljs/schema": "^0.1.3", + "find-up": "^5.0.0", + "foreground-child": "^3.1.1", + "istanbul-lib-coverage": "^3.2.0", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.1.6", + "test-exclude": "^7.0.1", + "v8-to-istanbul": "^9.0.0", + "yargs": "^17.7.2", + "yargs-parser": "^21.1.1" + }, + "bin": { + "c8": "bin/c8.js" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "monocart-coverage-reports": "^2" + }, + "peerDependenciesMeta": { + "monocart-coverage-reports": { + "optional": true + } + } + }, + "node_modules/c8/node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/c8/node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/c8/node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/c8/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/c8/node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/c8/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/c8/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, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/c8/node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/c8/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, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/c8/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, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/c8/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, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/c8/node_modules/test-exclude": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", + "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^10.4.1", + "minimatch": "^9.0.4" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/c8/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, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/cacache": { "version": "18.0.4", "resolved": "https://registry.npmjs.org/cacache/-/cacache-18.0.4.tgz", @@ -10730,6 +11158,248 @@ "node": ">= 10" } }, + "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, + "license": "MIT", + "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/clipboardy/node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/clipboardy/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, + "license": "MIT", + "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/clipboardy/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, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clipboardy/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, + "license": "Apache-2.0", + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/clipboardy/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, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clipboardy/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, + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clipboardy/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, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clipboardy/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, + "license": "MIT", + "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/clipboardy/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, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clipboardy/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, + "license": "MIT", + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clipboardy/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, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/clipboardy/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, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/clipboardy/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, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/clipboardy/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, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/clipboardy/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, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clipboardy/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, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -11089,6 +11759,13 @@ "dot-prop": "^5.1.0" } }, + "node_modules/compare-versions": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.1.1.tgz", + "integrity": "sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==", + "dev": true, + "license": "MIT" + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -13292,12 +13969,11 @@ } }, "node_modules/diff": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", - "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", "dev": true, "license": "BSD-3-Clause", - "peer": true, "engines": { "node": ">=0.3.1" } @@ -13476,6 +14152,49 @@ "dev": true, "license": "MIT" }, + "node_modules/duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "readable-stream": "^2.0.2" + } + }, + "node_modules/duplexer2/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, + "license": "MIT", + "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/duplexer2/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, + "license": "MIT" + }, + "node_modules/duplexer2/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, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -14272,6 +14991,66 @@ "eslint": ">=2.0.0" } }, + "node_modules/eslint-plugin-prettier": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.1.tgz", + "integrity": "sha512-dobTkHT6XaEVOo8IO90Q4DOSxnm3Y151QxPJlM/vKC0bVy+d6cVWQZLlFiuZPP0wS6vZwSKeJgKkcS+KfMBlRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.11.7" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-plugin-prettier" + }, + "peerDependencies": { + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0", + "prettier": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-prettier/node_modules/@pkgr/core": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.7.tgz", + "integrity": "sha512-YLT9Zo3oNPJoBjBc4q8G2mjU4tqIbf5CEOORbUUr48dCD9q3umJ3IPlVqOqDakPfd2HuwccBaqlGhN4Gmr5OWg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/pkgr" + } + }, + "node_modules/eslint-plugin-prettier/node_modules/synckit": { + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.8.tgz", + "integrity": "sha512-+XZ+r1XGIJGeQk3VvXhT6xx/VpbHsRzsTkGgF6E5RX9TTXD0118l87puaEBZ566FhqblC6U0d4XnubznJDm30A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pkgr/core": "^0.2.4" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/synckit" + } + }, "node_modules/eslint-plugin-unicorn": { "version": "58.0.0", "resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-58.0.0.tgz", @@ -14859,6 +15638,13 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "license": "MIT" }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/fast-glob": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", @@ -15440,6 +16226,16 @@ "node": ">= 6" } }, + "node_modules/form-data-encoder": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-4.1.0.tgz", + "integrity": "sha512-G6NsmEW15s0Uw9XnCg+33H3ViYRyiM0hMrMhhqQOR8NFc5GhYrI+6I3u7OTw7b91J2g8rtvMBZJDbcGb2YUniw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 18" + } + }, "node_modules/fromentries": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", @@ -18392,6 +19188,22 @@ "node": ">=8" } }, + "node_modules/is64bit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is64bit/-/is64bit-2.0.0.tgz", + "integrity": "sha512-jv+8jaWCl0g2lSBkNSVXdzfBA0npK1HGC2KtWM9FumFRoGS94g3NbCCLVnCYHLjp4GrW2KZeeSTMo5ddtznmGw==", + "dev": true, + "license": "MIT", + "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", @@ -22127,12 +22939,11 @@ "optional": true }, "node_modules/mocha": { - "version": "11.6.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.6.0.tgz", - "integrity": "sha512-i0JVb+OUBqw63X/1pC3jCyJsqYisgxySBbsQa8TKvefpA1oEnw7JXxXnftfMHRsw7bEEVGRtVlHcDYXBa7FzVw==", + "version": "11.7.1", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.7.1.tgz", + "integrity": "sha512-5EK+Cty6KheMS/YLPPMJC64g5V61gIR25KsRItHw6x4hEKT6Njp1n9LOlH4gpevuwMVS66SXaBBpg+RWZkza4A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "browser-stdout": "^1.3.1", "chokidar": "^4.0.1", @@ -22250,13 +23061,19 @@ "mocha": ">=3.1.2" } }, + "node_modules/mocha-steps": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/mocha-steps/-/mocha-steps-1.3.0.tgz", + "integrity": "sha512-KZvpMJTqzLZw3mOb+EEuYi4YZS41C9iTnb7skVFRxHjUd1OYbl64tCMSmpdIRM9LnwIrSOaRfPtNpF5msgv6Eg==", + "dev": true, + "license": "MIT" + }, "node_modules/mocha/node_modules/chokidar": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "readdirp": "^4.0.1" }, @@ -22273,7 +23090,6 @@ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -22283,13 +23099,22 @@ "node": ">= 8" } }, + "node_modules/mocha/node_modules/diff": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", + "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/mocha/node_modules/foreground-child": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" @@ -22307,7 +23132,6 @@ "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", @@ -22329,7 +23153,6 @@ "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", "dev": true, "license": "BlueOak-1.0.0", - "peer": true, "dependencies": { "@isaacs/cliui": "^8.0.2" }, @@ -22345,8 +23168,7 @@ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "dev": true, - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/mocha/node_modules/path-key": { "version": "3.1.1", @@ -22354,7 +23176,6 @@ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -22365,7 +23186,6 @@ "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "dev": true, "license": "BlueOak-1.0.0", - "peer": true, "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" @@ -22383,7 +23203,6 @@ "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 14.18.0" }, @@ -22398,7 +23217,6 @@ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "shebang-regex": "^3.0.0" }, @@ -22412,7 +23230,6 @@ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -22423,7 +23240,6 @@ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, "license": "ISC", - "peer": true, "engines": { "node": ">=14" }, @@ -22437,7 +23253,6 @@ "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -22454,7 +23269,6 @@ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "isexe": "^2.0.0" }, @@ -22584,6 +23398,16 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "license": "MIT" }, + "node_modules/ncp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz", + "integrity": "sha512-zIdGUrPRFTUELUvr3Gmc7KZ2Sw/h1PiVM0Af/oHB6zgnV1ikqSfRk+TOufi79aHYCW3NiOXmr1BP5nWbzojLaA==", + "dev": true, + "license": "MIT", + "bin": { + "ncp": "bin/ncp" + } + }, "node_modules/needle": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/needle/-/needle-3.3.1.tgz", @@ -27904,6 +28728,19 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/pretty-format": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", @@ -29391,6 +30228,10 @@ "resolved": "packages/salesforcedx-vscode-apex-replay-debugger", "link": true }, + "node_modules/salesforcedx-vscode-automation-tests": { + "resolved": "packages/salesforcedx-vscode-automation-tests", + "link": true + }, "node_modules/salesforcedx-vscode-core": { "resolved": "packages/salesforcedx-vscode-core", "link": true @@ -29669,6 +30510,32 @@ "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==", "license": "BSD-3-Clause" }, + "node_modules/selenium-webdriver": { + "version": "4.34.0", + "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.34.0.tgz", + "integrity": "sha512-zGfQFcsASAv3KrYzYh+iw4fFqB7iZAgHW7BU6rRz7isK1i1X4x3LvjmZad4bUUgHDwTnAhlqTzDh21byB+zHMg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/SeleniumHQ" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/selenium" + } + ], + "license": "Apache-2.0", + "dependencies": { + "@bazel/runfiles": "^6.3.1", + "jszip": "^3.10.1", + "tmp": "^0.2.3", + "ws": "^8.18.2" + }, + "engines": { + "node": ">= 20.0.0" + } + }, "node_modules/semver": { "version": "7.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", @@ -30194,16 +31061,6 @@ "@sinonjs/commons": "^1.7.0" } }, - "node_modules/sinon/node_modules/diff": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", - "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, "node_modules/sinon/node_modules/type-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", @@ -31097,6 +31954,19 @@ "url": "https://opencollective.com/unts" } }, + "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, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/table": { "version": "6.9.0", "resolved": "https://registry.npmjs.org/table/-/table-6.9.0.tgz", @@ -31242,6 +32112,123 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "license": "ISC" }, + "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, + "license": "MIT", + "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, + "license": "MIT", + "dependencies": { + "readable-stream": "^2.3.5", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/targz/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, + "license": "ISC" + }, + "node_modules/targz/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, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "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, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/targz/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, + "license": "MIT", + "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/targz/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, + "license": "MIT" + }, + "node_modules/targz/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, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/targz/node_modules/tar-fs": { + "version": "1.16.5", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-1.16.5.tgz", + "integrity": "sha512-1ergVCCysmwHQNrOS+Pjm4DQ4nrGp43+Xnu4MRGjCnQu/m3hEgLNS78d5z+B8OJ1hN5EejJdCSFZE1oM6AQXAQ==", + "dev": true, + "license": "MIT", + "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, + "license": "MIT", + "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/temp-dir": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-1.0.0.tgz", @@ -31615,6 +32602,28 @@ "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", "license": "BSD-3-Clause" }, + "node_modules/to-buffer": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.1.tgz", + "integrity": "sha512-tB82LpAIWjhLYbqjx3X4zEeHN6M8CiuOEy2JY8SEQVdYRe3CCHOFaqrBW1doLDrfpWhplcW7BL+bO3/6S3pcDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "isarray": "^2.0.5", + "safe-buffer": "^5.2.1", + "typed-array-buffer": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/to-buffer/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -32207,6 +33216,20 @@ "node": ">= 10.0.0" } }, + "node_modules/unzipper": { + "version": "0.12.3", + "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.12.3.tgz", + "integrity": "sha512-PZ8hTS+AqcGxsaQntl3IRBw65QrBI6lxzqDEL7IAo/XCEqRTKGfOX56Vea5TH9SZczRVxuzk1re04z/YjuYCJA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bluebird": "~3.7.2", + "duplexer2": "~0.1.4", + "fs-extra": "^11.2.0", + "graceful-fs": "^4.2.2", + "node-int64": "^0.4.0" + } + }, "node_modules/upath": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/upath/-/upath-2.0.1.tgz", @@ -32437,6 +33460,795 @@ "zone.js": "0.7.6" } }, + "node_modules/vscode-extension-tester": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/vscode-extension-tester/-/vscode-extension-tester-8.16.0.tgz", + "integrity": "sha512-kGJz9/qFkKYzJHRpnBWjSfTJVHOv9OvMFoHxE6ZIKexOB5doKjBRVC3aIEBMMLIxu7umOCQ4Ih5julK+yfU3XQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@redhat-developer/locators": "^1.14.0", + "@redhat-developer/page-objects": "^1.14.0", + "@types/selenium-webdriver": "^4.1.28", + "@vscode/vsce": "^3.6.0", + "c8": "^10.1.3", + "commander": "^14.0.0", + "compare-versions": "^6.1.1", + "find-up": "7.0.0", + "fs-extra": "^11.3.0", + "glob": "^11.0.3", + "got": "^14.4.7", + "hpagent": "^1.2.0", + "js-yaml": "^4.1.0", + "sanitize-filename": "^1.6.3", + "selenium-webdriver": "^4.34.0", + "targz": "^1.0.1", + "unzipper": "^0.12.3" + }, + "bin": { + "extest": "out/cli.js" + }, + "peerDependencies": { + "mocha": ">=5.2.0", + "typescript": ">=4.6.2" + } + }, + "node_modules/vscode-extension-tester/node_modules/@secretlint/config-creator": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/@secretlint/config-creator/-/config-creator-10.1.1.tgz", + "integrity": "sha512-TJ42CHZqqnEe9ORvIXVVMqdu3KAtyZRxLspjFexo6XgrwJ6CoFHQYzIihilqRjo2sJh9HMrpnYSj/5hopofGrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/types": "^10.1.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/vscode-extension-tester/node_modules/@secretlint/config-loader": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/@secretlint/config-loader/-/config-loader-10.1.1.tgz", + "integrity": "sha512-jBClVFmS6Yu/zI5ejBCRF5a5ASYsE4gOjogjB+WsaHbQHtGvnyY7I26Qtdg4ihCc/VPKYQg0LdM75pLTXzwsjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/profiler": "^10.1.1", + "@secretlint/resolver": "^10.1.1", + "@secretlint/types": "^10.1.1", + "ajv": "^8.17.1", + "debug": "^4.4.1", + "rc-config-loader": "^4.1.3" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/vscode-extension-tester/node_modules/@secretlint/core": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/@secretlint/core/-/core-10.1.1.tgz", + "integrity": "sha512-COLCxSoH/iVQdLeaZPVtBj0UWKOagO09SqYkCQgfFfZ+soGxKVK405dL317r4PnH9Pm8/s8xQC6OSY5rWTRObQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/profiler": "^10.1.1", + "@secretlint/types": "^10.1.1", + "debug": "^4.4.1", + "structured-source": "^4.0.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/vscode-extension-tester/node_modules/@secretlint/formatter": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/@secretlint/formatter/-/formatter-10.1.1.tgz", + "integrity": "sha512-Gpd8gTPN121SJ0h/9e6nWlZU7PitfhXUiEzW7Kyswg6kNGs+bSqmgTgWFtbo1VQ4ygJYiveWPNT05RCImBexJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/resolver": "^10.1.1", + "@secretlint/types": "^10.1.1", + "@textlint/linter-formatter": "^14.8.4", + "@textlint/module-interop": "^14.8.4", + "@textlint/types": "^14.8.4", + "chalk": "^4.1.2", + "debug": "^4.4.1", + "pluralize": "^8.0.0", + "strip-ansi": "^6.0.1", + "table": "^6.9.0", + "terminal-link": "^2.1.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/vscode-extension-tester/node_modules/@secretlint/node": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/@secretlint/node/-/node-10.1.1.tgz", + "integrity": "sha512-AhN+IGqljVObm8a+B33b23FY79wihu5E61Nd3oYSoZV7SxUvMjpafqhLfpt4frNSY7Ghf/pirWu7JY7GMujFrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/config-loader": "^10.1.1", + "@secretlint/core": "^10.1.1", + "@secretlint/formatter": "^10.1.1", + "@secretlint/profiler": "^10.1.1", + "@secretlint/source-creator": "^10.1.1", + "@secretlint/types": "^10.1.1", + "debug": "^4.4.1", + "p-map": "^7.0.3" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/vscode-extension-tester/node_modules/@secretlint/profiler": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/@secretlint/profiler/-/profiler-10.1.1.tgz", + "integrity": "sha512-kReI+Wr7IQz0LbVwYByzlnPbx4BEF2oEWJBc4Oa45g24alCjHu+jD9h9mzkTJqYUgMnVYD3o7HfzeqxFrV+9XA==", + "dev": true, + "license": "MIT" + }, + "node_modules/vscode-extension-tester/node_modules/@secretlint/resolver": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/@secretlint/resolver/-/resolver-10.1.1.tgz", + "integrity": "sha512-GdQzxnBtdBRjBULvZ8ERkaRqDp0njVwXrzBCav1pb0XshVk76C1cjeDqtTqM4RJ1Awo/g5U5MIWYztYv67v5Gg==", + "dev": true, + "license": "MIT" + }, + "node_modules/vscode-extension-tester/node_modules/@secretlint/secretlint-formatter-sarif": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/@secretlint/secretlint-formatter-sarif/-/secretlint-formatter-sarif-10.1.1.tgz", + "integrity": "sha512-Dyq8nzy6domjSlZKX1E5PEzuWxeTqjQJWrlXBmVmOjwLBLfRZDlm5Vq+AduBmEk03KEIKIZi4cZQwsniuRPO9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "node-sarif-builder": "^2.0.3" + } + }, + "node_modules/vscode-extension-tester/node_modules/@secretlint/secretlint-rule-no-dotenv": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/@secretlint/secretlint-rule-no-dotenv/-/secretlint-rule-no-dotenv-10.1.1.tgz", + "integrity": "sha512-a3/sOUUtEHuw1HCadtxUjViNeomiiohfJj+rwtHxJkCq4pjITS3HSYhQBXnNvkctQNljKIzFm7JUA/4QJ6I4sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/types": "^10.1.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/vscode-extension-tester/node_modules/@secretlint/secretlint-rule-preset-recommend": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/@secretlint/secretlint-rule-preset-recommend/-/secretlint-rule-preset-recommend-10.1.1.tgz", + "integrity": "sha512-+GeISCXVgpnoeRZE4ZPsuO97+fm6z8Ge23LNq6LvR9ZJAq018maXVftkJhHj4hnvYB5URUAEerBBkPGNk5/Ong==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/vscode-extension-tester/node_modules/@secretlint/source-creator": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/@secretlint/source-creator/-/source-creator-10.1.1.tgz", + "integrity": "sha512-IWjvHcE0bhC/x88a9M9jbZlFRZGUEbBzujxrs2KzI5IQ2BXTBRBRhRSjE/BEpWqDHILB22c3mfam8X+UjukphA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/types": "^10.1.1", + "istextorbinary": "^9.5.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/vscode-extension-tester/node_modules/@secretlint/types": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/@secretlint/types/-/types-10.1.1.tgz", + "integrity": "sha512-/JGAvVkurVHkargk3AC7UxRy+Ymc+52AVBO/fZA5pShuLW2dX4O/rKc4n8cyhQiOb/3ym5ACSlLQuQ8apPfxrQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/vscode-extension-tester/node_modules/@sindresorhus/is": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-7.0.2.tgz", + "integrity": "sha512-d9xRovfKNz1SKieM0qJdO+PQonjnnIfSNWfHYnBSJ9hkjm0ZPw6HlxscDXYstp3z+7V2GOFHc+J0CYrYTjqCJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/vscode-extension-tester/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, + "license": "MIT", + "dependencies": { + "defer-to-connect": "^2.0.1" + }, + "engines": { + "node": ">=14.16" + } + }, + "node_modules/vscode-extension-tester/node_modules/@vscode/vsce": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-3.6.0.tgz", + "integrity": "sha512-u2ZoMfymRNJb14aHNawnXJtXHLXDVKc1oKZaH4VELKT/9iWKRVgtQOdwxCgtwSxJoqYvuK4hGlBWQJ05wxADhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azure/identity": "^4.1.0", + "@secretlint/node": "^10.1.1", + "@secretlint/secretlint-formatter-sarif": "^10.1.1", + "@secretlint/secretlint-rule-no-dotenv": "^10.1.1", + "@secretlint/secretlint-rule-preset-recommend": "^10.1.1", + "@vscode/vsce-sign": "^2.0.0", + "azure-devops-node-api": "^12.5.0", + "chalk": "^4.1.2", + "cheerio": "^1.0.0-rc.9", + "cockatiel": "^3.1.2", + "commander": "^12.1.0", + "form-data": "^4.0.0", + "glob": "^11.0.0", + "hosted-git-info": "^4.0.2", + "jsonc-parser": "^3.2.0", + "leven": "^3.1.0", + "markdown-it": "^14.1.0", + "mime": "^1.3.4", + "minimatch": "^3.0.3", + "parse-semver": "^1.1.1", + "read": "^1.0.7", + "secretlint": "^10.1.1", + "semver": "^7.5.2", + "tmp": "^0.2.3", + "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": ">= 20" + }, + "optionalDependencies": { + "keytar": "^7.7.0" + } + }, + "node_modules/vscode-extension-tester/node_modules/@vscode/vsce/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/vscode-extension-tester/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, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/vscode-extension-tester/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/vscode-extension-tester/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, + "license": "MIT", + "engines": { + "node": ">=14.16" + } + }, + "node_modules/vscode-extension-tester/node_modules/cacheable-request": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-12.0.1.tgz", + "integrity": "sha512-Yo9wGIQUaAfIbk+qY0X4cDQgCosecfBe3V9NSyeY4qPC2SAkbCS4Xj79VP8WOzitpJUZKc/wsRCYF5ariDIwkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-cache-semantics": "^4.0.4", + "get-stream": "^9.0.1", + "http-cache-semantics": "^4.1.1", + "keyv": "^4.5.4", + "mimic-response": "^4.0.0", + "normalize-url": "^8.0.1", + "responselike": "^3.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/vscode-extension-tester/node_modules/commander": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.0.tgz", + "integrity": "sha512-2uM9rYjPvyq39NwLRqaiLtWHyDC1FvryJDa2ATTVims5YAS4PupsEQsDvP14FqhFr0P49CYDugi59xaxJlTXRA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/vscode-extension-tester/node_modules/find-up": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-7.0.0.tgz", + "integrity": "sha512-YyZM99iHrqLKjmt4LJDj58KI+fYyufRLBSYcqycxf//KpBk9FoewoGX0450m9nB44qrZnovzC2oeP5hUibxc/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^7.2.0", + "path-exists": "^5.0.0", + "unicorn-magic": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vscode-extension-tester/node_modules/get-stream": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", + "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sec-ant/readable-stream": "^0.4.1", + "is-stream": "^4.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vscode-extension-tester/node_modules/globby": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-14.1.0.tgz", + "integrity": "sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^2.1.0", + "fast-glob": "^3.3.3", + "ignore": "^7.0.3", + "path-type": "^6.0.0", + "slash": "^5.1.0", + "unicorn-magic": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vscode-extension-tester/node_modules/globby/node_modules/unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vscode-extension-tester/node_modules/got": { + "version": "14.4.7", + "resolved": "https://registry.npmjs.org/got/-/got-14.4.7.tgz", + "integrity": "sha512-DI8zV1231tqiGzOiOzQWDhsBmncFW7oQDH6Zgy6pDPrqJuVZMtoSgPLLsBZQj8Jg4JFfwoOsDA8NGtLQLnIx2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/is": "^7.0.1", + "@szmarczak/http-timer": "^5.0.1", + "cacheable-lookup": "^7.0.0", + "cacheable-request": "^12.0.1", + "decompress-response": "^6.0.0", + "form-data-encoder": "^4.0.2", + "http2-wrapper": "^2.2.1", + "lowercase-keys": "^3.0.0", + "p-cancelable": "^4.0.1", + "responselike": "^3.0.0", + "type-fest": "^4.26.1" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/vscode-extension-tester/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, + "license": "MIT", + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.2.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/vscode-extension-tester/node_modules/is-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vscode-extension-tester/node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, + "node_modules/vscode-extension-tester/node_modules/locate-path": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", + "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^6.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vscode-extension-tester/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, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vscode-extension-tester/node_modules/markdown-it": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", + "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "node_modules/vscode-extension-tester/node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "dev": true, + "license": "MIT" + }, + "node_modules/vscode-extension-tester/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, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vscode-extension-tester/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, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/vscode-extension-tester/node_modules/normalize-url": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.2.tgz", + "integrity": "sha512-Ee/R3SyN4BuynXcnTaekmaVdbDAEiNrHqjQIA37mHU8G9pf7aaAD4ZX3XjBLo6rsdcxA/gtkcNYZLt30ACgynw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vscode-extension-tester/node_modules/p-cancelable": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-4.0.1.tgz", + "integrity": "sha512-wBowNApzd45EIKdO1LaU+LrMBwAcjfPaYtVzV3lmfM3gf8Z4CHZsiIqlM8TZZ8okYvh5A1cP6gTfCRQtwUpaUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + } + }, + "node_modules/vscode-extension-tester/node_modules/p-limit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vscode-extension-tester/node_modules/p-locate": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", + "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vscode-extension-tester/node_modules/p-map": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.3.tgz", + "integrity": "sha512-VkndIv2fIB99swvQoA65bm+fsmt6UNdGeIB0oxBs+WhAhdh08QA04JXpI7rbB9r08/nkbysKoya9rtDERYOYMA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vscode-extension-tester/node_modules/parse-json": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-7.1.1.tgz", + "integrity": "sha512-SgOTCX/EZXtZxBE5eJ97P4yGM5n37BwRU+YMsH4vNzFqJV/oWFXXCmwFlgWUM4PrakybVOueJJ6pwHqSVhTFDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.21.4", + "error-ex": "^1.3.2", + "json-parse-even-better-errors": "^3.0.0", + "lines-and-columns": "^2.0.3", + "type-fest": "^3.8.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vscode-extension-tester/node_modules/parse-json/node_modules/type-fest": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz", + "integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vscode-extension-tester/node_modules/path-exists": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", + "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/vscode-extension-tester/node_modules/path-type": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-6.0.0.tgz", + "integrity": "sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vscode-extension-tester/node_modules/read-pkg": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-8.1.0.tgz", + "integrity": "sha512-PORM8AgzXeskHO/WEv312k9U03B8K9JSiWF/8N9sUuFjBa+9SF2u6K7VClzXwDXab51jCd8Nd36CNM+zR97ScQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/normalize-package-data": "^2.4.1", + "normalize-package-data": "^6.0.0", + "parse-json": "^7.0.0", + "type-fest": "^4.2.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vscode-extension-tester/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, + "license": "MIT", + "dependencies": { + "lowercase-keys": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vscode-extension-tester/node_modules/secretlint": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/secretlint/-/secretlint-10.1.1.tgz", + "integrity": "sha512-q50i+I9w6HH8P6o34LVq6M3hm5GZn2Eq5lYGHkEByOAbVqBHn8gsMGgyxjP1xSrSv1QjDtjxs/zKPm6JtkNzGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/config-creator": "^10.1.1", + "@secretlint/formatter": "^10.1.1", + "@secretlint/node": "^10.1.1", + "@secretlint/profiler": "^10.1.1", + "debug": "^4.4.1", + "globby": "^14.1.0", + "read-pkg": "^8.1.0" + }, + "bin": { + "secretlint": "bin/secretlint.js" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/vscode-extension-tester/node_modules/slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vscode-extension-tester/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, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/vscode-extension-tester/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vscode-extension-tester/node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "dev": true, + "license": "MIT" + }, + "node_modules/vscode-extension-tester/node_modules/yocto-queue": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.1.tgz", + "integrity": "sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/vscode-html-languageservice": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/vscode-html-languageservice/-/vscode-html-languageservice-3.2.0.tgz", @@ -32951,12 +34763,11 @@ "license": "MIT" }, "node_modules/workerpool": { - "version": "9.3.2", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-9.3.2.tgz", - "integrity": "sha512-Xz4Nm9c+LiBHhDR5bDLnNzmj6+5F+cyEAWPMkbs2awq/dYazR/efelZzUAjB/y3kNHL+uzkHvxVVpaOfGCPV7A==", + "version": "9.3.3", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-9.3.3.tgz", + "integrity": "sha512-slxCaKbYjEdFT/o2rH9xS1hf4uRDch1w7Uo+apxhZ+sf/1d9e0ZVkn42kPNGP2dgjIx6YFvSevj0zHvbWe2jdw==", "dev": true, - "license": "Apache-2.0", - "peer": true + "license": "Apache-2.0" }, "node_modules/wrap-ansi": { "version": "7.0.0", @@ -33144,6 +34955,28 @@ "node": ">=6" } }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "dev": true, + "license": "MIT", + "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/xml": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", @@ -33434,6 +35267,13 @@ "vscode": "^1.90.0" } }, + "packages/salesforcedx-apex-debugger/node_modules/@types/chai": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.3.tgz", + "integrity": "sha512-hC7OMnszpxhZPduX+m+nrx+uFoLkWOMiR4oa/AZF3MuSETYTZmFfJAHqZEM8MVlvfG7BEUcgvtwoCTxBp6hm3g==", + "dev": true, + "license": "MIT" + }, "packages/salesforcedx-apex-debugger/node_modules/@types/node": { "version": "20.17.30", "dev": true, @@ -33464,16 +35304,6 @@ "wrap-ansi": "^7.0.0" } }, - "packages/salesforcedx-apex-debugger/node_modules/diff": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", - "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, "packages/salesforcedx-apex-debugger/node_modules/faye": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/faye/-/faye-1.1.2.tgz", @@ -33491,6 +35321,9 @@ }, "packages/salesforcedx-apex-debugger/node_modules/glob": { "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, "license": "ISC", "dependencies": { @@ -33509,6 +35342,8 @@ }, "packages/salesforcedx-apex-debugger/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, "license": "ISC", "dependencies": { @@ -33520,6 +35355,8 @@ }, "packages/salesforcedx-apex-debugger/node_modules/mocha": { "version": "10.8.2", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.8.2.tgz", + "integrity": "sha512-VZlYo/WE8t1tstuRmqgeyBgCbJc/lEdopaa+axcKzTBJ+UIdlAB9XnmvTCAH4pwR4ElNInaedhEBmZD8iCSVEg==", "dev": true, "license": "MIT", "dependencies": { @@ -33595,6 +35432,8 @@ }, "packages/salesforcedx-apex-debugger/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, "license": "MIT", "dependencies": { @@ -33612,6 +35451,8 @@ }, "packages/salesforcedx-apex-debugger/node_modules/yargs-parser": { "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", "dev": true, "license": "ISC", "engines": { @@ -34123,6 +35964,358 @@ "dev": true, "license": "MIT" }, + "packages/salesforcedx-vscode-automation-tests": { + "version": "1.0.0", + "devDependencies": { + "@commitlint/config-conventional": "19.5.0", + "@salesforce/salesforcedx-vscode-test-tools": "*", + "@types/chai": "^4.3.17", + "@types/cross-spawn": "^6.0.6", + "@types/mocha": "^10.0.9", + "@types/semver": "^7.7.0", + "@typescript-eslint/eslint-plugin": "^8.12.2", + "chai": "^4.5.0", + "commitizen": "^4.3.1", + "cross-spawn": "^7.0.3", + "eslint": "^9.13.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-import": "2.31.0", + "eslint-plugin-prettier": "^5.2.1", + "fast-glob": "^3.3.2", + "husky": "9.1.6", + "mocha": "^10.8.2", + "prettier": "3.3.3", + "semver": "^7.6.3", + "ts-node": "10.9.2", + "typescript": "5.6.3", + "vscode-extension-tester": "^8.16.0" + } + }, + "packages/salesforcedx-vscode-automation-tests/node_modules/@commitlint/config-conventional": { + "version": "19.5.0", + "resolved": "https://registry.npmjs.org/@commitlint/config-conventional/-/config-conventional-19.5.0.tgz", + "integrity": "sha512-OBhdtJyHNPryZKg0fFpZNOBM1ZDbntMvqMuSmpfyP86XSfwzGw4CaoYRG4RutUPg0BTK07VMRIkNJT6wi2zthg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/types": "^19.5.0", + "conventional-changelog-conventionalcommits": "^7.0.2" + }, + "engines": { + "node": ">=v18" + } + }, + "packages/salesforcedx-vscode-automation-tests/node_modules/@commitlint/types": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-19.8.1.tgz", + "integrity": "sha512-/yCrWGCoA1SVKOks25EGadP9Pnj0oAIHGpl2wH2M2Y46dPM2ueb8wyCVOD7O3WCTkaJ0IkKvzhl1JY7+uCT2Dw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/conventional-commits-parser": "^5.0.0", + "chalk": "^5.3.0" + }, + "engines": { + "node": ">=v18" + } + }, + "packages/salesforcedx-vscode-automation-tests/node_modules/@types/semver": { + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.0.tgz", + "integrity": "sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA==", + "dev": true, + "license": "MIT" + }, + "packages/salesforcedx-vscode-automation-tests/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, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "packages/salesforcedx-vscode-automation-tests/node_modules/chalk": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", + "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "packages/salesforcedx-vscode-automation-tests/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, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "packages/salesforcedx-vscode-automation-tests/node_modules/conventional-changelog-conventionalcommits": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-7.0.2.tgz", + "integrity": "sha512-NKXYmMR/Hr1DevQegFB4MwfM5Vv0m4UIxKZTTYuD98lpTknaZlSRrDOG4X7wIXpGkfsYxZTghUN+Qq+T0YQI7w==", + "dev": true, + "license": "ISC", + "dependencies": { + "compare-func": "^2.0.0" + }, + "engines": { + "node": ">=16" + } + }, + "packages/salesforcedx-vscode-automation-tests/node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "packages/salesforcedx-vscode-automation-tests/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, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "packages/salesforcedx-vscode-automation-tests/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "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" + } + }, + "packages/salesforcedx-vscode-automation-tests/node_modules/husky": { + "version": "9.1.6", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.6.tgz", + "integrity": "sha512-sqbjZKK7kf44hfdE94EoX8MZNk0n7HeW37O4YrVGCF4wzgQjp+akPAkfUK5LZ6KuR/6sqeAVuXHji+RzQgOn5A==", + "dev": true, + "license": "MIT", + "bin": { + "husky": "bin.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, + "packages/salesforcedx-vscode-automation-tests/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, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "packages/salesforcedx-vscode-automation-tests/node_modules/mocha": { + "version": "10.8.2", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.8.2.tgz", + "integrity": "sha512-VZlYo/WE8t1tstuRmqgeyBgCbJc/lEdopaa+axcKzTBJ+UIdlAB9XnmvTCAH4pwR4ElNInaedhEBmZD8iCSVEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-colors": "^4.1.3", + "browser-stdout": "^1.3.1", + "chokidar": "^3.5.3", + "debug": "^4.3.5", + "diff": "^5.2.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.1.6", + "ms": "^2.1.3", + "serialize-javascript": "^6.0.2", + "strip-json-comments": "^3.1.1", + "supports-color": "^8.1.1", + "workerpool": "^6.5.1", + "yargs": "^16.2.0", + "yargs-parser": "^20.2.9", + "yargs-unparser": "^2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "packages/salesforcedx-vscode-automation-tests/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, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "packages/salesforcedx-vscode-automation-tests/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, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "packages/salesforcedx-vscode-automation-tests/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, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "packages/salesforcedx-vscode-automation-tests/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, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "packages/salesforcedx-vscode-automation-tests/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, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "packages/salesforcedx-vscode-automation-tests/node_modules/typescript": { + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "packages/salesforcedx-vscode-automation-tests/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, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "packages/salesforcedx-vscode-automation-tests/node_modules/workerpool": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", + "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==", + "dev": true, + "license": "Apache-2.0" + }, + "packages/salesforcedx-vscode-automation-tests/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, + "license": "MIT", + "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" + } + }, + "packages/salesforcedx-vscode-automation-tests/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, "packages/salesforcedx-vscode-core": { "version": "64.4.0", "license": "BSD-3-Clause", diff --git a/package.json b/package.json index a404902745..fecdc13b77 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "dependencies": { "@actions/core": "^1.11.0", "@actions/github": "^6.0.0", + "a": "3.0.1", "node": "^20.17.0", "npm": "^10", "semver": "^7.5.4", @@ -23,11 +24,14 @@ "@octokit/core": "^5", "@octokit/webhooks-types": "7.6.1", "@salesforce/dev-config": "^3.1.0", + "@salesforce/salesforcedx-vscode-test-tools": "*", "@stylistic/eslint-plugin-ts": "2.13.0", "@tony.ganchev/eslint-plugin-header": "3.1.2", "@tsconfig/node20": "20.1.4", + "@types/chai": "^4.3.17", "@types/cross-spawn": "6.0.6", "@types/jest": "^29.5.5", + "@types/mocha": "^10.0.10", "@types/semver": "7.5.8", "@types/vscode": "1.90.0", "@typescript-eslint/eslint-plugin": "8.33.0", @@ -57,7 +61,9 @@ "junit-report-merger": "7.0.0", "lerna": "8.2.2", "markdown-link-check": "^3.9.3", + "mocha": "^11.7.1", "mocha-multi-reporters": "^1.1.7", + "ncp": "^2.0.0", "nyc": "15.1.0", "ovsx": "0.10.2", "prettier": "3.3.3", @@ -66,6 +72,7 @@ "ts-loader": "^9.3.0", "ts-node": "10.9.2", "typescript": "^5.8.3", + "vscode-extension-tester": "^8.16.0", "yaml": "2.6.0" }, "scripts": { @@ -109,6 +116,9 @@ "link-lsp": "yarn link @salesforce/aura-language-server @salesforce/lwc-language-server @salesforce/lightning-lsp-common && lerna exec yarn link @salesforce/aura-language-server @salesforce/lwc-language-server @salesforce/lightning-lsp-common --scope salesforcedx-vscode-lightning && lerna exec yarn link @salesforce/lwc-language-server @salesforce/lightning-lsp-common --scope salesforcedx-vscode-lwc", "unlink-lsp": "yarn unlink @salesforce/aura-language-server @salesforce/lwc-language-server @salesforce/lightning-lsp-common && lerna exec yarn unlink @salesforce/aura-language-server @salesforce/lwc-language-server @salesforce/lightning-lsp-common --scope salesforcedx-vscode-lightning && lerna exec yarn unlink @salesforce/lwc-language-server @salesforce/lightning-lsp-common --scope salesforcedx-vscode-lwc", "report:installs": "ts-node scripts/reportInstalls.ts", + "automation-test": "rm -rf ./e2e-temp && npm install && npm run compile && [ ! -d ./extensions ] && npm run vscode:package && mkdir -p ./extensions && find ./packages -name '*.vsix' -exec cp {} ./extensions/ \\; || true && VSIX_TO_INSTALL=./extensions node ./node_modules/@salesforce/salesforcedx-vscode-test-tools/lib/src/test-setup-and-runner.js --spec ./packages/salesforcedx-vscode-automation-tests/lib/test/specs/manifestBuilder.e2e.js", + "gha-automation-tests": "node ./node_modules/@salesforce/salesforcedx-vscode-test-tools/lib/src/test-setup-and-runner.js --spec", + "setup": "node ./node_modules/@salesforce/salesforcedx-vscode-test-tools/lib/src/test-setup-and-runner.js --spec ./packages/salesforcedx-vscode-automation-tests/lib/test/setup/anInitialSetUp.e2e.js", "test-vsix": "ts-node scripts/installVSIXFromBranch.ts code" }, "husky": { diff --git a/packages/salesforcedx-apex-replay-debugger/src/core/logContext.ts b/packages/salesforcedx-apex-replay-debugger/src/core/logContext.ts index f1e11360e0..17d05dfd95 100644 --- a/packages/salesforcedx-apex-replay-debugger/src/core/logContext.ts +++ b/packages/salesforcedx-apex-replay-debugger/src/core/logContext.ts @@ -276,9 +276,23 @@ export class LogContext { } } catch (error) { success = false; - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - const result = JSON.parse(error) as OrgInfoError; - const errorMessage = `${nls.localize('unable_to_retrieve_org_info')} : ${result.message}`; + let errorMessage: string; + + // Check if error is already an Error object with a message + if (error instanceof Error) { + errorMessage = `${nls.localize('unable_to_retrieve_org_info')} : ${error.message}`; + } else { + // Try to parse as JSON (for backwards compatibility) + try { + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + const result = JSON.parse(error as string) as OrgInfoError; + errorMessage = `${nls.localize('unable_to_retrieve_org_info')} : ${result.message}`; + } catch { + // If JSON parsing fails, treat as string + errorMessage = `${nls.localize('unable_to_retrieve_org_info')} : ${String(error)}`; + } + } + this.session.errorToDebugConsole(errorMessage); } return success; diff --git a/packages/salesforcedx-vscode-apex-replay-debugger/src/breakpoints/checkpointService.ts b/packages/salesforcedx-vscode-apex-replay-debugger/src/breakpoints/checkpointService.ts index bb446fc691..ebd87670d1 100644 --- a/packages/salesforcedx-vscode-apex-replay-debugger/src/breakpoints/checkpointService.ts +++ b/packages/salesforcedx-vscode-apex-replay-debugger/src/breakpoints/checkpointService.ts @@ -84,8 +84,22 @@ export class CheckpointService implements TreeDataProvider { try { this.orgInfo = await new OrgDisplay().getOrgInfo(); } catch (error) { - const result = JSON.parse(error) as OrgInfoError; - const errorMessage = `${nls.localize('unable_to_retrieve_org_info')} : ${result.message}`; + let errorMessage: string; + + // Check if error is already an Error object with a message + if (error instanceof Error) { + errorMessage = `${nls.localize('unable_to_retrieve_org_info')} : ${error.message}`; + } else { + // Try to parse as JSON (for backwards compatibility) + try { + const result = JSON.parse(error as string) as OrgInfoError; + errorMessage = `${nls.localize('unable_to_retrieve_org_info')} : ${result.message}`; + } catch { + // If JSON parsing fails, treat as string + errorMessage = `${nls.localize('unable_to_retrieve_org_info')} : ${String(error)}`; + } + } + writeToDebuggerOutputWindow(errorMessage, true, VSCodeWindowTypeEnum.Error); return false; } diff --git a/packages/salesforcedx-vscode-automation-tests/.gitignore b/packages/salesforcedx-vscode-automation-tests/.gitignore new file mode 100644 index 0000000000..3bc5c5ce1e --- /dev/null +++ b/packages/salesforcedx-vscode-automation-tests/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/.DS_Store +/lib +/e2e-temp +/.sfdx diff --git a/packages/salesforcedx-vscode-automation-tests/package.json b/packages/salesforcedx-vscode-automation-tests/package.json new file mode 100644 index 0000000000..865f88f3aa --- /dev/null +++ b/packages/salesforcedx-vscode-automation-tests/package.json @@ -0,0 +1,42 @@ +{ + "name": "salesforcedx-vscode-automation-tests", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "lint": "npx eslint ./test **/*.ts", + "copy:files": "shx mkdir -p lib/test/testData/files && shx cp ./test/testData/files/* lib/test/testData/files", + "compile": "tsc --project ./tsconfig.json && npm run copy:files", + "extester": "extest --help", + "reinstall": "git checkout -- package-lock.json && git clean -xfd && npm install" + }, + "config": { + "commitizen": { + "path": "./node_modules/cz-conventional-changelog" + } + }, + "devDependencies": { + "@commitlint/config-conventional": "19.5.0", + "@types/chai": "^4.3.17", + "@types/cross-spawn": "^6.0.6", + "@types/mocha": "^10.0.9", + "@types/semver": "^7.7.0", + "@typescript-eslint/eslint-plugin": "^8.12.2", + "chai": "^4.5.0", + "commitizen": "^4.3.1", + "cross-spawn": "^7.0.3", + "eslint": "^9.13.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-import": "2.31.0", + "eslint-plugin-prettier": "^5.2.1", + "fast-glob": "^3.3.2", + "husky": "9.1.6", + "mocha": "^10.8.2", + "prettier": "3.3.3", + "@salesforce/salesforcedx-vscode-test-tools": "*", + "semver": "^7.6.3", + "ts-node": "10.9.2", + "typescript": "5.6.3", + "vscode-extension-tester": "^8.16.0" + } +} diff --git a/packages/salesforcedx-vscode-automation-tests/test/customSummaryReporter.ts b/packages/salesforcedx-vscode-automation-tests/test/customSummaryReporter.ts new file mode 100644 index 0000000000..69a4b10d50 --- /dev/null +++ b/packages/salesforcedx-vscode-automation-tests/test/customSummaryReporter.ts @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2025, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +import { Runner, Test } from 'mocha'; + +const { EVENT_RUN_END, EVENT_TEST_PASS, EVENT_TEST_FAIL, EVENT_TEST_PENDING } = Runner.constants; +class CustomSummaryReporter { + private passes: { title: string; duration: number }[] = []; + private failures: { title: string; error: string; duration: number }[] = []; + private pending: { title: string }[] = []; + + constructor(runner: Runner) { + // Listen for passed tests + runner.on(EVENT_TEST_PASS, (test: Test) => { + this.passes.push({ + title: test.title, + duration: test.duration || 0 + }); + }); + + // Listen for failed tests + runner.on(EVENT_TEST_FAIL, (test: Test, err: Error) => { + this.failures.push({ + title: test.title, + error: err.message, + duration: test.duration || 0 + }); + }); + + // Listen for pending (skipped) tests + runner.on(EVENT_TEST_PENDING, (test: Test) => { + this.pending.push({ title: test.title }); + }); + + // When all tests have finished running + runner.once(EVENT_RUN_END, () => { + this.printSummary(); + }); + } + + // Print the summary of passed, failed, and pending tests + private printSummary(): void { + console.log('\nTest Summary:\n'); + + // Print passed tests + console.log('Passing Tests:\n'); + this.passes.forEach(test => { + console.log(` ✔ ${test.title} (${test.duration}ms)`); + }); + + // Print failed tests + if (this.failures.length > 0) { + console.log('\nFailing Tests:\n'); + this.failures.forEach(test => { + console.log(` ✘ ${test.title} (${test.duration}ms)`); + console.log(` Error: ${test.error}\n`); + }); + } + + // Print pending tests + if (this.pending.length > 0) { + console.log('Skipped Tests:\n'); + this.pending.forEach(test => { + console.log(` ~ ${test.title}`); + }); + } + } +} + +export = CustomSummaryReporter; diff --git a/packages/salesforcedx-vscode-automation-tests/test/specs/anInitialSuite.e2e.ts b/packages/salesforcedx-vscode-automation-tests/test/specs/anInitialSuite.e2e.ts new file mode 100644 index 0000000000..3f76b3c1cf --- /dev/null +++ b/packages/salesforcedx-vscode-automation-tests/test/specs/anInitialSuite.e2e.ts @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2023, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ +import { pause, Duration, log } from '@salesforce/salesforcedx-vscode-test-tools/lib/src/core/miscellaneous'; +import { ProjectShapeOption, TestReqConfig } from '@salesforce/salesforcedx-vscode-test-tools/lib/src/core/types'; +import { + findExtensionsInRunningExtensionsList, + getExtensionsToVerifyActive +} from '@salesforce/salesforcedx-vscode-test-tools/lib/src/testing/extensionUtils'; +import { TestSetup } from '@salesforce/salesforcedx-vscode-test-tools/lib/src/testSetup'; +import { openCommandPromptWithCommand } from '@salesforce/salesforcedx-vscode-test-tools/lib/src/ui-interaction/commandPrompt'; +import { + getWorkbench, + zoom, + zoomReset +} from '@salesforce/salesforcedx-vscode-test-tools/lib/src/ui-interaction/workbench'; +import { expect } from 'chai'; +import { after } from 'vscode-extension-tester'; +/* +anInitialSuite.e2e.ts is a special case. We want to validate that the Salesforce extensions and +most SFDX commands are not present at start up. + +We also want to verify that after a project has been created, that the Salesforce extensions are loaded, +and that the SFDX commands are present. + +Because of this requirement, this suite needs to run first before the other suites. Since the +suites run in alphabetical order, this suite has been named so it runs first. + +Please note that none of the other suites depend on this suite to run, it's just that if this +suite does run, it needs to run first. +*/ + +describe('An Initial Suite', () => { + const testReqConfig: TestReqConfig = { + projectConfig: { + projectShape: ProjectShapeOption.NEW + }, + isOrgRequired: false, + testSuiteSuffixName: 'AnInitialSuite' + }; + + let testSetup: TestSetup; + describe('Verify our extensions are not initially loaded', () => { + it('Verify our extensions are not initially loaded', async () => { + await pause(Duration.seconds(20)); + await zoom('Out', 4, Duration.seconds(1)); + + const foundSfExtensions = await findExtensionsInRunningExtensionsList( + getExtensionsToVerifyActive().map((ext: { extensionId: string }) => ext.extensionId) + ); + await zoomReset(); + if (foundSfExtensions.length > 0) { + foundSfExtensions.forEach((ext: { extensionId: string }) => { + log( + `AnInitialSuite - extension ${ext.extensionId} was present, but wasn't expected before the extensions loaded` + ); + }); + throw new Error('AnInitialSuite - extension was found before the extensions loaded'); + } + }); + + it('Verify the default SFDX commands are present when no project is loaded', async () => { + const workbench = getWorkbench(); + const prompt = await openCommandPromptWithCommand(workbench, 'SFDX:'); + + const quickPicks = await prompt.getQuickPicks(); + let expectedSfdxCommandsFound = 0; + let unexpectedSfdxCommandWasFound = false; + for (const quickPick of quickPicks) { + const label = await quickPick.getLabel(); + switch (label) { + // These three commands are expected to always be present, + // even before the extensions have been loaded. + case 'SFDX: Create and Set Up Project for ISV Debugging': + case 'SFDX: Create Project': + case 'SFDX: Create Project with Manifest': + expectedSfdxCommandsFound++; + break; + + default: + // And if any other SFDX commands are present, this is unexpected and is an issue. + if (label.startsWith('SFDX:')) { + unexpectedSfdxCommandWasFound = true; + log(`AnInitialSuite - command ${label} was present, but wasn't expected before the extensions loaded`); + break; + } + } + } + + expect(expectedSfdxCommandsFound).to.be.equal(3); + expect(unexpectedSfdxCommandWasFound).to.be.false; + + // Escape out of the pick list. + await prompt.cancel(); + }); + }); + + describe('Verify that SFDX commands are present after an SFDX project has been created', () => { + before('Set up the testing environment', async () => { + testSetup = await TestSetup.setUp(testReqConfig); + }); + + it('Verify that SFDX commands are present after an SFDX project has been created', async () => { + const workbench = getWorkbench(); + const prompt = await openCommandPromptWithCommand(workbench, 'SFDX:'); + const quickPicks = await prompt.getQuickPicks(); + const commands = await Promise.all( + quickPicks.map((quickPick: { getLabel: () => Promise }) => quickPick.getLabel()) + ); + + // Look for the first few SFDX commands. + expect(commands).to.include('SFDX: Authorize a Dev Hub'); + expect(commands).to.include('SFDX: Authorize an Org'); + expect(commands).to.include('SFDX: Authorize an Org using Session ID'); + expect(commands).to.include('SFDX: Cancel Active Command'); + expect(commands).to.include('SFDX: Configure Apex Debug Exceptions'); + expect(commands).to.include('SFDX: Create a Default Scratch Org...'); + expect(commands).to.include('SFDX: Create and Set Up Project for ISV Debugging'); + expect(commands).to.include('SFDX: Create Apex Class'); + expect(commands).to.include('SFDX: Create Apex Trigger'); + // There are more, but just look for the first few commands. + + // Escape out of the pick list. + await prompt.cancel(); + }); + }); + + after('Tear down and clean up the testing environment', async () => { + await testSetup?.tearDown(); + }); +}); diff --git a/packages/salesforcedx-vscode-automation-tests/test/specs/apexLsp.e2e.ts b/packages/salesforcedx-vscode-automation-tests/test/specs/apexLsp.e2e.ts new file mode 100644 index 0000000000..1815585d85 --- /dev/null +++ b/packages/salesforcedx-vscode-automation-tests/test/specs/apexLsp.e2e.ts @@ -0,0 +1,271 @@ +/* + * Copyright (c) 2023, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +import { + Duration, + log, + pause, + ProjectShapeOption, + TestReqConfig +} from '@salesforce/salesforcedx-vscode-test-tools/lib/src/core'; +import { EnvironmentSettings } from '@salesforce/salesforcedx-vscode-test-tools/lib/src/environmentSettings'; +import { createApexClassWithTest } from '@salesforce/salesforcedx-vscode-test-tools/lib/src/salesforce-components'; +import { getFolderName, removeFolder } from '@salesforce/salesforcedx-vscode-test-tools/lib/src/system-operations'; +import { TestSetup } from '@salesforce/salesforcedx-vscode-test-tools/lib/src/testSetup'; +import { + getWorkbench, + getStatusBarItemWhichIncludes, + getTextEditor, + getOutputViewText +} from '@salesforce/salesforcedx-vscode-test-tools/lib/src/ui-interaction'; +import { + executeQuickPick, + selectQuickPickItem +} from '@salesforce/salesforcedx-vscode-test-tools/lib/src/ui-interaction/commandPrompt'; +import { expect } from 'chai'; +import * as fs from 'node:fs'; +import * as path from 'node:path'; +import { By, InputBox, WebElement, after } from 'vscode-extension-tester'; +import { logTestStart } from '../utils/loggingHelper'; + +// Types +interface LspStatus { + indexingComplete: string; + restarting: string; + preludeStarting: string; +} + +interface LspRestartOptions { + cleanDb: boolean; + option: string; +} + +// Constants +const PATHS = { + project: path.join(__dirname, '..', '..', '..', '..', '..', 'e2e-temp', 'TempProject-ApexLsp'), + apexClass: path.join( + __dirname, + '..', + '..', + '..', + '..', + '..', + 'e2e-temp', + 'TempProject-ApexLsp', + 'force-app', + 'main', + 'default', + 'classes' + ), + tools: path.join(__dirname, '..', '..', '..', '..', '..', 'e2e-temp', 'TempProject-ApexLsp', '.sfdx', 'tools') +} as const; + +const LSP_STATUS: LspStatus = { + indexingComplete: 'Indexing complete', + restarting: 'Apex Language Server is restarting', + preludeStarting: 'Apex Prelude Service STARTING' +} as const; + +const LSP_RESTART_OPTIONS: LspRestartOptions[] = [ + { cleanDb: false, option: 'Restart Only' }, + { cleanDb: true, option: 'Clean Apex DB and Restart' } +] as const; + +// Helper Functions +const findReleaseDir = (): string => { + const entries = fs.readdirSync(PATHS.tools); + const match = entries.find(entry => /^\d{3}$/.test(entry)); + return match || '254'; +}; + +const verifyLspStatus = async (expectedStatus: string): Promise => { + const statusBar = await getStatusBarItemWhichIncludes('Editor Language Status'); + await statusBar.click(); + const ariaLabel = await statusBar.getAttribute('aria-label'); + expect(ariaLabel).to.include(expectedStatus); + return statusBar; +}; + +const verifyLspRestart = async (cleanDb: boolean): Promise => { + const option = LSP_RESTART_OPTIONS.find(opt => opt.cleanDb === cleanDb)?.option; + if (!option) throw new Error(`Invalid cleanDb option: ${cleanDb}`); + // Wait for LSP to enter restarting state + await verifyLspStatus(LSP_STATUS.restarting); + // Allow time for LSP to fully restart and reindex + await pause(Duration.seconds(25)); + await verifyLspStatus(LSP_STATUS.indexingComplete); + + const outputViewText = await getOutputViewText('Apex Language Server'); + expect(outputViewText).to.contain(LSP_STATUS.preludeStarting); +}; + +const setupTestEnvironment = async (): Promise => { + log('ApexLsp - Set up the testing environment'); + log(`ApexLsp - JAVA_HOME: ${EnvironmentSettings.getInstance().javaHome}`); + // Allow time for VSCode to fully initialize and load extensions + await pause(Duration.seconds(10)); + await createApexClassWithTest('ExampleClass'); +}; + +const verifyIndexing = async (testSetup: TestSetup): Promise => { + logTestStart(testSetup, 'Verify LSP finished indexing'); + const workbench = getWorkbench(); + await getTextEditor(workbench, 'ExampleClass.cls'); + + await verifyLspStatus(LSP_STATUS.indexingComplete); + const outputViewText = await getOutputViewText('Apex Language Server'); + log(`Output view text: ${outputViewText}`); +}; + +const testGoToDefinition = async (testSetup: TestSetup): Promise => { + logTestStart(testSetup, 'Go to Definition'); + const workbench = getWorkbench(); + const textEditor = await getTextEditor(workbench, 'ExampleClassTest.cls'); + + await textEditor.moveCursor(6, 20); + // Allow time for LSP to process cursor movement and prepare definition lookup + await pause(Duration.seconds(2)); + // Wait for quick pick to appear and be clickable + await executeQuickPick('Go to Definition', Duration.seconds(3)); + + const editorView = workbench.getEditorView(); + const activeTab = await editorView.getActiveTab(); + const title = await activeTab?.getTitle(); + expect(title).to.equal('ExampleClass.cls'); +}; + +const testAutocompletion = async (testSetup: TestSetup): Promise => { + logTestStart(testSetup, 'Autocompletion'); + const workbench = getWorkbench(); + const textEditor = await getTextEditor(workbench, 'ExampleClassTest.cls'); + + await textEditor.typeTextAt(7, 1, '\tExampleClass.say'); + // Allow time for LSP to process text input and prepare autocompletion suggestions + await pause(Duration.seconds(2)); + + const autocompletionOptions = await workbench.findElements(By.css('div.monaco-list-row.show-file-icons')); + const ariaLabel = await autocompletionOptions[0].getAttribute('aria-label'); + expect(ariaLabel).to.contain('SayHello(name)'); + + await autocompletionOptions[0].click(); + await textEditor.typeText("'Jack"); + await textEditor.typeTextAt(7, 38, ';'); + await textEditor.save(); + + // Allow time for LSP to process the changes and update the editor + await pause(Duration.seconds(2)); + const line7Text = await textEditor.getTextAtLine(7); + expect(line7Text).to.include("ExampleClass.SayHello('Jack');"); +}; + +const testLspRestart = async (testSetup: TestSetup, cleanDb: boolean): Promise => { + const action = cleanDb ? 'with cleaned db' : 'alone'; + logTestStart(testSetup, `Cmd Palette: LSP Restart ${action}`); + + if (cleanDb) { + const releaseDir = findReleaseDir(); + const standardApexLibraryPath = path.normalize(path.join(PATHS.tools, releaseDir, 'StandardApexLibrary')); + await removeFolder(standardApexLibraryPath); + expect(await getFolderName(standardApexLibraryPath)).to.equal(null); + } + + const restartCommand = await executeQuickPick('Restart Apex Language Server'); + const quickPicks = await restartCommand.getQuickPicks(); + for (const quickPick of quickPicks) { + const label = await quickPick.getLabel(); + if (label === (cleanDb ? 'Clean Apex DB and Restart' : 'Restart Only')) { + await quickPick.select(); + break; + } + } + await verifyLspRestart(cleanDb); + + if (cleanDb) { + const releaseDir = findReleaseDir(); + const standardApexLibraryPath = path.normalize(path.join(PATHS.tools, releaseDir, 'StandardApexLibrary')); + expect(getFolderName(standardApexLibraryPath)).to.equal('StandardApexLibrary'); + } +}; + +const testStatusBarRestart = async (testSetup: TestSetup, cleanDb: boolean): Promise => { + const action = cleanDb ? 'with cleaned db' : 'alone'; + logTestStart(testSetup, `Apex Status Bar: LSP Restart ${action}`); + + const statusBar = await getStatusBarItemWhichIncludes('Editor Language Status'); + await statusBar.click(); + + // Allow time for status bar menu to appear and be clickable + await pause(Duration.seconds(3)); + const restartButton = getWorkbench().findElement(By.linkText('Restart Apex Language Server')); + await restartButton.click(); + + // Allow time for restart process to begin + const dropdown = await new InputBox().wait(); + await selectQuickPickItem(dropdown, cleanDb ? 'Clean Apex DB and Restart' : 'Restart Only'); + await verifyLspRestart(cleanDb); + + if (cleanDb) { + const releaseDir = findReleaseDir(); + expect(getFolderName(path.join(PATHS.tools, releaseDir, 'StandardApexLibrary'))).to.equal('StandardApexLibrary'); + } +}; + +describe('Apex LSP', () => { + let testSetup: TestSetup; + const testReqConfig: TestReqConfig = { + projectConfig: { + projectShape: ProjectShapeOption.NEW + }, + isOrgRequired: false, + testSuiteSuffixName: 'ApexLsp' + }; + + before('Set up the testing environment', async () => { + testSetup = await TestSetup.setUp(testReqConfig); + await setupTestEnvironment(); + }); + + beforeEach(function () { + if (this.currentTest?.parent?.tests.some(test => test.state === 'failed')) { + this.skip(); + } + }); + + it('Verify LSP finished indexing', async () => { + await verifyIndexing(testSetup); + }); + + it('Go to Definition', async () => { + await testGoToDefinition(testSetup); + }); + + it('Autocompletion', async () => { + await testAutocompletion(testSetup); + }); + + it('Restart LSP alone via Command Palette', async () => { + await testLspRestart(testSetup, false); + }); + + it('Restart LSP with cleaned db via Command Palette', async () => { + await testLspRestart(testSetup, true); + }); + + it('Verify LSP can restart alone via Status Bar', async () => { + await testStatusBarRestart(testSetup, false); + }); + + it('Verify LSP can restart with cleaned db via Status Bar', async () => { + await testStatusBarRestart(testSetup, true); + }); + + after('Tear down and clean up the testing environment', async () => { + log(`${testSetup.testSuiteSuffixName} - Tear down and clean up the testing environment`); + await removeFolder(PATHS.apexClass); + await testSetup?.tearDown(); + }); +}); diff --git a/packages/salesforcedx-vscode-automation-tests/test/specs/apexReplayDebugger.e2e.ts b/packages/salesforcedx-vscode-automation-tests/test/specs/apexReplayDebugger.e2e.ts new file mode 100644 index 0000000000..30c9bed63e --- /dev/null +++ b/packages/salesforcedx-vscode-automation-tests/test/specs/apexReplayDebugger.e2e.ts @@ -0,0 +1,288 @@ +/* + * Copyright (c) 2024, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ +import { + Duration, + log, + pause, + ProjectShapeOption, + TestReqConfig +} from '@salesforce/salesforcedx-vscode-test-tools/lib/src/core'; +import { + retryOperation, + verifyNotificationWithRetry +} from '@salesforce/salesforcedx-vscode-test-tools/lib/src/retryUtils'; +import { + createApexClassWithTest, + createAnonymousApexFile +} from '@salesforce/salesforcedx-vscode-test-tools/lib/src/salesforce-components'; +import { continueDebugging } from '@salesforce/salesforcedx-vscode-test-tools/lib/src/testing'; +import { TestSetup } from '@salesforce/salesforcedx-vscode-test-tools/lib/src/testSetup'; +import { + executeQuickPick, + getWorkbench, + getStatusBarItemWhichIncludes, + clearOutputView, + attemptToFindOutputPanelText, + getTextEditor, + waitForNotificationToGoAway +} from '@salesforce/salesforcedx-vscode-test-tools/lib/src/ui-interaction'; +import { expect } from 'chai'; +import * as path from 'node:path'; +import { InputBox, QuickOpenBox, TextEditor } from 'vscode-extension-tester'; +import { logTestStart } from '../utils/loggingHelper'; + +describe('Apex Replay Debugger', () => { + let prompt: QuickOpenBox | InputBox; + let testSetup: TestSetup; + let projectFolderPath: string; + let logFileTitle: string; + const testReqConfig: TestReqConfig = { + projectConfig: { + projectShape: ProjectShapeOption.NEW + }, + isOrgRequired: true, + testSuiteSuffixName: 'ApexReplayDebugger' + }; + + before('Set up the testing environment', async () => { + log('ApexReplayDebugger - Set up the testing environment'); + testSetup = await TestSetup.setUp(testReqConfig); + projectFolderPath = testSetup.projectFolderPath!; + + // Create Apex class file + await createApexClassWithTest('ExampleApexClass'); + + // Push source to org + await executeQuickPick('SFDX: Push Source to Default Org and Ignore Conflicts', Duration.seconds(1)); + + await verifyNotificationWithRetry( + /SFDX: Push Source to Default Org and Ignore Conflicts successfully ran/, + Duration.TEN_MINUTES + ); + }); + + // Since tests are sequential, we need to skip the rest of the tests if one fails + beforeEach(function () { + if (this.currentTest?.parent?.tests.some(test => test.state === 'failed')) { + this.skip(); + } + }); + + it('Verify LSP finished indexing', async () => { + logTestStart(testSetup, 'ApexReplayDebugger - Verify LSP finished indexing'); + + // Get Apex LSP Status Bar + const statusBar = await retryOperation(async () => await getStatusBarItemWhichIncludes('Editor Language Status')); + await statusBar.click(); + expect(await statusBar.getAttribute('aria-label')).to.contain('Indexing complete'); + }); + + it('SFDX: Turn On Apex Debug Log for Replay Debugger', async () => { + logTestStart(testSetup, 'ApexReplayDebugger - SFDX: Turn On Apex Debug Log for Replay Debugger'); + + // Clear output before running the command + await clearOutputView(); + + // Run SFDX: Turn On Apex Debug Log for Replay Debugger + await executeQuickPick('SFDX: Turn On Apex Debug Log for Replay Debugger', Duration.seconds(10)); + + // Look for the success notification that appears which says, "SFDX: Turn On Apex Debug Log for Replay Debugger successfully ran". + await verifyNotificationWithRetry( + /SFDX: Turn On Apex Debug Log for Replay Debugger successfully ran/, + Duration.TEN_MINUTES + ); + + // Verify content on vscode's Output section + const outputPanelText = await attemptToFindOutputPanelText( + 'Salesforce CLI', + 'Starting SFDX: Turn On Apex Debug Log for Replay Debugger', + 10 + ); + expect(outputPanelText).to.contain('SFDX: Turn On Apex Debug Log for Replay Debugger'); + expect(outputPanelText).to.contain('ended with exit code 0'); + }); + + it('Run the Anonymous Apex Debugger with Currently Selected Text', async () => { + logTestStart(testSetup, 'ApexReplayDebugger - Run the Anonymous Apex Debugger with Currently Selected Text'); + + // Clear output before running the command + await clearOutputView(); + + // Get open text editor + const workbench = getWorkbench(); + const textEditor = await getTextEditor(workbench, 'ExampleApexClassTest.cls'); + + // Select text + const findWidget = await textEditor.openFindWidget(); + await findWidget.setSearchText("ExampleApexClass.SayHello('Cody');"); + await pause(Duration.seconds(1)); + // Close finder tool + await findWidget.close(); + await pause(Duration.seconds(1)); + + // Run SFDX: Launch Apex Replay Debugger with Currently Selected Text. + await executeQuickPick('SFDX: Execute Anonymous Apex with Currently Selected Text', Duration.seconds(1)); + + await verifyNotificationWithRetry(/Execute Anonymous Apex successfully ran/, Duration.TEN_MINUTES); + + // Verify content on vscode's Output section + const outputPanelText = await attemptToFindOutputPanelText('Apex', 'Starting Execute Anonymous Apex', 10); + expect(outputPanelText).to.contain('Compiled successfully.'); + expect(outputPanelText).to.contain('Executed successfully.'); + expect(outputPanelText).to.contain('|EXECUTION_STARTED'); + expect(outputPanelText).to.contain('|EXECUTION_FINISHED'); + expect(outputPanelText).to.contain('ended Execute Anonymous Apex'); + }); + + it('SFDX: Get Apex Debug Logs', async () => { + logTestStart(testSetup, 'ApexReplayDebugger - SFDX: Get Apex Debug Logs'); + + // Run SFDX: Get Apex Debug Logs + const workbench = getWorkbench(); + await clearOutputView(); + await pause(Duration.seconds(2)); + prompt = await executeQuickPick('SFDX: Get Apex Debug Logs', Duration.seconds(0)); + + // Wait for the command to execute + await waitForNotificationToGoAway(/Getting Apex debug logs/, Duration.TEN_MINUTES); + await pause(Duration.seconds(5)); // Increased pause to allow quickpick to fully load + + // Select a log file with error handling + await retryOperation(async () => { + const quickPicks = await prompt.getQuickPicks(); + expect(quickPicks).to.not.be.undefined; + expect(quickPicks.length).to.be.greaterThanOrEqual(0); + await prompt.selectQuickPick('User User - Api'); + }); + + await verifyNotificationWithRetry(/SFDX: Get Apex Debug Logs successfully ran/, Duration.TEN_MINUTES); + + // Verify content on vscode's Output section + const outputPanelText = await attemptToFindOutputPanelText('Apex', 'Starting SFDX: Get Apex Debug Logs', 10); + expect(outputPanelText).to.contain('|EXECUTION_STARTED'); + expect(outputPanelText).to.contain('|EXECUTION_FINISHED'); + expect(outputPanelText).to.contain('ended SFDX: Get Apex Debug Logs'); + + // Verify content on log file + const textEditor = await retryOperation(async () => { + const editorView = workbench.getEditorView(); + const activeTab = await editorView.getActiveTab(); + const title = await activeTab?.getTitle(); + return await editorView.openEditor(title!); + }); + + if (!(textEditor instanceof TextEditor)) { + throw new Error(`Expected TextEditor but got different editor type: ${typeof textEditor}`); + } + const executionStarted = await textEditor.getLineOfText('|EXECUTION_STARTED'); + const executionFinished = await textEditor.getLineOfText('|EXECUTION_FINISHED'); + expect(executionStarted).to.be.greaterThanOrEqual(1); + expect(executionFinished).to.be.greaterThanOrEqual(1); + }); + + it('SFDX: Launch Apex Replay Debugger with Last Log File', async () => { + logTestStart(testSetup, 'ApexReplayDebugger - SFDX: Launch Apex Replay Debugger with Last Log File'); + + // Get open text editor + const workbench = getWorkbench(); + const editorView = workbench.getEditorView(); + + // Get file path from open text editor + const activeTab = await editorView.getActiveTab(); + expect(activeTab).to.not.be.undefined; + const title = await activeTab?.getTitle(); + if (title) logFileTitle = title; + const logFilePath = path.join(projectFolderPath, '.sfdx', 'tools', 'debug', 'logs', logFileTitle); + console.log(`*** logFilePath = ${logFilePath}`); + + // Run SFDX: Launch Apex Replay Debugger with Last Log File + prompt = await executeQuickPick('SFDX: Launch Apex Replay Debugger with Last Log File', Duration.seconds(1)); + await prompt.setText(logFilePath); + await prompt.confirm(); + await pause(); + + // Continue with the debug session + await continueDebugging(2, 30); + }); + + it('SFDX: Launch Apex Replay Debugger with Current File - log file', async () => { + logTestStart(testSetup, 'ApexReplayDebugger - SFDX: Launch Apex Replay Debugger with Current File - log file'); + + const workbench = getWorkbench(); + await getTextEditor(workbench, logFileTitle); + + // Run SFDX: Launch Apex Replay Debugger with Current File + await executeQuickPick('SFDX: Launch Apex Replay Debugger with Current File', Duration.seconds(3)); + + // Continue with the debug session + await continueDebugging(2, 30); + }); + + it('SFDX: Launch Apex Replay Debugger with Current File - test class', async () => { + logTestStart(testSetup, 'ApexReplayDebugger - SFDX: Launch Apex Replay Debugger with Current File - test class'); + + // Run SFDX: Launch Apex Replay Debugger with Current File + const workbench = getWorkbench(); + await getTextEditor(workbench, 'ExampleApexClassTest.cls'); + await executeQuickPick('SFDX: Launch Apex Replay Debugger with Current File', Duration.seconds(10)); + + // Continue with the debug session + await continueDebugging(2, 30); + + await verifyNotificationWithRetry(/Debug Test\(s\) successfully ran/, Duration.TEN_MINUTES); + }); + + it('Run the Anonymous Apex Debugger using the Command Palette', async () => { + logTestStart(testSetup, 'ApexReplayDebugger - Run the Anonymous Apex Debugger using the Command Palette'); + + // Clear output before running the command + await clearOutputView(); + + // Create anonymous apex file + await createAnonymousApexFile(); + + // Run SFDX: Launch Apex Replay Debugger with Editor Contents", using the Command Palette. + await executeQuickPick('SFDX: Execute Anonymous Apex with Editor Contents', Duration.seconds(10)); + + await verifyNotificationWithRetry(/Execute Anonymous Apex successfully ran/, Duration.TEN_MINUTES); + + // Verify content on vscode's Output section + const outputPanelText = await attemptToFindOutputPanelText('Apex', 'Starting Execute Anonymous Apex', 10); + expect(outputPanelText).to.contain('Compiled successfully.'); + expect(outputPanelText).to.contain('Executed successfully.'); + expect(outputPanelText).to.contain('|EXECUTION_STARTED'); + expect(outputPanelText).to.contain('|EXECUTION_FINISHED'); + expect(outputPanelText).to.contain('ended Execute Anonymous Apex'); + }); + + it('SFDX: Turn Off Apex Debug Log for Replay Debugger', async () => { + logTestStart(testSetup, 'ApexReplayDebugger - SFDX: Turn Off Apex Debug Log for Replay Debugger'); + + // Run SFDX: Turn Off Apex Debug Log for Replay Debugger + await clearOutputView(); + prompt = await executeQuickPick('SFDX: Turn Off Apex Debug Log for Replay Debugger', Duration.seconds(1)); + + // Look for the success notification that appears which says, "SFDX: Turn Off Apex Debug Log for Replay Debugger successfully ran". + await verifyNotificationWithRetry( + /SFDX: Turn Off Apex Debug Log for Replay Debugger successfully ran/, + Duration.TEN_MINUTES + ); + + // Verify content on vscode's Output section + const outputPanelText = await attemptToFindOutputPanelText( + 'Salesforce CLI', + 'Starting SFDX: Turn Off Apex Debug Log for Replay Debugger', + 10 + ); + expect(outputPanelText).to.contain('ended with exit code 0'); + }); + + after('Tear down and clean up the testing environment', async () => { + log('ApexReplayDebugger - Tear down and clean up the testing environment'); + await testSetup?.tearDown(); + }); +}); diff --git a/packages/salesforcedx-vscode-automation-tests/test/specs/auraLsp.e2e.ts b/packages/salesforcedx-vscode-automation-tests/test/specs/auraLsp.e2e.ts new file mode 100644 index 0000000000..c6e375d6ce --- /dev/null +++ b/packages/salesforcedx-vscode-automation-tests/test/specs/auraLsp.e2e.ts @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2023, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ +import { Duration, TestReqConfig, ProjectShapeOption } from '@salesforce/salesforcedx-vscode-test-tools/lib/src/core'; +import { log, pause } from '@salesforce/salesforcedx-vscode-test-tools/lib/src/core/miscellaneous'; +import { createAura } from '@salesforce/salesforcedx-vscode-test-tools/lib/src/salesforce-components'; +import { TestSetup } from '@salesforce/salesforcedx-vscode-test-tools/lib/src/testSetup'; +import { + executeQuickPick, + getOutputViewText, + getTextEditor, + getWorkbench, + reloadWindow +} from '@salesforce/salesforcedx-vscode-test-tools/lib/src/ui-interaction'; +import { expect } from 'chai'; +import { By, after } from 'vscode-extension-tester'; +import { logTestStart } from '../utils/loggingHelper'; + +describe('Aura LSP', () => { + let testSetup: TestSetup; + + const testReqConfig: TestReqConfig = { + projectConfig: { + projectShape: ProjectShapeOption.NEW + }, + isOrgRequired: false, + testSuiteSuffixName: 'AuraLsp' + }; + + before('Set up the testing environment', async () => { + log('AuraLsp - Set up the testing environment'); + testSetup = await TestSetup.setUp(testReqConfig); + + // Create Aura Component + await createAura('aura1'); + + // Reload the VSCode window to allow the Aura Component to be indexed by the Aura Language Server + await reloadWindow(Duration.seconds(20)); + }); + + // Since tests are sequential, we need to skip the rest of the tests if one fails + beforeEach(function () { + if (this.currentTest?.parent?.tests.some(test => test.state === 'failed')) { + this.skip(); + } + }); + + it('Verify LSP finished indexing', async () => { + logTestStart(testSetup, 'Verify LSP finished indexing'); + + // Get output text from the LSP + const outputViewText = await getOutputViewText('Aura Language Server'); + log('Output view text'); + log(outputViewText); + expect(outputViewText).to.contain('language server started'); + }); + + it('Go to Definition', async () => { + logTestStart(testSetup, 'Go to Definition'); + // Get open text editor + const workbench = await getWorkbench(); + const textEditor = await getTextEditor(workbench, 'aura1.cmp'); + + // Move cursor to the middle of "simpleNewContact" + await textEditor.moveCursor(8, 15); + + // Go to definition through F12 + await executeQuickPick('Go to Definition', Duration.seconds(2)); + + // Verify 'Go to definition' + const definition = await textEditor.getCoordinates(); + expect(definition[0]).to.equal(3); + expect(definition[1]).to.equal(27); + }); + + it('Autocompletion', async () => { + logTestStart(testSetup, 'Autocompletion'); + // Get open text editor + const workbench = await getWorkbench(); + const textEditor = await getTextEditor(workbench, 'aura1.cmp'); + await textEditor.typeTextAt(2, 1, ''); + await textEditor.save(); + await pause(Duration.seconds(1)); + const line3Text = await textEditor.getTextAtLine(2); + expect(line3Text).to.include('aura:application'); + }); + + after('Tear down and clean up the testing environment', async () => { + log(`${testSetup.testSuiteSuffixName} - Tear down and clean up the testing environment`); + await testSetup?.tearDown(); + }); +}); diff --git a/packages/salesforcedx-vscode-automation-tests/test/specs/authentication.e2e.ts b/packages/salesforcedx-vscode-automation-tests/test/specs/authentication.e2e.ts new file mode 100644 index 0000000000..181f1f1117 --- /dev/null +++ b/packages/salesforcedx-vscode-automation-tests/test/specs/authentication.e2e.ts @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2023, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +import { + Duration, + pause, + ProjectShapeOption, + TestReqConfig +} from '@salesforce/salesforcedx-vscode-test-tools/lib/src/core'; +import { EnvironmentSettings } from '@salesforce/salesforcedx-vscode-test-tools/lib/src/environmentSettings'; +import { verifyNotificationWithRetry } from '@salesforce/salesforcedx-vscode-test-tools/lib/src/retryUtils'; +import { + authorizeDevHub, + createDefaultScratchOrg +} from '@salesforce/salesforcedx-vscode-test-tools/lib/src/salesforce-components'; +import { TestSetup } from '@salesforce/salesforcedx-vscode-test-tools/lib/src/testSetup'; +import { + attemptToFindOutputPanelText, + executeQuickPick, + findQuickPickItem, + getStatusBarItemWhichIncludes, + getWorkbench +} from '@salesforce/salesforcedx-vscode-test-tools/lib/src/ui-interaction'; +import { expect } from 'chai'; +import { By, InputBox, after } from 'vscode-extension-tester'; + +describe('Authentication', () => { + let scratchOrgAliasName: string; + let testSetup: TestSetup; + const testReqConfig: TestReqConfig = { + projectConfig: { + projectShape: ProjectShapeOption.NEW + }, + isOrgRequired: false, + testSuiteSuffixName: 'Authentication' + }; + + before('Set up the testing environment', async () => { + testSetup = await TestSetup.setUp(testReqConfig); + }); + + // Since tests are sequential, we need to skip the rest of the tests if one fails + beforeEach(function () { + if (this.currentTest?.parent?.tests.some(test => test.state === 'failed')) { + this.skip(); + } + }); + + it('Run SFDX: Authorize a Dev Hub', async () => { + // In the initial state, the org picker button should be set to "No Default Org Set". + const noDefaultOrgSetItem = await getStatusBarItemWhichIncludes('No Default Org Set'); + expect(noDefaultOrgSetItem).to.not.be.undefined; + + // This is essentially the "SFDX: Authorize a Dev Hub" command, but using the CLI and an auth file instead of the UI. + await authorizeDevHub(testSetup); + }); + + it('Run SFDX: Set a Default Org', async () => { + // This is "SFDX: Set a Default Org", using the button in the status bar. + // Could also run the command, "SFDX: Set a Default Org" but this exercises more UI elements. + + // Click on "No default Org Set" (in the bottom bar). + const workbench = await getWorkbench(); + const changeDefaultOrgSetItem = await getStatusBarItemWhichIncludes('No Default Org Set'); + expect(changeDefaultOrgSetItem).to.not.be.undefined; + await changeDefaultOrgSetItem.click(); + await pause(Duration.seconds(5)); + + const orgPickerOptions = await workbench.findElements( + By.css( + 'div.monaco-list#quickInput_list > div.monaco-scrollable-element > div.monaco-list-rows > div.monaco-list-row' + ) + ); + // In the drop down menu that appears, verify the SFDX auth org commands are present... + const expectedSfdxCommands = [ + ' SFDX: Authorize an Org', + ' SFDX: Authorize a Dev Hub', + ' SFDX: Create a Default Scratch Org...', + ' SFDX: Authorize an Org using Session ID', + ' SFDX: Remove Deleted and Expired Orgs' + ]; + const foundSfdxCommands: string[] = []; + for (const quickPick of orgPickerOptions) { + const label = (await quickPick.getAttribute('aria-label')).slice(5); + if (expectedSfdxCommands.includes(label)) { + foundSfdxCommands.push(label); + } + } + + if (expectedSfdxCommands.length !== foundSfdxCommands.length) { + // Something is wrong - the count of matching menus isn't what we expected. + expectedSfdxCommands.forEach(expectedSfdxCommand => { + expect(foundSfdxCommands).to.contain(expectedSfdxCommand); + }); + } + + // In the drop down menu that appears, select "vscodeOrg - user_name". + const environmentSettings = EnvironmentSettings.getInstance(); + const devHubAliasName = environmentSettings.devHubAliasName; + const devHubUserName = environmentSettings.devHubUserName; + const inputBox = await InputBox.create(); + await inputBox.selectQuickPick(`${devHubAliasName} - ${devHubUserName}`); + + // Need to pause here for the "set a default org" command to finish. + await pause(Duration.seconds(5)); + + // Look for the notification that appears which says, "SFDX: Set a Default Org successfully ran". + await verifyNotificationWithRetry(/SFDX: Set a Default Org successfully ran/, Duration.TEN_MINUTES); + + const expectedOutputWasFound = await attemptToFindOutputPanelText( + 'Salesforce CLI', + `target-org ${devHubAliasName} true`, + 5 + ); + expect(expectedOutputWasFound).to.not.be.undefined; + + // Look for "vscodeOrg" in the status bar. + const statusBar = workbench.getStatusBar(); + const vscodeOrgItem = await statusBar.getItem(`plug ${devHubAliasName}, Change Default Org`); + expect(vscodeOrgItem).to.not.be.undefined; + }); + + it('Run SFDX: Create a Default Scratch Org', async () => { + try { + const orgAlias = await createDefaultScratchOrg(); + expect(orgAlias).to.be.a('string'); + scratchOrgAliasName = orgAlias; + } catch (error) { + throw new Error(`Failed to create scratch org: ${String(error)}`); + } + }); + + it('Run SFDX: Set the Scratch Org As the Default Org', async () => { + const inputBox = await executeQuickPick('SFDX: Set a Default Org', Duration.seconds(10)); + + const scratchOrgQuickPickItemWasFound = await findQuickPickItem(inputBox, scratchOrgAliasName, false, true); + expect(scratchOrgQuickPickItemWasFound).to.equal(true); + + await pause(Duration.seconds(3)); + + await verifyNotificationWithRetry(/SFDX: Set a Default Org successfully ran/, Duration.TEN_MINUTES); + + // Look for the org's alias name in the list of status bar items. + const scratchOrgStatusBarItem = await getStatusBarItemWhichIncludes(scratchOrgAliasName); + expect(scratchOrgStatusBarItem).to.not.be.undefined; + }); + + after('Tear down and clean up the testing environment', async () => { + await testSetup?.tearDown(); + }); +}); diff --git a/packages/salesforcedx-vscode-automation-tests/test/specs/createOasDoc.e2e.ts b/packages/salesforcedx-vscode-automation-tests/test/specs/createOasDoc.e2e.ts new file mode 100644 index 0000000000..6f9bef085b --- /dev/null +++ b/packages/salesforcedx-vscode-automation-tests/test/specs/createOasDoc.e2e.ts @@ -0,0 +1,658 @@ +/* + * Copyright (c) 2025, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +import { + TestReqConfig, + ProjectShapeOption, + pause, + createCommand, + openFile, + Duration +} from '@salesforce/salesforcedx-vscode-test-tools/lib/src/core'; +import { log } from '@salesforce/salesforcedx-vscode-test-tools/lib/src/core/miscellaneous'; +import { + retryOperation, + verifyNotificationWithRetry +} from '@salesforce/salesforcedx-vscode-test-tools/lib/src/retryUtils'; +import { + createApexClass, + runAndValidateCommand +} from '@salesforce/salesforcedx-vscode-test-tools/lib/src/salesforce-components'; +import { setSettingValue } from '@salesforce/salesforcedx-vscode-test-tools/lib/src/system-operations'; +import { + verifyExtensionsAreRunning, + getExtensionsToVerifyActive +} from '@salesforce/salesforcedx-vscode-test-tools/lib/src/testing'; +import { TestSetup } from '@salesforce/salesforcedx-vscode-test-tools/lib/src/testSetup'; +import { + executeQuickPick, + reloadWindow, + getWorkbench, + getStatusBarItemWhichIncludes, + getTextEditor, + countProblemsInProblemsTab, + clearOutputView, + clickButtonOnModalDialog, + isCommandAvailable, + overrideTextInFile, + zoom, + zoomReset +} from '@salesforce/salesforcedx-vscode-test-tools/lib/src/ui-interaction'; +import { expect } from 'chai'; +import * as path from 'node:path'; +import { + InputBox, + QuickOpenBox, + ExtensionsViewSection, + ActivityBar, + after, + By, + ExtensionsViewItem, + DefaultTreeItem +} from 'vscode-extension-tester'; +import { + getIdealCaseManagerOASDoc, + getSfdxProjectJson, + getIdealSimpleAccountResourceYaml, + getIdealSimpleAccountResourceXml +} from '../testData/oasDocs'; +import { caseManagerClassText, simpleAccountResourceClassText } from '../testData/sampleClassData'; +import { logTestStart } from '../utils/loggingHelper'; + +describe('Create OpenAPI v3 Specifications', () => { + let prompt: QuickOpenBox | InputBox; + let testSetup: TestSetup; + const testReqConfig: TestReqConfig = { + projectConfig: { + projectShape: ProjectShapeOption.NEW + }, + isOrgRequired: true, + testSuiteSuffixName: 'CreateOASDoc' + }; + + before('Set up the testing environment', async () => { + log('\nCreateOASDoc - Set up the testing environment'); + testSetup = await TestSetup.setUp(testReqConfig); + + // Set SF_LOG_LEVEL to 'debug' to get the logs in the 'llm_logs' folder when the OAS doc is generated + await setSettingValue('salesforcedx-vscode-core.SF_LOG_LEVEL', 'debug', true); + + // Set a telemetry tag to distinguish it as an E2E test run + await setSettingValue('salesforcedx-vscode-core.telemetry-tag', 'e2e-test', true); + + // Disable preview mode for opening editors + await setSettingValue('workbench.editor.enablePreview', false, false); + await executeQuickPick('View: Close Editor'); + await reloadWindow(); + + // Install A4D extension + const extensionsView = await (await new ActivityBar().getViewControl('Extensions'))?.openView(); + await pause(Duration.seconds(5)); + const extensionsList = await extensionsView?.getContent().getSection('Installed'); + if (!(extensionsList instanceof ExtensionsViewSection)) { + throw new Error(`Expected ExtensionsViewSection but got different section type: ${typeof extensionsList}`); + } + const a4dExtension = await extensionsList?.findItem('Agentforce for Developers'); + await a4dExtension?.install(); + await executeQuickPick('View: Close Editor'); + + // Create the Apex class which the decomposed OAS doc will be generated from + await retryOperation( + () => createApexClass('CaseManager', caseManagerClassText), + 2, + 'CreateOASDoc - Error creating Apex class CaseManager' + ); + + // Create the Apex class which the composed OAS doc will be generated from + await retryOperation( + () => createApexClass('SimpleAccountResource', simpleAccountResourceClassText), + 2, + 'CreateOASDoc - Error creating Apex class SimpleAccountResource' + ); + + // Create an ineligible Apex class (the default Apex class from the template is a good example) + await retryOperation( + () => createCommand('Apex Class', 'IneligibleApexClass', 'classes', 'cls'), + 2, + 'CreateOASDoc - Error creating Apex class IneligibleApexClass' + ); + + // Push source to org + await executeQuickPick('SFDX: Push Source to Default Org and Ignore Conflicts', Duration.seconds(1)); + + await verifyNotificationWithRetry( + /SFDX: Push Source to Default Org and Ignore Conflicts successfully ran/, + Duration.TEN_MINUTES + ); + + log(`${testSetup.testSuiteSuffixName} - Verify LSP finished indexing`); + + // Get Apex LSP Status Bar + const statusBar = await getStatusBarItemWhichIncludes('Editor Language Status'); + await statusBar.click(); + expect(await statusBar.getAttribute('aria-label')).to.contain('Indexing complete'); + }); + + it('Try to generate OAS doc from an ineligible Apex class', async () => { + logTestStart(testSetup, 'Try to generate OAS doc from an ineligible Apex class'); + await openFile( + path.join(testSetup.projectFolderPath!, 'force-app', 'main', 'default', 'classes', 'IneligibleApexClass.cls') + ); + if (process.platform === 'win32') { + await reloadWindow(); + await verifyExtensionsAreRunning(getExtensionsToVerifyActive()); + const workbench = getWorkbench(); + await getTextEditor(workbench, 'IneligibleApexClass.cls'); + } else { + await pause(Duration.seconds(5)); + } + + await executeQuickPick('SFDX: Create OpenAPI Document from This Class (Beta)'); + + await verifyNotificationWithRetry( + /Failed to create OpenAPI Document: The Apex Class IneligibleApexClass is not valid for OpenAPI document generation\./, + Duration.TEN_MINUTES + ); + }); + + describe('Composed mode', () => { + it('Generate OAS doc from a valid Apex class using command palette - Composed mode, initial generation', async () => { + logTestStart( + testSetup, + 'Generate OAS doc from a valid Apex class using command palette - Composed mode, initial generation' + ); + await executeQuickPick('View: Close All Editors'); + await openFile( + path.join(testSetup.projectFolderPath!, 'force-app', 'main', 'default', 'classes', 'CaseManager.cls') + ); + await pause(Duration.seconds(5)); + prompt = await executeQuickPick('SFDX: Create OpenAPI Document from This Class (Beta)'); + await prompt.confirm(); + + await verifyNotificationWithRetry(/OpenAPI Document created for class: CaseManager\./); + + // Verify the generated OAS doc is open in the Editor View + await executeQuickPick('View: Open Last Editor in Group'); + await pause(Duration.seconds(2)); // Allow time for editor to switch + const workbench = getWorkbench(); + const editorView = workbench.getEditorView(); + + await retryOperation( + async () => { + const activeTab = await editorView.getActiveTab(); + if (!activeTab) { + throw new Error('No active tab found'); + } + const title = await activeTab.getTitle(); + expect(title).to.equal('CaseManager.externalServiceRegistration-meta.xml'); + }, + 5, + 'CreateOASDoc - Error waiting for CaseManager tab' + ); + }); + + it('Check for warnings and errors in the Problems Tab', async () => { + logTestStart(testSetup, 'Check for warnings and errors in the Problems Tab'); + await countProblemsInProblemsTab(0); + }); + + it('Fix the OAS doc to get rid of the problems in the Problems Tab', async () => { + // NOTE: The "fix" is actually replacing the OAS doc with the ideal solution + logTestStart(testSetup, 'Fix the OAS doc to get rid of the problems in the Problems Tab'); + + const workbench = getWorkbench(); + const textEditor = await getTextEditor(workbench, 'CaseManager.externalServiceRegistration-meta.xml'); + const xmlText = getIdealCaseManagerOASDoc(); + await overrideTextInFile(textEditor, xmlText); + }); + + it('Revalidate the OAS doc', async () => { + logTestStart(testSetup, 'Revalidate the OAS doc'); + await executeQuickPick('SFDX: Validate OpenAPI Document (Beta)'); + await verifyNotificationWithRetry( + /Validated OpenAPI Document CaseManager.externalServiceRegistration-meta.xml successfully/ + ); + + const problems = await countProblemsInProblemsTab(2); + expect(await problems[0].getLabel()).to.equal('CaseManager.externalServiceRegistration-meta.xml'); + expect(await problems[1].getLabel()).to.equal('operations.responses.content should be application/json'); + }); + + it('Deploy the composed ESR to the org', async () => { + logTestStart(testSetup, 'Deploy the composed ESR to the org'); + const workbench = getWorkbench(); + // Clear the Output view first. + await clearOutputView(Duration.seconds(2)); + await getTextEditor(workbench, 'CaseManager.externalServiceRegistration-meta.xml'); + await runAndValidateCommand('Deploy', 'to', 'ST', 'ExternalServiceRegistration', 'CaseManager', 'Created '); + }); + + it('Generate OAS doc from a valid Apex class using command palette - Composed mode, manual merge', async () => { + logTestStart( + testSetup, + 'Generate OAS doc from a valid Apex class using command palette - Composed mode, manual merge' + ); + await executeQuickPick('View: Close All Editors'); + await openFile( + path.join(testSetup.projectFolderPath!, 'force-app', 'main', 'default', 'classes', 'CaseManager.cls') + ); + await pause(Duration.seconds(5)); + prompt = await executeQuickPick('SFDX: Create OpenAPI Document from This Class (Beta)'); + await prompt.confirm(); + + // Click the Manual Merge button on the popup + await clickButtonOnModalDialog('Manually merge with existing ESR'); + + await verifyNotificationWithRetry( + /A new OpenAPI Document class CaseManager_\d{8}_\d{6} is created for CaseManager\. Manually merge the two files using the diff editor\./ + ); + + // Verify the generated OAS doc and the diff editor are both open in the Editor View + await executeQuickPick('View: Open First Editor in Group'); + const workbench = getWorkbench(); + await executeQuickPick('Explorer: Focus on Open Editors View'); + const sidebar = await workbench.getSideBar().wait(); + const content = await sidebar.getContent().wait(); + const openEditorsView = await content.getSection('Open Editors'); + + const openTabs = await openEditorsView?.getVisibleItems(); + expect(openTabs?.length).to.equal(3); + + // Locate each tab in the Open Editors View using the selector (there is a bug in vscode-extension-tester) + const firstTab = await openEditorsView.findElement(By.css('.monaco-list-row:nth-child(1)')); + const firstTabLabel = await firstTab.getText(); + expect(firstTabLabel).to.match(/CaseManager\.cls/); + + const secondTab = await openEditorsView.findElement(By.css('.monaco-list-row:nth-child(2)')); + const secondTabLabel = await secondTab.getText(); + expect(secondTabLabel).to.match(/CaseManager_\d{8}_\d{6}\.externalServiceRegistration-meta\.xml/); + + const thirdTab = await openEditorsView.findElement(By.css('.monaco-list-row:nth-child(3)')); + const thirdTabLabel = await thirdTab.getText(); + expect(thirdTabLabel).to.match(/Manual Diff of ESR XML Files/); + }); + }); + + describe('Decomposed mode', () => { + it('Add "decomposeExternalServiceRegistrationBeta" setting to sfdx-project.json', async () => { + logTestStart(testSetup, 'Add "decomposeExternalServiceRegistrationBeta" setting to sfdx-project.json'); + const workbench = getWorkbench(); + await openFile(path.join(testSetup.projectFolderPath!, 'sfdx-project.json')); + const textEditor = await getTextEditor(workbench, 'sfdx-project.json'); + const sfdxProjectJson = getSfdxProjectJson(); + await overrideTextInFile(textEditor, sfdxProjectJson); + await executeQuickPick('View: Close All Editors'); + await reloadWindow(); + }); + + it('Generate OAS doc from a valid Apex class using command palette - Decomposed mode, initial generation', async () => { + logTestStart( + testSetup, + 'Generate OAS doc from a valid Apex class using command palette - Decomposed mode, initial generation' + ); + await executeQuickPick('View: Close All Editors'); + await openFile( + path.join(testSetup.projectFolderPath!, 'force-app', 'main', 'default', 'classes', 'SimpleAccountResource.cls') + ); + await pause(Duration.seconds(5)); + prompt = await executeQuickPick('SFDX: Create OpenAPI Document from This Class (Beta)'); + await prompt.confirm(); + + await verifyNotificationWithRetry(/OpenAPI Document created for class: SimpleAccountResource\./); + + // Zoom out the editor view + await zoom('Out', 2); // Zoom out the editor view + + // Verify both the YAML and XML files of the generated OAS doc are open in the Editor View + await retryOperation( + async () => { + await pause(Duration.seconds(3)); // Allow time for files to be opened after generation + const workbench = getWorkbench(); + const editorView = workbench.getEditorView(); + + // Get all open tabs + const openTabs = await editorView.getOpenTabs(); + if (!openTabs || openTabs.length === 0) { + throw new Error('No tabs are open in the editor view'); + } + + // Collect all tab titles for verification + const tabTitles = []; + for (const tab of openTabs) { + try { + const title = await tab.getTitle(); + tabTitles.push(title); + } catch (error) { + tabTitles.push(`[Error: ${String(error)}]`); + } + } + + // Check if both expected files are open + const hasYamlTab = tabTitles.includes('SimpleAccountResource.yaml'); + const hasXmlTab = tabTitles.includes('SimpleAccountResource.externalServiceRegistration-meta.xml'); + + if (!hasYamlTab && !hasXmlTab) { + throw new Error(`Neither YAML nor XML tab found. Open tabs: [${tabTitles.join(', ')}]`); + } else if (!hasYamlTab) { + throw new Error(`YAML tab not found. Open tabs: [${tabTitles.join(', ')}]`); + } else if (!hasXmlTab) { + throw new Error(`XML tab not found. Open tabs: [${tabTitles.join(', ')}]`); + } + + // Both tabs are open - success! + await zoomReset(); + }, + 5, + 'CreateOASDoc - Error verifying generated files are open' + ); + }); + + it('Check for warnings and errors in the Problems Tab', async () => { + logTestStart(testSetup, 'Check for warnings and errors in the Problems Tab'); + await countProblemsInProblemsTab(0); + }); + + it('Fix the OAS doc to get rid of the problems in the Problems Tab', async () => { + // NOTE: The "fix" is actually replacing the OAS doc with the ideal solution from the EMU repo + logTestStart(testSetup, 'Fix the OAS doc to get rid of the problems in the Problems Tab'); + + const workbench = getWorkbench(); + let textEditor = await getTextEditor(workbench, 'SimpleAccountResource.yaml'); + const yamlText = getIdealSimpleAccountResourceYaml(); + await overrideTextInFile(textEditor, yamlText); + + textEditor = await getTextEditor(workbench, 'SimpleAccountResource.externalServiceRegistration-meta.xml'); + const xmlText = getIdealSimpleAccountResourceXml(); + await overrideTextInFile(textEditor, xmlText); + }); + + it('Revalidate the OAS doc', async () => { + logTestStart(testSetup, 'Revalidate the OAS doc'); + const workbench = getWorkbench(); + const textEditor = await getTextEditor(workbench, 'SimpleAccountResource.yaml'); + + // Use context menu for Windows and Ubuntu, command palette for Mac + if (process.platform !== 'darwin') { + const contextMenu = await textEditor.openContextMenu(); + await contextMenu.select('SFDX: Validate OpenAPI Document (Beta)'); + } else { + await executeQuickPick('SFDX: Validate OpenAPI Document (Beta)'); + } + await verifyNotificationWithRetry(/Validated OpenAPI Document SimpleAccountResource.yaml successfully/); + + await countProblemsInProblemsTab(0); + }); + + it('Deploy the decomposed ESR to the org', async () => { + logTestStart(testSetup, 'Deploy the decomposed ESR to the org'); + const workbench = getWorkbench(); + // Clear the Output view first. + await clearOutputView(Duration.seconds(2)); + await getTextEditor(workbench, 'SimpleAccountResource.externalServiceRegistration-meta.xml'); + await runAndValidateCommand( + 'Deploy', + 'to', + 'ST', + 'ExternalServiceRegistration', + 'SimpleAccountResource', + 'Created ' + ); + }); + + it('Generate OAS doc from a valid Apex class using context menu in Editor View - Decomposed mode, overwrite', async () => { + logTestStart( + testSetup, + 'Generate OAS doc from a valid Apex class using context menu in Editor View - Decomposed mode, overwrite' + ); + await executeQuickPick('View: Close All Editors'); + await openFile( + path.join(testSetup.projectFolderPath!, 'force-app', 'main', 'default', 'classes', 'SimpleAccountResource.cls') + ); + await pause(Duration.seconds(5)); + + // Use context menu for Windows and Ubuntu, command palette for Mac + if (process.platform !== 'darwin') { + log('Not Mac - can use context menu'); + const wrkbench = getWorkbench(); + const textEditor = await getTextEditor(wrkbench, 'SimpleAccountResource.cls'); + const contextMenu = await textEditor.openContextMenu(); + const menu = await contextMenu.select('SFDX: Create OpenAPI Document from This Class (Beta)'); + // Wait for the command palette prompt to appear + if (menu) { + const result = await getQuickOpenBoxOrInputBox(); + if (!result) { + throw new Error('Failed to get QuickOpenBox or InputBox'); + } + prompt = result; + } + } else { + log('Mac - must use command palette'); + prompt = await executeQuickPick('SFDX: Create OpenAPI Document from This Class (Beta)'); + } + await prompt.confirm(); + + // Click the Overwrite button on the popup + await clickButtonOnModalDialog('Overwrite'); + + await verifyNotificationWithRetry(/OpenAPI Document created for class: SimpleAccountResource\./); + + await zoom('Out', 2); // Zoom out the editor view + + // Verify both the YAML and XML files of the generated OAS doc are open in the Editor View + await retryOperation( + async () => { + await pause(Duration.seconds(3)); // Allow time for files to be opened after generation + const workbench = getWorkbench(); + const editorView = workbench.getEditorView(); + + // Get all open tabs + const openTabs = await editorView.getOpenTabs(); + if (!openTabs || openTabs.length === 0) { + throw new Error('No tabs are open in the editor view'); + } + + // Collect all tab titles for verification + const tabTitles = []; + for (const tab of openTabs) { + try { + const title = await tab.getTitle(); + tabTitles.push(title); + } catch (error) { + tabTitles.push(`[Error: ${String(error)}]`); + } + } + + // Check if both expected files are open + const hasYamlTab = tabTitles.includes('SimpleAccountResource.yaml'); + const hasXmlTab = tabTitles.includes('SimpleAccountResource.externalServiceRegistration-meta.xml'); + + if (!hasYamlTab && !hasXmlTab) { + throw new Error(`Neither YAML nor XML tab found. Open tabs: [${tabTitles.join(', ')}]`); + } else if (!hasYamlTab) { + throw new Error(`YAML tab not found. Open tabs: [${tabTitles.join(', ')}]`); + } else if (!hasXmlTab) { + throw new Error(`XML tab not found. Open tabs: [${tabTitles.join(', ')}]`); + } + + // Both tabs are open - success! + await zoomReset(); + }, + 5, + 'CreateOASDoc - Error verifying generated files are open (overwrite mode)' + ); + }); + + it('Generate OAS doc from a valid Apex class using context menu in Explorer View - Decomposed mode, manual merge', async () => { + logTestStart( + testSetup, + 'Generate OAS doc from a valid Apex class using context menu in Explorer View - Decomposed mode, manual merge' + ); + await executeQuickPick('View: Close All Editors'); + await openFile( + path.join(testSetup.projectFolderPath!, 'force-app', 'main', 'default', 'classes', 'SimpleAccountResource.cls') + ); + await pause(Duration.seconds(5)); + + // Use context menu for Windows and Ubuntu, command palette for Mac + if (process.platform !== 'darwin') { + log('Not Mac - can use context menu'); + await executeQuickPick('File: Focus on Files Explorer'); + await pause(Duration.seconds(2)); + const wrkbench = getWorkbench(); + const workbenchSidebar = await wrkbench.getSideBar().wait(); + const cont = await workbenchSidebar.getContent().wait(); + const treeViewSection = await cont.getSection(testSetup.tempProjectName); + if (!treeViewSection) { + throw new Error( + 'In verifyProjectLoaded(), getSection() returned a treeViewSection with a value of null (or undefined)' + ); + } + + // The force-app/main/default and classes folders are already expanded, so we can find the file directly + const simpleAccountResourceFile = await treeViewSection.findItem('SimpleAccountResource.cls'); + if (!(simpleAccountResourceFile instanceof DefaultTreeItem)) { + throw new Error(`Expected DefaultTreeItem but got different item type: ${typeof simpleAccountResourceFile}`); + } + const contextMenu = await simpleAccountResourceFile.openContextMenu(); + const menu = await contextMenu.select('SFDX: Create OpenAPI Document from This Class (Beta)'); + + // Wait for the command palette prompt to appear + if (menu) { + const result = await getQuickOpenBoxOrInputBox(); + if (!result) { + throw new Error('Failed to get QuickOpenBox or InputBox'); + } + prompt = result; + } + } else { + log('Mac - must use command palette'); + prompt = await executeQuickPick('SFDX: Create OpenAPI Document from This Class (Beta)'); + } + await prompt.confirm(); + + // Click the Manual Merge button on the popup + await clickButtonOnModalDialog('Manually merge with existing ESR'); + + await verifyNotificationWithRetry( + /A new OpenAPI Document class SimpleAccountResource_\d{8}_\d{6} is created for SimpleAccountResource\. Manually merge the two files using the diff editor\./ + ); + + // Verify the generated OAS doc and the diff editor are both open in the Editor View + await executeQuickPick('View: Open First Editor in Group'); + const workbench = getWorkbench(); + await executeQuickPick('Explorer: Focus on Open Editors View'); + const sidebar = await workbench.getSideBar().wait(); + const content = await sidebar.getContent().wait(); + const openEditorsView = await content.getSection('Open Editors'); + + const openTabs = await openEditorsView?.getVisibleItems(); + expect(openTabs?.length).to.equal(5); + + // Locate each tab in the Open Editors View using the selector (there is a bug in vscode-extension-tester) + const firstTab = await openEditorsView.findElement(By.css('.monaco-list-row:nth-child(1)')); + const firstTabLabel = await firstTab.getText(); + expect(firstTabLabel).to.match(/SimpleAccountResource\.cls/); + + const secondTab = await openEditorsView.findElement(By.css('.monaco-list-row:nth-child(2)')); + const secondTabLabel = await secondTab.getText(); + expect(secondTabLabel).to.match(/SimpleAccountResource_\d{8}_\d{6}\.externalServiceRegistration-meta\.xml/); + + const thirdTab = await openEditorsView.findElement(By.css('.monaco-list-row:nth-child(3)')); + const thirdTabLabel = await thirdTab.getText(); + expect(thirdTabLabel).to.match(/SimpleAccountResource_\d{8}_\d{6}\.yaml/); + + const fourthTab = await openEditorsView.findElement(By.css('.monaco-list-row:nth-child(4)')); + const fourthTabLabel = await fourthTab.getText(); + expect(fourthTabLabel).to.match(/Manual Diff of ESR XML Files/); + + const fifthTab = await openEditorsView.findElement(By.css('.monaco-list-row:nth-child(5)')); + const fifthTabLabel = await fifthTab.getText(); + expect(fifthTabLabel).to.match(/Manual Diff of ESR YAML Files/); + }); + }); + + describe('Disable A4D extension and ensure the commands to generate and validate OAS docs are not present', () => { + it('Disable A4D extension', async () => { + logTestStart(testSetup, 'Disable A4D extension'); + + const extensionsView = await (await new ActivityBar().getViewControl('Extensions'))?.openView(); + await pause(Duration.seconds(5)); + const extensionsList = await extensionsView?.getContent().getSection('Installed'); + if (!(extensionsList instanceof ExtensionsViewSection)) { + throw new Error(`Expected ExtensionsViewSection but got different section type: ${typeof extensionsList}`); + } + const a4dExtension = await extensionsList?.findItem('Agentforce for Developers'); + if (!(a4dExtension instanceof ExtensionsViewItem)) { + throw new Error(`Expected ExtensionsViewItem but got different item type: ${typeof a4dExtension}`); + } + await a4dExtension.click(); + + // In the extension details view, click the Disable button + const disableButton = await a4dExtension.findElement( + By.xpath("//a[contains(@class, 'action-label') and contains(@class, 'extension-action') and text()='Disable']") + ); + await disableButton?.click(); + await pause(Duration.seconds(5)); + + // Click the Restart Extensions button + const restartExtensionsButton = await a4dExtension.findElement( + By.xpath("//a[contains(@class, 'action-label') and contains(@class, 'reload') and text()='Restart Extensions']") + ); + await restartExtensionsButton?.click(); + await pause(Duration.seconds(5)); + + // Verify the A4D extension is disabled + expect(await a4dExtension.isInstalled()).to.equal(true); + expect(await a4dExtension.isEnabled()).to.equal(false); + }); + + it('Ensure the commands to generate and validate OAS docs are not present', async () => { + logTestStart(testSetup, 'Ensure the commands to generate and validate OAS docs are not present'); + await executeQuickPick('View: Close All Editors'); + await reloadWindow(Duration.seconds(5)); + + await openFile( + path.join(testSetup.projectFolderPath!, 'force-app', 'main', 'default', 'classes', 'CaseManager.cls') + ); + await pause(Duration.seconds(5)); + expect(await isCommandAvailable('SFDX: Create OpenAPI Document from This Class (Beta)')).to.equal(false); + + await openFile( + path.join( + testSetup.projectFolderPath!, + 'force-app', + 'main', + 'default', + 'externalServiceRegistrations', + 'SimpleAccountResource.yaml' + ) + ); + await pause(Duration.seconds(5)); + expect(await isCommandAvailable('SFDX: Validate OpenAPI Document (Beta)')).to.equal(false); + }); + }); + + after('Tear down and clean up the testing environment', async () => { + log('\nCreateOASDoc - Tear down and clean up the testing environment'); + await testSetup?.tearDown(); + }); + + const getQuickOpenBoxOrInputBox = async (): Promise => { + log('Enter getQuickOpenBoxOrInputBox()'); + try { + const quickOpenBox = await new QuickOpenBox().wait(); + return quickOpenBox; + } catch { + try { + const inputBox = await new InputBox().wait(); + return inputBox; + } catch { + return undefined; + } + } + }; +}); diff --git a/packages/salesforcedx-vscode-automation-tests/test/specs/createProject.e2e.ts b/packages/salesforcedx-vscode-automation-tests/test/specs/createProject.e2e.ts new file mode 100644 index 0000000000..1bd0ac7550 --- /dev/null +++ b/packages/salesforcedx-vscode-automation-tests/test/specs/createProject.e2e.ts @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2024, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ +import { + TestReqConfig, + ProjectShapeOption, + pause, + Duration +} from '@salesforce/salesforcedx-vscode-test-tools/lib/src/core'; +import { log } from '@salesforce/salesforcedx-vscode-test-tools/lib/src/core/miscellaneous'; +import { TestSetup } from '@salesforce/salesforcedx-vscode-test-tools/lib/src/testSetup'; +import { + executeQuickPick, + waitForQuickPick, + clickFilePathOkButton, + verifyProjectLoaded +} from '@salesforce/salesforcedx-vscode-test-tools/lib/src/ui-interaction'; +import { after } from 'vscode-extension-tester'; + +describe('SFDX: Create Project', () => { + let testSetup: TestSetup; + + const testReqConfig: TestReqConfig = { + projectConfig: { + projectShape: ProjectShapeOption.NONE + }, + isOrgRequired: false, + testSuiteSuffixName: 'sfdxCreateProject' + }; + + before('Set up testing environment', async () => { + testSetup = await TestSetup.setUp(testReqConfig); + }); + + it('Execute command SFDX: Create Project', async () => { + log('Starting command SFDX: Create Project...'); + const prompt = await executeQuickPick('SFDX: Create Project'); + await waitForQuickPick(prompt, 'Standard', { + msg: 'Expected extension salesforcedx-core to be available within 5 seconds', + timeout: Duration.seconds(5) + }); + + // Enter the project's name. + await pause(Duration.seconds(1)); + await prompt.setText(testSetup.tempProjectName); + await pause(Duration.seconds(2)); + + // Press Enter/Return. + await prompt.confirm(); + + // Set the location of the project. + await prompt.setText(testSetup.tempFolderPath!); + await pause(Duration.seconds(2)); + await clickFilePathOkButton(); + await pause(Duration.seconds(2)); + }); + + it('Verify the project is created and open in the workspace', async () => { + // Verify the project was created and was loaded. + await verifyProjectLoaded(testSetup.tempProjectName); + }); + + after('Tear down and clean up the testing environment', async () => { + await testSetup?.tearDown(); + }); +}); diff --git a/packages/salesforcedx-vscode-automation-tests/test/specs/debugApexTests.e2e.ts b/packages/salesforcedx-vscode-automation-tests/test/specs/debugApexTests.e2e.ts new file mode 100644 index 0000000000..64d99b067e --- /dev/null +++ b/packages/salesforcedx-vscode-automation-tests/test/specs/debugApexTests.e2e.ts @@ -0,0 +1,264 @@ +/* + * Copyright (c) 2023, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ +import { + TestReqConfig, + ProjectShapeOption, + pause, + Duration +} from '@salesforce/salesforcedx-vscode-test-tools/lib/src/core'; +import { log } from '@salesforce/salesforcedx-vscode-test-tools/lib/src/core/miscellaneous'; +import { + retryOperation, + verifyNotificationWithRetry +} from '@salesforce/salesforcedx-vscode-test-tools/lib/src/retryUtils'; +import { createApexClassWithTest } from '@salesforce/salesforcedx-vscode-test-tools/lib/src/salesforce-components'; +import { + continueDebugging, + getTestsSection, + verifyTestItemsInSideBar +} from '@salesforce/salesforcedx-vscode-test-tools/lib/src/testing'; +import { TestSetup } from '@salesforce/salesforcedx-vscode-test-tools/lib/src/testSetup'; +import { + executeQuickPick, + getWorkbench, + getStatusBarItemWhichIncludes, + getTextEditor, + dismissAllNotifications, + waitForAndGetCodeLens +} from '@salesforce/salesforcedx-vscode-test-tools/lib/src/ui-interaction'; +import { expect } from 'chai'; +import { TreeItem, after } from 'vscode-extension-tester'; +import { logTestStart } from '../utils/loggingHelper'; + +describe('Debug Apex Tests', () => { + let testSetup: TestSetup; + const testReqConfig: TestReqConfig = { + projectConfig: { + projectShape: ProjectShapeOption.NEW + }, + isOrgRequired: true, + testSuiteSuffixName: 'DebugApexTests' + }; + + before('Set up the testing environment', async () => { + log('DebugApexTests - Set up the testing environment'); + testSetup = await TestSetup.setUp(testReqConfig); + + // Create Apex class 1 and test + await retryOperation( + () => createApexClassWithTest('ExampleApexClass1'), + 2, + 'DebugApexTests - Error creating Apex class ExampleApexClass1' + ); + + // Create Apex class 2 and test + await retryOperation( + () => createApexClassWithTest('ExampleApexClass2'), + 2, + 'DebugApexTests - Error creating Apex class ExampleApexClass2' + ); + + // Push source to org + await executeQuickPick('SFDX: Push Source to Default Org and Ignore Conflicts', Duration.seconds(1)); + + // Look for the success notification that appears which says, "SFDX: Push Source to Default Org and Ignore Conflicts successfully ran". + await verifyNotificationWithRetry( + /SFDX: Push Source to Default Org and Ignore Conflicts successfully ran/, + Duration.TEN_MINUTES + ); + }); + + it('Verify LSP finished indexing', async () => { + logTestStart(testSetup, 'Verify LSP finished indexing'); + + // Get Apex LSP Status Bar + const statusBar = await getStatusBarItemWhichIncludes('Editor Language Status'); + await statusBar.click(); + expect(await statusBar.getAttribute('aria-label')).to.contain('Indexing complete'); + }); + + it('Debug All Tests via Apex Class', async () => { + logTestStart(testSetup, 'Debug All Tests via Apex Class'); + const workbench = getWorkbench(); + const textEditor = await getTextEditor(workbench, 'ExampleApexClass1Test.cls'); + + // Dismiss all notifications. + await dismissAllNotifications(); + + // Click the "Debug All Tests" code lens at the top of the class + const debugAllTestsOption = await waitForAndGetCodeLens(textEditor, 'Debug All Tests'); + expect(debugAllTestsOption).to.not.be.undefined; + log('DebugApexTests - Debug All Tests via Apex Class - clicking debug all tests option'); + await retryOperation( + async () => { + await pause(Duration.seconds(2)); + await debugAllTestsOption!.click(); + }, + 3, + 'DebugApexTests - Error clicking debug all tests option' + ); + + await pause(Duration.seconds(20)); + + // Look for the success notification that appears which says, "Debug Test(s) successfully ran". + await verifyNotificationWithRetry(/Debug Test\(s\) successfully ran/, Duration.minutes(1)); + + // Continue with the debug session + await continueDebugging(2, 30); + }); + + it('Debug Single Test via Apex Class', async () => { + logTestStart(testSetup, 'Debug Single Test via Apex Class'); + const workbench = getWorkbench(); + const textEditor = await getTextEditor(workbench, 'ExampleApexClass2Test.cls'); + + // Dismiss all notifications. + await dismissAllNotifications(); + + // Click the "Debug Test" code lens at the top of one of the test methods + const debugTestOption = await waitForAndGetCodeLens(textEditor, 'Debug Test'); + expect(debugTestOption).to.not.be.undefined; + await retryOperation( + async () => { + await pause(Duration.seconds(2)); + await debugTestOption!.click(); + }, + 3, + 'DebugApexTests - Error clicking debug test option' + ); + + // Look for the success notification that appears which says, "Debug Test(s) successfully ran". + await verifyNotificationWithRetry(/Debug Test\(s\) successfully ran/, Duration.minutes(1)); + + // Continue with the debug session + await continueDebugging(2, 30); + }); + + it('Debug all Apex Methods on a Class via the Test Sidebar', async () => { + logTestStart(testSetup, 'Debug All Apex Methods on a Class via the Test Sidebar'); + const workbench = getWorkbench(); + + await retryOperation( + async () => { + await executeQuickPick('Testing: Focus on Apex Tests View'); + }, + 3, + 'DebugApexTests - Error focusing on apex tests view' + ); + + // Open the Test Sidebar + const apexTestsSection = await retryOperation( + async () => await getTestsSection(workbench, 'Apex Tests'), + 3, + 'DebugApexTests - Error getting apex tests section' + ); + const expectedItems = ['ExampleApexClass1Test', 'ExampleApexClass2Test']; + + await verifyTestItemsInSideBar(apexTestsSection, 'Refresh Tests', expectedItems, 4, 2); + + // Click the debug tests button that is shown to the right when you hover a test class name on the Test sidebar + let apexTestItem: TreeItem; + await retryOperation( + async () => { + await pause(Duration.seconds(2)); + await apexTestsSection.click(); + await apexTestsSection.wait(20_000); + const foundItem = await apexTestsSection.findItem('ExampleApexClass1Test'); + if (!foundItem) { + throw new Error('Expected TreeItem but got undefined'); + } + if (!(foundItem instanceof TreeItem)) { + throw new Error(`Expected TreeItem but got different item type: ${typeof foundItem}`); + } + apexTestItem = foundItem; + await apexTestItem.wait(20_000); + await apexTestItem.select(); + }, + 3, + 'DebugApexTests - Error clicking apex tests section' + ); + + const debugTestsAction = await apexTestItem!.getActionButton('Debug Tests'); + expect(debugTestsAction).to.not.be.undefined; + await retryOperation( + async () => { + await pause(Duration.seconds(2)); + await debugTestsAction?.click(); + }, + 3, + 'DebugApexTests - Error clicking debug tests action' + ); + + // Look for the success notification that appears which says, "Debug Test(s) successfully ran". + await verifyNotificationWithRetry(/Debug Test\(s\) successfully ran/, Duration.minutes(1)); + + // Continue with the debug session + await continueDebugging(2, 30); + }); + + it('Debug a Single Apex Test Method via the Test Sidebar', async () => { + logTestStart(testSetup, 'Debug Single Apex Test Method via the Test Sidebar'); + const workbench = getWorkbench(); + + await retryOperation( + async () => { + await executeQuickPick('Testing: Focus on Apex Tests View'); + }, + 3, + 'DebugApexTests - Error focusing on apex tests view' + ); + + // Open the Test Sidebar + const apexTestsSection = await retryOperation( + async () => await getTestsSection(workbench, 'Apex Tests'), + 3, + 'DebugApexTests - Error getting apex tests section' + ); + + // Hover a test name under one of the test class sections and click the debug button that is shown to the right of the test name on the Test sidebar + let apexTestItem: TreeItem; + await retryOperation( + async () => { + await apexTestsSection.click(); + await apexTestsSection.wait(20_000); + const foundItem = await apexTestsSection.findItem('validateSayHello'); + if (!foundItem) { + throw new Error('Expected TreeItem but got undefined'); + } + if (!(foundItem instanceof TreeItem)) { + throw new Error(`Expected TreeItem but got different item type: ${typeof foundItem}`); + } + apexTestItem = foundItem; + await apexTestItem.wait(20_000); + await apexTestItem.select(); + }, + 3, + 'DebugApexTests - Error selecting apex test item' + ); + const debugTestAction = await apexTestItem!.getActionButton('Debug Single Test'); + expect(debugTestAction).to.not.be.undefined; + await retryOperation( + async () => { + await pause(Duration.seconds(2)); + await debugTestAction?.click(); + }, + 3, + 'DebugApexTests - Error clicking debug test action' + ); + + // Look for the success notification that appears which says, "Debug Test(s) successfully ran". + await verifyNotificationWithRetry(/Debug Test\(s\) successfully ran/, Duration.minutes(1)); + + // Continue with the debug session + await continueDebugging(2, 30); + }); + + after('Tear down and clean up the testing environment', async () => { + log('DebugApexTests - Tear down and clean up the testing environment'); + await testSetup?.tearDown(); + }); +}); diff --git a/packages/salesforcedx-vscode-automation-tests/test/specs/debugLwcTests.e2e.ts b/packages/salesforcedx-vscode-automation-tests/test/specs/debugLwcTests.e2e.ts new file mode 100644 index 0000000000..653b9c3072 --- /dev/null +++ b/packages/salesforcedx-vscode-automation-tests/test/specs/debugLwcTests.e2e.ts @@ -0,0 +1,305 @@ +/* + * Copyright (c) 2024, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ +import { + TestReqConfig, + ProjectShapeOption, + Duration, + log, + pause +} from '@salesforce/salesforcedx-vscode-test-tools/lib/src/core'; +import { retryOperation } from '@salesforce/salesforcedx-vscode-test-tools/lib/src/retryUtils'; +import { createLwc } from '@salesforce/salesforcedx-vscode-test-tools/lib/src/salesforce-components'; +import { installJestUTToolsForLwc } from '@salesforce/salesforcedx-vscode-test-tools/lib/src/system-operations'; +import { + getTestsSection, + verifyTestItemsInSideBar, + continueDebugging, + verifyTestIconColor +} from '@salesforce/salesforcedx-vscode-test-tools/lib/src/testing'; +import { TestSetup } from '@salesforce/salesforcedx-vscode-test-tools/lib/src/testSetup'; +import { + closeAllEditors, + reloadWindow, + getWorkbench, + executeQuickPick, + getTerminalViewText, + verifyOutputPanelText, + runCommandFromCommandPrompt, + getTextEditor, + waitForAndGetCodeLens +} from '@salesforce/salesforcedx-vscode-test-tools/lib/src/ui-interaction'; +import { expect } from 'chai'; +import * as path from 'node:path'; +import { SideBarView, TreeItem, after } from 'vscode-extension-tester'; +import { logTestStart } from '../utils/loggingHelper'; + +describe('Debug LWC Tests', () => { + let testSetup: TestSetup; + const testReqConfig: TestReqConfig = { + projectConfig: { + projectShape: ProjectShapeOption.NEW + }, + isOrgRequired: false, + testSuiteSuffixName: 'DebugLWCTests' + }; + + before('Set up the testing environment', async () => { + testSetup = await TestSetup.setUp(testReqConfig); + + // Close both Welcome and Running Extensions tabs + await closeAllEditors(); + + // Create LWC1 and test + await createLwc('lwc1'); + + // Create LWC2 and test + await createLwc('lwc2'); + + // Install Jest unit testing tools for LWC + await installJestUTToolsForLwc(testSetup.projectFolderPath); + await reloadWindow(Duration.seconds(30)); + }); + + it('Debug All Tests on a LWC via the Test Sidebar', async () => { + logTestStart(testSetup, 'Debug All tests on a LWC via the Test Sidebar'); + const workbench = getWorkbench(); + await executeQuickPick('Testing: Focus on LWC Tests View', Duration.seconds(3)); + + // Open the Test Sidebar + const lwcTestsSection = await getTestsSection(workbench, 'LWC Tests'); + const expectedItems = ['lwc1', 'lwc2', 'displays greeting', 'is defined']; + const lwcTestsItems = await verifyTestItemsInSideBar( + lwcTestsSection, + 'SFDX: Refresh Lightning Web Component Test Explorer', + expectedItems, + 6, + 2 + ); + + // Click the debug test button that is shown to the right when you hover a test class name on the Test sidebar + await lwcTestsItems[0].select(); + const debugTestsAction = await lwcTestsItems[0].getActionButton('SFDX: Debug Lightning Web Component Test File'); + expect(debugTestsAction).to.not.be.undefined; + await debugTestsAction!.click(); + await pause(Duration.seconds(15)); + + // Continue with the debug session + await continueDebugging(2); + + // Verify test results are listed on the terminal + // Also verify that all tests pass + const terminalText = await getTerminalViewText(workbench, 10); + const expectedTexts = [ + 'PASS force-app/main/default/lwc/lwc1/__tests__/lwc1.test.js', + 'Test Suites: 1 passed, 1 total', + 'Tests: 2 passed, 2 total', + 'Snapshots: 0 total', + 'Ran all test suites within paths', + `${path.join('force-app', 'main', 'default', 'lwc', 'lwc1', '__tests__', 'lwc1.test.js')}` + ]; + expect(terminalText).to.not.be.undefined; + await verifyOutputPanelText(terminalText!, expectedTexts); + + // Verify the tests that are passing are labeled with a green dot on the Test sidebar + await executeQuickPick('Testing: Focus on LWC Tests View', Duration.seconds(3)); + await verifyTestIconColor(lwcTestsItems[0], 'testPass'); + }); + + it('Debug Single Test via the Test Sidebar', async () => { + logTestStart(testSetup, 'Debug Single Test via the Test Sidebar'); + const workbench = getWorkbench(); + const testingView = await workbench.getActivityBar().getViewControl('Testing'); + expect(testingView).to.not.be.undefined; + // Open the Test Sidebar + const testingSideBarView = await testingView!.openView(); + expect(testingSideBarView).to.be.instanceOf(SideBarView); + + // Hover a test name under one of the test lwc sections and click the debug button that is shown to the right of the test name on the Test sidebar + const lwcTestsSection = await getTestsSection(workbench, 'LWC Tests'); + const lwcTestItem = await lwcTestsSection.findItem('displays greeting'); + if (!lwcTestItem) { + throw new Error('Expected TreeItem but got undefined'); + } + if (!(lwcTestItem instanceof TreeItem)) { + throw new Error(`Expected TreeItem but got different item type: ${typeof lwcTestItem}`); + } + await lwcTestItem.select(); + const debugTestAction = await lwcTestItem.getActionButton('SFDX: Debug Lightning Web Component Test Case'); + expect(debugTestAction).to.not.be.undefined; + await debugTestAction!.click(); + await pause(Duration.seconds(15)); + + // Continue with the debug session + await continueDebugging(1); + + // Verify test results are listed on the terminal + // Also verify that all tests pass + const terminalText = await getTerminalViewText(workbench, 10); + const expectedTexts = [ + 'PASS force-app/main/default/lwc/lwc1/__tests__/lwc1.test.js', + 'Test Suites: 1 passed, 1 total', + 'Tests: 1 skipped, 1 passed, 2 total', + 'Snapshots: 0 total', + 'Ran all test suites within paths', + `${path.join('force-app', 'main', 'default', 'lwc', 'lwc1', '__tests__', 'lwc1.test.js')}` + ]; + expect(terminalText).to.not.be.undefined; + await verifyOutputPanelText(terminalText!, expectedTexts); + + // Verify the tests that are passing are labeled with a green dot on the Test sidebar + await runCommandFromCommandPrompt(workbench, 'Testing: Focus on LWC Tests View', Duration.seconds(3)); + await verifyTestIconColor(lwcTestItem, 'testPass'); + }); + + it('SFDX: Debug Current Lightning Web Component Test File from Command Palette', async () => { + logTestStart(testSetup, 'SFDX: Debug Current Lightning Web Component Test File from Command Palette'); + + // Debug SFDX: Debug Current Lightning Web Component Test File + const workbench = getWorkbench(); + await executeQuickPick('SFDX: Debug Current Lightning Web Component Test File', Duration.seconds(15)); + + // Continue with the debug session + await continueDebugging(2); + + // Verify test results are listed on vscode's Output section + // Also verify that all tests pass + const terminalText = await getTerminalViewText(workbench, 10); + const expectedTexts = [ + 'PASS force-app/main/default/lwc/lwc1/__tests__/lwc1.test.js', + 'Test Suites: 1 passed, 1 total', + 'Tests: 2 passed, 2 total', + 'Snapshots: 0 total', + 'Ran all test suites within paths', + `${path.join('force-app', 'main', 'default', 'lwc', 'lwc1', '__tests__', 'lwc1.test.js')}` + ]; + expect(terminalText).to.not.be.undefined; + await verifyOutputPanelText(terminalText!, expectedTexts); + }); + + // TODO: This test is skipped because of W-15666391 https://gus.lightning.force.com/lightning/r/ADM_Work__c/a07EE00001qWb1IYAS/view + it.skip('Debug All Tests via Code Lens action', async () => { + logTestStart(testSetup, 'Debug All Tests via Code Lens action'); + const workbench = getWorkbench(); + const textEditor = await getTextEditor(workbench, 'lwc1.test.js'); + + // Go to the top of the file + log('Go to the top of the file'); + const inputBox = await executeQuickPick('Go to Line/Column', Duration.seconds(3)); + await inputBox.setText(':1'); + await inputBox.confirm(); + log('Go to the top of the file done'); + + // Click the "Debug" code lens at the top of the class + await retryOperation( + async () => { + log('Debug All Tests: Finding code lens'); + const debugAllTestsOption = await waitForAndGetCodeLens(textEditor, 'Debug'); + expect(debugAllTestsOption).to.not.be.undefined; + log('Debug All Tests: Code lens found, waiting before click'); + await pause(Duration.seconds(2)); + log('Debug All Tests: Clicking code lens'); + await debugAllTestsOption!.click(); + log('Debug All Tests: Code lens clicked successfully'); + }, + 3, + 'DebugLwcTests - Error clicking debug all tests option' + ); + await pause(Duration.seconds(15)); + + // Continue with the debug session + await continueDebugging(2, 30); + + // Verify test results are listed on the terminal + // Also verify that all tests pass + const terminalText = await getTerminalViewText(workbench, 15); + const expectedTexts = [ + 'PASS force-app/main/default/lwc/lwc1/__tests__/lwc1.test.js', + 'Test Suites: 1 passed, 1 total', + 'Tests: 2 passed, 2 total', + 'Snapshots: 0 total', + 'Ran all test suites matching', + `${path.join('force-app', 'main', 'default', 'lwc', 'lwc1', '__tests__', 'lwc1.test.js')}` + ]; + expect(terminalText).to.not.be.undefined; + await verifyOutputPanelText(terminalText, expectedTexts); + }); + + it('Debug Single Test via Code Lens action', async () => { + logTestStart(testSetup, 'Debug Single Test via Code Lens action'); + + // Click the "Debug Test" code lens at the top of one of the test methods + const workbench = getWorkbench(); + const textEditor = await getTextEditor(workbench, 'lwc2.test.js'); + + await retryOperation( + async () => { + log('Debug Single Test: Finding code lens'); + const debugTestOption = await waitForAndGetCodeLens(textEditor, 'Debug Test'); + expect(debugTestOption).to.not.be.undefined; + log('Debug Single Test: Code lens found, waiting before click'); + await pause(Duration.seconds(2)); + log('Debug Single Test: Clicking code lens'); + await debugTestOption!.click(); + log('Debug Single Test: Code lens clicked successfully'); + }, + 3, + 'DebugLwcTests - Error clicking debug test option' + ); + + await pause(Duration.seconds(15)); + + // Continue with the debug session + await continueDebugging(1); + + // Verify test results are listed on the terminal + // Also verify that all tests pass + const terminalText = await getTerminalViewText(workbench, 10); + const expectedTexts = [ + 'PASS force-app/main/default/lwc/lwc2/__tests__/lwc2.test.js', + 'Test Suites: 1 passed, 1 total', + 'Tests: 1 skipped, 1 passed, 2 total', + 'Snapshots: 0 total', + 'Ran all test suites within paths', + `${path.join('force-app', 'main', 'default', 'lwc', 'lwc2', '__tests__', 'lwc2.test.js')}` + ]; + expect(terminalText).to.not.be.undefined; + await verifyOutputPanelText(terminalText!, expectedTexts); + }); + + it('SFDX: Debug Current Lightning Web Component Test File from main toolbar', async () => { + logTestStart(testSetup, 'SFDX: Debug Current Lightning Web Component Test File from main toolbar'); + + // Debug SFDX: Debug Current Lightning Web Component Test File + const workbench = getWorkbench(); + const editorView = workbench.getEditorView(); + const debugTestButtonToolbar = await editorView.getAction('SFDX: Debug Current Lightning Web Component Test File'); + expect(debugTestButtonToolbar).to.not.be.undefined; + await debugTestButtonToolbar!.click(); + await pause(Duration.seconds(15)); + + // Continue with the debug session + await continueDebugging(2); + + // Verify test results are listed on vscode's Output section + // Also verify that all tests pass + const terminalText = await getTerminalViewText(workbench, 10); + const expectedTexts = [ + 'PASS force-app/main/default/lwc/lwc2/__tests__/lwc2.test.js', + 'Test Suites: 1 passed, 1 total', + 'Tests: 2 passed, 2 total', + 'Snapshots: 0 total', + 'Ran all test suites within paths', + `${path.join('force-app', 'main', 'default', 'lwc', 'lwc2', '__tests__', 'lwc2.test.js')}` + ]; + expect(terminalText).to.not.be.undefined; + await verifyOutputPanelText(terminalText!, expectedTexts); + }); + + after('Tear down and clean up the testing environment', async () => { + await testSetup?.tearDown(); + }); +}); diff --git a/packages/salesforcedx-vscode-automation-tests/test/specs/deployAndRetrieve.e2e.ts b/packages/salesforcedx-vscode-automation-tests/test/specs/deployAndRetrieve.e2e.ts new file mode 100644 index 0000000000..4b87358361 --- /dev/null +++ b/packages/salesforcedx-vscode-automation-tests/test/specs/deployAndRetrieve.e2e.ts @@ -0,0 +1,559 @@ +/* + * Copyright (c) 2023, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ +import { + createCommand, + Duration, + log, + pause, + ProjectShapeOption, + TestReqConfig, + WORKSPACE_SETTING_KEYS as WSK +} from '@salesforce/salesforcedx-vscode-test-tools/lib/src/core'; +import { verifyNotificationWithRetry } from '@salesforce/salesforcedx-vscode-test-tools/lib/src/retryUtils'; +import { + createApexClass, + runAndValidateCommand, + validateCommand +} from '@salesforce/salesforcedx-vscode-test-tools/lib/src/salesforce-components'; +import { + disableBooleanSetting, + enableBooleanSetting, + isBooleanSettingEnabled +} from '@salesforce/salesforcedx-vscode-test-tools/lib/src/system-operations'; +import { + getExtensionsToVerifyActive, + verifyExtensionsAreRunning +} from '@salesforce/salesforcedx-vscode-test-tools/lib/src/testing'; +import { TestSetup } from '@salesforce/salesforcedx-vscode-test-tools/lib/src/testSetup'; +import { + acceptNotification, + attemptToFindOutputPanelText, + clearOutputView, + dismissAllNotifications, + executeQuickPick, + getTextEditor, + reloadWindow, + verifyOutputPanelText, + getWorkbench +} from '@salesforce/salesforcedx-vscode-test-tools/lib/src/ui-interaction'; +import { expect } from 'chai'; +import * as path from 'node:path'; +import { after, DefaultTreeItem } from 'vscode-extension-tester'; +import { logTestStart } from '../utils/loggingHelper'; + +describe('Deploy and Retrieve', () => { + const pathToClass = path.join('force-app', 'main', 'default', 'classes', 'MyClass'); + let testSetup: TestSetup; + const testReqConfig: TestReqConfig = { + projectConfig: { + projectShape: ProjectShapeOption.NEW + }, + isOrgRequired: true, + testSuiteSuffixName: 'DeployAndRetrieve' + }; + before('Set up the testing environment', async () => { + log('Deploy and Retrieve - Set up the testing environment'); + testSetup = await TestSetup.setUp(testReqConfig); + + // Create Apex Class + const classText = [ + 'public with sharing class MyClass {', + '', + '\tpublic static void SayHello(string name){', + "\t\tSystem.debug('Hello, ' + name + '!');", + '\t}', + '}' + ].join('\n'); + await dismissAllNotifications(); + await createApexClass('MyClass', classText); + const successNotificationWasFound = await verifyNotificationWithRetry( + /SFDX: Create Apex Class successfully ran/, + Duration.TEN_MINUTES + ); + expect(successNotificationWasFound).to.equal(true); + + const outputPanelText = await attemptToFindOutputPanelText( + 'Salesforce CLI', + 'Finished SFDX: Create Apex Class', + 10 + ); + expect(outputPanelText).to.not.be.undefined; + expect(outputPanelText).to.contain(`${pathToClass}.cls`); + expect(outputPanelText).to.contain(`${pathToClass}.cls-meta.xml`); + }); + + it('Verify Source Tracking Setting is enabled', async () => { + logTestStart(testSetup, 'Verify Source Tracking Setting is enabled'); + expect(await isBooleanSettingEnabled(WSK.ENABLE_SOURCE_TRACKING_FOR_DEPLOY_AND_RETRIEVE)); + }); + + it('Deploy with SFDX: Deploy This Source to Org - ST enabled', async () => { + logTestStart(testSetup, 'Deploy with SFDX: Deploy This Source to Org - ST enabled'); + const workbench = getWorkbench(); + // Clear the Output view first. + await clearOutputView(Duration.seconds(2)); + await getTextEditor(workbench, 'MyClass.cls'); + await runAndValidateCommand('Deploy', 'to', 'ST', 'ApexClass', 'MyClass'); + }); + + it('Deploy again (with no changes) - ST enabled', async () => { + logTestStart(testSetup, 'Deploy again (with no changes) - ST enabled'); + const workbench = getWorkbench(); + // Clear the Output view first. + await clearOutputView(Duration.seconds(2)); + await getTextEditor(workbench, 'MyClass.cls'); + + await runAndValidateCommand('Deploy', 'to', 'ST', 'ApexClass', 'MyClass', 'Unchanged '); + }); + + it('Modify the file and deploy again - ST enabled', async () => { + logTestStart(testSetup, 'Modify the file and deploy again - ST enabled'); + const workbench = getWorkbench(); + // Clear the Output view first. + await clearOutputView(Duration.seconds(2)); + + // Modify the file by adding a comment. + const textEditor = await getTextEditor(workbench, 'MyClass.cls'); + await textEditor.setTextAtLine(2, '\t//say hello to a given name'); + await textEditor.save(); + + // Deploy running SFDX: Deploy This Source to Org + await runAndValidateCommand('Deploy', 'to', 'ST', 'ApexClass', 'MyClass', 'Changed '); + }); + + // Use context menu only for Windows and Ubuntu + if (process.platform !== 'darwin') { + it('Deploy with context menu from editor view', async () => { + logTestStart(testSetup, 'Deploy with context menu from editor view'); + const workbench = getWorkbench(); + // Clear the Output view first. + await clearOutputView(Duration.seconds(2)); + + const textEditor = await getTextEditor(workbench, 'MyClass.cls'); + const contextMenu = await textEditor.openContextMenu(); + await contextMenu.select('SFDX: Deploy This Source to Org'); + + await validateCommand('Deploy', 'to', 'ST', 'ApexClass', ['MyClass'], 'Unchanged '); + }); + } + + if (process.platform !== 'darwin') { + it('Deploy with context menu from explorer view', async () => { + logTestStart(testSetup, 'Deploy with context menu from explorer view'); + // Clear the Output view first. + await clearOutputView(Duration.seconds(2)); + await executeQuickPick('File: Focus on Files Explorer'); + await pause(Duration.seconds(2)); + const workbench = getWorkbench(); + const sidebar = await workbench.getSideBar().wait(); + const content = await sidebar.getContent().wait(); + const treeViewSection = await content.getSection(testSetup.tempProjectName); + if (!treeViewSection) { + throw new Error( + 'In verifyProjectLoaded(), getSection() returned a treeViewSection with a value of null (or undefined)' + ); + } + await treeViewSection.expand(); + + // The force-app/main/default and classes folders are already expanded, so we can find the file directly + const myClassFile = await treeViewSection.findItem('MyClass.cls'); + if (!myClassFile) { + throw new Error('Expected DefaultTreeItem but got undefined'); + } + if (!(myClassFile instanceof DefaultTreeItem)) { + throw new Error(`Expected DefaultTreeItem but got different item type: ${typeof myClassFile}`); + } + const contextMenu = await myClassFile.openContextMenu(); + await contextMenu.select('SFDX: Deploy This Source to Org'); + + await validateCommand('Deploy', 'to', 'ST', 'ApexClass', ['MyClass'], 'Unchanged '); + }); + } + + it('Retrieve with SFDX: Retrieve This Source from Org', async () => { + logTestStart(testSetup, 'Retrieve with SFDX: Retrieve This Source from Org'); + const workbench = getWorkbench(); + // Clear the Output view first. + await clearOutputView(Duration.seconds(2)); + await getTextEditor(workbench, 'MyClass.cls'); + + await runAndValidateCommand('Retrieve', 'from', 'ST', 'ApexClass', 'MyClass'); + }); + + it('Modify the file and retrieve again', async () => { + logTestStart(testSetup, 'Modify the file and retrieve again'); + const workbench = getWorkbench(); + // Clear the Output view first. + await clearOutputView(Duration.seconds(2)); + + // Modify the file by changing the comment. + const textEditor = await getTextEditor(workbench, 'MyClass.cls'); + await textEditor.setTextAtLine(2, '\t//modified comment'); + await textEditor.save(); + + // Retrieve running SFDX: Retrieve This Source from Org + + await runAndValidateCommand('Retrieve', 'from', 'ST', 'ApexClass', 'MyClass'); + // Retrieve operation will overwrite the file, hence the the comment will remain as before the modification + const textAfterRetrieve = await textEditor.getText(); + expect(textAfterRetrieve).to.not.contain('modified comment'); + }); + + // Use context menu only for Windows and Ubuntu + if (process.platform !== 'darwin') { + it('Retrieve with context menu from editor view', async () => { + logTestStart(testSetup, 'Retrieve with context menu from editor view'); + const workbench = getWorkbench(); + // Clear the Output view first. + await clearOutputView(Duration.seconds(2)); + + const textEditor = await getTextEditor(workbench, 'MyClass.cls'); + const contextMenu = await textEditor.openContextMenu(); + await contextMenu.select('SFDX: Retrieve This Source from Org'); + + await validateCommand('Retrieve', 'from', 'ST', 'ApexClass', ['MyClass']); + }); + } + + if (process.platform !== 'darwin') { + it('Retrieve with context menu from explorer view', async () => { + logTestStart(testSetup, 'Retrieve with context menu from explorer view'); + // Clear the Output view first. + await clearOutputView(Duration.seconds(2)); + await executeQuickPick('File: Focus on Files Explorer'); + await pause(Duration.seconds(2)); + const workbench = getWorkbench(); + const sidebar = await workbench.getSideBar().wait(); + const content = await sidebar.getContent().wait(); + const treeViewSection = await content.getSection(testSetup.tempProjectName); + if (!treeViewSection) { + throw new Error( + 'In verifyProjectLoaded(), getSection() returned a treeViewSection with a value of null (or undefined)' + ); + } + + // The force-app/main/default and classes folders are already expanded, so we can find the file directly + const myClassFile = await treeViewSection.findItem('MyClass.cls'); + if (!myClassFile) { + throw new Error('Expected DefaultTreeItem but got undefined'); + } + if (!(myClassFile instanceof DefaultTreeItem)) { + throw new Error(`Expected DefaultTreeItem but got different item type: ${typeof myClassFile}`); + } + const contextMenu = await myClassFile.openContextMenu(); + await contextMenu.select('SFDX: Retrieve This Source from Org'); + + await validateCommand('Retrieve', 'from', 'ST', 'ApexClass', ['MyClass']); + }); + } + + it('Prefer Deploy on Save when `Push or deploy on save` is enabled', async () => { + logTestStart(testSetup, "Prefer Deploy on Save when 'Push or deploy on save' is enabled"); + const workbench = getWorkbench(); + // Clear the Output view first. + await clearOutputView(Duration.seconds(2)); + + expect(await enableBooleanSetting(WSK.PUSH_OR_DEPLOY_ON_SAVE_ENABLED)).to.equal(true); + await pause(Duration.seconds(3)); + + expect(await enableBooleanSetting(WSK.PUSH_OR_DEPLOY_ON_SAVE_PREFER_DEPLOY_ON_SAVE)).to.equal(true); + + // Clear all notifications so clear output button is reachable + await executeQuickPick('Notifications: Clear All Notifications', Duration.seconds(1)); + + // Clear the Output view first. + await clearOutputView(Duration.seconds(2)); + // Modify the file and save to trigger deploy + const textEditor = await getTextEditor(workbench, 'MyClass.cls'); + await textEditor.setTextAtLine(2, "\t// let's trigger deploy"); + await textEditor.save(); + await pause(Duration.seconds(5)); + + // At this point there should be no conflicts since this is a new class. + await validateCommand('Deploy', 'to', 'on save', 'ApexClass', ['MyClass']); + }); + + it('Disable Source Tracking Setting', async () => { + logTestStart(testSetup, 'Disable Source Tracking Setting'); + await executeQuickPick('Notifications: Clear All Notifications', Duration.seconds(1)); + + expect(await disableBooleanSetting(WSK.ENABLE_SOURCE_TRACKING_FOR_DEPLOY_AND_RETRIEVE)).to.equal(false); + + // Reload window to update cache and get the setting behavior to work + await reloadWindow(); + await verifyExtensionsAreRunning(getExtensionsToVerifyActive(), Duration.seconds(100)); + }); + + it('Deploy with SFDX: Deploy This Source to Org - ST disabled', async () => { + logTestStart(testSetup, 'Deploy with SFDX: Deploy This Source to Org - ST disabled'); + const workbench = getWorkbench(); + // Clear all notifications so clear output button is visible + await executeQuickPick('Notifications: Clear All Notifications'); + // Clear the Output view first. + await clearOutputView(Duration.seconds(2)); + await getTextEditor(workbench, 'MyClass.cls'); + + await runAndValidateCommand('Deploy', 'to', 'no-ST', 'ApexClass', 'MyClass'); + }); + + it('Deploy again (with no changes) - ST disabled', async () => { + logTestStart(testSetup, 'Deploy again (with no changes) - ST enabled'); + const workbench = getWorkbench(); + // Clear the Output view first. + await clearOutputView(Duration.seconds(2)); + await getTextEditor(workbench, 'MyClass.cls'); + + await runAndValidateCommand('Deploy', 'to', 'no-ST', 'ApexClass', 'MyClass', 'Unchanged '); + }); + + it('Modify the file and deploy again - ST disabled', async () => { + logTestStart(testSetup, 'Modify the file and deploy again - ST disabled'); + const workbench = getWorkbench(); + // Clear the Output view first. + await clearOutputView(Duration.seconds(2)); + + // Modify the file by adding a comment. + const textEditor = await getTextEditor(workbench, 'MyClass.cls'); + await textEditor.setTextAtLine(2, '\t//say hello to a given name'); + await textEditor.save(); + + // Deploy running SFDX: Deploy This Source to Org + await runAndValidateCommand('Deploy', 'to', 'no-ST', 'ApexClass', 'MyClass', 'Changed '); + }); + + it('SFDX: Delete This from Project and Org - Command Palette', async () => { + logTestStart(testSetup, 'SFDX: Delete This from Project and Org - Command Palette'); + const workbench = getWorkbench(); + + // Run SFDX: Push Source to Default Org and Ignore Conflicts to be in sync with remote + await executeQuickPick('SFDX: Push Source to Default Org and Ignore Conflicts', Duration.seconds(10)); + + // Clear the Output view first. + await clearOutputView(); + + // Clear notifications + await dismissAllNotifications(); + + await getTextEditor(workbench, 'MyClass.cls'); + await pause(Duration.seconds(1)); + await executeQuickPick('SFDX: Delete This from Project and Org', Duration.seconds(2)); + + // Make sure we get a notification for the source delete + const notificationFound = await verifyNotificationWithRetry( + /Deleting source files deletes the files from your computer and removes the corresponding metadata from your default org\. Are you sure you want to delete this source from your project and your org\?/, + Duration.ONE_MINUTE + ); + + expect(notificationFound).to.equal(true); + + // Confirm deletion + const accepted = await acceptNotification( + 'Deleting source files deletes the files from your computer and removes the corresponding metadata from your default org. Are you sure you want to delete this source from your project and your org?', + 'Delete Source', + Duration.seconds(5) + ); + expect(accepted).to.equal(true); + const successNotificationWasFound = await verifyNotificationWithRetry( + /SFDX: Delete from Project and Org successfully ran/, + Duration.TEN_MINUTES + ); + expect(successNotificationWasFound).to.equal(true); + + // TODO: see how the test can accommodate the new output from CLI. + // Verify Output tab + const outputPanelText = await attemptToFindOutputPanelText( + 'Salesforce CLI', + 'Starting SFDX: Delete from Project and Org', + 10 + ); + log(`Output panel text is: ${outputPanelText}`); + + const expectedTexts = [ + '=== Deleted Source', + 'MyClass', + 'ApexClass', + `${pathToClass}.cls`, + `${pathToClass}.cls-meta.xml`, + 'ended with exit code 0' + ]; + + await verifyOutputPanelText(outputPanelText, expectedTexts); + }); + + if (process.platform !== 'darwin') { + it('Create and push 2 apex classes', async () => { + logTestStart(testSetup, 'Create and push 2 apex classes'); + + // Create the Apex Classes. + await createCommand('Apex Class', 'ExampleApexClass1', 'classes', 'cls'); + await createCommand('Apex Class', 'ExampleApexClass2', 'classes', 'cls'); + + // Reload the VSCode window to allow the LWC to be indexed by the Apex Language Server + await reloadWindow(Duration.seconds(20)); + + // Push source to org + await executeQuickPick('SFDX: Push Source to Default Org and Ignore Conflicts', Duration.seconds(1)); + + // Look for the success notification that appears which says, "SFDX: Push Source to Default Org and Ignore Conflicts successfully ran". + await verifyNotificationWithRetry( + /SFDX: Push Source to Default Org and Ignore Conflicts successfully ran/, + Duration.TEN_MINUTES + ); + }); + + it('SFDX: Delete This from Project and Org - Right click from editor view', async () => { + logTestStart(testSetup, 'SFDX: Delete This from Project and Org - Right click from editor view'); + const workbench = getWorkbench(); + // Clear the Output view first. + await clearOutputView(); + + // Clear notifications + await dismissAllNotifications(); + + const textEditor = await getTextEditor(workbench, 'ExampleApexClass1.cls'); + const contextMenu = await textEditor.openContextMenu(); + await contextMenu.select('SFDX: Delete This from Project and Org'); + + // Make sure we get a notification for the source delete + const notificationFound = await verifyNotificationWithRetry( + /Deleting source files deletes the files from your computer and removes the corresponding metadata from your default org\. Are you sure you want to delete this source from your project and your org\?/, + Duration.ONE_MINUTE + ); + + expect(notificationFound).to.equal(true); + + // Confirm deletion + const accepted = await acceptNotification( + 'Deleting source files deletes the files from your computer and removes the corresponding metadata from your default org. Are you sure you want to delete this source from your project and your org?', + 'Delete Source', + Duration.seconds(5) + ); + expect(accepted).to.equal(true); + + const successNotificationWasFound = await verifyNotificationWithRetry( + /SFDX: Delete from Project and Org successfully ran/, + Duration.TEN_MINUTES + ); + expect(successNotificationWasFound).to.equal(true); + + // TODO: see how the test can accommodate the new output from CLI. + // Verify Output tab + const outputPanelText = await attemptToFindOutputPanelText( + 'Salesforce CLI', + 'Starting SFDX: Delete from Project and Org', + 10 + ); + log(`Output panel text is: ${outputPanelText}`); + + const pathToClassDeleteFromProjectAndOrg = path.join( + 'force-app', + 'main', + 'default', + 'classes', + 'ExampleApexClass1' + ); + + const expectedTexts = [ + '=== Deleted Source', + 'ExampleApexClass1', + 'ApexClass', + `${pathToClassDeleteFromProjectAndOrg}.cls`, + `${pathToClassDeleteFromProjectAndOrg}.cls-meta.xml`, + 'ended with exit code 0' + ]; + + await verifyOutputPanelText(outputPanelText, expectedTexts); + }); + + it('SFDX: Delete This from Project and Org - Right click from explorer view', async () => { + logTestStart(testSetup, 'SFDX: Delete This from Project and Org - Right click from explorer view'); + // Clear the Output view first. + await clearOutputView(); + + // Clear notifications + await dismissAllNotifications(); + + await executeQuickPick('File: Focus on Files Explorer'); + await pause(Duration.seconds(2)); + const workbench = getWorkbench(); + const sidebar = await workbench.getSideBar().wait(); + const content = await sidebar.getContent().wait(); + const treeViewSection = await content.getSection(testSetup.tempProjectName); + if (!treeViewSection) { + throw new Error( + 'In verifyProjectLoaded(), getSection() returned a treeViewSection with a value of null (or undefined)' + ); + } + + // The force-app/main/default and classes folders are already expanded, so we can find the file directly + const myClassFile = await treeViewSection.findItem('ExampleApexClass2.cls'); + if (!myClassFile) { + throw new Error('Expected DefaultTreeItem but got undefined'); + } + if (!(myClassFile instanceof DefaultTreeItem)) { + throw new Error(`Expected DefaultTreeItem but got different item type: ${typeof myClassFile}`); + } + const contextMenu = await myClassFile.openContextMenu(); + await contextMenu.select('SFDX: Delete from Project and Org'); + + // Make sure we get a notification for the source delete + const notificationFound = await verifyNotificationWithRetry( + /Deleting source files deletes the files from your computer and removes the corresponding metadata from your default org\. Are you sure you want to delete this source from your project and your org\?/, + Duration.ONE_MINUTE + ); + + expect(notificationFound).to.equal(true); + + // Confirm deletion + const accepted = await acceptNotification( + 'Deleting source files deletes the files from your computer and removes the corresponding metadata from your default org. Are you sure you want to delete this source from your project and your org?', + 'Delete Source', + Duration.seconds(5) + ); + expect(accepted).to.equal(true); + + const successNotificationWasFound = await verifyNotificationWithRetry( + /SFDX: Delete from Project and Org successfully ran/, + Duration.TEN_MINUTES + ); + expect(successNotificationWasFound).to.equal(true); + + // TODO: see how the test can accommodate the new output from CLI. + // Verify Output tab + const outputPanelText = await attemptToFindOutputPanelText( + 'Salesforce CLI', + 'Starting SFDX: Delete from Project and Org', + 10 + ); + log(`Output panel text is: ${outputPanelText}`); + + const pathToClassDeleteFromProjectAndOrg = path.join( + 'force-app', + 'main', + 'default', + 'classes', + 'ExampleApexClass2' + ); + + const expectedTexts = [ + '=== Deleted Source', + 'ExampleApexClass2', + 'ApexClass', + `${pathToClassDeleteFromProjectAndOrg}.cls`, + `${pathToClassDeleteFromProjectAndOrg}.cls-meta.xml`, + 'ended with exit code 0' + ]; + + await verifyOutputPanelText(outputPanelText, expectedTexts); + }); + } + + after('Tear down and clean up the testing environment', async () => { + log('Deploy and Retrieve - Tear down and clean up the testing environment'); + await testSetup?.tearDown(); + }); +}); diff --git a/packages/salesforcedx-vscode-automation-tests/test/specs/lwcLsp.e2e.ts b/packages/salesforcedx-vscode-automation-tests/test/specs/lwcLsp.e2e.ts new file mode 100644 index 0000000000..5f6692da80 --- /dev/null +++ b/packages/salesforcedx-vscode-automation-tests/test/specs/lwcLsp.e2e.ts @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2023, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ +import { + Duration, + pause, + log, + ProjectShapeOption, + TestReqConfig +} from '@salesforce/salesforcedx-vscode-test-tools/lib/src/core'; +import { createLwc } from '@salesforce/salesforcedx-vscode-test-tools/lib/src/salesforce-components'; +import { TestSetup } from '@salesforce/salesforcedx-vscode-test-tools/lib/src/testSetup'; +import { + executeQuickPick, + getWorkbench, + getTextEditor, + reloadWindow +} from '@salesforce/salesforcedx-vscode-test-tools/lib/src/ui-interaction'; +import { expect } from 'chai'; +import { By, after } from 'vscode-extension-tester'; +import { logTestStart } from '../utils/loggingHelper'; + +describe('LWC LSP', () => { + let testSetup: TestSetup; + const testReqConfig: TestReqConfig = { + projectConfig: { + projectShape: ProjectShapeOption.NEW + }, + isOrgRequired: false, + testSuiteSuffixName: 'LwcLsp' + }; + + before('Set up the testing environment', async () => { + log('LwcLsp - Set up the testing environment'); + testSetup = await TestSetup.setUp(testReqConfig); + + // Create Lightning Web Component + await createLwc('lwc1'); + + // Reload the VSCode window to allow the LWC to be indexed by the LWC Language Server + await reloadWindow(Duration.seconds(20)); + }); + + it('Go to Definition (JavaScript)', async () => { + logTestStart(testSetup, 'Go to Definition (Javascript)'); + // Get open text editor + const workbench = await getWorkbench(); + const textEditor = await getTextEditor(workbench, 'lwc1.js'); + + // Move cursor to the middle of "LightningElement" + await textEditor.moveCursor(3, 40); + + // Go to definition through F12 + await executeQuickPick('Go to Definition', Duration.seconds(2)); + + // Verify 'Go to definition' took us to the definition file + const editorView = workbench.getEditorView(); + const activeTab = await editorView.getActiveTab(); + const title = await activeTab?.getTitle(); + expect(title).to.equal('engine.d.ts'); + }); + + it('Go to Definition (HTML)', async () => { + if (process.platform !== 'win32') { + logTestStart(testSetup, 'Go to Definition (HTML)'); + // Get open text editor + const workbench = await getWorkbench(); + const textEditor = await getTextEditor(workbench, 'lwc1.html'); + + // Move cursor to the middle of "greeting" + await textEditor.moveCursor(3, 58); + + // Go to definition through F12 + await executeQuickPick('Go to Definition', Duration.seconds(2)); + + // Verify 'Go to definition' took us to the definition file + const editorView = workbench.getEditorView(); + const activeTab = await editorView.getActiveTab(); + const title = await activeTab?.getTitle(); + expect(title).to.equal('lwc1.js'); + } + }); + + it('Autocompletion', async () => { + logTestStart(testSetup, 'Autocompletion'); + // Get open text editor + const workbench = await getWorkbench().wait(); + const textEditor = await getTextEditor(workbench, 'lwc1.html'); + await textEditor.typeTextAt(5, 1, ''); + await textEditor.save(); + await pause(Duration.seconds(1)); + const line5Text = await textEditor.getTextAtLine(5); + expect(line5Text).to.contain('lightning-accordion'); + }); + + after('Tear down and clean up the testing environment', async () => { + log(`${testSetup.testSuiteSuffixName} - Tear down and clean up the testing environment`); + await testSetup?.tearDown(); + }); +}); diff --git a/packages/salesforcedx-vscode-automation-tests/test/specs/manifestBuilder.e2e.ts b/packages/salesforcedx-vscode-automation-tests/test/specs/manifestBuilder.e2e.ts new file mode 100644 index 0000000000..d6cabea2cd --- /dev/null +++ b/packages/salesforcedx-vscode-automation-tests/test/specs/manifestBuilder.e2e.ts @@ -0,0 +1,327 @@ +/* + * Copyright (c) 2023, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ +import { + Duration, + log, + openFile, + pause, + ProjectShapeOption, + TestReqConfig +} from '@salesforce/salesforcedx-vscode-test-tools/lib/src/core'; +import { retryOperation } from '@salesforce/salesforcedx-vscode-test-tools/lib/src/retryUtils'; +import { validateCommand } from '@salesforce/salesforcedx-vscode-test-tools/lib/src/salesforce-components'; +import { createCustomObjects } from '@salesforce/salesforcedx-vscode-test-tools/lib/src/system-operations'; +import { TestSetup } from '@salesforce/salesforcedx-vscode-test-tools/lib/src/testSetup'; +import { + clearOutputView, + dismissAllNotifications, + executeQuickPick, + getTextEditor, + getWorkbench +} from '@salesforce/salesforcedx-vscode-test-tools/lib/src/ui-interaction'; +import { expect } from 'chai'; +import * as path from 'node:path'; +import { DefaultTreeItem, InputBox, after } from 'vscode-extension-tester'; +import { logTestStart } from '../utils/loggingHelper'; + +describe('Manifest Builder', () => { + let testSetup: TestSetup; + const testReqConfig: TestReqConfig = { + projectConfig: { + projectShape: ProjectShapeOption.NEW + }, + isOrgRequired: true, + testSuiteSuffixName: 'ManifestBuilder' + }; + + before('Set up the testing environment', async () => { + testSetup = await TestSetup.setUp(testReqConfig); + }); + + it('Generate Manifest File', async () => { + logTestStart(testSetup, 'Generate Manifest File'); + // Normally we would want to run the 'SFDX: Generate Manifest File' command here, but it is only + // accessible via a context menu, and wdio-vscode-service isn't able to interact with + // context menus, so instead the manifest file is manually created: + + // update testSetup.testDataFolderPath to be the path to the salesforcedx-vscode-automation-tests package + testSetup.testDataFolderPath = testSetup.tempFolderPath.replace( + 'e2e-temp', + 'packages/salesforcedx-vscode-automation-tests/test/testData/CustomSObjects' + ); + log(`testSetup.testDataFolderPath: ${String(testSetup.testDataFolderPath || 'undefined')}`); + + log(`${testSetup.testSuiteSuffixName} - calling createCustomObjects()`); + await createCustomObjects(testSetup); + + if (process.platform !== 'darwin') { + log(`${testSetup.testSuiteSuffixName} - creating manifest file`); + + const workbench = getWorkbench(); + const sidebar = await workbench.getSideBar().wait(); + const content = await sidebar.getContent().wait(); + const treeViewSection = await content.getSection(testSetup.tempProjectName); + if (!treeViewSection) { + throw new Error( + 'In verifyProjectLoaded(), getSection() returned a treeViewSection with a value of null (or undefined)' + ); + } + + const objectTreeItem = await treeViewSection.findItem('objects'); + if (!objectTreeItem) { + throw new Error('Expected DefaultTreeItem but got undefined'); + } + if (!(objectTreeItem instanceof DefaultTreeItem)) { + throw new Error(`Expected DefaultTreeItem but got different item type: ${typeof objectTreeItem}`); + } + if (!objectTreeItem) { + throw new Error( + 'In verifyProjectLoaded(), findItem() returned a forceAppTreeItem with a value of null (or undefined)' + ); + } + + expect(objectTreeItem).to.not.be.undefined; + await (await objectTreeItem.wait()).expand(); + + const contextMenu = await objectTreeItem.openContextMenu(); + await contextMenu.select('SFDX: Generate Manifest File'); + const inputBox = await InputBox.create(); + await inputBox.setText('manifest'); + await inputBox.confirm(); + } + + if (process.platform === 'darwin') { + // Using the Command palette, run File: New File... + const inputBox = await executeQuickPick('Create: New File...', Duration.seconds(1)); + + // Set the name of the new manifest file + const filePath = path.join('manifest', 'manifest.xml'); + await inputBox.setText(filePath); + + // The following 3 confirms are just confirming the file creation and the folder it will belong to + await inputBox.confirm(); + await inputBox.confirm(); + await inputBox.confirm(); + + const workbench = getWorkbench(); + const textEditor = await getTextEditor(workbench, 'manifest.xml'); + const content = [ + '', + '', + '\t', + '\t\t*', + '\t\tCustomObject', + '\t', + '\t57.0', + '' + ].join('\n'); + + await textEditor.setText(content); + await textEditor.save(); + await pause(Duration.seconds(1)); + } + }); + + // This test seems to failing due to bug in command palette command: SFDX: Deploy Source in Manifest to Org. + // TODO: Re-enable with WI-19015026: https://gus.lightning.force.com/lightning/r/ADM_Work__c/a07EE00002HzT7qYAF/view + it.skip('SFDX: Deploy Source in Manifest to Org', async () => { + logTestStart(testSetup, 'SFDX: Deploy Source in Manifest to Org'); + log(`Deploy: Current platform is: ${process.platform}`); + + log('Deploy: Getting workbench'); + const workbench = getWorkbench(); + await retryOperation( + async () => { + await pause(Duration.seconds(2)); + await dismissAllNotifications(); + }, + 3, + 'Deploy: Error dismissing all notifications' + ); + + // Clear output before running the command + log('Deploy: Clearing output view'); + + await pause(Duration.seconds(2)); + await clearOutputView(); + log('Deploy: Opening manifest.xml file'); + await getTextEditor(workbench, 'manifest.xml'); + log('Deploy: manifest.xml file opened'); + + log(`Deploy: Checking if platform is Linux (current: ${process.platform})`); + if (process.platform === 'linux') { + log('Deploy: Running on Linux platform - using context menu approach'); + + // Dismiss all notifications using the button in the status bar + await dismissAllNotifications(); + + // Using the Context menu, run SFDX: Deploy Source in Manifest to Org + log('Deploy: Getting sidebar and content'); + const sidebar = await workbench.getSideBar().wait(); + const content = await sidebar.getContent().wait(); + log(`Deploy: Looking for tree section: ${testSetup.tempProjectName}`); + const treeViewSection = await content.getSection(testSetup.tempProjectName); + if (!treeViewSection) { + throw new Error( + 'In verifyProjectLoaded(), getSection() returned a treeViewSection with a value of null (or undefined)' + ); + } + log('Deploy: Found tree view section'); + + log('Deploy: Looking for manifest tree item'); + const manifestTreeItem = await treeViewSection.findItem('manifest'); + if (!manifestTreeItem) { + throw new Error('Expected DefaultTreeItem but got undefined'); + } + if (!(manifestTreeItem instanceof DefaultTreeItem)) { + throw new Error(`Expected DefaultTreeItem but got different item type: ${typeof manifestTreeItem}`); + } + if (!manifestTreeItem) { + throw new Error( + 'In verifyProjectLoaded(), findItem() returned a forceAppTreeItem with a value of null (or undefined)' + ); + } + log('Deploy: Found manifest tree item'); + + expect(manifestTreeItem).to.not.be.undefined; + log('Deploy: Expanding manifest tree item'); + await (await manifestTreeItem.wait()).expand(); + log('Deploy: Manifest tree item expanded, waiting for UI to settle'); + await pause(Duration.seconds(1)); // Wait for expansion to complete + + // Locate the "manifest.xml" file within the expanded "manifest" folder + log('Deploy: Looking for manifest.xml file in expanded folder'); + const manifestXmlFile = await treeViewSection.findItem('manifest.xml'); + if (!manifestXmlFile) { + throw new Error('Expected DefaultTreeItem but got undefined'); + } + if (!(manifestXmlFile instanceof DefaultTreeItem)) { + throw new Error(`Expected DefaultTreeItem but got different item type: ${typeof manifestXmlFile}`); + } + if (!manifestXmlFile) { + log('Deploy: ERROR - manifest.xml file not found after expansion'); + throw new Error('No manifest.xml file found'); + } + log('Deploy: Found manifest.xml file'); + expect(manifestXmlFile).to.not.be.undefined; + + // Ensure the file is visible and interactable + log('Deploy: Selecting manifest.xml file to ensure it is interactable'); + await manifestXmlFile.select(); + log('Deploy: File selected, waiting for UI to update'); + await pause(Duration.milliseconds(500)); + + log('Deploy: Opening context menu on manifest.xml'); + const contextMenu = await manifestXmlFile.openContextMenu(); + log('Deploy: Context menu opened, selecting deploy command'); + await contextMenu.select('SFDX: Deploy Source in Manifest to Org'); + log('Deploy: Deploy command selected from context menu'); + } else { + log(`Deploy: Not running on Linux (platform: ${process.platform}) - using command palette approach`); + log('Deploy: Opening manifest.xml file to ensure focus'); + await openFile(path.join(`${testSetup.projectFolderPath!}`, 'manifest', 'manifest.xml')); + log('Deploy: manifest.xml file opened'); + // Using the Command palette, run SFDX: Deploy Source in Manifest to Org + await executeQuickPick('SFDX: Deploy Source in Manifest to Org', Duration.seconds(10)); + log('Deploy: Command palette deploy command completed'); + } + + log('Deploy: Starting command validation'); + await validateCommand('Deploy', 'to', 'ST', 'CustomObject', ['Customer__c', 'Product__c'], 'Created '); + log('Deploy: Command validation completed'); + }); + + // This test seems to failing due to bug in command palette command: SFDX: Deploy Source in Manifest to Org. + // TODO: Re-enable with WI-19015026: https://gus.lightning.force.com/lightning/r/ADM_Work__c/a07EE00002HzT7qYAF/view + it.skip('SFDX: Retrieve Source in Manifest from Org', async () => { + logTestStart(testSetup, 'SFDX: Retrieve Source in Manifest from Org'); + + // Clear output before running the command + await clearOutputView(); + const workbench = getWorkbench(); + await getTextEditor(workbench, 'manifest.xml'); + + if (process.platform === 'linux') { + log('Retrieve: Running on Linux platform - using context menu approach'); + + // Dismiss all notifications using the button in the status bar + await dismissAllNotifications(); + + // Using the Context menu, run SFDX: Retrieve Source in Manifest from Org + log('Retrieve: Getting sidebar and content'); + const sidebar = await workbench.getSideBar().wait(); + const content = await sidebar.getContent().wait(); + log(`Retrieve: Looking for tree section: ${testSetup.tempProjectName}`); + const treeViewSection = await content.getSection(testSetup.tempProjectName); + if (!treeViewSection) { + throw new Error( + 'In verifyProjectLoaded(), getSection() returned a treeViewSection with a value of null (or undefined)' + ); + } + log('Retrieve: Found tree view section'); + + log('Retrieve: Looking for manifest tree item'); + const manifestTreeItem = await treeViewSection.findItem('manifest'); + if (!manifestTreeItem) { + throw new Error('Expected DefaultTreeItem but got undefined'); + } + if (!(manifestTreeItem instanceof DefaultTreeItem)) { + throw new Error(`Expected DefaultTreeItem but got different item type: ${typeof manifestTreeItem}`); + } + if (!manifestTreeItem) { + throw new Error( + 'In verifyProjectLoaded(), findItem() returned a forceAppTreeItem with a value of null (or undefined)' + ); + } + log('Retrieve: Found manifest tree item'); + + expect(manifestTreeItem).to.not.be.undefined; + log('Retrieve: Expanding manifest tree item'); + await (await manifestTreeItem.wait()).expand(); + log('Retrieve: Manifest tree item expanded, waiting for UI to settle'); + await pause(Duration.seconds(1)); // Wait for expansion to complete + + // Locate the "manifest.xml" file within the expanded "manifest" folder + log('Retrieve: Looking for manifest.xml file in expanded folder'); + const manifestXmlFile = await treeViewSection.findItem('manifest.xml'); + if (!manifestXmlFile) { + throw new Error('Expected DefaultTreeItem but got undefined'); + } + if (!(manifestXmlFile instanceof DefaultTreeItem)) { + throw new Error(`Expected DefaultTreeItem but got different item type: ${typeof manifestXmlFile}`); + } + if (!manifestXmlFile) { + log('Retrieve: ERROR - manifest.xml file not found after expansion'); + throw new Error('No manifest.xml file found'); + } + log('Retrieve: Found manifest.xml file'); + expect(manifestXmlFile).to.not.be.undefined; + + // Ensure the file is visible and interactable + log('Retrieve: Selecting manifest.xml file to ensure it is interactable'); + await manifestXmlFile.select(); + log('Retrieve: File selected, waiting for UI to update'); + await pause(Duration.milliseconds(500)); + + log('Retrieve: Opening context menu on manifest.xml'); + const contextMenu = await manifestXmlFile.openContextMenu(); + log('Retrieve: Context menu opened, selecting retrieve command'); + await contextMenu.select('SFDX: Retrieve Source in Manifest from Org'); + log('Retrieve: Retrieve command selected from context menu'); + } else { + // Using the Command palette, run SFDX: Retrieve Source in Manifest from Org + await executeQuickPick('SFDX: Retrieve Source in Manifest from Org', Duration.seconds(10)); + } + + await validateCommand('Retrieve', 'from', 'ST', 'CustomObject', ['Customer__c', 'Product__c']); + }); + + after('Tear down and clean up the testing environment', async () => { + log(`${testSetup.testSuiteSuffixName} - Tear down and clean up the testing environment`); + await testSetup?.tearDown(); + }); +}); diff --git a/packages/salesforcedx-vscode-automation-tests/test/specs/metadataDeployRetrieve.e2e.ts b/packages/salesforcedx-vscode-automation-tests/test/specs/metadataDeployRetrieve.e2e.ts new file mode 100644 index 0000000000..33f0e1f8a8 --- /dev/null +++ b/packages/salesforcedx-vscode-automation-tests/test/specs/metadataDeployRetrieve.e2e.ts @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2024, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ +import { + log, + openFile, + TestReqConfig, + ProjectShapeOption +} from '@salesforce/salesforcedx-vscode-test-tools/lib/src/core'; +import { runAndValidateCommand } from '@salesforce/salesforcedx-vscode-test-tools/lib/src/salesforce-components'; +import { gitCheckout } from '@salesforce/salesforcedx-vscode-test-tools/lib/src/system-operations'; +import { TestSetup } from '@salesforce/salesforcedx-vscode-test-tools/lib/src/testSetup'; +import { + attemptToFindTextEditorText, + clearOutputView, + closeAllEditors +} from '@salesforce/salesforcedx-vscode-test-tools/lib/src/ui-interaction'; +import { expect } from 'chai'; +import * as path from 'node:path'; +import { after } from 'vscode-extension-tester'; + +// In future we will merge the test together with deployAndRetrieve +describe('metadata mdDeployRetrieve', () => { + let testSetup: TestSetup; + const testReqConfig: TestReqConfig = { + projectConfig: { + projectShape: ProjectShapeOption.NAMED, + githubRepoUrl: 'https://github.com/mingxuanzhangsfdx/DeployInv.git' + }, + isOrgRequired: true, + testSuiteSuffixName: 'mdDeployRetrieve' + }; + let mdPath: string; + let textV1: string; + let textV2: string; + let textV2AfterRetrieve: string; + + before('Set up the testing environment', async () => { + log('mdDeployRetrieve - Set up the testing environment'); + testSetup = await TestSetup.setUp(testReqConfig); + mdPath = path.join( + testSetup.projectFolderPath!, + 'force-app/main/default/objects/Account/fields/Deploy_Test__c.field-meta.xml' + ); + }); + + it('Open and deploy MD v1', async () => { + log('mdDeployRetrieve - Open and deploy MD v1'); + await openFile(mdPath); + textV1 = await attemptToFindTextEditorText(mdPath); + await runAndValidateCommand('Deploy', 'to', 'ST', 'CustomField', 'Account.Deploy_Test__c'); + await clearOutputView(); + await closeAllEditors(); // close editor to make sure editor is up to date + }); + + it('Update MD v2 and deploy again', async () => { + log('mdDeployRetrieve - Update MD v2 and deploy again'); + await gitCheckout('updated-md', testSetup.projectFolderPath); + await openFile(mdPath); + textV2 = await attemptToFindTextEditorText(mdPath); + expect(textV1).not.to.equal(textV2); // MD file should be updated + await runAndValidateCommand('Deploy', 'to', 'ST', 'CustomField', 'Account.Deploy_Test__c'); + await clearOutputView(); + }); + + it('Retrieve MD v2 and verify the text not changed', async () => { + log('mdDeployRetrieve - Retrieve MD v2 and verify the text not changed'); + await openFile(mdPath); + await runAndValidateCommand('Retrieve', 'from', 'ST', 'CustomField', 'Account.Deploy_Test__c'); + textV2AfterRetrieve = await attemptToFindTextEditorText(mdPath); + + expect(textV2AfterRetrieve).to.contain(textV2); // should be same + }); + + after('Tear down and clean up the testing environment', async () => { + log('mdDeployRetrieve - Tear down and clean up the testing environment'); + await gitCheckout('main', testSetup.projectFolderPath); + await testSetup?.tearDown(); + }); +}); diff --git a/packages/salesforcedx-vscode-automation-tests/test/specs/miscellaneous.e2e.ts b/packages/salesforcedx-vscode-automation-tests/test/specs/miscellaneous.e2e.ts new file mode 100644 index 0000000000..bacc471674 --- /dev/null +++ b/packages/salesforcedx-vscode-automation-tests/test/specs/miscellaneous.e2e.ts @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2024, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ +import { + Duration, + pause, + TestReqConfig, + ProjectShapeOption, + log +} from '@salesforce/salesforcedx-vscode-test-tools/lib/src/core'; +import { createAnonymousApexFile } from '@salesforce/salesforcedx-vscode-test-tools/lib/src/salesforce-components'; +import { createGlobalSnippetsFile } from '@salesforce/salesforcedx-vscode-test-tools/lib/src/system-operations'; +import { TestSetup } from '@salesforce/salesforcedx-vscode-test-tools/lib/src/testSetup'; +import { + getWorkbench, + getTextEditor, + executeQuickPick +} from '@salesforce/salesforcedx-vscode-test-tools/lib/src/ui-interaction'; +import { expect } from 'chai'; +import { By, after } from 'vscode-extension-tester'; +import { logTestStart } from '../utils/loggingHelper'; + +describe('Miscellaneous', () => { + let testSetup: TestSetup; + const testReqConfig: TestReqConfig = { + projectConfig: { + projectShape: ProjectShapeOption.NEW + }, + isOrgRequired: false, + testSuiteSuffixName: 'Miscellaneous' + }; + + before('Set up the testing environment', async () => { + testSetup = await TestSetup.setUp(testReqConfig); + }); + + it.skip('Use out-of-the-box Apex Snippets', async () => { + logTestStart(testSetup, 'Use Apex Snippets'); + const workbench = await getWorkbench(); + const apexSnippet = 'String.isBlank(inputString)'; + + // Create anonymous apex file + await createAnonymousApexFile(); + + // Type snippet "isb" in a new line and check it inserted the expected string + const textEditor = await getTextEditor(workbench, 'Anonymous.apex'); + const inputBox = await executeQuickPick('Snippets: Insert Snippet', Duration.seconds(1)); + await inputBox.setText('isb'); + await pause(Duration.seconds(1)); + await inputBox.confirm(); + await textEditor.save(); + const fileContent = await textEditor.getText(); + await expect(fileContent).to.contain(apexSnippet); + }); + + it.skip('Use Custom Apex Snippets', async () => { + logTestStart(testSetup, 'Use Apex Snippets'); + + // Using the Command palette, run Snippets: Configure Snippets + const workbench = await getWorkbench(); + await createGlobalSnippetsFile(testSetup); + + // Create anonymous apex file + await createAnonymousApexFile(); + + // Type snippet "soql" and check it inserted the expected query + const textEditor = await getTextEditor(workbench, 'Anonymous.apex'); + await textEditor.typeText('soql'); + await pause(Duration.seconds(1)); + const autocompletionOptions = await workbench.findElements(By.css('div.monaco-list-row.show-file-icons')); + const ariaLabel = await autocompletionOptions[0].getAttribute('aria-label'); + expect(ariaLabel).to.contain('soql'); + + // Verify autocompletion options can be selected and therefore automatically inserted into the file + await autocompletionOptions[0].click(); + await textEditor.save(); + const fileContent = await textEditor.getText(); + expect(fileContent).to.contain('[SELECT field1, field2 FROM SobjectName WHERE clause];'); + }); + + it.skip('Use out-of-the-box LWC Snippets - HTML', async () => { + logTestStart(testSetup, 'Use out-of-the-box LWC Snippets - HTML'); + const workbench = await getWorkbench(); + + const lwcSnippet = [ + '' + ].join('\n'); + + // Create simple lwc.html file + let inputBox = await executeQuickPick('Create: New File...', Duration.seconds(1)); + await inputBox.setText('lwc.html'); + await inputBox.confirm(); + await inputBox.confirm(); + + // Type snippet "lwc-button" and check it inserted the right lwc + const textEditor = await getTextEditor(workbench, 'lwc.html'); + + inputBox = await executeQuickPick('Snippets: Insert Snippet', Duration.seconds(1)); + await inputBox.setText('lwc-button'); + await pause(Duration.seconds(2)); + await inputBox.confirm(); + await textEditor.save(); + const fileContent = await textEditor.getText(); + + const fileContentWithoutTrailingSpaces = fileContent + .split('\n') + .map(line => line.trimEnd()) + .join('\n'); + + await expect(fileContentWithoutTrailingSpaces).to.contain(lwcSnippet); + }); + + it('Use out-of-the-box LWC Snippets - JS', async () => { + logTestStart(testSetup, 'Use out-of-the-box LWC Snippets - JS'); + const workbench = await getWorkbench(); + + const lwcSnippet = 'this.dispatchEvent(new CustomEvent("event-name"));'; + + // Create simple lwc.js file + const inputBox = await executeQuickPick('Create: New File...', Duration.seconds(1)); + await inputBox.setText('lwc.js'); + await inputBox.confirm(); + await inputBox.confirm(); + + // Type snippet "lwc", select "lwc-event" and check it inserted the right thing + const textEditor = await getTextEditor(workbench, 'lwc.js'); + await textEditor.typeText('lwc'); + await pause(Duration.seconds(1)); + const autocompletionOptions = await workbench.findElements(By.css('div.monaco-list-row.show-file-icons')); + const ariaLabel = await autocompletionOptions[2].getAttribute('aria-label'); + expect(ariaLabel).to.contain('lwc-event'); + + // Verify autocompletion options can be selected and therefore automatically inserted into the file + await autocompletionOptions[2].click(); + await textEditor.save(); + const fileContent = await textEditor.getText(); + + await expect(fileContent).to.contain(lwcSnippet); + }); + + after('Tear down and clean up the testing environment', async () => { + log(`${testSetup.testSuiteSuffixName} - Tear down and clean up the testing environment`); + await testSetup?.tearDown(); + }); +}); diff --git a/packages/salesforcedx-vscode-automation-tests/test/specs/orgBrowser.e2e.ts b/packages/salesforcedx-vscode-automation-tests/test/specs/orgBrowser.e2e.ts new file mode 100644 index 0000000000..70f2e95d08 --- /dev/null +++ b/packages/salesforcedx-vscode-automation-tests/test/specs/orgBrowser.e2e.ts @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2023, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ +import { + Duration, + findElementByText, + pause, + TestReqConfig, + ProjectShapeOption, + log +} from '@salesforce/salesforcedx-vscode-test-tools/lib/src/core'; +import { + createApexClass, + findTypeInOrgBrowser, + openOrgBrowser, + runAndValidateCommand, + validateCommand, + verifyOrgBrowserIsOpen +} from '@salesforce/salesforcedx-vscode-test-tools/lib/src/salesforce-components'; +import { TestSetup } from '@salesforce/salesforcedx-vscode-test-tools/lib/src/testSetup'; +import { + closeCurrentEditor, + dismissAllNotifications, + getWorkbench +} from '@salesforce/salesforcedx-vscode-test-tools/lib/src/ui-interaction'; +import { expect } from 'chai'; +import { By, ModalDialog, after } from 'vscode-extension-tester'; +import { logTestStart } from '../utils/loggingHelper'; + +describe('Org Browser', () => { + let testSetup: TestSetup; + const testReqConfig: TestReqConfig = { + projectConfig: { + projectShape: ProjectShapeOption.NEW + }, + isOrgRequired: true, + testSuiteSuffixName: 'OrgBrowser' + }; + + before('Set up the testing environment', async () => { + testSetup = await TestSetup.setUp(testReqConfig); + }); + + it('Check Org Browser is connected to target org', async () => { + logTestStart(testSetup, 'Check Org Browser is connected to target org'); + + await openOrgBrowser(Duration.seconds(10)); + await verifyOrgBrowserIsOpen(); + + log(`${testSetup.testSuiteSuffixName} - Org Browser is connected to target org`); + }); + + it('Check some metadata types are available', async () => { + logTestStart(testSetup, 'Check some metadata types are available'); + const metadataTypes = [ + 'AI Applications', + 'Apex Classes', + 'Apex Test Suites', + 'Apex Triggers', + 'App Menus', + 'Assignment Rules', + 'Aura Components', + 'Auth Providers', + 'Branding Sets', + 'Certificates', + 'Communities' + ]; + for (const type of metadataTypes) { + const element = await findTypeInOrgBrowser(type); + expect(element).to.not.be.undefined; + } + }); + + it('Verify there are no Apex Classes available', async () => { + logTestStart(testSetup, 'Verify there are no Apex Classes available'); + // Check there are no classes displayed + const apexClassesLabelEl = await findTypeInOrgBrowser('Apex Classes'); + expect(apexClassesLabelEl).to.not.be.undefined; + await apexClassesLabelEl?.click(); + await pause(Duration.seconds(10)); + const noCompsAvailableLabelEl = await findElementByText('div', 'aria-label', 'No components available'); + expect(noCompsAvailableLabelEl).to.not.be.undefined; + }); + + it('Create Apex Class and deploy to org', async () => { + logTestStart(testSetup, 'Create Apex Class and deploy to org'); + + // Create Apex Class + const classText = [ + 'public with sharing class MyClass {', + '', + '\tpublic static void SayHello(string name){', + "\t\tSystem.debug('Hello, ' + name + '!');", + '\t}', + '}' + ].join('\n'); + await createApexClass('MyClass', classText); + await runAndValidateCommand('Deploy', 'to', 'ST', 'ApexClass', 'MyClass', 'Created '); + + await closeCurrentEditor(); + }); + + it('Refresh Org Browser and check MyClass is there', async () => { + logTestStart(testSetup, 'Refresh Apex Classes'); + // Check MyClass is present under Apex Classes section + const apexClassesItem = await findTypeInOrgBrowser('Apex Classes'); + expect(apexClassesItem).to.not.be.undefined; + const refreshComponentsButton = (await apexClassesItem?.findElements(By.css('a.action-label')))![1]; + expect(refreshComponentsButton).to.not.be.undefined; + await refreshComponentsButton?.click(); + await pause(Duration.seconds(10)); + const myClassLabelEl = await findTypeInOrgBrowser('MyClass'); + expect(myClassLabelEl).to.not.be.undefined; + }); + + it('Retrieve This Source from Org', async () => { + logTestStart(testSetup, 'Retrieve This Source from Org'); + const myClassLabelEl = await findTypeInOrgBrowser('MyClass'); + expect(myClassLabelEl).to.not.be.undefined; + await myClassLabelEl?.click(); + await pause(Duration.seconds(1)); + const retrieveSourceButton = (await myClassLabelEl?.findElements(By.css('a.action-label')))![1]; + expect(retrieveSourceButton).to.not.be.undefined; + await retrieveSourceButton.click(); + await pause(Duration.seconds(2)); + // Confirm Overwrite + const modalDialog = new ModalDialog(); + expect(modalDialog).to.not.be.undefined; + await modalDialog.pushButton('Overwrite'); + + await validateCommand('Retrieve', 'from', 'ST', 'ApexClass', ['MyClass']); + }); + + it('Retrieve and Open Source', async () => { + logTestStart(testSetup, 'Retrieve and Open Source'); + // Close all notifications + await dismissAllNotifications(); + const myClassLabelEl = await findTypeInOrgBrowser('MyClass'); + expect(myClassLabelEl).to.not.be.undefined; + await myClassLabelEl?.click(); + await pause(Duration.seconds(1)); + const retrieveAndOpenSourceButton = (await myClassLabelEl?.findElements(By.css('a.action-label')))![0]; + expect(retrieveAndOpenSourceButton).to.not.be.undefined; + await retrieveAndOpenSourceButton.click(); + await pause(Duration.seconds(2)); + // Confirm Overwrite + const modalDialog = new ModalDialog(); + expect(modalDialog).to.not.be.undefined; + await modalDialog.pushButton('Overwrite'); + + await validateCommand('Retrieve', 'from', 'ST', 'ApexClass', ['MyClass']); + + // Verify 'Retrieve and Open Source' took us to MyClass.cls + const workbench = getWorkbench(); + const editorView = workbench.getEditorView(); + const activeTab = await editorView.getActiveTab(); + const title = await activeTab?.getTitle(); + expect(title).to.equal('MyClass.cls'); + }); + + after('Tear down and clean up the testing environment', async () => { + log(`${testSetup.testSuiteSuffixName} - Tear down and clean up the testing environment`); + await testSetup?.tearDown(); + }); +}); diff --git a/packages/salesforcedx-vscode-automation-tests/test/specs/pushAndPull.e2e.ts b/packages/salesforcedx-vscode-automation-tests/test/specs/pushAndPull.e2e.ts new file mode 100644 index 0000000000..53543eb455 --- /dev/null +++ b/packages/salesforcedx-vscode-automation-tests/test/specs/pushAndPull.e2e.ts @@ -0,0 +1,311 @@ +/* + * Copyright (c) 2023, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +import { + createCommand, + Duration, + log, + pause, + ProjectShapeOption, + TestReqConfig +} from '@salesforce/salesforcedx-vscode-test-tools/lib/src/core'; +import { verifyNotificationWithRetry } from '@salesforce/salesforcedx-vscode-test-tools/lib/src/retryUtils'; +import { runCliCommand } from '@salesforce/salesforcedx-vscode-test-tools/lib/src/system-operations'; +import { TestSetup } from '@salesforce/salesforcedx-vscode-test-tools/lib/src/testSetup'; +import { + attemptToFindOutputPanelText, + clearOutputView, + executeQuickPick, + getTextEditor, + getWorkbench, + overrideTextInFile, + reloadWindow +} from '@salesforce/salesforcedx-vscode-test-tools/lib/src/ui-interaction'; +import { expect } from 'chai'; +import * as path from 'node:path'; +import { after } from 'vscode-extension-tester'; +import { logTestStart } from '../utils/loggingHelper'; + +describe('Push and Pull', () => { + let testSetup1: TestSetup; + let testSetup2: TestSetup; + const testReqConfig: TestReqConfig = { + projectConfig: { + projectShape: ProjectShapeOption.NEW + }, + isOrgRequired: true, + testSuiteSuffixName: 'PushAndPull' + }; + + before('Set up the testing environment', async () => { + log('Push And Pull - Set up the testing environment'); + testSetup1 = await TestSetup.setUp(testReqConfig); + }); + + it('SFDX: View All Changes (Local and in Default Org)', async () => { + logTestStart(testSetup1, 'Push And Pull - SFDX: View All Changes (Local and in Default Org)'); + await executeQuickPick('SFDX: View All Changes (Local and in Default Org)', Duration.seconds(5)); + + // Check the output. + const outputPanelText = await attemptToFindOutputPanelText('Salesforce CLI', 'Source Status', 10); + expect(outputPanelText).to.contain('No local or remote changes found'); + }); + + it('Create an Apex class', async () => { + logTestStart(testSetup1, 'Push And Pull - Create an Apex class'); + // Create an Apex Class. + await createCommand('Apex Class', 'ExampleApexClass1', 'classes', 'cls'); + }); + + it('SFDX: View Local Changes', async () => { + logTestStart(testSetup1, 'Push And Pull - SFDX: View Local Changes'); + await executeQuickPick('SFDX: View Local Changes', Duration.seconds(5)); + + // Check the output. + const outputPanelText = await attemptToFindOutputPanelText('Salesforce CLI', 'Source Status', 10); + expect(outputPanelText).to.contain( + `Local Add ExampleApexClass1 ApexClass ${path.join('force-app', 'main', 'default', 'classes', 'ExampleApexClass1.cls')}` + ); + expect(outputPanelText).to.contain( + `Local Add ExampleApexClass1 ApexClass ${path.join('force-app', 'main', 'default', 'classes', 'ExampleApexClass1.cls-meta.xml')}` + ); + }); + + it('Push the Apex class', async () => { + logTestStart(testSetup1, 'Push And Pull - Push the Apex class'); + await executeQuickPick('SFDX: Push Source to Default Org', Duration.seconds(5)); + + await verifyPushSuccess(); + // Check the output. + await verifyPushAndPullOutputText('Push', 'to', 'Created'); + }); + + it('Push again (with no changes)', async () => { + logTestStart(testSetup1, 'Push And Pull - Push again (with no changes)'); + // Clear the Output view first. + await clearOutputView(Duration.seconds(2)); + + // Now push + await executeQuickPick('SFDX: Push Source to Default Org', Duration.seconds(5)); + + await verifyPushSuccess(); + // Check the output. + await verifyPushAndPullOutputText('Push', 'to'); + }); + + it('Modify the file and push the changes', async () => { + logTestStart(testSetup1, 'Push And Pull - Modify the file and push the changes'); + const newText = `public with sharing class ExampleApexClass1 { + public ExampleApexClass1() { + // sample comment + } + }`; + + // Clear the Output view first. + await clearOutputView(Duration.seconds(2)); + + // Modify the file by adding a comment. + const workbench = getWorkbench(); + const textEditor = await getTextEditor(workbench, 'ExampleApexClass1.cls'); + + // Wait for editor to stabilize before continuing + await pause(Duration.seconds(1)); + + // Push the file. + await executeQuickPick('SFDX: Push Source to Default Org', Duration.seconds(5)); + + await verifyPushSuccess(); + // Check the output. + await verifyPushAndPullOutputText('Push', 'to'); + + // Clear the Output view again. + await clearOutputView(Duration.seconds(2)); + + // Don't save the file just yet. + await overrideTextInFile(textEditor, newText, false); + + // An now push the changes. + await executeQuickPick('SFDX: Push Source to Default Org', Duration.seconds(5)); + + await verifyPushSuccess(); + // Check the output. + const outputPanelText = await verifyPushAndPullOutputText('Push', 'to', 'Changed'); + + expect(outputPanelText).to.contain( + path.join( + 'e2e-temp', + 'TempProject-PushAndPull', + 'force-app', + 'main', + 'default', + 'classes', + 'ExampleApexClass1.cls' + ) + ); + expect(outputPanelText).to.contain( + path.join( + 'e2e-temp', + 'TempProject-PushAndPull', + 'force-app', + 'main', + 'default', + 'classes', + 'ExampleApexClass1.cls-meta.xml' + ) + ); + }); + + it('Pull the Apex class', async () => { + logTestStart(testSetup1, 'Push And Pull - Pull the Apex class'); + // With this test, it's going to pull twice... + // Clear the Output view first. + await clearOutputView(Duration.seconds(2)); + + await executeQuickPick('SFDX: Pull Source from Default Org', Duration.seconds(5)); + // At this point there should be no conflicts since there have been no changes. + await verifyPullSuccess(); + // Check the output. + let outputPanelText = await verifyPushAndPullOutputText('Pull', 'from', 'Created'); + // The first time a pull is performed, force-app/main/default/profiles/Admin.profile-meta.xml is pulled down. + expect(outputPanelText).to.contain(path.join('force-app', 'main', 'default', 'profiles', 'Admin.profile-meta.xml')); + + // Second pull... + // Clear the output again. + await clearOutputView(Duration.seconds(2)); + + // And pull again. + await executeQuickPick('SFDX: Pull Source from Default Org', Duration.seconds(5)); + await verifyPullSuccess(); + // Check the output. + outputPanelText = await verifyPushAndPullOutputText('Pull', 'from'); + expect(outputPanelText).to.not.contain('Created Admin'); + }); + + it("Modify the file (but don't save), then pull", async () => { + logTestStart(testSetup1, "Push And Pull - Modify the file (but don't save), then pull"); + // Clear the Output view first. + await clearOutputView(Duration.seconds(2)); + + // Modify the file by adding a comment. + const workbench = getWorkbench(); + const textEditor = await getTextEditor(workbench, 'ExampleApexClass1.cls'); + const newText = `public with sharing class ExampleApexClass1 { + public ExampleApexClass1() { + // sample comment for the pull test + } + }`; + // Don't save the file just yet. + await overrideTextInFile(textEditor, newText, false); + + // Wait for editor to stabilize before continuing + await pause(Duration.seconds(1)); + + // Pull the file. + await executeQuickPick('SFDX: Pull Source from Default Org', Duration.seconds(5)); + await verifyPullSuccess(); + // Check the output. + await verifyPushAndPullOutputText('Pull', 'from'); + }); + + it('Save the modified file, then pull', async () => { + logTestStart(testSetup1, 'Push And Pull - Save the modified file, then pull'); + // Clear the Output view first. + await clearOutputView(Duration.seconds(2)); + + // Now save the file. + const workbench = getWorkbench(); + const textEditor = await getTextEditor(workbench, 'ExampleApexClass1.cls'); + await textEditor.save(); + + // An now pull the changes. + await executeQuickPick('SFDX: Pull Source from Default Org', Duration.seconds(5)); + await verifyPullSuccess(); + // Check the output. + await verifyPushAndPullOutputText('Pull', 'from'); + }); + + const testReqConfig2: TestReqConfig = { + projectConfig: { + projectShape: ProjectShapeOption.NEW + }, + isOrgRequired: false, + testSuiteSuffixName: 'ViewChanges' + }; + + it('SFDX: View Changes in Default Org', async () => { + log('Push And Pull - SFDX: View Changes in Default Org'); + // Create second Project to then view Remote Changes + // The new project will connect to the scratch org automatically on GHA, but does not work locally + testSetup2 = await TestSetup.setUp(testReqConfig2); + await runCliCommand('config set', `target-org=${testSetup1.scratchOrgAliasName}`); + + // Run SFDX: View Changes in Default Org command to view remote changes + await executeQuickPick('SFDX: View Changes in Default Org', Duration.seconds(5)); + + // Reload window to update cache + await reloadWindow(Duration.seconds(20)); + + // Run SFDX: View Changes in Default Org command to view remote changes + await executeQuickPick('SFDX: View Changes in Default Org', Duration.seconds(5)); + + // Check the output. + const outputPanelText = await attemptToFindOutputPanelText('Salesforce CLI', 'Source Status', 10); + expect(outputPanelText).to.contain('Remote Add ExampleApexClass1 ApexClass'); + }); + + // TODO: at this point write e2e tests for conflict detection + // but there's a bug - when the 2nd user is created the code thinks + // it's a source tracked org and push & pull are no longer available + // (yet deploy & retrieve are). Spoke with Ken and we think this will + // be fixed with the check in of his PR this week. + + after('Tear down and clean up the testing environment', async () => { + log('Push and Pull - Tear down and clean up the testing environment'); + await testSetup1?.tearDown(false); + await testSetup2?.tearDown(); + }); +}); + +/** + * @param operation identifies if it's a pull or push operation + * @param fromTo indicates if changes are coming from or going to the org + * @param type indicates if the metadata is expected to have been created, changed or deleted + * @returns the output panel text after + */ +const verifyPushAndPullOutputText = async ( + operation: string, + fromTo: string, + type?: string +): Promise => { + await verifyNotificationWithRetry( + new RegExp(`SFDX: ${operation} Source ${fromTo} Default Org successfully ran`), + Duration.TEN_MINUTES + ); + // Check the output. + const outputPanelText = await attemptToFindOutputPanelText('Salesforce CLI', `=== ${operation}ed Source`, 10); + + log(`outputPanelText: ${outputPanelText}`); + if (type) { + if (operation === 'Push') { + expect(outputPanelText).to.contain(`${type} ExampleApexClass1 ApexClass`); + } else { + expect(outputPanelText).to.contain(`${type} Admin`); + } + } else { + expect(outputPanelText).to.contain('No results found'); + } + expect(outputPanelText).to.contain('ended with exit code 0'); + return outputPanelText; +}; + +const verifyPushSuccess = async (wait = Duration.TEN_MINUTES) => { + await verifyNotificationWithRetry(/SFDX: Push Source to Default Org successfully ran/, wait); +}; + +const verifyPullSuccess = async (wait = Duration.TEN_MINUTES) => { + await verifyNotificationWithRetry(/SFDX: Pull Source from Default Org successfully ran/, wait); +}; diff --git a/packages/salesforcedx-vscode-automation-tests/test/specs/runApexTests.e2e.ts b/packages/salesforcedx-vscode-automation-tests/test/specs/runApexTests.e2e.ts new file mode 100644 index 0000000000..e7740fb92a --- /dev/null +++ b/packages/salesforcedx-vscode-automation-tests/test/specs/runApexTests.e2e.ts @@ -0,0 +1,494 @@ +/* + * Copyright (c) 2023, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ +import { + Duration, + log, + pause, + ProjectShapeOption, + TestReqConfig +} from '@salesforce/salesforcedx-vscode-test-tools/lib/src/core'; +import { EnvironmentSettings } from '@salesforce/salesforcedx-vscode-test-tools/lib/src/environmentSettings'; +import { + retryOperation, + verifyNotificationWithRetry +} from '@salesforce/salesforcedx-vscode-test-tools/lib/src/retryUtils'; +import { + createApexClassWithBugs, + createApexClassWithTest +} from '@salesforce/salesforcedx-vscode-test-tools/lib/src/salesforce-components'; +import { + getTestsSection, + runTestCaseFromSideBar, + verifyTestIconColor, + verifyTestItemsInSideBar +} from '@salesforce/salesforcedx-vscode-test-tools/lib/src/testing'; +import { TestSetup } from '@salesforce/salesforcedx-vscode-test-tools/lib/src/testSetup'; +import { + attemptToFindOutputPanelText, + clearOutputView, + clickFilePathOkButton, + dismissAllNotifications, + executeQuickPick, + getStatusBarItemWhichIncludes, + getTextEditor, + getWorkbench, + verifyOutputPanelText, + waitForAndGetCodeLens +} from '@salesforce/salesforcedx-vscode-test-tools/lib/src/ui-interaction'; +import { expect } from 'chai'; +import * as semver from 'semver'; +import { By, InputBox, QuickOpenBox, SideBarView } from 'vscode-extension-tester'; +import { logTestStart } from '../utils/loggingHelper'; + +describe('Run Apex Tests', () => { + let prompt: InputBox | QuickOpenBox; + let testSetup: TestSetup; + const testReqConfig: TestReqConfig = { + projectConfig: { + projectShape: ProjectShapeOption.NEW + }, + isOrgRequired: true, + testSuiteSuffixName: 'RunApexTests' + }; + + before('Set up the testing environment', async () => { + log('RunApexTests - Set up the testing environment'); + testSetup = await TestSetup.setUp(testReqConfig); + + // Create Apex class 1 and test + await retryOperation( + () => createApexClassWithTest('ExampleApexClass1'), + 2, + 'RunApexTests - Error creating Apex class 1 and test' + ); + + // Create Apex class 2 and test + await retryOperation( + () => createApexClassWithTest('ExampleApexClass2'), + 2, + 'RunApexTests - Error creating Apex class 2 and test' + ); + + // Create Apex class 3 and test + await retryOperation( + () => createApexClassWithTest('ExampleApexClass3'), + 2, + 'RunApexTests - Error creating Apex class 3 and test' + ); + + // Push source to org + await executeQuickPick('SFDX: Push Source to Default Org and Ignore Conflicts', Duration.seconds(1)); + + // Look for the success notification that appears which says, "SFDX: Push Source to Default Org and Ignore Conflicts successfully ran". + await verifyNotificationWithRetry( + /SFDX: Push Source to Default Org and Ignore Conflicts successfully ran/, + Duration.TEN_MINUTES + ); + }); + + it('Verify LSP finished indexing', async () => { + logTestStart(testSetup, 'Verify LSP finished indexing'); + + // Get Apex LSP Status Bar + const statusBar = await retryOperation(async () => await getStatusBarItemWhichIncludes('Editor Language Status')); + await statusBar.click(); + expect(await statusBar.getAttribute('aria-label')).to.contain('Indexing complete'); + }); + + it('Run All Tests via Apex Class', async () => { + logTestStart(testSetup, 'Run All Tests via Apex Class'); + const workbench = getWorkbench(); + const textEditor = await getTextEditor(workbench, 'ExampleApexClass1Test.cls'); + + // Clear the Output view. + await dismissAllNotifications(); + await clearOutputView(Duration.seconds(2)); + + // Click the "Run All Tests" code lens at the top of the class + const runAllTestsOption = await waitForAndGetCodeLens(textEditor, 'Run All Tests'); + expect(runAllTestsOption).to.not.be.undefined; + await runAllTestsOption!.click(); + // Look for the success notification that appears which says, "SFDX: Run Apex Tests successfully ran". + await verifyNotificationWithRetry(/SFDX: Run Apex Tests successfully ran/, Duration.TEN_MINUTES); + + // Verify test results are listed on vscode's Output section + // Also verify that all tests pass + const outputPanelText = await attemptToFindOutputPanelText('Apex', '=== Test Results', 10); + const expectedTexts = [ + '=== Test Summary', + 'Outcome Passed', + 'Tests Ran 1', + 'Pass Rate 100%', + 'TEST NAME', + 'ExampleApexClass1Test.validateSayHello Pass', + 'ended SFDX: Run Apex Tests' + ]; + + await verifyOutputPanelText(outputPanelText, expectedTexts); + }); + + it('Run Single Test via Apex Class', async () => { + logTestStart(testSetup, 'Run Single Test via Apex Class'); + const workbench = getWorkbench(); + const textEditor = await getTextEditor(workbench, 'ExampleApexClass2Test.cls'); + + // Clear the Output view. + await dismissAllNotifications(); + await clearOutputView(Duration.seconds(2)); + + // Click the "Run Test" code lens at the top of one of the test methods + const runTestOption = await waitForAndGetCodeLens(textEditor, 'Run Test'); + expect(runTestOption).to.not.be.undefined; + await runTestOption!.click(); + // Look for the success notification that appears which says, "SFDX: Run Apex Tests successfully ran". + await verifyNotificationWithRetry(/SFDX: Run Apex Tests successfully ran/, Duration.TEN_MINUTES); + + // Verify test results are listed on vscode's Output section + // Also verify that all tests pass + const outputPanelText = await attemptToFindOutputPanelText('Apex', '=== Test Results', 10); + const expectedTexts = [ + '=== Test Summary', + 'Outcome Passed', + 'Tests Ran 1', + 'Pass Rate 100%', + 'TEST NAME', + 'ExampleApexClass2Test.validateSayHello Pass', + 'ended SFDX: Run Apex Tests' + ]; + + await verifyOutputPanelText(outputPanelText, expectedTexts); + }); + + it('Run All Tests via Command Palette', async () => { + logTestStart(testSetup, 'Run All Tests via Command Palette'); + // Clear the Output view. + await dismissAllNotifications(); + await clearOutputView(Duration.seconds(2)); + + // Run SFDX: Run Apex tests. + prompt = await executeQuickPick('SFDX: Run Apex Tests', Duration.seconds(1)); + + // Select the "All Tests" option + await prompt.selectQuickPick('All Tests'); + // Look for the success notification that appears which says, "SFDX: Run Apex Tests successfully ran". + await verifyNotificationWithRetry(/SFDX: Run Apex Tests successfully ran/, Duration.TEN_MINUTES); + + await pause(Duration.seconds(10)); // Remove this once we have a way to wait for the tests to finish running + + // Verify test results are listed on vscode's Output section + // Also verify that all tests pass + const outputPanelText = await attemptToFindOutputPanelText('Apex', '=== Test Results', 10); + const expectedTexts = [ + '=== Test Summary', + 'Outcome Passed', + 'Tests Ran 3', + 'Pass Rate 100%', + 'TEST NAME', + 'ExampleApexClass1Test.validateSayHello Pass', + 'ExampleApexClass2Test.validateSayHello Pass', + 'ExampleApexClass3Test.validateSayHello Pass', + 'ended SFDX: Run Apex Tests' + ]; + + await verifyOutputPanelText(outputPanelText, expectedTexts); + }); + + it('Run Single Class via Command Palette', async () => { + logTestStart(testSetup, 'Run Single Class via Command Palette'); + // Clear the Output view. + await dismissAllNotifications(); + await clearOutputView(Duration.seconds(2)); + + // Run SFDX: Run Apex tests. + prompt = await executeQuickPick('SFDX: Run Apex Tests', Duration.seconds(1)); + + // Select the "ExampleApexClass1Test" file + await prompt.selectQuickPick('ExampleApexClass1Test'); + // Look for the success notification that appears which says, "SFDX: Run Apex Tests successfully ran". + await verifyNotificationWithRetry(/SFDX: Run Apex Tests successfully ran/, Duration.TEN_MINUTES); + + // Verify test results are listed on vscode's Output section + // Also verify that all tests pass + const outputPanelText = await attemptToFindOutputPanelText('Apex', '=== Test Results', 10); + const expectedTexts = [ + '=== Test Summary', + 'Outcome Passed', + 'Tests Ran 1', + 'Pass Rate 100%', + 'TEST NAME', + 'ExampleApexClass1Test.validateSayHello Pass', + 'ended SFDX: Run Apex Tests' + ]; + await verifyOutputPanelText(outputPanelText, expectedTexts); + }); + + it('Run All tests via Test Sidebar', async () => { + logTestStart(testSetup, 'Run All tests via Test Sidebar'); + const workbench = getWorkbench(); + const testingView = await workbench.getActivityBar().getViewControl('Testing'); + expect(testingView).to.not.be.undefined; + // Open the Test Sidebar + const testingSideBarView = await testingView?.openView(); + expect(testingSideBarView).to.be.instanceOf(SideBarView); + + const apexTestsSection = await getTestsSection(workbench, 'Apex Tests'); + await pause(Duration.seconds(10)); // Wait for test section to load + const expectedItems = ['ExampleApexClass1Test', 'ExampleApexClass2Test', 'ExampleApexClass3Test']; + const apexTestsItems = await verifyTestItemsInSideBar(apexTestsSection, 'Refresh Tests', expectedItems, 6, 3); + + // Clear the Output view. + await dismissAllNotifications(); + await clearOutputView(Duration.seconds(2)); + + // Click the run tests button on the top right corner of the Test sidebar + await apexTestsSection.click(); + const runTestsAction = await apexTestsSection.getAction('Run Tests'); + await runTestsAction!.click(); + // Look for the success notification that appears which says, "SFDX: Run Apex Tests successfully ran". + await verifyNotificationWithRetry(/SFDX: Run Apex Tests successfully ran/, Duration.TEN_MINUTES); + + // Verify test results are listed on vscode's Output section + // Also verify that all tests pass + const outputPanelText = await attemptToFindOutputPanelText('Apex', '=== Test Results', 10); + const expectedTexts = [ + '=== Test Summary', + 'Outcome Passed', + 'Tests Ran 3', + 'Pass Rate 100%', + 'TEST NAME', + 'ExampleApexClass1Test.validateSayHello Pass', + 'ExampleApexClass2Test.validateSayHello Pass', + 'ExampleApexClass3Test.validateSayHello Pass', + 'ended SFDX: Run Apex Tests' + ]; + await verifyOutputPanelText(outputPanelText, expectedTexts); + + // Verify the tests that are passing are labeled with a green dot on the Test sidebar + for (const item of apexTestsItems) { + await verifyTestIconColor(item, 'testPass'); + } + }); + + it('Run All Tests on a Class via the Test Sidebar', async () => { + logTestStart(testSetup, 'Run All Tests on a Class via the Test Sidebar'); + const workbench = getWorkbench(); + // Clear the Output view. + await dismissAllNotifications(); + await clearOutputView(Duration.seconds(2)); + const terminalText = await runTestCaseFromSideBar(workbench, 'Apex Tests', 'ExampleApexClass2Test', 'Run Tests'); + const expectedTexts = [ + '=== Test Summary', + 'Outcome Passed', + 'Tests Ran 1', + 'Pass Rate 100%', + 'TEST NAME', + 'ExampleApexClass2Test.validateSayHello Pass', + 'ended SFDX: Run Apex Tests' + ]; + expect(terminalText).to.not.be.undefined; + await verifyOutputPanelText(terminalText!, expectedTexts); + }); + + it('Run Single Test via the Test Sidebar', async () => { + logTestStart(testSetup, 'Run Single Test via the Test Sidebar'); + const workbench = getWorkbench(); + // Clear the Output view. + await dismissAllNotifications(); + await clearOutputView(Duration.seconds(2)); + const terminalText = await runTestCaseFromSideBar( + workbench, + 'Apex Tests', + 'validateSayHello', + 'Run Single Test' + ); + const expectedTexts = [ + '=== Test Summary', + 'Outcome Passed', + 'Tests Ran 1', + 'Pass Rate 100%', + 'TEST NAME', + 'ExampleApexClass1Test.validateSayHello Pass', + 'ended SFDX: Run Apex Tests' + ]; + expect(terminalText).to.not.be.undefined; + await verifyOutputPanelText(terminalText!, expectedTexts); + }); + + it('Run a test that fails and fix it', async () => { + logTestStart(testSetup, 'Run a test that fails and fix it'); + // Create Apex class AccountService + await createApexClassWithBugs(); + + // Push source to org + const workbench = getWorkbench(); + await executeQuickPick('SFDX: Push Source to Default Org and Ignore Conflicts', Duration.seconds(1)); + + // Look for the success notification that appears which says, "SFDX: Push Source to Default Org and Ignore Conflicts successfully ran". + await verifyNotificationWithRetry( + /SFDX: Push Source to Default Org and Ignore Conflicts successfully ran/, + Duration.TEN_MINUTES + ); + + // Clear the Output view. + await dismissAllNotifications(); + await clearOutputView(Duration.seconds(2)); + + // Run SFDX: Run Apex tests. + prompt = await executeQuickPick('SFDX: Run Apex Tests', Duration.seconds(1)); + + // Select the "AccountServiceTest" file + await prompt.setText('AccountServiceTest'); + await prompt.confirm(); + // Look for the success notification that appears which says, "SFDX: Run Apex Tests successfully ran". + await verifyNotificationWithRetry(/SFDX: Run Apex Tests successfully ran/, Duration.TEN_MINUTES); + + // Verify test results are listed on vscode's Output section + // Also verify that the test fails + let outputPanelText = await attemptToFindOutputPanelText('Apex', '=== Test Results', 10); + let expectedTexts = ['Assertion Failed: incorrect ticker symbol', 'Expected: CRM, Actual: SFDC']; + + await verifyOutputPanelText(outputPanelText, expectedTexts); + + // Fix test + const textEditor = await getTextEditor(workbench, 'AccountService.cls'); + await textEditor.setTextAtLine(6, '\t\t\tTickerSymbol = tickerSymbol'); + await textEditor.save(); + await pause(Duration.seconds(1)); + + // Push source to org + await executeQuickPick('SFDX: Push Source to Default Org and Ignore Conflicts', Duration.seconds(1)); + + // Look for the success notification that appears which says, "SFDX: Push Source to Default Org and Ignore Conflicts successfully ran". + await verifyNotificationWithRetry( + /SFDX: Push Source to Default Org and Ignore Conflicts successfully ran/, + Duration.TEN_MINUTES + ); + + // Clear the Output view. + await dismissAllNotifications(); + await clearOutputView(Duration.seconds(2)); + + // Run SFDX: Run Apex tests to verify fix + prompt = await executeQuickPick('SFDX: Run Apex Tests', Duration.seconds(1)); + + // Select the "AccountServiceTest" file + await prompt.setText('AccountServiceTest'); + await prompt.confirm(); + + // Look for the success notification that appears which says, "SFDX: Run Apex Tests successfully ran". + await verifyNotificationWithRetry(/SFDX: Run Apex Tests successfully ran/, Duration.TEN_MINUTES); + + // Verify test results are listed on vscode's Output section + outputPanelText = await attemptToFindOutputPanelText('Apex', '=== Test Results', 10); + expectedTexts = [ + '=== Test Summary', + 'Outcome Passed', + 'Tests Ran 1', + 'Pass Rate 100%', + 'TEST NAME', + 'AccountServiceTest.should_create_account Pass', + 'ended SFDX: Run Apex Tests' + ]; + + await verifyOutputPanelText(outputPanelText, expectedTexts); + }); + + it('Create Apex Test Suite', async () => { + logTestStart(testSetup, 'Create Apex Test Suite'); + // Run SFDX: Create Apex Test Suite. + prompt = await executeQuickPick('SFDX: Create Apex Test Suite', Duration.seconds(2)); + + // Set the name of the new Apex Test Suite + await prompt.setText('ApexTestSuite'); + await prompt.confirm(); + await pause(Duration.seconds(2)); + + // Choose tests that will belong to the new Apex Test Suite + await prompt.setText('ExampleApexClass1Test'); + // Use different selector depending on VSCode version + const selector = + EnvironmentSettings.getInstance().vscodeVersion === 'latest' || + semver.gte(EnvironmentSettings.getInstance().vscodeVersion, '1.100.0') + ? 'div.monaco-custom-toggle.codicon.codicon-check.monaco-checkbox' + : 'input.quick-input-list-checkbox'; + const checkbox = await prompt.findElement(By.css(selector)); + await checkbox.click(); + await clickFilePathOkButton(); + + // Look for the success notification that appears which says, "SFDX: Build Apex Test Suite successfully ran". + await verifyNotificationWithRetry(/SFDX: Build Apex Test Suite successfully ran/, Duration.TEN_MINUTES); + }); + + it('Add test to Apex Test Suite', async () => { + logTestStart(testSetup, 'Add test to Apex Test Suite'); + // Run SFDX: Add Tests to Apex Test Suite. + prompt = await executeQuickPick('SFDX: Add Tests to Apex Test Suite', Duration.seconds(1)); + + // Select the suite recently created called ApexTestSuite + await prompt.selectQuickPick('ApexTestSuite'); + await pause(Duration.seconds(2)); + + // Choose tests that will belong to the already created Apex Test Suite + await prompt.setText('ExampleApexClass2Test'); + // Use different selector depending on VSCode version + const selector = + EnvironmentSettings.getInstance().vscodeVersion === 'latest' || + semver.gte(EnvironmentSettings.getInstance().vscodeVersion, '1.100.0') + ? 'div.monaco-custom-toggle.codicon.codicon-check.monaco-checkbox' + : 'input.quick-input-list-checkbox'; + + await retryOperation( + async () => { + const checkbox = await prompt.findElement(By.css(selector)); + await checkbox.click(); + }, + 2, + 'RunApexTests - Error clicking checkbox' + ); + await clickFilePathOkButton(); + + // Look for the success notification that appears which says, "SFDX: Build Apex Test Suite successfully ran". + await verifyNotificationWithRetry(/SFDX: Build Apex Test Suite successfully ran/, Duration.TEN_MINUTES); + }); + + it('Run Apex Test Suite', async () => { + logTestStart(testSetup, 'Run Apex Test Suite'); + // Clear the Output view. + await dismissAllNotifications(); + await clearOutputView(Duration.seconds(2)); + + // Run SFDX: Run Apex Test Suite. + await executeQuickPick('SFDX: Run Apex Test Suite', Duration.seconds(1)); + + // Select the suite recently created called ApexTestSuite + await prompt.selectQuickPick('ApexTestSuite'); + // Look for the success notification that appears which says, "SFDX: Run Apex Tests successfully ran". + await verifyNotificationWithRetry(/SFDX: Run Apex Tests successfully ran/, Duration.TEN_MINUTES); + + // Verify test results are listed on vscode's Output section + // Also verify that all tests pass + const outputPanelText = await attemptToFindOutputPanelText('Apex', '=== Test Results', 10); + const expectedTexts = [ + '=== Test Summary', + 'TEST NAME', + 'ended SFDX: Run Apex Tests', + 'Outcome Passed', + 'Tests Ran 2', + 'Pass Rate 100%', + 'TEST NAME', + 'ExampleApexClass1Test.validateSayHello Pass', + 'ExampleApexClass2Test.validateSayHello Pass', + 'ended SFDX: Run Apex Tests' + ]; + await verifyOutputPanelText(outputPanelText, expectedTexts); + }); + + after('Tear down and clean up the testing environment', async () => { + log('RunApexTests - Tear down and clean up the testing environment'); + await testSetup?.tearDown(); + }); +}); diff --git a/packages/salesforcedx-vscode-automation-tests/test/specs/runLwcTests.e2e.ts b/packages/salesforcedx-vscode-automation-tests/test/specs/runLwcTests.e2e.ts new file mode 100644 index 0000000000..0b911d2896 --- /dev/null +++ b/packages/salesforcedx-vscode-automation-tests/test/specs/runLwcTests.e2e.ts @@ -0,0 +1,336 @@ +/* + * Copyright (c) 2024, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ +import { + Duration, + pause, + ProjectShapeOption, + TestReqConfig +} from '@salesforce/salesforcedx-vscode-test-tools/lib/src/core'; +import { + installJestUTToolsForLwc, + createLwc +} from '@salesforce/salesforcedx-vscode-test-tools/lib/src/salesforce-components'; +import { + getTestsSection, + runTestCaseFromSideBar, + verifyTestIconColor, + verifyTestItemsInSideBar +} from '@salesforce/salesforcedx-vscode-test-tools/lib/src/testing'; +import { TestSetup } from '@salesforce/salesforcedx-vscode-test-tools/lib/src/testSetup'; +import { + closeAllEditors, + executeQuickPick, + getTerminalViewText, + getTextEditor, + getWorkbench, + reloadWindow, + verifyOutputPanelText, + waitForAndGetCodeLens +} from '@salesforce/salesforcedx-vscode-test-tools/lib/src/ui-interaction'; +import { expect } from 'chai'; +import * as path from 'node:path'; +import { TreeItem, after } from 'vscode-extension-tester'; +import { logTestStart } from '../utils/loggingHelper'; + +describe('Run LWC Tests', () => { + let testSetup: TestSetup; + const testReqConfig: TestReqConfig = { + projectConfig: { + projectShape: ProjectShapeOption.NEW + }, + isOrgRequired: false, + testSuiteSuffixName: 'RunLWCTests' + }; + + before('Set up the testing environment', async () => { + testSetup = await TestSetup.setUp(testReqConfig); + + // Close both Welcome and Running Extensions tabs + await closeAllEditors(); + + // Create LWC1 and test + await createLwc('lwc1'); + + // Create LWC2 and test + await createLwc('lwc2'); + + // Install Jest unit testing tools for LWC + await installJestUTToolsForLwc(testSetup.projectFolderPath); + }); + + it('SFDX: Run All Lightning Web Component Tests from Command Palette', async () => { + logTestStart(testSetup, 'SFDX: Run All Lightning Web Component Tests from Command Palette'); + + // Run SFDX: Run All Lightning Web Component Tests. + await executeQuickPick('SFDX: Run All Lightning Web Component Tests', Duration.seconds(1)); + + // Verify test results are listed on the terminal + // Also verify that all tests pass + const workbench = getWorkbench(); + const terminalText = await getTerminalViewText(workbench, 10); + + const expectedTexts = [ + 'PASS force-app/main/default/lwc/lwc1/__tests__/lwc1.test.js', + 'PASS force-app/main/default/lwc/lwc2/__tests__/lwc2.test.js', + 'Test Suites: 2 passed, 2 total', + 'Tests: 4 passed, 4 total', + 'Snapshots: 0 total', + 'Ran all test suites.' + ]; + expect(terminalText).to.not.be.undefined; + await verifyOutputPanelText(terminalText!, expectedTexts); + }); + + it('SFDX: Refresh Lightning Web Component Test Explorer', async () => { + logTestStart(testSetup, 'SFDX: Refresh Lightning Web Component Test Explorer'); + await executeQuickPick('Testing: Focus on LWC Tests View', Duration.seconds(1)); + // Run command SFDX: Refresh Lightning Web Component Test Explorer + await executeQuickPick('SFDX: Refresh Lightning Web Component Test Explorer', Duration.seconds(2)); + // Open the Tests Sidebar + const workbench = getWorkbench(); + const lwcTestsSection = await getTestsSection(workbench, 'LWC Tests'); + let lwcTestsItems = await lwcTestsSection.getVisibleItems(); + if (!Array.isArray(lwcTestsItems)) { + throw new Error(`Expected Array but got different item type: ${typeof lwcTestsItems}`); + } + + // Run command SFDX: Run All Lightning Web Component Tests + await executeQuickPick('SFDX: Run All Lightning Web Component Tests', Duration.seconds(2)); + + // Get tree items again + lwcTestsItems = await lwcTestsSection.getVisibleItems(); + if (!Array.isArray(lwcTestsItems)) { + throw new Error(`Expected Array but got different item type: ${typeof lwcTestsItems}`); + } + + // Verify the tests that ran are labeled with a green dot on the Test sidebar + for (const item of lwcTestsItems) { + if (!(item instanceof TreeItem)) { + throw new Error(`Expected TreeItem but got different item type: ${typeof item}`); + } + await verifyTestIconColor(item, 'testPass'); + } + + // Run command SFDX: Refresh Lightning Web Component Test Explorer again to reset status + await executeQuickPick('SFDX: Refresh Lightning Web Component Test Explorer', Duration.seconds(2)); + + // Get tree items again + lwcTestsItems = await lwcTestsSection.getVisibleItems(); + if (!Array.isArray(lwcTestsItems)) { + throw new Error(`Expected Array but got different item type: ${typeof lwcTestsItems}`); + } + + // Verify the tests are now labeled with a blue dot on the Test sidebar + for (const item of lwcTestsItems) { + if (!(item instanceof TreeItem)) { + throw new Error(`Expected TreeItem but got different item type: ${typeof item}`); + } + await verifyTestIconColor(item, 'testNotRun'); + } + }); + + it('Run All tests via Test Sidebar', async () => { + logTestStart(testSetup, 'Run All tests via Test Sidebar'); + const workbench = getWorkbench(); + const lwcTestsSection = await getTestsSection(workbench, 'LWC Tests'); + const expectedItems = ['lwc1', 'lwc2', 'displays greeting', 'is defined']; + const lwcTestsItems = await verifyTestItemsInSideBar( + lwcTestsSection, + 'SFDX: Refresh Lightning Web Component Test Explorer', + expectedItems, + 6, + 2 + ); + + // Click the run tests button on the top right corner of the Test sidebar + await lwcTestsSection.click(); + const runTestsAction = await lwcTestsSection.getAction('SFDX: Run All Lightning Web Component Tests'); + expect(runTestsAction).to.not.be.undefined; + await runTestsAction!.click(); + + // Verify test results are listed on the terminal + // Also verify that all tests pass + const terminalText = await getTerminalViewText(workbench, 10); + + const expectedTexts = [ + 'PASS force-app/main/default/lwc/lwc1/__tests__/lwc1.test.js', + 'PASS force-app/main/default/lwc/lwc2/__tests__/lwc2.test.js', + 'Test Suites: 2 passed, 2 total', + 'Tests: 4 passed, 4 total', + 'Snapshots: 0 total', + 'Ran all test suites.' + ]; + expect(terminalText).to.not.be.undefined; + await verifyOutputPanelText(terminalText!, expectedTexts); + + // Verify the tests that are passing are labeled with a green dot on the Test sidebar + for (const item of lwcTestsItems) { + await verifyTestIconColor(item, 'testPass'); + } + }); + + it('Run All Tests on a LWC via the Test Sidebar', async () => { + logTestStart(testSetup, 'Run All Tests on a LWC via the Test Sidebar'); + const workbench = getWorkbench(); + + // Click the run test button that is shown to the right when you hover a test class name on the Test sidebar + const terminalText = await runTestCaseFromSideBar( + workbench, + 'LWC Tests', + 'lwc1', + 'SFDX: Run Lightning Web Component Test File' + ); + + const expectedTexts = [ + 'PASS force-app/main/default/lwc/lwc1/__tests__/lwc1.test.js', + 'Test Suites: 1 passed, 1 total', + 'Tests: 2 passed, 2 total', + 'Snapshots: 0 total', + 'Ran all test suites within paths', + `${path.join('force-app', 'main', 'default', 'lwc', 'lwc1', '__tests__', 'lwc1.test.js')}` + ]; + expect(terminalText).to.not.be.undefined; + await verifyOutputPanelText(terminalText!, expectedTexts); + await closeAllEditors(); + }); + + it('Run Single Test via the Test Sidebar', async () => { + logTestStart(testSetup, 'Run Single Test via the Test Sidebar'); + const workbench = getWorkbench(); + + // Hover a test name under one of the test lwc sections and click the run button that is shown to the right of the test name on the Test sidebar + const terminalText = await runTestCaseFromSideBar( + workbench, + 'LWC Tests', + 'displays greeting', + 'SFDX: Run Lightning Web Component Test Case' + ); + const expectedTexts = [ + 'PASS force-app/main/default/lwc/lwc1/__tests__/lwc1.test.js', + 'Test Suites: 1 passed, 1 total', + 'Tests: 1 skipped, 1 passed, 2 total', + 'Snapshots: 0 total', + 'Ran all test suites within paths', + `${path.join('force-app', 'main', 'default', 'lwc', 'lwc1', '__tests__', 'lwc1.test.js')}` + ]; + expect(terminalText).to.not.be.undefined; + await verifyOutputPanelText(terminalText!, expectedTexts); + }); + + it('SFDX: Navigate to Lightning Web Component Test', async () => { + // Verify that having clicked the test case took us to the test file. + await reloadWindow(); + await pause(Duration.seconds(10)); + const workbench = getWorkbench(); + const editorView = workbench.getEditorView(); + const activeTab = await editorView.getActiveTab(); + const title = await activeTab?.getTitle(); + expect(title).to.equal('lwc1.test.js'); + }); + + it('SFDX: Run Current Lightning Web Component Test File from Command Palette', async () => { + logTestStart(testSetup, 'SFDX: Run Current Lightning Web Component Test File'); + + // Run SFDX: Run Current Lightning Web Component Test File + await executeQuickPick('SFDX: Run Current Lightning Web Component Test File', Duration.seconds(1)); + + // Verify test results are listed on vscode's Output section + // Also verify that all tests pass + const workbench = getWorkbench(); + const terminalText = await getTerminalViewText(workbench, 10); + const expectedTexts = [ + 'PASS force-app/main/default/lwc/lwc1/__tests__/lwc1.test.js', + 'Test Suites: 1 passed, 1 total', + 'Tests: 2 passed, 2 total', + 'Snapshots: 0 total', + 'Ran all test suites within paths', + `${path.join('force-app', 'main', 'default', 'lwc', 'lwc1', '__tests__', 'lwc1.test.js')}` + ]; + expect(terminalText).to.not.be.undefined; + await verifyOutputPanelText(terminalText!, expectedTexts); + }); + + it.skip('Run All Tests via Code Lens action', async () => { + // Skipping as this feature is currently not working + logTestStart(testSetup, 'Run All Tests via Code Lens action'); + const workbench = getWorkbench(); + const textEditor = await getTextEditor(workbench, 'lwc1.test.js'); + + // Click the "Run" code lens at the top of the class + const runAllTestsOption = await waitForAndGetCodeLens(textEditor, 'Run'); + expect(runAllTestsOption).to.not.be.undefined; + await runAllTestsOption!.click(); + + // Verify test results are listed on the terminal + // Also verify that all tests pass + const terminalText = await getTerminalViewText(workbench, 10); + const expectedTexts = [ + 'PASS force-app/main/default/lwc/lwc1/__tests__/lwc1.test.js', + 'Test Suites: 1 passed, 1 total', + 'Tests: 2 passed, 2 total', + 'Snapshots: 0 total', + 'Ran all test suites within paths', + `${path.join('force-app', 'main', 'default', 'lwc', 'lwc1', '__tests__', 'lwc1.test.js')}` + ]; + expect(terminalText).to.not.be.undefined; + await verifyOutputPanelText(terminalText!, expectedTexts); + }); + + it('Run Single Test via Code Lens action', async () => { + logTestStart(testSetup, 'Run Single Test via Code Lens action'); + + // Click the "Run Test" code lens at the top of one of the test methods + const workbench = getWorkbench(); + const textEditor = await getTextEditor(workbench, 'lwc2.test.js'); + const runTestOption = await waitForAndGetCodeLens(textEditor, 'Run Test'); + expect(runTestOption).to.not.be.undefined; + await runTestOption!.click(); + + // Verify test results are listed on the terminal + // Also verify that all tests pass + const terminalText = await getTerminalViewText(workbench, 10); + const expectedTexts = [ + 'PASS force-app/main/default/lwc/lwc2/__tests__/lwc2.test.js', + 'Test Suites: 1 passed, 1 total', + 'Tests: 1 skipped, 1 passed, 2 total', + 'Snapshots: 0 total', + 'Ran all test suites within paths', + `${path.join('force-app', 'main', 'default', 'lwc', 'lwc2', '__tests__', 'lwc2.test.js')}` + ]; + expect(terminalText).to.not.be.undefined; + await verifyOutputPanelText(terminalText!, expectedTexts); + }); + + it('SFDX: Run Current Lightning Web Component Test File from main toolbar', async () => { + logTestStart(testSetup, 'SFDX: Run Current Lightning Web Component Test File from main toolbar'); + + // Run SFDX: Run Current Lightning Web Component Test File + const workbench = getWorkbench(); + const editorView = workbench.getEditorView(); + const runTestButtonToolbar = await editorView.getAction('SFDX: Run Current Lightning Web Component Test File'); + expect(runTestButtonToolbar).to.not.be.undefined; + await runTestButtonToolbar?.click(); + + // Verify test results are listed on vscode's Output section + // Also verify that all tests pass + const terminalText = await getTerminalViewText(workbench, 10); + const expectedTexts = [ + 'PASS force-app/main/default/lwc/lwc2/__tests__/lwc2.test.js', + 'Test Suites: 1 passed, 1 total', + 'Tests: 2 passed, 2 total', + 'Snapshots: 0 total', + 'Ran all test suites within paths', + `${path.join('force-app', 'main', 'default', 'lwc', 'lwc2', '__tests__', 'lwc2.test.js')}` + ]; + expect(terminalText).to.not.be.undefined; + await verifyOutputPanelText(terminalText!, expectedTexts); + }); + + after('Tear down and clean up the testing environment', async () => { + await testSetup?.tearDown(); + }); +}); diff --git a/packages/salesforcedx-vscode-automation-tests/test/specs/sObjectsDefinitions.e2e.ts b/packages/salesforcedx-vscode-automation-tests/test/specs/sObjectsDefinitions.e2e.ts new file mode 100644 index 0000000000..eef786807c --- /dev/null +++ b/packages/salesforcedx-vscode-automation-tests/test/specs/sObjectsDefinitions.e2e.ts @@ -0,0 +1,234 @@ +/* + * Copyright (c) 2023, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ +import { + Duration, + log, + pause, + TestReqConfig, + ProjectShapeOption +} from '@salesforce/salesforcedx-vscode-test-tools/lib/src/core'; +import { verifyNotificationWithRetry } from '@salesforce/salesforcedx-vscode-test-tools/lib/src/retryUtils'; +import { createCustomObjects } from '@salesforce/salesforcedx-vscode-test-tools/lib/src/system-operations'; +import { TestSetup } from '@salesforce/salesforcedx-vscode-test-tools/lib/src/testSetup'; +import { + attemptToFindOutputPanelText, + clearOutputView, + executeQuickPick, + getWorkbench, + verifyOutputPanelText +} from '@salesforce/salesforcedx-vscode-test-tools/lib/src/ui-interaction'; +import { expect } from 'chai'; +import { DefaultTreeItem, TreeItem, Workbench } from 'vscode-extension-tester'; +import { logTestStart } from '../utils/loggingHelper'; + +describe('SObjects Definitions', () => { + let testSetup: TestSetup; + const testReqConfig: TestReqConfig = { + projectConfig: { + projectShape: ProjectShapeOption.NEW + }, + isOrgRequired: true, + testSuiteSuffixName: 'sObjectsDefinitions' + }; + let projectName: string; + + before('Set up the testing environment', async () => { + testSetup = await TestSetup.setUp(testReqConfig); + projectName = testSetup.tempProjectName; + + log(`${testSetup.testSuiteSuffixName} - calling createCustomObjects()`); + + // update testSetup.testDataFolderPath to be the path to the salesforcedx-vscode-automation-tests package + testSetup.testDataFolderPath = testSetup.tempFolderPath.replace( + 'e2e-temp', + 'packages/salesforcedx-vscode-automation-tests/test/testData/CustomSObjects' + ); + log(`testSetup.testDataFolderPath: ${String(testSetup.testDataFolderPath || 'undefined')}`); + await createCustomObjects(testSetup); + }); + + it("Check Custom Objects 'Customer__c' and 'Product__c' are within objects folder", async () => { + logTestStart(testSetup, "Check Custom Objects 'Customer__c' and 'Product__c' are within objects folder"); + const workbench = await getWorkbench(); + const sidebar = await workbench.getSideBar().wait(); + const content = await sidebar.getContent().wait(); + + const treeViewSection = await content.getSection(projectName); + expect(treeViewSection).to.not.be.undefined; + + const objectTreeItem = await treeViewSection.findItem('objects'); + expect(objectTreeItem).to.not.be.undefined; + if (!(objectTreeItem instanceof DefaultTreeItem)) { + throw new Error(`Expected DefaultTreeItem but got different item type: ${typeof objectTreeItem}`); + } + await objectTreeItem.select(); + + const customerObjectFolder = await objectTreeItem.findChildItem('Customer__c'); + expect(customerObjectFolder).to.not.be.undefined; + if (!(customerObjectFolder instanceof DefaultTreeItem)) { + throw new Error(`Expected DefaultTreeItem but got different item type: ${typeof customerObjectFolder}`); + } + + await customerObjectFolder?.expand(); + expect(await customerObjectFolder?.isExpanded()).to.equal(true); + + const customerCustomObject = await customerObjectFolder.findChildItem('Customer__c.object-meta.xml'); + expect(customerCustomObject).to.not.be.undefined; + + const productObjectFolder = await objectTreeItem.findChildItem('Product__c'); + expect(productObjectFolder).to.not.be.undefined; + if (!(productObjectFolder instanceof DefaultTreeItem)) { + throw new Error(`Expected DefaultTreeItem but got different item type: ${typeof productObjectFolder}`); + } + + await productObjectFolder?.expand(); + expect(await productObjectFolder?.isExpanded()).to.equal(true); + + const productCustomObject = await productObjectFolder.findChildItem('Product__c.object-meta.xml'); + expect(productCustomObject).to.not.be.undefined; + }); + + it('Push Source to Org', async () => { + logTestStart(testSetup, 'Push Source to Org'); + await executeQuickPick('SFDX: Push Source to Default Org', Duration.seconds(5)); + await pause(Duration.seconds(1)); + + await verifyNotificationWithRetry(/SFDX: Push Source to Default Org successfully ran/, Duration.TEN_MINUTES); + + const outputPanelText = await attemptToFindOutputPanelText( + 'Salesforce CLI', + 'Starting SFDX: Push Source to Default Org', + 5 + ); + expect(outputPanelText).to.not.be.undefined; + expect(outputPanelText).to.contain('Pushed Source'); + }); + + it('Refresh SObject Definitions for Custom SObjects', async () => { + logTestStart(testSetup, 'Refresh SObject Definitions for Custom SObjects'); + await refreshSObjectDefinitions('Custom SObjects'); + + await verifyOutputPanelTxt('Custom sObjects', 2); + + const workbench = await getWorkbench(); + const treeViewSection = await verifySObjectFolders(workbench, projectName, 'customObjects'); + + // Verify if custom Objects Customer__c and Product__c are within 'customObjects' folder + const customerCustomObject = await treeViewSection.findItem('Customer__c.cls'); + expect(customerCustomObject).to.not.be.undefined; + const productCustomObject = await treeViewSection.findItem('Product__c.cls'); + expect(productCustomObject).to.not.be.undefined; + }); + + it('Refresh SObject Definitions for Standard SObjects', async () => { + logTestStart(testSetup, 'Refresh SObject Definitions for Standard SObjects'); + await refreshSObjectDefinitions('Standard SObjects'); + + await verifyOutputPanelTxt('Standard sObjects'); + + const workbench = await getWorkbench(); + const treeViewSection = await verifySObjectFolders(workbench, projectName, 'standardObjects'); + + const accountSObject = await treeViewSection.findItem('Account.cls'); + expect(accountSObject).to.not.be.undefined; + + const accountCleanInfoSObject = await treeViewSection.findItem('AccountCleanInfo.cls'); + expect(accountCleanInfoSObject).to.not.be.undefined; + + const acceptedEventRelationSObject = await treeViewSection.findItem('AcceptedEventRelation.cls'); + expect(acceptedEventRelationSObject).to.not.be.undefined; + }); + + it('Refresh SObject Definitions for All SObjects', async () => { + logTestStart(testSetup, 'Refresh SObject Definitions for All SObjects'); + await refreshSObjectDefinitions('All SObjects'); + + await verifyOutputPanelTxt('Standard sObjects'); + await verifyOutputPanelTxt('Custom sObjects', 2); + }); + + after('Tear down and clean up the testing environment', async () => { + await testSetup?.tearDown(); + }); +}); + +const verifyOutputPanelTxt = async (type: string, qty?: number) => { + log(`calling verifyOutputPanelText(${type})`); + const outputPanelText = await attemptToFindOutputPanelText('Salesforce CLI', 'sObjects', 10); + if (!outputPanelText) { + throw new Error('Expected output panel text but got undefined'); + } + const expectedTexts = [ + 'Starting SFDX: Refresh SObject Definitions', + 'sf sobject definitions refresh', + `Processed ${qty || ''}`, + `${type}`, + 'ended with exit code 0' + ]; + await verifyOutputPanelText(outputPanelText, expectedTexts); +}; + +const refreshSObjectDefinitions = async (type: string) => { + log(`calling refreshSObjectDefinitions(${type})`); + await clearOutputView(Duration.seconds(2)); + const prompt = await executeQuickPick('SFDX: Refresh SObject Definitions', Duration.seconds(2)); + await prompt.setText(type); + await prompt.selectQuickPick(type); + await pause(Duration.seconds(1)); + + await verifyNotificationWithRetry(/SFDX: Refresh SObject Definitions successfully ran/, Duration.TEN_MINUTES); +}; + +const verifySObjectFolders = async (workbench: Workbench, projectName: string, folder: string) => { + log(`calling verifySObjectFolders(workbench, ${projectName}, ${folder})`); + const sidebar = workbench.getSideBar(); + const content = sidebar.getContent(); + const treeViewSection = await content.getSection(projectName); + expect(treeViewSection).to.not.be.undefined; + + // Verify if '.sfdx' folder is in side panel + const sfdxTreeItem = await treeViewSection.findItem('.sfdx'); + expect(sfdxTreeItem).to.not.be.undefined; + if (!(sfdxTreeItem instanceof DefaultTreeItem)) { + throw new Error(`Expected DefaultTreeItem but got different item type: ${typeof sfdxTreeItem}`); + } + await sfdxTreeItem.expand(); + expect(await sfdxTreeItem.isExpanded()).to.equal(true); + await pause(Duration.seconds(1)); + + // Verify if 'tools' folder is within '.sfdx' + const toolsTreeItem = await sfdxTreeItem.findChildItem('tools'); + expect(toolsTreeItem).to.not.be.undefined; + if (!(toolsTreeItem instanceof TreeItem)) { + throw new Error(`Expected TreeItem but got different item type: ${typeof toolsTreeItem}`); + } + await toolsTreeItem.expand(); + expect(await toolsTreeItem.isExpanded()).to.equal(true); + await pause(Duration.seconds(1)); + + // Verify if 'sobjects' folder is within 'tools' + const sobjectsTreeItem = await toolsTreeItem.findChildItem('sobjects'); + expect(sobjectsTreeItem).to.not.be.undefined; + if (!(sobjectsTreeItem instanceof TreeItem)) { + throw new Error(`Expected TreeItem but got different item type: ${typeof sobjectsTreeItem}`); + } + await sobjectsTreeItem.expand(); + expect(await sobjectsTreeItem.isExpanded()).to.equal(true); + await pause(Duration.seconds(1)); + + // Verify if 'type' folder is within 'sobjects' + const objectsTreeItem = await sobjectsTreeItem.findChildItem(folder); + expect(objectsTreeItem).to.not.be.undefined; + if (!(objectsTreeItem instanceof TreeItem)) { + throw new Error(`Expected TreeItem but got different item type: ${typeof objectsTreeItem}`); + } + await objectsTreeItem.expand(); + expect(await objectsTreeItem.isExpanded()).to.equal(true); + await pause(Duration.seconds(1)); + + return treeViewSection; +}; diff --git a/packages/salesforcedx-vscode-automation-tests/test/specs/sfdxProjectJson.e2e.ts b/packages/salesforcedx-vscode-automation-tests/test/specs/sfdxProjectJson.e2e.ts new file mode 100644 index 0000000000..ff8edcd36e --- /dev/null +++ b/packages/salesforcedx-vscode-automation-tests/test/specs/sfdxProjectJson.e2e.ts @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2024, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ +import { + openFile, + pause, + TestReqConfig, + ProjectShapeOption +} from '@salesforce/salesforcedx-vscode-test-tools/lib/src/core'; +import { + getExtensionsToVerifyActive, + reloadAndEnableExtensions, + verifyExtensionsAreRunning +} from '@salesforce/salesforcedx-vscode-test-tools/lib/src/testing'; +import { TestSetup } from '@salesforce/salesforcedx-vscode-test-tools/lib/src/testSetup'; +import { getTextEditor, getWorkbench } from '@salesforce/salesforcedx-vscode-test-tools/lib/src/ui-interaction'; +import { expect } from 'chai'; +import * as path from 'node:path'; +import { after } from 'vscode-extension-tester'; + +describe('Customize sfdx-project.json', () => { + let testSetup: TestSetup; + const testReqConfig: TestReqConfig = { + projectConfig: { + projectShape: ProjectShapeOption.NEW + }, + isOrgRequired: false, + testSuiteSuffixName: 'sfdxProjectJson' + }; + + before('Set up the testing environment', async () => { + testSetup = await TestSetup.setUp(testReqConfig); + await createSfdxProjectJsonWithAllFields(testSetup); + await reloadAndEnableExtensions(); + }); + + it('Verify our extensions are loaded after updating sfdx-project.json', async () => { + expect(await verifyExtensionsAreRunning(getExtensionsToVerifyActive())).to.equal(true); + }); + + after('Tear down and clean up the testing environment', async () => { + await testSetup?.tearDown(); + }); +}); + +const createSfdxProjectJsonWithAllFields = async (testSetup: TestSetup): Promise => { + const workbench = getWorkbench(); + const sfdxConfig = [ + '{', + '\t"packageDirectories": [', + '\t\t{', + '\t\t\t"path": "force-app",', + '\t\t\t"default": true', + '\t\t}', + '\t],', + '\t"namespace": "",', + '\t"sourceApiVersion": "61.0",', + '\t"sourceBehaviorOptions": ["decomposeCustomLabelsBeta", "decomposePermissionSetBeta", "decomposeWorkflowBeta", "decomposeSharingRulesBeta"]', + '}' + ].join('\n'); + await openFile(path.join(testSetup.projectFolderPath!, 'sfdx-project.json')); + const textEditor = await getTextEditor(workbench, 'sfdx-project.json'); + await textEditor.setText(sfdxConfig); + await textEditor.save(); + await pause(); +}; diff --git a/packages/salesforcedx-vscode-automation-tests/test/specs/soql.e2e.ts b/packages/salesforcedx-vscode-automation-tests/test/specs/soql.e2e.ts new file mode 100644 index 0000000000..49c3cd29e2 --- /dev/null +++ b/packages/salesforcedx-vscode-automation-tests/test/specs/soql.e2e.ts @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2024, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ +import { + Duration, + log, + pause, + TestReqConfig, + ProjectShapeOption +} from '@salesforce/salesforcedx-vscode-test-tools/lib/src/core'; +import { TestSetup } from '@salesforce/salesforcedx-vscode-test-tools/lib/src/testSetup'; +import { + executeQuickPick, + getTextEditor, + getWorkbench, + reloadWindow +} from '@salesforce/salesforcedx-vscode-test-tools/lib/src/ui-interaction'; +import { expect } from 'chai'; +import { after } from 'vscode-extension-tester'; +import { logTestStart } from '../utils/loggingHelper'; + +describe('SOQL', () => { + let testSetup: TestSetup; + const testReqConfig: TestReqConfig = { + projectConfig: { + projectShape: ProjectShapeOption.NEW + }, + isOrgRequired: false, + testSuiteSuffixName: 'SOQL' + }; + + before('Set up the testing environment', async () => { + testSetup = await TestSetup.setUp(testReqConfig); + }); + + it('SFDX: Create Query in SOQL Builder', async () => { + logTestStart(testSetup, 'SFDX: Create Query in SOQL Builder'); + await pause(Duration.seconds(20)); + // Run SFDX: Create Query in SOQL Builder + await executeQuickPick('SFDX: Create Query in SOQL Builder', Duration.seconds(3)); + + // Verify the command took us to the soql builder + const workbench = await getWorkbench(); + const editorView = workbench.getEditorView(); + const activeTab = await editorView.getActiveTab(); + const title = await activeTab?.getTitle(); + expect(title).to.equal('untitled.soql'); + }); + + it('Switch Between SOQL Builder and Text Editor - from SOQL Builder', async () => { + logTestStart(testSetup, 'Switch Between SOQL Builder and Text Editor - from SOQL Builder'); + + // Click Switch Between SOQL Builder and Text Editor + const workbench = await getWorkbench(); + const editorView = workbench.getEditorView(); + const toggleSOQLButton = await editorView.getAction('Switch Between SOQL Builder and Text Editor'); + expect(toggleSOQLButton).to.not.be.undefined; + await toggleSOQLButton?.click(); + + // Verify 'Switch Between SOQL Builder and Text Editor' took us to the soql builder + const activeTab = await editorView.getActiveTab(); + const title = await activeTab?.getTitle(); + expect(title).to.equal('untitled.soql'); + const openTabs = await editorView.getOpenEditorTitles(); + expect(openTabs.length).to.equal(3); + expect(openTabs[1]).to.equal('untitled.soql'); + expect(openTabs[2]).to.equal('untitled.soql'); + }); + + it('Switch Between SOQL Builder and Text Editor - from file', async () => { + logTestStart(testSetup, 'Switch Between SOQL Builder and Text Editor - from file'); + await reloadWindow(Duration.seconds(5)); + + // Click Switch Between SOQL Builder and Text Editor + const workbench = await getWorkbench(); + const editorView = workbench.getEditorView(); + const toggleSOQLButton = await editorView.getAction('Switch Between SOQL Builder and Text Editor'); + expect(toggleSOQLButton).to.not.be.undefined; + }); + + it.skip('Verify the contents of the SOQL Builder', async () => { + //TODO + }); + + it.skip('Create query in SOQL Builder', async () => { + //TODO + }); + + it.skip('Verify the contents of the soql file', async () => { + const expectedText = ['SELECT COUNT()', 'from Account'].join('\n'); + const workbench = await getWorkbench(); + const textEditor = await getTextEditor(workbench, 'countAccounts.soql'); + const textGeneratedFromTemplate = (await textEditor.getText()).trimEnd().replace(/\r\n/g, '\n'); + expect(textGeneratedFromTemplate).to.be(expectedText); + }); + + after('Tear down and clean up the testing environment', async () => { + log(`${testSetup.testSuiteSuffixName} - Tear down and clean up the testing environment`); + await testSetup?.tearDown(); + }); +}); diff --git a/packages/salesforcedx-vscode-automation-tests/test/specs/templates.e2e.ts b/packages/salesforcedx-vscode-automation-tests/test/specs/templates.e2e.ts new file mode 100644 index 0000000000..8c86a56941 --- /dev/null +++ b/packages/salesforcedx-vscode-automation-tests/test/specs/templates.e2e.ts @@ -0,0 +1,452 @@ +/* + * Copyright (c) 2023, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ +import { + createCommand, + Duration, + log, + pause, + ProjectShapeOption, + TestReqConfig +} from '@salesforce/salesforcedx-vscode-test-tools/lib/src/core'; +import { verifyNotificationWithRetry } from '@salesforce/salesforcedx-vscode-test-tools/lib/src/retryUtils'; +import { TestSetup } from '@salesforce/salesforcedx-vscode-test-tools/lib/src/testSetup'; +import { + getWorkbench, + getTextEditor, + getFilteredVisibleTreeViewItemLabels, + zoom, + attemptToFindOutputPanelText, + executeQuickPick, + clearOutputView, + getVisibleItemsFromSidebar +} from '@salesforce/salesforcedx-vscode-test-tools/lib/src/ui-interaction'; +import { expect } from 'chai'; +import * as path from 'node:path'; +import { after } from 'vscode-extension-tester'; +import * as analyticsTemplate from '../testData/sampleAnalyticsTemplateData'; +import { logTestStart } from '../utils/loggingHelper'; + +describe('Templates', () => { + let testSetup: TestSetup; + let projectName: string; + const testReqConfig: TestReqConfig = { + projectConfig: { + projectShape: ProjectShapeOption.NEW + }, + isOrgRequired: false, + testSuiteSuffixName: 'Templates' + }; + + // Set up + before('Set up the testing environment', async () => { + log('Templates - Set up the testing environment'); + testSetup = await TestSetup.setUp(testReqConfig); + projectName = testSetup.tempProjectName; + }); + + // Apex Class + it('Create an Apex Class', async () => { + logTestStart(testSetup, 'Create an Apex Class'); + // Using the Command palette, run SFDX: Create Apex Class. + await createCommand('Apex Class', 'ApexClass1', 'classes', 'cls'); + + // Check for expected items in the Explorer view. + const workbench = await getWorkbench(); + + // Get the matching (visible) items within the tree which contains "ApexClass1". + const filteredTreeViewItems = await getFilteredVisibleTreeViewItemLabels(workbench, projectName, 'ApexClass1'); + + expect(filteredTreeViewItems.includes('ApexClass1.cls')).to.equal(true); + expect(filteredTreeViewItems.includes('ApexClass1.cls-meta.xml')).to.equal(true); + }); + + it('Verify the contents of the Apex Class', async () => { + logTestStart(testSetup, 'Verify the contents of the Apex Class'); + const expectedText = ['public with sharing class ApexClass1 {', ' public ApexClass1() {', '', ' }', '}'].join( + '\n' + ); + const workbench = await getWorkbench(); + const textEditor = await getTextEditor(workbench, 'ApexClass1.cls'); + const textGeneratedFromTemplate = (await textEditor.getText()).trimEnd().replace(/\r\n/g, '\n'); + expect(textGeneratedFromTemplate).to.equal(expectedText); + }); + + // Apex Unit Test Class + it('Create an Apex Unit Test Class', async () => { + logTestStart(testSetup, 'Create an Apex Unit Test Class'); + // Using the Command palette, run SFDX: Create Apex Unit Test Class. + await createCommand('Apex Unit Test Class', 'ApexUnitTestClass1', 'classes', 'cls'); + + // Check for expected items in the Explorer view. + const workbench = await getWorkbench(); + + // Get the matching (visible) items within the tree which contains "ApexUnitTestClass1". + const filteredTreeViewItems = await getFilteredVisibleTreeViewItemLabels( + workbench, + projectName, + 'ApexUnitTestClass1' + ); + + expect(filteredTreeViewItems.includes('ApexUnitTestClass1.cls')).to.equal(true); + expect(filteredTreeViewItems.includes('ApexUnitTestClass1.cls-meta.xml')).to.equal(true); + }); + + it('Verify the contents of the Apex Unit Test Class', async () => { + logTestStart(testSetup, 'Verify the contents of the Apex Unit Test Class'); + const expectedText = [ + '@isTest', + 'private class ApexUnitTestClass1 {', + '', + ' @isTest', + ' static void myUnitTest() {', + ' // TO DO: implement unit test', + ' }', + '}' + ].join('\n'); + const workbench = await getWorkbench(); + const textEditor = await getTextEditor(workbench, 'ApexUnitTestClass1.cls'); + const textGeneratedFromTemplate = (await textEditor.getText()).trimEnd().replace(/\r\n/g, '\n'); + expect(textGeneratedFromTemplate).to.contain(expectedText); + }); + + // Apex Trigger + it('Create an Apex Trigger', async () => { + logTestStart(testSetup, 'Create an Apex Trigger'); + // Using the Command palette, run "SFDX: Create Apex Trigger". + await createCommand('Apex Trigger', 'ApexTrigger1', 'triggers', 'trigger'); + + // Check for expected items in the Explorer view. + const workbench = await getWorkbench(); + + // Get the matching (visible) items within the tree which contains "ApexTrigger1". + const filteredTreeViewItems = await getFilteredVisibleTreeViewItemLabels(workbench, projectName, 'ApexTrigger1'); + expect(filteredTreeViewItems.includes('ApexTrigger1.trigger')).to.equal(true); + expect(filteredTreeViewItems.includes('ApexTrigger1.trigger-meta.xml')).to.equal(true); + }); + + it('Verify the contents of the Apex Trigger', async () => { + logTestStart(testSetup, 'Verify the contents of the Apex Trigger'); + // Verify the default trigger. + const expectedText = ['trigger ApexTrigger1 on SOBJECT (before insert) {', '', '}'].join('\n'); + const workbench = await getWorkbench(); + const textEditor = await getTextEditor(workbench, 'ApexTrigger1.trigger'); + const textGeneratedFromTemplate = (await textEditor.getText()).trimEnd().replace(/\r\n/g, '\n'); + expect(textGeneratedFromTemplate).to.equal(expectedText); + }); + + // Aura App + it('Create an Aura App', async () => { + logTestStart(testSetup, 'Create an Aura App'); + // Clear the output panel, then use the Command palette to run, "SFDX: Create Aura App". + const outputPanelText = await createCommand('Aura App', 'AuraApp1', path.join('aura', 'AuraApp1'), 'app'); + const basePath = path.join('force-app', 'main', 'default', 'aura', 'AuraApp1'); + const docPath = path.join(basePath, 'AuraApp1.auradoc'); + expect(outputPanelText).to.contain(`create ${docPath}`); + + const cssPath = path.join(basePath, 'AuraApp1.css'); + expect(outputPanelText).to.contain(`create ${cssPath}`); + + const svgPath = path.join(basePath, 'AuraApp1.svg'); + expect(outputPanelText).to.contain(`create ${svgPath}`); + + const controllerPath = path.join(basePath, 'AuraApp1Controller.js'); + expect(outputPanelText).to.contain(`create ${controllerPath}`); + + const helperPath = path.join(basePath, 'AuraApp1Helper.js'); + expect(outputPanelText).to.contain(`create ${helperPath}`); + + const rendererPath = path.join(basePath, 'AuraApp1Renderer.js'); + expect(outputPanelText).to.contain(`create ${rendererPath}`); + + // Get the matching (visible) items within the tree which contains "AuraApp1". + const workbench = await getWorkbench(); + const filteredTreeViewItems = await getFilteredVisibleTreeViewItemLabels(workbench, projectName, 'AuraApp1'); + expect(filteredTreeViewItems.includes('AuraApp1.app')).to.equal(true); + expect(filteredTreeViewItems.includes('AuraApp1.app-meta.xml')).to.equal(true); + expect(filteredTreeViewItems.includes('AuraApp1.auradoc')).to.equal(true); + expect(filteredTreeViewItems.includes('AuraApp1.css')).to.equal(true); + expect(filteredTreeViewItems.includes('AuraApp1.svg')).to.equal(true); + expect(filteredTreeViewItems.includes('AuraApp1Controller.js')).to.equal(true); + expect(filteredTreeViewItems.includes('AuraApp1Helper.js')).to.equal(true); + expect(filteredTreeViewItems.includes('AuraApp1Renderer.js')).to.equal(true); + }); + + it('Verify the contents of the Aura App', async () => { + logTestStart(testSetup, 'Verify the contents of the Aura App'); + // Verify the default code for an Aura App. + const expectedText = ['', '', ''].join('\n'); + const workbench = await getWorkbench(); + const textEditor = await getTextEditor(workbench, 'AuraApp1.app'); + const textGeneratedFromTemplate = (await textEditor.getText()).trimEnd().replace(/\r\n/g, '\n'); + expect(textGeneratedFromTemplate).to.equal(expectedText); + }); + + // Aura Component + it('Create an Aura Component', async () => { + logTestStart(testSetup, 'Create an Aura Component'); + // Using the Command palette, run SFDX: Create Aura Component. + await createCommand('Aura Component', 'auraComponent1', path.join('aura', 'auraComponent1'), 'cmp'); + // Zoom out so all tree items are visible + const workbench = await getWorkbench(); + await zoom('Out', 1, Duration.seconds(2)); + // Check for the presence of the directory, "auraComponent1". + const filteredTreeViewItems = await getFilteredVisibleTreeViewItemLabels(workbench, projectName, 'auraComponent1'); + expect(filteredTreeViewItems.includes('auraComponent1')).to.equal(true); + + // It's a tree, but it's also a list. Everything in the view is actually flat + // and returned from the call to visibleItems.reduce(). + expect(filteredTreeViewItems.includes('auraComponent1.cmp')).to.equal(true); + expect(filteredTreeViewItems.includes('auraComponent1.cmp-meta.xml')).to.equal(true); + expect(filteredTreeViewItems.includes('auraComponent1Controller.js')).to.equal(true); + expect(filteredTreeViewItems.includes('auraComponent1Helper.js')).to.equal(true); + expect(filteredTreeViewItems.includes('auraComponent1Renderer.js')).to.equal(true); + + // Could also check for .auradoc, .css, .design, and .svg, but not as critical + // and since this could change w/o our knowing, only check for what we need to here. + }); + + it('Verify the contents of the Aura Component', async () => { + logTestStart(testSetup, 'Verify the contents of the Aura Component'); + const expectedText = ['', '', ''].join('\n'); + const workbench = await getWorkbench(); + const textEditor = await getTextEditor(workbench, 'auraComponent1.cmp'); + const textGeneratedFromTemplate = (await textEditor.getText()).trimEnd().replace(/\r\n/g, '\n'); + expect(textGeneratedFromTemplate).to.equal(expectedText); + }); + + // Aura Event + it('Create an Aura Event', async () => { + logTestStart(testSetup, 'Create an Aura Event'); + // Using the Command palette, run SFDX: Create Aura Component. + await createCommand('Aura Event', 'auraEvent1', path.join('aura', 'auraEvent1'), 'evt'); + + // Check for expected items in the Explorer view. + const workbench = await getWorkbench(); + + // Check for the presence of the directory, "auraEvent1". + const filteredTreeViewItems = await getFilteredVisibleTreeViewItemLabels(workbench, projectName, 'auraEvent1'); + expect(filteredTreeViewItems.includes('auraEvent1')).to.equal(true); + expect(filteredTreeViewItems.includes('auraEvent1.evt')).to.equal(true); + expect(filteredTreeViewItems.includes('auraEvent1.evt-meta.xml')).to.equal(true); + }); + + it('Verify the contents of the Aura Event', async () => { + logTestStart(testSetup, 'Verify the contents of the Aura Event'); + const expectedText = [''].join('\n'); + const workbench = await getWorkbench(); + const textEditor = await getTextEditor(workbench, 'auraEvent1.evt'); + const textGeneratedFromTemplate = (await textEditor.getText()).trimEnd().replace(/\r\n/g, '\n'); + expect(textGeneratedFromTemplate).to.equal(expectedText); + }); + + // Aura Interface + it('Create an Aura Interface', async () => { + logTestStart(testSetup, 'Create an Aura Interface'); + // Using the Command palette, run "SFDX: Create Aura Interface". + await createCommand('Aura Interface', 'AuraInterface1', path.join('aura', 'AuraInterface1'), 'intf'); + + // Get the matching (visible) items within the tree which contains "AuraInterface1". + const workbench = await getWorkbench(); + const filteredTreeViewItems = await getFilteredVisibleTreeViewItemLabels(workbench, projectName, 'AuraInterface1'); + + expect(filteredTreeViewItems.includes('AuraInterface1.intf')).to.equal(true); + expect(filteredTreeViewItems.includes('AuraInterface1.intf-meta.xml')).to.equal(true); + }); + + it('Verify the contents of the Aura Interface', async () => { + logTestStart(testSetup, 'Verify the contents of the Aura Interface'); + // Verify the default code for an Aura Interface. + const expectedText = [ + '', + ' ', + '' + ].join('\n'); + const workbench = await getWorkbench(); + const textEditor = await getTextEditor(workbench, 'AuraInterface1.intf'); + const textGeneratedFromTemplate = (await textEditor.getText()).trimEnd().replace(/\r\n/g, '\n'); + expect(textGeneratedFromTemplate).to.equal(expectedText); + }); + + // Lightning Web Component + it('Create Lightning Web Component', async () => { + logTestStart(testSetup, 'Create Lightning Web Component'); + // Using the Command palette, run SFDX: Create Lightning Web Component. + await createCommand( + 'Lightning Web Component', + 'lightningWebComponent1', + path.join('lwc', 'lightningWebComponent1'), + 'js' + ); + + // Check for expected items in the Explorer view. + const workbench = await getWorkbench(); + + // Check for the presence of the directory, "lightningWebComponent1". + const filteredTreeViewItems = await getFilteredVisibleTreeViewItemLabels( + workbench, + projectName, + 'lightningWebComponent1' + ); + expect(filteredTreeViewItems.includes('lightningWebComponent1')).to.equal(true); + expect(filteredTreeViewItems.includes('lightningWebComponent1.html')).to.equal(true); + expect(filteredTreeViewItems.includes('lightningWebComponent1.js')).to.equal(true); + expect(filteredTreeViewItems.includes('lightningWebComponent1.js-meta.xml')).to.equal(true); + }); + + it('Verify the contents of the Lightning Web Component', async () => { + logTestStart(testSetup, 'Verify the contents of the Lightning Web Component'); + const expectedText = [ + "import { LightningElement } from 'lwc';", + '', + 'export default class LightningWebComponent1 extends LightningElement {}' + ].join('\n'); + const workbench = await getWorkbench(); + const textEditor = await getTextEditor(workbench, 'lightningWebComponent1.js'); + const textGeneratedFromTemplate = (await textEditor.getText()).trimEnd().replace(/\r\n/g, '\n'); + expect(textGeneratedFromTemplate).to.equal(expectedText); + }); + + // Visualforce Component + it('Create a Visualforce Component', async () => { + logTestStart(testSetup, 'Create a Visualforce Component'); + // Using the Command palette, run "SFDX: Create Visualforce Component". + await createCommand('Visualforce Component', 'VisualforceCmp1', 'components', 'component'); + // Get the matching (visible) items within the tree which contains "AuraInterface1". + const workbench = await getWorkbench(); + const filteredTreeViewItems = await getFilteredVisibleTreeViewItemLabels(workbench, projectName, 'VisualforceCmp1'); + expect(filteredTreeViewItems.includes('VisualforceCmp1.component')).to.equal(true); + expect(filteredTreeViewItems.includes('VisualforceCmp1.component-meta.xml')).to.equal(true); + }); + + it('Verify the contents of the Visualforce Component', async () => { + logTestStart(testSetup, 'Verify the contents of the Visualforce Component'); + // Verify the default code for a Visualforce Component. + const expectedText = [ + '', + '', + '

Congratulations

', + 'This is your new Component', + '', + '
' + ].join('\n'); + const workbench = await getWorkbench(); + const textEditor = await getTextEditor(workbench, 'VisualforceCmp1.component'); + const textGeneratedFromTemplate = (await textEditor.getText()).trimEnd().replace(/\r\n/g, '\n'); + expect(textGeneratedFromTemplate).to.equal(expectedText); + }); + + // Visualforce Page + it('Create a Visualforce Page', async () => { + logTestStart(testSetup, 'Create a Visualforce Page'); + // Using the Command palette, run "SFDX: Create Visualforce Page". + await createCommand('Visualforce Page', 'VisualforcePage1', 'pages', 'page'); + + // Get the matching (visible) items within the tree which contains "AuraInterface1". + const workbench = await getWorkbench(); + const filteredTreeViewItems = await getFilteredVisibleTreeViewItemLabels( + workbench, + projectName, + 'VisualforcePage1' + ); + expect(filteredTreeViewItems.includes('VisualforcePage1.page')).to.equal(true); + expect(filteredTreeViewItems.includes('VisualforcePage1.page-meta.xml')).to.equal(true); + }); + + it('Verify the contents of the Visualforce Page', async () => { + logTestStart(testSetup, 'Verify the contents of the Visualforce Page'); + // Verify the default code for a Visualforce Page. + const expectedText = [ + '', + '', + '

Congratulations

', + 'This is your new Page', + '', + '
' + ].join('\n'); + const workbench = await getWorkbench(); + const textEditor = await getTextEditor(workbench, 'VisualforcePage1.page'); + const textGeneratedFromTemplate = (await textEditor.getText()).trimEnd().replace(/\r\n/g, '\n'); + expect(textGeneratedFromTemplate).to.equal(expectedText); + }); + + // Sample Analytics Template + it('Create a Sample Analytics Template', async () => { + logTestStart(testSetup, 'Create a Sample Analytics Template'); + // Clear the output panel, then use the Command palette to run, "SFDX: Create Sample Analytics Template". + const workbench = await getWorkbench(); + await clearOutputView(); + const inputBox = await executeQuickPick('SFDX: Create Sample Analytics Template', Duration.seconds(1)); + + // Set the name of the new page to sat1 + await inputBox.setText('sat1'); + await inputBox.confirm(); + await pause(Duration.seconds(1)); + + // Select the default directory (press Enter/Return). + await inputBox.confirm(); + + await verifyNotificationWithRetry(/SFDX: Create Sample Analytics Template successfully ran/, Duration.TEN_MINUTES); + + const outputPanelText = await attemptToFindOutputPanelText( + 'Salesforce CLI', + 'Finished SFDX: Create Sample Analytics Template', + 10 + ); + expect(outputPanelText).to.not.be.undefined; + + // Check for expected items in the Explorer view. + + // Check for the presence of the corresponding files + const treeViewItems = await getVisibleItemsFromSidebar(workbench, projectName); + expect(treeViewItems.includes('dashboards')).to.equal(true); + expect(treeViewItems.includes('app-to-template-rules.json')).to.equal(true); + expect(treeViewItems.includes('folder.json')).to.equal(true); + expect(treeViewItems.includes('releaseNotes.html')).to.equal(true); + expect(treeViewItems.includes('template-info.json')).to.equal(true); + expect(treeViewItems.includes('template-to-app-rules.json')).to.equal(true); + expect(treeViewItems.includes('ui.json')).to.equal(true); + expect(treeViewItems.includes('variables.json')).to.equal(true); + }); + + it('Verify the contents of the Sample Analytics Template', async () => { + logTestStart(testSetup, 'Verify the contents of the Sample Analytics Template'); + // Verify the default code for a Sample Analytics Template. + const workbench = await getWorkbench(); + let textEditor = await getTextEditor(workbench, 'app-to-template-rules.json'); + let textGeneratedFromTemplate = (await textEditor.getText()).trimEnd().replace(/\r\n/g, '\n'); + expect(textGeneratedFromTemplate).to.equal(analyticsTemplate.appToTemplateRules); + + textEditor = await getTextEditor(workbench, 'folder.json'); + textGeneratedFromTemplate = (await textEditor.getText()).trimEnd().replace(/\r\n/g, '\n'); + expect(textGeneratedFromTemplate).to.equal(analyticsTemplate.folder); + + textEditor = await getTextEditor(workbench, 'releaseNotes.html'); + textGeneratedFromTemplate = (await textEditor.getText()).trimEnd().replace(/\r\n/g, '\n'); + expect(textGeneratedFromTemplate).to.equal(analyticsTemplate.releaseNotes); + + textEditor = await getTextEditor(workbench, 'template-info.json'); + textGeneratedFromTemplate = (await textEditor.getText()).trimEnd().replace(/\r\n/g, '\n'); + expect(textGeneratedFromTemplate).to.equal(analyticsTemplate.templateInfo); + + textEditor = await getTextEditor(workbench, 'template-to-app-rules.json'); + textGeneratedFromTemplate = (await textEditor.getText()).trimEnd().replace(/\r\n/g, '\n'); + expect(textGeneratedFromTemplate).to.equal(analyticsTemplate.templateToAppRules); + + textEditor = await getTextEditor(workbench, 'ui.json'); + textGeneratedFromTemplate = (await textEditor.getText()).trimEnd().replace(/\r\n/g, '\n'); + expect(textGeneratedFromTemplate).to.equal(analyticsTemplate.ui); + + textEditor = await getTextEditor(workbench, 'variables.json'); + textGeneratedFromTemplate = (await textEditor.getText()).trimEnd().replace(/\r\n/g, '\n'); + expect(textGeneratedFromTemplate).to.equal(analyticsTemplate.variables); + }); + + // Tear down + after('Tear down and clean up the testing environment', async () => { + await testSetup?.tearDown(); + }); +}); diff --git a/packages/salesforcedx-vscode-automation-tests/test/specs/trailApexReplayDebugger.e2e.ts b/packages/salesforcedx-vscode-automation-tests/test/specs/trailApexReplayDebugger.e2e.ts new file mode 100644 index 0000000000..59aef2924e --- /dev/null +++ b/packages/salesforcedx-vscode-automation-tests/test/specs/trailApexReplayDebugger.e2e.ts @@ -0,0 +1,250 @@ +/* + * Copyright (c) 2023, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ +import { + Duration, + log, + pause, + ProjectShapeOption, + TestReqConfig +} from '@salesforce/salesforcedx-vscode-test-tools/lib/src/core'; +import { verifyNotificationWithRetry } from '@salesforce/salesforcedx-vscode-test-tools/lib/src/retryUtils'; +import { createApexClassWithBugs } from '@salesforce/salesforcedx-vscode-test-tools/lib/src/salesforce-components'; +import { continueDebugging } from '@salesforce/salesforcedx-vscode-test-tools/lib/src/testing'; +import { TestSetup } from '@salesforce/salesforcedx-vscode-test-tools/lib/src/testSetup'; +import { + attemptToFindOutputPanelText, + clearOutputView, + executeQuickPick, + getStatusBarItemWhichIncludes, + getTextEditor, + getWorkbench, + waitForNotificationToGoAway +} from '@salesforce/salesforcedx-vscode-test-tools/lib/src/ui-interaction'; +import { expect } from 'chai'; +import { By, InputBox, QuickOpenBox, TextEditor } from 'vscode-extension-tester'; +import { logTestStart } from '../utils/loggingHelper'; + +/** + * This test suite walks through the same steps performed in the "Find and Fix Bugs with Apex Replay Debugger" Trailhead Module; + * which can be found with the following link: + * https://trailhead.salesforce.com/content/learn/projects/find-and-fix-bugs-with-apex-replay-debugger + */ +describe('"Find and Fix Bugs with Apex Replay Debugger" Trailhead Module', () => { + let prompt: QuickOpenBox | InputBox; + let testSetup: TestSetup; + const testReqConfig: TestReqConfig = { + projectConfig: { + projectShape: ProjectShapeOption.NEW + }, + isOrgRequired: true, + testSuiteSuffixName: 'TrailApexReplayDebugger' + }; + + before('Set up the testing environment', async () => { + log('TrailApexReplayDebugger - Set up the testing environment'); + testSetup = await TestSetup.setUp(testReqConfig); + + // Create Apex class AccountService + await createApexClassWithBugs(); + + // Push source to org + await executeQuickPick('SFDX: Push Source to Default Org and Ignore Conflicts', Duration.seconds(1)); + + await verifyNotificationWithRetry( + /SFDX: Push Source to Default Org and Ignore Conflicts successfully ran/, + Duration.TEN_MINUTES + ); + }); + + it('Verify LSP finished indexing', async () => { + logTestStart(testSetup, 'Verify LSP finished indexing'); + + // Get Apex LSP Status Bar + const statusBar = await getStatusBarItemWhichIncludes('Editor Language Status'); + await statusBar.click(); + expect(await statusBar.getAttribute('aria-label')).to.contain('Indexing complete'); + }); + + it('Run Apex Tests', async () => { + logTestStart(testSetup, 'Run Apex Tests'); + // Run SFDX: Run Apex tests. + await clearOutputView(); + prompt = await executeQuickPick('SFDX: Run Apex Tests', Duration.seconds(1)); + + // Select the "AccountServiceTest" file + await prompt.selectQuickPick('AccountServiceTest'); + + await verifyNotificationWithRetry(/SFDX: Run Apex Tests successfully ran/, Duration.TEN_MINUTES); + + // Verify test results are listed on vscode's Output section + const outputPanelText = await attemptToFindOutputPanelText('Apex', '=== Test Results', 10); + expect(outputPanelText).to.contain('Assertion Failed: incorrect ticker symbol'); + expect(outputPanelText).to.contain('Expected: CRM, Actual: SFDC'); + }); + + // This is broken and will be fixed with W-19004812 https://gus.lightning.force.com/a07EE00002Ht4taYAB + it.skip('Set Breakpoints and Checkpoints', async () => { + logTestStart(testSetup, 'Set Breakpoints and Checkpoints'); + // Get open text editor + const workbench = getWorkbench(); + const textEditor = await getTextEditor(workbench, 'AccountService.cls'); + await textEditor.moveCursor(8, 5); + await pause(Duration.seconds(1)); + + // Run SFDX: Toggle Checkpoint. + prompt = await executeQuickPick('SFDX: Toggle Checkpoint', Duration.seconds(1)); + + // Verify checkpoint is present + const breakpoints = await workbench.findElements(By.css('div.codicon-debug-breakpoint-conditional')); + expect(breakpoints.length).to.equal(1); + + // Run SFDX: Update Checkpoints in Org. + prompt = await executeQuickPick('SFDX: Update Checkpoints in Org', Duration.seconds(20)); + // Verify checkpoints updating results are listed on vscode's Output section + const outputPanelText = await attemptToFindOutputPanelText( + 'Apex Replay Debugger', + 'Starting SFDX: Update Checkpoints in Org', + 10 + ); + expect(outputPanelText).to.contain( + 'SFDX: Update Checkpoints in Org, Step 6 of 6: Confirming successful checkpoint creation' + ); + expect(outputPanelText).to.contain('Ending SFDX: Update Checkpoints in Org'); + }); + + it('SFDX: Turn On Apex Debug Log for Replay Debugger', async () => { + logTestStart(testSetup, 'SFDX: Turn On Apex Debug Log for Replay Debugger'); + // Run SFDX: Turn On Apex Debug Log for Replay Debugger + await clearOutputView(); + await executeQuickPick('SFDX: Turn On Apex Debug Log for Replay Debugger', Duration.seconds(10)); + + // Look for the success notification that appears which says, "SFDX: Turn On Apex Debug Log for Replay Debugger successfully ran". + await verifyNotificationWithRetry( + /SFDX: Turn On Apex Debug Log for Replay Debugger successfully ran/, + Duration.TEN_MINUTES + ); + + // Verify content on vscode's Output section + const outputPanelText = await attemptToFindOutputPanelText( + 'Salesforce CLI', + 'Starting SFDX: Turn On Apex Debug Log for Replay Debugger', + 10 + ); + expect(outputPanelText).to.contain('SFDX: Turn On Apex Debug Log for Replay Debugger'); + expect(outputPanelText).to.contain('ended with exit code 0'); + }); + + it('Run Apex Tests', async () => { + logTestStart(testSetup, 'Run Apex Tests'); + // Run SFDX: Run Apex tests. + await clearOutputView(); + prompt = await executeQuickPick('SFDX: Run Apex Tests', Duration.seconds(1)); + + // Select the "AccountServiceTest" file + await prompt.selectQuickPick('AccountServiceTest'); + + await verifyNotificationWithRetry(/SFDX: Run Apex Tests successfully ran/, Duration.TEN_MINUTES); + + // Verify test results are listed on vscode's Output section + const outputPanelText = await attemptToFindOutputPanelText('Apex', '=== Test Results', 10); + expect(outputPanelText).to.not.be.undefined; + expect(outputPanelText).to.contain('Assertion Failed: incorrect ticker symbol'); + expect(outputPanelText).to.contain('Expected: CRM, Actual: SFDC'); + }); + + // This is broken and will be fixed with W-19004812 https://gus.lightning.force.com/a07EE00002Ht4taYAB + it.skip('SFDX: Get Apex Debug Logs', async () => { + logTestStart(testSetup, 'SFDX: Get Apex Debug Logs'); + // Run SFDX: Get Apex Debug Logs + const workbench = getWorkbench(); + prompt = await executeQuickPick('SFDX: Get Apex Debug Logs', Duration.seconds(0)); + + // Wait for the command to execute + await waitForNotificationToGoAway(/Getting Apex debug logs/, Duration.TEN_MINUTES); + await pause(Duration.seconds(2)); + // Select a log file + const quickPicks = await prompt.getQuickPicks(); + expect(quickPicks).to.not.be.undefined; + expect(quickPicks.length).to.be.greaterThan(0); + await prompt.selectQuickPick('User User - ApexTestHandler'); + + await verifyNotificationWithRetry(/SFDX: Get Apex Debug Logs successfully ran/, Duration.TEN_MINUTES); + + // Verify content on vscode's Output section + const outputPanelText = await attemptToFindOutputPanelText('Apex', 'Starting SFDX: Get Apex Debug Logs', 10); + expect(outputPanelText).to.contain('|EXECUTION_STARTED'); + expect(outputPanelText).to.contain('|EXECUTION_FINISHED'); + expect(outputPanelText).to.contain('ended SFDX: Get Apex Debug Logs'); + + // Verify content on log file + const editorView = workbench.getEditorView(); + const activeTab = await editorView.getActiveTab(); + const title = await activeTab?.getTitle(); + const textEditor = await editorView.openEditor(title!); + if (!(textEditor instanceof TextEditor)) { + throw new Error(`Expected TextEditor but got different editor type: ${typeof textEditor}`); + } + const executionStarted = await textEditor.getLineOfText('|EXECUTION_STARTED'); + const executionFinished = await textEditor.getLineOfText('|EXECUTION_FINISHED'); + expect(executionStarted).to.be.greaterThan(0); + expect(executionFinished).to.be.greaterThan(0); + }); + + // This is broken and will be fixed with W-19004812 https://gus.lightning.force.com/a07EE00002Ht4taYAB + it.skip('Replay an Apex Debug Log', async () => { + logTestStart(testSetup, 'Replay an Apex Debug Log'); + // Run SFDX: Launch Apex Replay Debugger with Current File + await executeQuickPick('SFDX: Launch Apex Replay Debugger with Current File', Duration.seconds(30)); + + // Continue with the debug session + await continueDebugging(2, 30); + }); + + it('Push Fixed Metadata to Org', async () => { + if (process.platform === 'darwin') { + logTestStart(testSetup, 'Push Fixed Metadata to Org'); + // Get open text editor + const workbench = getWorkbench(); + const textEditor = await getTextEditor(workbench, 'AccountService.cls'); + await textEditor.setTextAtLine(6, '\t\t\tTickerSymbol = tickerSymbol'); + await textEditor.save(); + await pause(Duration.seconds(2)); + + // Push source to org + await executeQuickPick('SFDX: Push Source to Default Org and Ignore Conflicts', Duration.seconds(10)); + + await verifyNotificationWithRetry( + /SFDX: Push Source to Default Org and Ignore Conflicts successfully ran/, + Duration.TEN_MINUTES + ); + } + }); + + it('Run Apex Tests to Verify Fix', async () => { + if (process.platform === 'darwin') { + logTestStart(testSetup, 'Run Apex Tests to Verify Fix'); + // Run SFDX: Run Apex tests. + await clearOutputView(); + prompt = await executeQuickPick('SFDX: Run Apex Tests', Duration.seconds(1)); + + // Select the "AccountServiceTest" file + await prompt.selectQuickPick('AccountServiceTest'); + + await verifyNotificationWithRetry(/SFDX: Run Apex Tests successfully ran/, Duration.TEN_MINUTES); + + // Verify test results are listed on vscode's Output section + const outputPanelText = await attemptToFindOutputPanelText('Apex', '=== Test Results', 10); + expect(outputPanelText).to.contain('AccountServiceTest.should_create_account'); + expect(outputPanelText).to.contain('Pass'); + } + }); + + after('Tear down and clean up the testing environment', async () => { + log('TrailApexReplayDebugger - Tear down and clean up the testing environment'); + await testSetup?.tearDown(); + }); +}); diff --git a/packages/salesforcedx-vscode-automation-tests/test/specs/visualforceLsp.e2e.ts b/packages/salesforcedx-vscode-automation-tests/test/specs/visualforceLsp.e2e.ts new file mode 100644 index 0000000000..87dba4e8a5 --- /dev/null +++ b/packages/salesforcedx-vscode-automation-tests/test/specs/visualforceLsp.e2e.ts @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2023, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +import { + Duration, + log, + pause, + ProjectShapeOption, + TestReqConfig +} from '@salesforce/salesforcedx-vscode-test-tools/lib/src/core'; +import { + createApexController, + createVisualforcePage +} from '@salesforce/salesforcedx-vscode-test-tools/lib/src/salesforce-components'; +import { TestSetup } from '@salesforce/salesforcedx-vscode-test-tools/lib/src/testSetup'; +import { + attemptToFindOutputPanelText, + clearOutputView, + executeQuickPick, + getTextEditor, + getWorkbench, + verifyOutputPanelText +} from '@salesforce/salesforcedx-vscode-test-tools/lib/src/ui-interaction'; +import { expect } from 'chai'; +import * as path from 'node:path'; +import { By, after } from 'vscode-extension-tester'; +import { logTestStart } from '../utils/loggingHelper'; + +describe('Visualforce LSP', () => { + let testSetup: TestSetup; + const testReqConfig: TestReqConfig = { + projectConfig: { + projectShape: ProjectShapeOption.NEW + }, + isOrgRequired: false, + testSuiteSuffixName: 'VisualforceLsp' + }; + + before('Set up the testing environment', async () => { + log('VisualforceLsp - Set up the testing environment'); + testSetup = await TestSetup.setUp(testReqConfig); + + // Create Apex controller for the Visualforce Page + await createApexController(); + + // Clear output before running the command + await clearOutputView(); + log(`${testSetup.testSuiteSuffixName} - calling createVisualforcePage()`); + await createVisualforcePage(); + + const pathToPagesFolder = path.join(testSetup.projectFolderPath!, 'force-app', 'main', 'default', 'pages'); + const pathToPage = path.join('force-app', 'main', 'default', 'pages', 'FooPage.page'); + + // Create an array of strings for the expected output text + const expectedTexts = [ + `target dir = ${pathToPagesFolder.replace(/^[A-Z]:/, match => match.toLowerCase())}`, + `create ${pathToPage}`, + `create ${pathToPage}-meta.xml`, + 'Finished SFDX: Create Visualforce Page' + ]; + // Check output panel to validate file was created... + const outputPanelText = await attemptToFindOutputPanelText( + 'Salesforce CLI', + 'Starting SFDX: Create Visualforce Page', + 10 + ); + await verifyOutputPanelText(outputPanelText, expectedTexts); + + // Get open text editor and verify file content + const workbench = await getWorkbench(); + const textEditor = await getTextEditor(workbench, 'FooPage.page'); + const fileContent = await textEditor.getText(); + expect(fileContent).to.contain(''); + expect(fileContent).to.contain(''); + }); + + it.skip('Go to Definition', async () => { + logTestStart(testSetup, 'Go to Definition'); + // Get open text editor + const workbench = await getWorkbench(); + const textEditor = await getTextEditor(workbench, 'FooPage.page'); + await textEditor.moveCursor(1, 25); + + // Go to definition through F12 + await executeQuickPick('Go to Definition', Duration.seconds(2)); + await pause(Duration.seconds(1)); + + // TODO: go to definition is actually not working + + // // Verify 'Go to definition' took us to the definition file + // const activeTab = await editorView.getActiveTab(); + // const title = await activeTab?.getTitle(); + // expect(title).toBe('MyController.cls'); + }); + + it('Autocompletion', async () => { + logTestStart(testSetup, 'Autocompletion'); + // Get open text editor + const workbench = await getWorkbench(); + const textEditor = await getTextEditor(workbench, 'FooPage.page'); + await textEditor.typeTextAt(3, 1, '\t\t'); + await textEditor.save(); + await pause(Duration.seconds(1)); + const line3Text = await textEditor.getTextAtLine(3); + expect(line3Text).to.contain('apex:pageMessage'); + }); + + after('Tear down and clean up the testing environment', async () => { + log(`${testSetup.testSuiteSuffixName} - Tear down and clean up the testing environment`); + await testSetup?.tearDown(); + }); +}); diff --git a/packages/salesforcedx-vscode-automation-tests/test/testData/CustomSObjects/Customer__c/Customer__c.object-meta.xml b/packages/salesforcedx-vscode-automation-tests/test/testData/CustomSObjects/Customer__c/Customer__c.object-meta.xml new file mode 100644 index 0000000000..0622de2026 --- /dev/null +++ b/packages/salesforcedx-vscode-automation-tests/test/testData/CustomSObjects/Customer__c/Customer__c.object-meta.xml @@ -0,0 +1,165 @@ + + + + Accept + Default + + + Accept + Large + Default + + + Accept + Small + Default + + + CancelEdit + Default + + + CancelEdit + Large + Default + + + CancelEdit + Small + Default + + + Clone + Default + + + Clone + Large + Default + + + Clone + Small + Default + + + Delete + Default + + + Delete + Large + Default + + + Delete + Small + Default + + + Edit + Default + + + Edit + Large + Default + + + Edit + Small + Default + + + List + Default + + + List + Large + Default + + + List + Small + Default + + + New + Default + + + New + Large + Default + + + New + Small + Default + + + SaveEdit + Default + + + SaveEdit + Large + Default + + + SaveEdit + Small + Default + + + Tab + Default + + + Tab + Large + Default + + + Tab + Small + Default + + + View + Default + + + View + Large + Default + + + View + Small + Default + + false + SYSTEM + Deployed + false + true + false + false + false + false + false + true + true + Private + + + + Text + + Customers + + ReadWrite + Public + diff --git a/packages/salesforcedx-vscode-automation-tests/test/testData/CustomSObjects/Product__c/Product__c.object-meta.xml b/packages/salesforcedx-vscode-automation-tests/test/testData/CustomSObjects/Product__c/Product__c.object-meta.xml new file mode 100644 index 0000000000..b6465207af --- /dev/null +++ b/packages/salesforcedx-vscode-automation-tests/test/testData/CustomSObjects/Product__c/Product__c.object-meta.xml @@ -0,0 +1,165 @@ + + + + Accept + Default + + + Accept + Large + Default + + + Accept + Small + Default + + + CancelEdit + Default + + + CancelEdit + Large + Default + + + CancelEdit + Small + Default + + + Clone + Default + + + Clone + Large + Default + + + Clone + Small + Default + + + Delete + Default + + + Delete + Large + Default + + + Delete + Small + Default + + + Edit + Default + + + Edit + Large + Default + + + Edit + Small + Default + + + List + Default + + + List + Large + Default + + + List + Small + Default + + + New + Default + + + New + Large + Default + + + New + Small + Default + + + SaveEdit + Default + + + SaveEdit + Large + Default + + + SaveEdit + Small + Default + + + Tab + Default + + + Tab + Large + Default + + + Tab + Small + Default + + + View + Default + + + View + Large + Default + + + View + Small + Default + + false + SYSTEM + Deployed + false + true + false + false + false + false + false + true + true + Private + + + + Text + + Products + + ReadWrite + Public + diff --git a/packages/salesforcedx-vscode-automation-tests/test/testData/files/idealCaseManagerOASDoc.xml b/packages/salesforcedx-vscode-automation-tests/test/testData/files/idealCaseManagerOASDoc.xml new file mode 100644 index 0000000000..e170a8311b --- /dev/null +++ b/packages/salesforcedx-vscode-automation-tests/test/testData/files/idealCaseManagerOASDoc.xml @@ -0,0 +1,58 @@ + + + This is the ideal OpenAPI v3 specification for CaseManager.cls. + + openapi: 3.0.0 +info: + title: CaseManager + version: '1.0.0' + description: This is the ideal OpenAPI v3 specification for CaseManager.cls. +servers: + - url: /services/apexrest + description: Apex rest +paths: + /apex-rest-examples/v1/Cases: + description: The endpoint that contains the POST method. + post: + summary: Create a new case + description: Creates a new case with the provided information. + operationId: createCase + requestBody: + description: The properties of the case to create. + content: + application/json: + schema: + type: object + properties: + subject: + type: string + description: The subject of the case + status: + type: string + description: The status of the case + origin: + type: string + description: The origin of the case + priority: + type: string + description: The priority of the case + responses: + '200': + description: The ID of the newly created case. + content: + text/plain: + schema: + type: string + OpenApi3 + yaml + casemanager_openapi + Complete + 3 + + createCase + true + + CaseManager + ApexRest + null + diff --git a/packages/salesforcedx-vscode-automation-tests/test/testData/files/idealSimpleAccountResource.xml b/packages/salesforcedx-vscode-automation-tests/test/testData/files/idealSimpleAccountResource.xml new file mode 100644 index 0000000000..f07363052a --- /dev/null +++ b/packages/salesforcedx-vscode-automation-tests/test/testData/files/idealSimpleAccountResource.xml @@ -0,0 +1,17 @@ + + + This is the ideal OpenAPI v3 specification for SimpleAccountResource.cls. + + OpenApi3 + yaml + simpleaccountresource_openapi + Complete + 3 + + getAccount + true + + SimpleAccountResource + ApexRest + null + diff --git a/packages/salesforcedx-vscode-automation-tests/test/testData/files/idealSimpleAccountResource.yaml b/packages/salesforcedx-vscode-automation-tests/test/testData/files/idealSimpleAccountResource.yaml new file mode 100644 index 0000000000..885ce66844 --- /dev/null +++ b/packages/salesforcedx-vscode-automation-tests/test/testData/files/idealSimpleAccountResource.yaml @@ -0,0 +1,41 @@ +openapi: 3.0.0 +servers: + - url: /services/apexrest +info: + title: SimpleAccountResource + version: '1.0.0' + description: This is the ideal OpenAPI v3 specification for SimpleAccountResource.cls. +paths: + /apex-rest-examples/v1/{accountId}: + description: The endpoint that contains the GET method. + get: + summary: Get Account + operationId: getAccount + description: Returns the Account that matches the ID specified in the URL + parameters: + - name: accountId + in: path + required: true + description: The ID of the Account to retrieve + schema: + type: string + responses: + '200': + description: The Account with the provided ID + content: + application/json: + schema: + type: object + properties: + Id: + type: string + description: The ID of the Account + Name: + type: string + description: The name of the Account + Phone: + type: string + description: The phone number of the Account + Website: + type: string + description: The website of the Account diff --git a/packages/salesforcedx-vscode-automation-tests/test/testData/files/sfdxProject.json b/packages/salesforcedx-vscode-automation-tests/test/testData/files/sfdxProject.json new file mode 100644 index 0000000000..6fa32c43d6 --- /dev/null +++ b/packages/salesforcedx-vscode-automation-tests/test/testData/files/sfdxProject.json @@ -0,0 +1,15 @@ +{ + "packageDirectories": [ + { + "path": "force-app", + "default": true + } + ], + "name": "TempProject-CreateOASDoc", + "namespace": "", + "sfdcLoginUrl": "https://login.salesforce.com", + "sourceApiVersion": "63.0", + "sourceBehaviorOptions": [ + "decomposeExternalServiceRegistrationBeta" + ] +} diff --git a/packages/salesforcedx-vscode-automation-tests/test/testData/oasDocs.ts b/packages/salesforcedx-vscode-automation-tests/test/testData/oasDocs.ts new file mode 100644 index 0000000000..d560bffbeb --- /dev/null +++ b/packages/salesforcedx-vscode-automation-tests/test/testData/oasDocs.ts @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2025, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +import * as fs from 'node:fs'; +import * as path from 'node:path'; + +const readJsonFile = (filename: string): unknown => { + const filePath = path.join(__dirname, filename); + const content = fs.readFileSync(filePath, 'utf8'); + return JSON.parse(content); +}; + +const readXmlFile = (filename: string): string => { + const filePath = path.join(__dirname, filename); + return fs.readFileSync(filePath, 'utf8'); +}; + +const readYamlFile = (filename: string): string => { + const filePath = path.join(__dirname, filename); + return fs.readFileSync(filePath, 'utf8'); +}; + +export const getIdealCaseManagerOASDoc = (): string => readXmlFile('./files/idealCaseManagerOASDoc.xml'); + +export const getSfdxProjectJson = (): string => { + const project = readJsonFile('./files/sfdxProject.json'); + if (!project) { + throw new Error('sfdxProject.json not found'); + } + return JSON.stringify(project, null, 2); +}; + +export const getIdealSimpleAccountResourceYaml = (): string => readYamlFile('./files/idealSimpleAccountResource.yaml'); + +export const getIdealSimpleAccountResourceXml = (): string => readXmlFile('./files/idealSimpleAccountResource.xml'); diff --git a/packages/salesforcedx-vscode-automation-tests/test/testData/sampleAnalyticsTemplateData.ts b/packages/salesforcedx-vscode-automation-tests/test/testData/sampleAnalyticsTemplateData.ts new file mode 100644 index 0000000000..931067cf33 --- /dev/null +++ b/packages/salesforcedx-vscode-automation-tests/test/testData/sampleAnalyticsTemplateData.ts @@ -0,0 +1,234 @@ +/* + * Copyright (c) 2024, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ +export const variables = [ + '{', + ' "Overrides": {', + ' "label": "Overrides",', + ' "description": "Internal configuration to allow asset creation overrides, not to be displayed in UI.",', + ' "defaultValue": {', + ' "createAllDashboards": true,', + ' "createAllLenses": true,', + ' "createAllExternalFiles": true,', + ' "createDataflow": true,', + ' "createAllDatasetFiles": true,', + ' "createAllImages": true', + ' },', + ' "required": true,', + ' "excludeSelected": false,', + ' "excludes": [],', + ' "variableType": {', + ' "required": [', + ' "createAllExternalFiles",', + ' "createAllDashboards",', + ' "createAllImages",', + ' "createAllDatasetFiles",', + ' "createAllLenses",', + ' "createDataflow"', + ' ],', + ' "type": "ObjectType",', + ' "properties": {', + ' "createAllDashboards": {', + ' "type": "BooleanType",', + ' "enums": [true, false]', + ' },', + ' "createAllLenses": {', + ' "type": "BooleanType",', + ' "enums": [true, false]', + ' },', + ' "createAllExternalFiles": {', + ' "type": "BooleanType",', + ' "enums": [true, false]', + ' },', + ' "createDataflow": {', + ' "type": "BooleanType",', + ' "enums": [true, false]', + ' },', + ' "createAllDatasetFiles": {', + ' "type": "BooleanType",', + ' "enums": [true, false]', + ' },', + ' "createAllImages": {', + ' "type": "BooleanType",', + ' "enums": [true, false]', + ' }', + ' },', + ' "strictValidation": true', + ' }', + ' }', + '}' +].join('\n'); + +export const ui = ['{', ' "pages": [],', ' "displayMessages": []', '}'].join('\n'); + +export const templateToAppRules = ['{', ' "constants": [],', ' "macros": [],', ' "rules": []', '}'].join('\n'); + +export const templateInfo = [ + '{', + ' "templateType": "app",', + ' "label": "sat1",', + ' "name": "sat1",', + ' "description": "",', + ' "assetVersion": 57.0,', + ' "variableDefinition": "variables.json",', + ' "uiDefinition": "ui.json",', + ' "rules": [', + ' {', + ' "type": "templateToApp",', + ' "file": "template-to-app-rules.json"', + ' },', + ' {', + ' "type": "appToTemplate",', + ' "file": "app-to-template-rules.json"', + ' }', + ' ],', + ' "releaseInfo": {', + ' "templateVersion": "1.0",', + ' "notesFile": "releaseNotes.html"', + ' },', + ' "folderDefinition": "folder.json",', + ' "externalFiles": [],', + ' "lenses": [],', + ' "dashboards": [', + ' {', + ' "label": "sat1Dashboard",', + ' "name": "sat1Dashboard_tp",', + ' "condition": "${Variables.Overrides.createAllDashboards}",', + ' "file": "dashboards/sat1Dashboard.json"', + ' }', + ' ],', + ' "eltDataflows": [],', + ' "datasetFiles": [],', + ' "imageFiles": [],', + ' "extendedTypes": {},', + ' "templateDependencies": [],', + ' "icons": {', + ' "appBadge": {', + ' "name": "16.png"', + ' },', + ' "templateBadge": {', + ' "name": "default.png"', + ' },', + ' "templatePreviews": []', + ' }', + '}' +].join('\n'); + +export const releaseNotes = '

Release Notes for Template

'; + +export const folder = [ + '{', + ' "name": "sat1",', + ' "label": "sat1",', + ' "description": "Analytics template with one simple dashboard",', + ' "featuredAssets": {},', + ' "shares": []', + '}' +].join('\n'); + +export const appToTemplateRules = ['{', ' "constants": [],', ' "macros": [],', ' "rules": []', '}'].join('\n'); + +export const dashboard = [ + '{', + ' "name": "sat1Dashboard_tp",', + ' "label": "sat1Dashboard",', + ' "description": "sat1 dashboard",', + ' "folder": {', + ' "id": "${App.Folder.Id}"', + ' },', + ' "dateVersion": 1,', + ' "mobileDisabled": false,', + ' "xmd": {', + ' "measures": [],', + ' "derivedDimensions": [],', + ' "organizations": [],', + ' "showDetailsDefaultFields": [],', + ' "dates": [],', + ' "derivedMeasures": [],', + ' "dimensions": []', + ' },', + ' "state": {', + ' "dataSourceLinksInfo": {', + ' "enableAutomaticLinking": false,', + ' "excludeRelationships": [],', + ' "links": []', + ' },', + ' "filters": [],', + ' "gridLayouts": [', + ' {', + ' "name": "Default",', + ' "numColumns": 12,', + ' "pages": [', + ' {', + ' "label": "Untitled",', + ' "name": "c07278f3-c01f-4fd6-9139-3878a548f264",', + ' "navigationHidden": false,', + ' "widgets": [', + ' {', + ' "colspan": 8,', + ' "column": 0,', + ' "name": "text_1",', + ' "row": 0,', + ' "rowspan": 13,', + ' "widgetStyle": {', + ' "borderEdges": []', + ' }', + ' }', + ' ]', + ' }', + ' ],', + ' "rowHeight": "normal",', + ' "selectors": [],', + ' "style": {', + ' "alignmentX": "left",', + ' "alignmentY": "top",', + ' "backgroundColor": "#F2F6FA",', + ' "cellSpacingX": 8,', + ' "cellSpacingY": 8,', + ' "fit": "original",', + ' "gutterColor": "#C5D3E0"', + ' },', + ' "version": 1.0', + ' }', + ' ],', + ' "layouts": [],', + ' "steps": {},', + ' "widgetStyle": {', + ' "backgroundColor": "#FFFFFF",', + ' "borderColor": "#E6ECF2",', + ' "borderEdges": [],', + ' "borderRadius": 0,', + ' "borderWidth": 1', + ' },', + ' "widgets": {', + ' "text_1": {', + ' "parameters": {', + ' "content": {', + ' "richTextContent": [', + ' {', + ' "attributes": {', + ' "size": "48px",', + ' "color": "rgb(9, 26, 62)"', + ' },', + ' "insert": "sat1 Analytics Dashboard"', + ' },', + ' {', + ' "attributes": {', + ' "align": "center"', + ' },', + ' "insert": "\\n"', + ' }', + ' ]', + ' },', + ' "interactions": [],', + ' "showActionMenu": true', + ' },', + ' "type": "text"', + ' }', + ' }', + ' }', + '}' +].join('\n'); diff --git a/packages/salesforcedx-vscode-automation-tests/test/testData/sampleClassData.ts b/packages/salesforcedx-vscode-automation-tests/test/testData/sampleClassData.ts new file mode 100644 index 0000000000..55d64421c4 --- /dev/null +++ b/packages/salesforcedx-vscode-automation-tests/test/testData/sampleClassData.ts @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2025, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +export const caseManagerClassText = [ + "@RestResource(urlMapping='/apex-rest-examples/v1/Cases/*')", + 'global with sharing class CaseManager {', + ' @HttpPost', + ' global static ID createCase(String subject, String status,', + ' String origin, String priority) {', + ' Case thisCase = new Case(', + ' Subject=subject,', + ' Status=status,', + ' Origin=origin,', + ' Priority=priority);', + ' insert thisCase;', + ' return thisCase.Id;', + ' }', + '}' +].join('\n'); + +export const simpleAccountResourceClassText = [ + "@RestResource(urlMapping='/apex-rest-examples/v1/*')", + 'global with sharing class SimpleAccountResource {', + ' @HttpGet', + ' global static Account getAccount() {', + ' RestRequest req = RestContext.request;', + ' RestResponse res = RestContext.response;', + " String accountId = req.requestURI.substring(req.requestURI.lastIndexOf('/')+1);", + ' Account result = [SELECT Id, Name, Phone, Website FROM Account WHERE Id = :accountId];', + ' return result;', + ' }', + '}' +].join('\n'); diff --git a/packages/salesforcedx-vscode-automation-tests/test/utils/loggingHelper.ts b/packages/salesforcedx-vscode-automation-tests/test/utils/loggingHelper.ts new file mode 100644 index 0000000000..6d2b7f30a2 --- /dev/null +++ b/packages/salesforcedx-vscode-automation-tests/test/utils/loggingHelper.ts @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2025, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +import { log } from '@salesforce/salesforcedx-vscode-test-tools/lib/src/core/miscellaneous'; +import { TestSetup } from '@salesforce/salesforcedx-vscode-test-tools/lib/src/testSetup'; + +/** + * Helper method to format test log messages with line break and "Running test: " prefix + * @param testSetup The test setup instance containing the test suite suffix name + * @param testDescription The description of the test being run + */ +export const logTestStart = (testSetup: TestSetup, testDescription: string): void => { + log('--------------------------------'); + log(`Running test: ${testSetup.testSuiteSuffixName} - ${testDescription}`); +}; diff --git a/packages/salesforcedx-vscode-automation-tests/test/utils/sampleClassData.ts b/packages/salesforcedx-vscode-automation-tests/test/utils/sampleClassData.ts new file mode 100644 index 0000000000..17a593b74b --- /dev/null +++ b/packages/salesforcedx-vscode-automation-tests/test/utils/sampleClassData.ts @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2025, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +export const caseManagerClassText = [ + "@RestResource(urlMapping='/apex-rest-examples/v1/Cases/*')", + 'global with sharing class CaseManager {', + ' @HttpPost', + ' global static ID createCase(String subject, String status,', + ' String origin, String priority) {', + ' Case thisCase = new Case(', + ' Subject=subject,', + ' Status=status,', + ' Origin=origin,', + ' Priority=priority);', + ' insert thisCase;', + ' return thisCase.Id;', + ' }', + '}' +].join('\n'); diff --git a/packages/salesforcedx-vscode-automation-tests/tsconfig.json b/packages/salesforcedx-vscode-automation-tests/tsconfig.json new file mode 100644 index 0000000000..ad9ab18b5b --- /dev/null +++ b/packages/salesforcedx-vscode-automation-tests/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../../tsconfig.common.json", + "compilerOptions": { + "rootDir": ".", + "outDir": "./lib", // Specify the output directory + "module": "commonjs", + "moduleResolution": "node", + "types": ["node"], + "target": "ES2020" + }, + "include": ["./test/**/*.ts", "./test/customSummaryReporter.ts"], + "exclude": ["node_modules", "lib"] +} diff --git a/tsconfig.json b/tsconfig.json index 4f0eb64c40..f73de82f0a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -57,6 +57,9 @@ }, { "path": "./.github/actions/check-feature-request" + }, + { + "path": "packages/salesforcedx-vscode-automation-tests" } ] }