diff --git a/site/e2e/helpers.ts b/site/e2e/helpers.ts
index 756c307ff59c9..f655b14c192e3 100644
--- a/site/e2e/helpers.ts
+++ b/site/e2e/helpers.ts
@@ -31,6 +31,8 @@ export const createWorkspace = async (
await page.goto("/templates/" + templateName + "/workspace", {
waitUntil: "networkidle",
})
+ await expect(page).toHaveURL("/templates/" + templateName + "/workspace")
+
const name = randomName()
await page.getByLabel("name").fill(name)
@@ -117,7 +119,10 @@ export const createTemplate = async (
await page.addInitScript({
content: "window.playwright = true",
})
+
await page.goto("/templates/new", { waitUntil: "networkidle" })
+ await expect(page).toHaveURL("/templates/new")
+
await page.getByTestId("file-upload").setInputFiles({
buffer: await createTemplateVersionTar(responses),
mimeType: "application/x-tar",
@@ -616,3 +621,99 @@ export const fillParameters = async (
}
}
}
+
+export const updateTemplate = async (
+ page: Page,
+ templateName: string,
+ responses?: EchoProvisionerResponses,
+) => {
+ const tarball = await createTemplateVersionTar(responses)
+
+ const sessionToken = await findSessionToken(page)
+ const child = spawn(
+ "go",
+ [
+ "run",
+ coderMainPath(),
+ "templates",
+ "push",
+ "--test.provisioner",
+ "echo",
+ "-y",
+ "-d",
+ "-",
+ templateName,
+ ],
+ {
+ env: {
+ ...process.env,
+ CODER_SESSION_TOKEN: sessionToken,
+ CODER_URL: "http://localhost:3000",
+ },
+ },
+ )
+
+ const uploaded = new Awaiter()
+ child.on("exit", (code) => {
+ if (code === 0) {
+ uploaded.done()
+ return
+ }
+
+ throw new Error(`coder templates push failed with code ${code}`)
+ })
+
+ child.stdin.write(tarball)
+ child.stdin.end()
+
+ await uploaded.wait()
+}
+
+export const updateWorkspace = async (
+ page: Page,
+ workspaceName: string,
+ richParameters: RichParameter[] = [],
+ buildParameters: WorkspaceBuildParameter[] = [],
+) => {
+ await page.goto("/@admin/" + workspaceName, {
+ waitUntil: "domcontentloaded",
+ })
+ await expect(page).toHaveURL("/@admin/" + workspaceName)
+
+ await page.getByTestId("workspace-update-button").click()
+ await page.getByTestId("confirm-button").click()
+
+ await fillParameters(page, richParameters, buildParameters)
+ await page.getByTestId("form-submit").click()
+
+ await page.waitForSelector(
+ "span[data-testid='build-status'] >> text=Running",
+ {
+ state: "visible",
+ },
+ )
+}
+
+export const updateWorkspaceParameters = async (
+ page: Page,
+ workspaceName: string,
+ richParameters: RichParameter[] = [],
+ buildParameters: WorkspaceBuildParameter[] = [],
+) => {
+ await page.goto("/@admin/" + workspaceName + "/settings/parameters", {
+ waitUntil: "domcontentloaded",
+ })
+ await expect(page).toHaveURL(
+ "/@admin/" + workspaceName + "/settings/parameters",
+ )
+
+ await fillParameters(page, richParameters, buildParameters)
+ await page.getByTestId("form-submit").click()
+
+ await page.waitForSelector(
+ "span[data-testid='build-status'] >> text=Running",
+ {
+ state: "visible",
+ },
+ )
+}
diff --git a/site/e2e/playwright.config.ts b/site/e2e/playwright.config.ts
index 78eb4495ce371..b25393e529e4a 100644
--- a/site/e2e/playwright.config.ts
+++ b/site/e2e/playwright.config.ts
@@ -41,6 +41,7 @@ export default defineConfig({
`--access-url=http://localhost:${port} ` +
`--http-address=localhost:${port} ` +
`--in-memory --telemetry=false ` +
+ `--dangerous-disable-rate-limits ` +
`--provisioner-daemons 10 ` +
`--provisioner-daemons-echo ` +
`--provisioner-daemon-poll-interval 50ms`,
diff --git a/site/e2e/tests/createWorkspace.spec.ts b/site/e2e/tests/createWorkspace.spec.ts
index 1effc01976651..8a4937ee019ea 100644
--- a/site/e2e/tests/createWorkspace.spec.ts
+++ b/site/e2e/tests/createWorkspace.spec.ts
@@ -1,4 +1,4 @@
-import { test } from "@playwright/test"
+import { test, Page } from "@playwright/test"
import {
createTemplate,
createWorkspace,
@@ -17,6 +17,11 @@ import {
} from "../parameters"
import { RichParameter } from "../provisionerGenerated"
+test.beforeEach(async ({ page }: { page: Page }) => {
+ // eslint-disable-next-line no-console -- For debugging purposes
+ page.on("console", (msg) => console.log("Console: " + msg.text()))
+})
+
test("create workspace", async ({ page }) => {
const template = await createTemplate(page, {
apply: [
@@ -107,6 +112,7 @@ test("create workspace and overwrite default parameters", async ({ page }) => {
page,
echoResponsesWithParameters(richParameters),
)
+
const workspaceName = await createWorkspace(
page,
template,
diff --git a/site/e2e/tests/updateWorkspace.spec.ts b/site/e2e/tests/updateWorkspace.spec.ts
new file mode 100644
index 0000000000000..ef84f19ba4ee0
--- /dev/null
+++ b/site/e2e/tests/updateWorkspace.spec.ts
@@ -0,0 +1,136 @@
+import { test, Page } from "@playwright/test"
+
+import {
+ createTemplate,
+ createWorkspace,
+ echoResponsesWithParameters,
+ updateTemplate,
+ updateWorkspace,
+ updateWorkspaceParameters,
+ verifyParameters,
+} from "../helpers"
+
+import {
+ fifthParameter,
+ firstParameter,
+ secondParameter,
+ sixthParameter,
+ secondBuildOption,
+} from "../parameters"
+import { RichParameter } from "../provisionerGenerated"
+
+test.beforeEach(async ({ page }: { page: Page }) => {
+ // eslint-disable-next-line no-console -- For debugging purposes
+ page.on("console", (msg) => console.log("Console: " + msg.text()))
+})
+
+test("update workspace, new optional, immutable parameter added", async ({
+ page,
+}) => {
+ const richParameters: RichParameter[] = [firstParameter, secondParameter]
+ const template = await createTemplate(
+ page,
+ echoResponsesWithParameters(richParameters),
+ )
+
+ const workspaceName = await createWorkspace(page, template)
+
+ // Verify that parameter values are default.
+ await verifyParameters(page, workspaceName, richParameters, [
+ { name: firstParameter.name, value: firstParameter.defaultValue },
+ { name: secondParameter.name, value: secondParameter.defaultValue },
+ ])
+
+ // Push updated template.
+ const updatedRichParameters = [...richParameters, fifthParameter]
+ await updateTemplate(
+ page,
+ template,
+ echoResponsesWithParameters(updatedRichParameters),
+ )
+
+ // Now, update the workspace, and select the value for immutable parameter.
+ await updateWorkspace(page, workspaceName, updatedRichParameters, [
+ { name: fifthParameter.name, value: fifthParameter.options[0].value },
+ ])
+
+ // Verify parameter values.
+ await verifyParameters(page, workspaceName, updatedRichParameters, [
+ { name: firstParameter.name, value: firstParameter.defaultValue },
+ { name: secondParameter.name, value: secondParameter.defaultValue },
+ { name: fifthParameter.name, value: fifthParameter.options[0].value },
+ ])
+})
+
+test("update workspace, new required, mutable parameter added", async ({
+ page,
+}) => {
+ const richParameters: RichParameter[] = [firstParameter, secondParameter]
+ const template = await createTemplate(
+ page,
+ echoResponsesWithParameters(richParameters),
+ )
+
+ const workspaceName = await createWorkspace(page, template)
+
+ // Verify that parameter values are default.
+ await verifyParameters(page, workspaceName, richParameters, [
+ { name: firstParameter.name, value: firstParameter.defaultValue },
+ { name: secondParameter.name, value: secondParameter.defaultValue },
+ ])
+
+ // Push updated template.
+ const updatedRichParameters = [...richParameters, sixthParameter]
+ await updateTemplate(
+ page,
+ template,
+ echoResponsesWithParameters(updatedRichParameters),
+ )
+
+ // Now, update the workspace, and provide the parameter value.
+ const buildParameters = [{ name: sixthParameter.name, value: "99" }]
+ await updateWorkspace(
+ page,
+ workspaceName,
+ updatedRichParameters,
+ buildParameters,
+ )
+
+ // Verify parameter values.
+ await verifyParameters(page, workspaceName, updatedRichParameters, [
+ { name: firstParameter.name, value: firstParameter.defaultValue },
+ { name: secondParameter.name, value: secondParameter.defaultValue },
+ ...buildParameters,
+ ])
+})
+
+test("update workspace with ephemeral parameter enabled", async ({ page }) => {
+ const richParameters: RichParameter[] = [firstParameter, secondBuildOption]
+ const template = await createTemplate(
+ page,
+ echoResponsesWithParameters(richParameters),
+ )
+
+ const workspaceName = await createWorkspace(page, template)
+
+ // Verify that parameter values are default.
+ await verifyParameters(page, workspaceName, richParameters, [
+ { name: firstParameter.name, value: firstParameter.defaultValue },
+ { name: secondBuildOption.name, value: secondBuildOption.defaultValue },
+ ])
+
+ // Now, update the workspace, and select the value for ephemeral parameter.
+ const buildParameters = [{ name: secondBuildOption.name, value: "true" }]
+ await updateWorkspaceParameters(
+ page,
+ workspaceName,
+ richParameters,
+ buildParameters,
+ )
+
+ // Verify that parameter values are default.
+ await verifyParameters(page, workspaceName, richParameters, [
+ { name: firstParameter.name, value: firstParameter.defaultValue },
+ { name: secondBuildOption.name, value: secondBuildOption.defaultValue },
+ ])
+})
diff --git a/site/src/pages/WorkspacePage/UpdateBuildParametersDialog.tsx b/site/src/pages/WorkspacePage/UpdateBuildParametersDialog.tsx
index 860969267fb7e..2293fb530ffb3 100644
--- a/site/src/pages/WorkspacePage/UpdateBuildParametersDialog.tsx
+++ b/site/src/pages/WorkspacePage/UpdateBuildParametersDialog.tsx
@@ -111,7 +111,13 @@ export const UpdateBuildParametersDialog: FC<
-