From e0fb03b84851756e629fc0c30e079de05de5072b Mon Sep 17 00:00:00 2001 From: zackverham <96081108+zackverham@users.noreply.github.com> Date: Thu, 5 Feb 2026 15:28:41 -0500 Subject: [PATCH 1/5] test: Add E2E test for last selected deployment on init Add integration test verifying that the last selected deployment is automatically loaded when the extension initializes. This is a regression test for #2459 which was fixed in #2460. Closes #2473 Co-Authored-By: Claude Opus 4.5 --- test/e2e/tests/deployments.cy.js | 51 ++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/test/e2e/tests/deployments.cy.js b/test/e2e/tests/deployments.cy.js index bc9ecf3b77..cb1d74008e 100644 --- a/test/e2e/tests/deployments.cy.js +++ b/test/e2e/tests/deployments.cy.js @@ -59,6 +59,57 @@ describe("Deployments Section", () => { ).should("exist"); }); + it("Last selected deployment loads on initialization", () => { + // Regression test for https://github.com/posit-dev/publisher/issues/2473 + // Verifies that the last selected deployment is restored after reload. + + const deploymentTitle = "last-selection-test"; + + // Ensure Publisher is in the expected initial state + cy.expectInitialPublisherState(); + + // Create a deployment (this also selects it) + cy.createPCSDeployment( + "static", + "index.html", + deploymentTitle, + (tomlFiles) => { + const { contents: config } = tomlFiles.config; + expect(config.title).to.equal(deploymentTitle); + }, + ); + + // Verify deployment is currently selected (entrypoint-label visible, not select-deployment) + cy.retryWithBackoff( + () => cy.findInPublisherWebview('[data-automation="entrypoint-label"]'), + 5, + 500, + ).should("exist"); + + // Reload the page to simulate VS Code restart + cy.reload(); + + // Re-open the Publisher sidebar + cy.getPublisherSidebarIcon().click(); + cy.waitForPublisherIframe(); + + // Verify the last selected deployment is automatically loaded + // If working correctly: entrypoint-label should show with the deployment title + // If broken: select-deployment would show with "Select..." instead + cy.retryWithBackoff( + () => cy.findInPublisherWebview('[data-automation="entrypoint-label"]'), + 10, + 1000, + ) + .should("exist") + .and("contain.text", deploymentTitle); + + // Also verify the deployment section is present + cy.findInPublisherWebview( + '[data-automation="publisher-deployment-section"]', + ).should("exist"); + }); + // Unable to run this, // as we will need to install the renv package - install.packages("renv") // as well as call renv::restore(), before we can deploy. This will use From 0fc22ce554570d57cabfacedf82d9ddd57f5f637 Mon Sep 17 00:00:00 2001 From: zackverham <96081108+zackverham@users.noreply.github.com> Date: Fri, 6 Feb 2026 11:50:05 -0500 Subject: [PATCH 2/5] test: Add explicit cleanup to last selection test Ensure the static project deployments are cleaned up after the test to prevent affecting other E2E tests. Co-Authored-By: Claude Opus 4.5 --- test/e2e/tests/deployments.cy.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/e2e/tests/deployments.cy.js b/test/e2e/tests/deployments.cy.js index cb1d74008e..526e65d362 100644 --- a/test/e2e/tests/deployments.cy.js +++ b/test/e2e/tests/deployments.cy.js @@ -108,6 +108,9 @@ describe("Deployments Section", () => { cy.findInPublisherWebview( '[data-automation="publisher-deployment-section"]', ).should("exist"); + + // Explicit cleanup to ensure other tests aren't affected + cy.clearupDeployments("static"); }); // Unable to run this, From 118154eefa085795359c50def8c423ade5b643a5 Mon Sep 17 00:00:00 2001 From: zackverham <96081108+zackverham@users.noreply.github.com> Date: Fri, 6 Feb 2026 16:36:25 -0500 Subject: [PATCH 3/5] fix: Await saveSelectionState to ensure deployment selection persists The propagateDeploymentSelection function was not awaiting the async saveSelectionState call, causing a race condition where the UI would update before the workspace state was persisted. This caused the E2E test for last selection initialization to fail because cy.reload() could happen before the selection was saved. Co-Authored-By: Claude Opus 4.5 --- extensions/vscode/src/views/homeView.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/extensions/vscode/src/views/homeView.ts b/extensions/vscode/src/views/homeView.ts index 42c97ae8b4..0c24fd0008 100644 --- a/extensions/vscode/src/views/homeView.ts +++ b/extensions/vscode/src/views/homeView.ts @@ -1004,13 +1004,13 @@ export class HomeViewProvider implements WebviewViewProvider, Disposable { } } - private propagateDeploymentSelection( + private async propagateDeploymentSelection( deploymentSelector: DeploymentSelector | null, ) { // We have to break our protocol and go ahead and write this into storage, // in case this multi-stepper is actually running ahead of the webview // being brought up. - this.saveSelectionState(deploymentSelector); + await this.saveSelectionState(deploymentSelector); // Now push down into the webview this.updateWebViewViewCredentials(); this.updateWebViewViewConfigurations(); @@ -1057,7 +1057,7 @@ export class HomeViewProvider implements WebviewViewProvider, Disposable { deploymentName: targetContentRecord.saveName, projectDir: targetContentRecord.projectDir, }; - this.propagateDeploymentSelection(deploymentSelector); + await this.propagateDeploymentSelection(deploymentSelector); const credential = this.state.findCredentialForContentRecord(targetContentRecord); @@ -1143,7 +1143,7 @@ export class HomeViewProvider implements WebviewViewProvider, Disposable { projectDir: contentRecord.projectDir, }; - this.propagateDeploymentSelection(deploymentSelector); + await this.propagateDeploymentSelection(deploymentSelector); // Credentials aren't auto-refreshed, so we have to trigger it ourselves. if (refreshCredentials) { this.refreshCredentials(); From 399abd7d8b20684ae0043a554a0b540d01f4c2a9 Mon Sep 17 00:00:00 2001 From: zackverham <96081108+zackverham@users.noreply.github.com> Date: Fri, 6 Feb 2026 18:18:47 -0500 Subject: [PATCH 4/5] revert: Remove async/await from propagateDeploymentSelection Co-Authored-By: Claude Opus 4.5 --- extensions/vscode/src/views/homeView.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/extensions/vscode/src/views/homeView.ts b/extensions/vscode/src/views/homeView.ts index 0c24fd0008..42c97ae8b4 100644 --- a/extensions/vscode/src/views/homeView.ts +++ b/extensions/vscode/src/views/homeView.ts @@ -1004,13 +1004,13 @@ export class HomeViewProvider implements WebviewViewProvider, Disposable { } } - private async propagateDeploymentSelection( + private propagateDeploymentSelection( deploymentSelector: DeploymentSelector | null, ) { // We have to break our protocol and go ahead and write this into storage, // in case this multi-stepper is actually running ahead of the webview // being brought up. - await this.saveSelectionState(deploymentSelector); + this.saveSelectionState(deploymentSelector); // Now push down into the webview this.updateWebViewViewCredentials(); this.updateWebViewViewConfigurations(); @@ -1057,7 +1057,7 @@ export class HomeViewProvider implements WebviewViewProvider, Disposable { deploymentName: targetContentRecord.saveName, projectDir: targetContentRecord.projectDir, }; - await this.propagateDeploymentSelection(deploymentSelector); + this.propagateDeploymentSelection(deploymentSelector); const credential = this.state.findCredentialForContentRecord(targetContentRecord); @@ -1143,7 +1143,7 @@ export class HomeViewProvider implements WebviewViewProvider, Disposable { projectDir: contentRecord.projectDir, }; - await this.propagateDeploymentSelection(deploymentSelector); + this.propagateDeploymentSelection(deploymentSelector); // Credentials aren't auto-refreshed, so we have to trigger it ourselves. if (refreshCredentials) { this.refreshCredentials(); From 9e7ff4621388e14b8c6209c4054b9a71dafead80 Mon Sep 17 00:00:00 2001 From: zackverham <96081108+zackverham@users.noreply.github.com> Date: Fri, 13 Feb 2026 08:31:46 -0500 Subject: [PATCH 5/5] fix(e2e): improve test reliability for last-selection and error-config tests Changes to deployments.cy.js: - Add cy.debugIframes() after reload for consistency with beforeEach - Wait for webview to fully initialize before checking for entrypoint-label - Use two-phase check: first wait for any UI element, then verify selection Changes to error-err-config.cy.js: - Add retryWithBackoff when searching for error deployments in quick-input - Use jQuery pattern to avoid Cypress timeout issues when list is loading These changes address timing issues in CI where the webview or quick-input list may not be immediately ready after page load. Co-Authored-By: Claude Opus 4.5 --- test/e2e/tests/deployments.cy.js | 34 +++++++++++++++++++++------ test/e2e/tests/error-err-config.cy.js | 32 ++++++++++++++++++------- 2 files changed, 51 insertions(+), 15 deletions(-) diff --git a/test/e2e/tests/deployments.cy.js b/test/e2e/tests/deployments.cy.js index 7af0eb4215..1ce422c681 100644 --- a/test/e2e/tests/deployments.cy.js +++ b/test/e2e/tests/deployments.cy.js @@ -82,18 +82,38 @@ describe("Deployments Section", () => { // Reload the page to simulate VS Code restart cy.reload(); - // Re-open the Publisher sidebar + // Re-open the Publisher sidebar (matching beforeEach pattern) cy.getPublisherSidebarIcon().click(); cy.waitForPublisherIframe(); + cy.debugIframes(); - // Verify the last selected deployment is automatically loaded - // If working correctly: entrypoint-label should show with the deployment title - // If broken: select-deployment would show with "Select..." instead + // Wait for the Publisher webview to fully initialize after reload. + // The extension needs time to: load saved state, restore last selection. + // Use a longer retry with more attempts to handle slow CI environments. cy.retryWithBackoff( - () => cy.findInPublisherWebview('[data-automation="entrypoint-label"]'), - 10, + () => + cy.publisherWebview().then(($webview) => { + // Check if either entrypoint-label OR select-deployment is visible + // This tells us the webview has finished initializing + const entrypoint = $webview.find( + '[data-automation="entrypoint-label"]', + ); + const selectBtn = $webview.find( + '[data-automation="select-deployment"]', + ); + if (entrypoint.length > 0 || selectBtn.length > 0) { + return cy.wrap($webview); + } + return Cypress.$(); + }), + 15, 1000, - ) + ).should("exist"); + + // Now verify the last selected deployment is automatically loaded + // If working correctly: entrypoint-label should show with the deployment title + // If broken: select-deployment would show with "Select..." instead + cy.findInPublisherWebview('[data-automation="entrypoint-label"]') .should("exist") .and("contain.text", deploymentTitle); diff --git a/test/e2e/tests/error-err-config.cy.js b/test/e2e/tests/error-err-config.cy.js index 67718dad80..3b9bf48300 100644 --- a/test/e2e/tests/error-err-config.cy.js +++ b/test/e2e/tests/error-err-config.cy.js @@ -40,10 +40,18 @@ describe("Detect errors in config", () => { cy.get(".quick-input-widget").should("be.visible"); cy.get(".quick-input-titlebar").should("have.text", "Select Deployment"); - // select our error case - cy.get(".quick-input-widget") - .contains("Unknown Title • Error in quarto-project-8G2B") - .click(); + // select our error case (retry to allow list to populate) + cy.retryWithBackoff( + () => + cy.get("body", { timeout: 0 }).then(($body) => { + const match = $body.find( + '.quick-input-widget .monaco-list-row:contains("Unknown Title • Error in quarto-project-8G2B")', + ); + return match.length > 0 ? cy.wrap(match) : Cypress.$(); + }), + 10, + 500, + ).click(); // confirm that the selector shows the error cy.findUniqueInPublisherWebview( @@ -73,10 +81,18 @@ describe("Detect errors in config", () => { cy.get(".quick-input-widget").should("be.visible"); cy.get(".quick-input-titlebar").should("have.text", "Select Deployment"); - // select our error case - cy.get(".quick-input-widget") - .contains("Unknown Title Due to Missing Config fastapi-simple-DHJL") - .click(); + // select our error case (retry to allow list to populate) + cy.retryWithBackoff( + () => + cy.get("body", { timeout: 0 }).then(($body) => { + const match = $body.find( + '.quick-input-widget .monaco-list-row:contains("Unknown Title Due to Missing Config fastapi-simple-DHJL")', + ); + return match.length > 0 ? cy.wrap(match) : Cypress.$(); + }), + 10, + 500, + ).click(); // confirm that the selector shows the error cy.findUniqueInPublisherWebview(