mirror of https://github.com/coder/coder.git
test(site): e2e: create workspace with rich parameters (#9185)
This commit is contained in:
parent
545a256b57
commit
8a1da743cc
|
@ -13,29 +13,135 @@ import {
|
|||
Provision_Complete,
|
||||
Provision_Response,
|
||||
Resource,
|
||||
RichParameter,
|
||||
} from "./provisionerGenerated"
|
||||
import { port } from "./playwright.config"
|
||||
import * as ssh from "ssh2"
|
||||
import { Duplex } from "stream"
|
||||
import { WorkspaceBuildParameter } from "api/typesGenerated"
|
||||
|
||||
// createWorkspace creates a workspace for a template.
|
||||
// It does not wait for it to be running, but it does navigate to the page.
|
||||
export const createWorkspace = async (
|
||||
page: Page,
|
||||
templateName: string,
|
||||
richParameters: RichParameter[] = [],
|
||||
buildParameters: WorkspaceBuildParameter[] = [],
|
||||
): Promise<string> => {
|
||||
await page.goto("/templates/" + templateName + "/workspace", {
|
||||
waitUntil: "networkidle",
|
||||
})
|
||||
const name = randomName()
|
||||
await page.getByLabel("name").fill(name)
|
||||
|
||||
for (const buildParameter of buildParameters) {
|
||||
const richParameter = richParameters.find(
|
||||
(richParam) => richParam.name === buildParameter.name,
|
||||
)
|
||||
if (!richParameter) {
|
||||
throw new Error(
|
||||
"build parameter is expected to be present in rich parameter schema",
|
||||
)
|
||||
}
|
||||
|
||||
const parameterLabel = await page.waitForSelector(
|
||||
"[data-testid='parameter-field-" + richParameter.name + "']",
|
||||
{ state: "visible" },
|
||||
)
|
||||
|
||||
if (richParameter.type === "bool") {
|
||||
const parameterField = await parameterLabel.waitForSelector(
|
||||
"[data-testid='parameter-field-bool'] .MuiRadio-root input[value='" +
|
||||
buildParameter.value +
|
||||
"']",
|
||||
)
|
||||
await parameterField.check()
|
||||
} else if (richParameter.options.length > 0) {
|
||||
const parameterField = await parameterLabel.waitForSelector(
|
||||
"[data-testid='parameter-field-options'] .MuiRadio-root input[value='" +
|
||||
buildParameter.value +
|
||||
"']",
|
||||
)
|
||||
await parameterField.check()
|
||||
} else if (richParameter.type === "list(string)") {
|
||||
throw new Error("not implemented yet") // FIXME
|
||||
} else {
|
||||
// text or number
|
||||
const parameterField = await parameterLabel.waitForSelector(
|
||||
"[data-testid='parameter-field-text'] input",
|
||||
)
|
||||
await parameterField.fill(buildParameter.value)
|
||||
}
|
||||
}
|
||||
|
||||
await page.getByTestId("form-submit").click()
|
||||
|
||||
await expect(page).toHaveURL("/@admin/" + name)
|
||||
await page.getByTestId("build-status").isVisible()
|
||||
await page.waitForSelector("[data-testid='build-status']", {
|
||||
state: "visible",
|
||||
})
|
||||
return name
|
||||
}
|
||||
|
||||
export const verifyParameters = async (
|
||||
page: Page,
|
||||
workspaceName: string,
|
||||
richParameters: RichParameter[],
|
||||
expectedBuildParameters: WorkspaceBuildParameter[],
|
||||
) => {
|
||||
await page.goto("/@admin/" + workspaceName + "/settings/parameters", {
|
||||
waitUntil: "networkidle",
|
||||
})
|
||||
await expect(page).toHaveURL(
|
||||
"/@admin/" + workspaceName + "/settings/parameters",
|
||||
)
|
||||
|
||||
for (const buildParameter of expectedBuildParameters) {
|
||||
const richParameter = richParameters.find(
|
||||
(richParam) => richParam.name === buildParameter.name,
|
||||
)
|
||||
if (!richParameter) {
|
||||
throw new Error(
|
||||
"build parameter is expected to be present in rich parameter schema",
|
||||
)
|
||||
}
|
||||
|
||||
const parameterLabel = await page.waitForSelector(
|
||||
"[data-testid='parameter-field-" + richParameter.name + "']",
|
||||
{ state: "visible" },
|
||||
)
|
||||
|
||||
const muiDisabled = richParameter.mutable ? "" : ".Mui-disabled"
|
||||
|
||||
if (richParameter.type === "bool") {
|
||||
const parameterField = await parameterLabel.waitForSelector(
|
||||
"[data-testid='parameter-field-bool'] .MuiRadio-root.Mui-checked" +
|
||||
muiDisabled +
|
||||
" input",
|
||||
)
|
||||
const value = await parameterField.inputValue()
|
||||
expect(value).toEqual(buildParameter.value)
|
||||
} else if (richParameter.options.length > 0) {
|
||||
const parameterField = await parameterLabel.waitForSelector(
|
||||
"[data-testid='parameter-field-options'] .MuiRadio-root.Mui-checked" +
|
||||
muiDisabled +
|
||||
" input",
|
||||
)
|
||||
const value = await parameterField.inputValue()
|
||||
expect(value).toEqual(buildParameter.value)
|
||||
} else if (richParameter.type === "list(string)") {
|
||||
throw new Error("not implemented yet") // FIXME
|
||||
} else {
|
||||
// text or number
|
||||
const parameterField = await parameterLabel.waitForSelector(
|
||||
"[data-testid='parameter-field-text'] input" + muiDisabled,
|
||||
)
|
||||
const value = await parameterField.inputValue()
|
||||
expect(value).toEqual(buildParameter.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// createTemplate navigates to the /templates/new page and uploads a template
|
||||
// with the resources provided in the responses argument.
|
||||
export const createTemplate = async (
|
||||
|
@ -401,3 +507,28 @@ const findSessionToken = async (page: Page): Promise<string> => {
|
|||
}
|
||||
return sessionCookie.value
|
||||
}
|
||||
|
||||
export const echoResponsesWithParameters = (
|
||||
richParameters: RichParameter[],
|
||||
): EchoProvisionerResponses => {
|
||||
return {
|
||||
plan: [
|
||||
{
|
||||
complete: {
|
||||
parameters: richParameters,
|
||||
},
|
||||
},
|
||||
],
|
||||
apply: [
|
||||
{
|
||||
complete: {
|
||||
resources: [
|
||||
{
|
||||
name: "example",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,138 @@
|
|||
import { RichParameter } from "./provisionerGenerated"
|
||||
|
||||
// Rich parameters
|
||||
|
||||
const emptyParameter: RichParameter = {
|
||||
name: "",
|
||||
description: "",
|
||||
type: "",
|
||||
mutable: false,
|
||||
defaultValue: "",
|
||||
icon: "",
|
||||
options: [],
|
||||
validationRegex: "",
|
||||
validationError: "",
|
||||
validationMin: undefined,
|
||||
validationMax: undefined,
|
||||
validationMonotonic: "",
|
||||
required: false,
|
||||
displayName: "",
|
||||
order: 0,
|
||||
ephemeral: false,
|
||||
}
|
||||
|
||||
// firstParameter is mutable string with a default value (parameter value not required).
|
||||
export const firstParameter: RichParameter = {
|
||||
...emptyParameter,
|
||||
|
||||
name: "first_parameter",
|
||||
displayName: "First parameter",
|
||||
type: "number",
|
||||
options: [],
|
||||
description: "This is first parameter.",
|
||||
icon: "/emojis/1f310.png",
|
||||
defaultValue: "123",
|
||||
mutable: true,
|
||||
order: 1,
|
||||
}
|
||||
|
||||
// secondParameter is immutable string with a default value (parameter value not required).
|
||||
export const secondParameter: RichParameter = {
|
||||
...emptyParameter,
|
||||
|
||||
name: "second_parameter",
|
||||
displayName: "Second parameter",
|
||||
type: "string",
|
||||
options: [],
|
||||
description: "This is second parameter.",
|
||||
defaultValue: "abc",
|
||||
icon: "",
|
||||
order: 2,
|
||||
}
|
||||
|
||||
// thirdParameter is mutable string with an empty default value (parameter value not required).
|
||||
export const thirdParameter: RichParameter = {
|
||||
...emptyParameter,
|
||||
|
||||
name: "third_parameter",
|
||||
type: "string",
|
||||
options: [],
|
||||
description: "This is third parameter.",
|
||||
defaultValue: "",
|
||||
mutable: true,
|
||||
order: 3,
|
||||
}
|
||||
|
||||
// fourthParameter is immutable boolean with a default "true" value (parameter value not required).
|
||||
export const fourthParameter: RichParameter = {
|
||||
...emptyParameter,
|
||||
|
||||
name: "fourth_parameter",
|
||||
type: "bool",
|
||||
options: [],
|
||||
description: "This is fourth parameter.",
|
||||
defaultValue: "true",
|
||||
icon: "",
|
||||
order: 3,
|
||||
}
|
||||
|
||||
// fifthParameter is immutable "string with options", with a default option selected (parameter value not required).
|
||||
export const fifthParameter: RichParameter = {
|
||||
...emptyParameter,
|
||||
|
||||
name: "fifth_parameter",
|
||||
displayName: "Fifth parameter",
|
||||
type: "string",
|
||||
options: [
|
||||
{
|
||||
name: "ABC",
|
||||
description: "This is ABC",
|
||||
value: "abc",
|
||||
icon: "",
|
||||
},
|
||||
{
|
||||
name: "DEF",
|
||||
description: "This is DEF",
|
||||
value: "def",
|
||||
icon: "",
|
||||
},
|
||||
{
|
||||
name: "GHI",
|
||||
description: "This is GHI",
|
||||
value: "ghi",
|
||||
icon: "",
|
||||
},
|
||||
],
|
||||
description: "This is fifth parameter.",
|
||||
defaultValue: "def",
|
||||
icon: "",
|
||||
order: 3,
|
||||
}
|
||||
|
||||
// sixthParameter is mutable string without a default value (parameter value is required).
|
||||
export const sixthParameter: RichParameter = {
|
||||
...emptyParameter,
|
||||
|
||||
name: "sixth_parameter",
|
||||
displayName: "Sixth parameter",
|
||||
type: "number",
|
||||
options: [],
|
||||
description: "This is sixth parameter.",
|
||||
icon: "/emojis/1f310.png",
|
||||
required: true,
|
||||
mutable: true,
|
||||
order: 1,
|
||||
}
|
||||
|
||||
// seventhParameter is immutable string without a default value (parameter value is required).
|
||||
export const seventhParameter: RichParameter = {
|
||||
...emptyParameter,
|
||||
|
||||
name: "seventh_parameter",
|
||||
displayName: "Seventh parameter",
|
||||
type: "string",
|
||||
options: [],
|
||||
description: "This is seventh parameter.",
|
||||
required: true,
|
||||
order: 1,
|
||||
}
|
|
@ -1,5 +1,21 @@
|
|||
import { test } from "@playwright/test"
|
||||
import { createTemplate, createWorkspace } from "../helpers"
|
||||
import {
|
||||
createTemplate,
|
||||
createWorkspace,
|
||||
echoResponsesWithParameters,
|
||||
verifyParameters,
|
||||
} from "../helpers"
|
||||
|
||||
import {
|
||||
secondParameter,
|
||||
fourthParameter,
|
||||
fifthParameter,
|
||||
firstParameter,
|
||||
thirdParameter,
|
||||
seventhParameter,
|
||||
sixthParameter,
|
||||
} from "../parameters"
|
||||
import { RichParameter } from "../provisionerGenerated"
|
||||
|
||||
test("create workspace", async ({ page }) => {
|
||||
const template = await createTemplate(page, {
|
||||
|
@ -17,3 +33,85 @@ test("create workspace", async ({ page }) => {
|
|||
})
|
||||
await createWorkspace(page, template)
|
||||
})
|
||||
|
||||
test("create workspace with default immutable parameters", async ({ page }) => {
|
||||
const richParameters: RichParameter[] = [
|
||||
secondParameter,
|
||||
fourthParameter,
|
||||
fifthParameter,
|
||||
]
|
||||
const template = await createTemplate(
|
||||
page,
|
||||
echoResponsesWithParameters(richParameters),
|
||||
)
|
||||
const workspaceName = await createWorkspace(page, template)
|
||||
await verifyParameters(page, workspaceName, richParameters, [
|
||||
{ name: secondParameter.name, value: secondParameter.defaultValue },
|
||||
{ name: fourthParameter.name, value: fourthParameter.defaultValue },
|
||||
{ name: fifthParameter.name, value: fifthParameter.defaultValue },
|
||||
])
|
||||
})
|
||||
|
||||
test("create workspace with default mutable parameters", async ({ page }) => {
|
||||
const richParameters: RichParameter[] = [firstParameter, thirdParameter]
|
||||
const template = await createTemplate(
|
||||
page,
|
||||
echoResponsesWithParameters(richParameters),
|
||||
)
|
||||
const workspaceName = await createWorkspace(page, template)
|
||||
await verifyParameters(page, workspaceName, richParameters, [
|
||||
{ name: firstParameter.name, value: firstParameter.defaultValue },
|
||||
{ name: thirdParameter.name, value: thirdParameter.defaultValue },
|
||||
])
|
||||
})
|
||||
|
||||
test("create workspace with default and required parameters", async ({
|
||||
page,
|
||||
}) => {
|
||||
const richParameters: RichParameter[] = [
|
||||
secondParameter,
|
||||
fourthParameter,
|
||||
sixthParameter,
|
||||
seventhParameter,
|
||||
]
|
||||
const buildParameters = [
|
||||
{ name: sixthParameter.name, value: "12345" },
|
||||
{ name: seventhParameter.name, value: "abcdef" },
|
||||
]
|
||||
const template = await createTemplate(
|
||||
page,
|
||||
echoResponsesWithParameters(richParameters),
|
||||
)
|
||||
const workspaceName = await createWorkspace(
|
||||
page,
|
||||
template,
|
||||
richParameters,
|
||||
buildParameters,
|
||||
)
|
||||
await verifyParameters(page, workspaceName, richParameters, [
|
||||
// user values:
|
||||
...buildParameters,
|
||||
// default values:
|
||||
{ name: secondParameter.name, value: secondParameter.defaultValue },
|
||||
{ name: fourthParameter.name, value: fourthParameter.defaultValue },
|
||||
])
|
||||
})
|
||||
|
||||
test("create workspace and overwrite default parameters", async ({ page }) => {
|
||||
const richParameters: RichParameter[] = [secondParameter, fourthParameter]
|
||||
const buildParameters = [
|
||||
{ name: secondParameter.name, value: "AAAAA" },
|
||||
{ name: fourthParameter.name, value: "false" },
|
||||
]
|
||||
const template = await createTemplate(
|
||||
page,
|
||||
echoResponsesWithParameters(richParameters),
|
||||
)
|
||||
const workspaceName = await createWorkspace(
|
||||
page,
|
||||
template,
|
||||
richParameters,
|
||||
buildParameters,
|
||||
)
|
||||
await verifyParameters(page, workspaceName, richParameters, buildParameters)
|
||||
})
|
||||
|
|
|
@ -84,6 +84,7 @@ export const RichParameterInput: FC<RichParameterInputProps> = ({
|
|||
direction="column"
|
||||
spacing={size === "small" ? 1.25 : 2}
|
||||
className={size}
|
||||
data-testid={`parameter-field-${parameter.name}`}
|
||||
>
|
||||
<ParameterLabel id={fieldProps.id} parameter={parameter} />
|
||||
<Box sx={{ display: "flex", flexDirection: "column" }}>
|
||||
|
@ -114,6 +115,7 @@ const RichParameterField: React.FC<RichParameterInputProps> = ({
|
|||
if (isBoolean(parameter)) {
|
||||
return (
|
||||
<RadioGroup
|
||||
data-testid="parameter-field-bool"
|
||||
className={styles.radioGroup}
|
||||
defaultValue={parameterValue}
|
||||
onChange={(event) => {
|
||||
|
@ -139,6 +141,7 @@ const RichParameterField: React.FC<RichParameterInputProps> = ({
|
|||
if (parameter.options.length > 0) {
|
||||
return (
|
||||
<RadioGroup
|
||||
data-testid="parameter-field-options"
|
||||
className={styles.radioGroup}
|
||||
defaultValue={parameterValue}
|
||||
onChange={(event) => {
|
||||
|
@ -185,6 +188,7 @@ const RichParameterField: React.FC<RichParameterInputProps> = ({
|
|||
|
||||
return (
|
||||
<MultiTextField
|
||||
data-testid="parameter-field-list-of-string"
|
||||
label={props.label as string}
|
||||
values={values}
|
||||
onChange={(values) => {
|
||||
|
@ -206,6 +210,7 @@ const RichParameterField: React.FC<RichParameterInputProps> = ({
|
|||
return (
|
||||
<TextField
|
||||
{...props}
|
||||
data-testid="parameter-field-text"
|
||||
className={styles.textField}
|
||||
type={parameter.type}
|
||||
disabled={disabled}
|
||||
|
|
Loading…
Reference in New Issue