Merge pull request #371 from mfrances17/add-kerberos

User fed: Add Kerberos provider and tests
This commit is contained in:
mfrances17 2021-02-17 14:22:37 -05:00 committed by GitHub
commit 473d7840c6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 426 additions and 35 deletions

View file

@ -58,6 +58,7 @@ export const KeycloakCard = ({
<CardActions> <CardActions>
{dropdownItems && ( {dropdownItems && (
<Dropdown <Dropdown
data-cy={`${title}-dropdown`}
isPlain isPlain
position={"right"} position={"right"}
toggle={<KebabToggle onToggle={onDropdownToggle} />} toggle={<KebabToggle onToggle={onDropdownToggle} />}
@ -67,7 +68,7 @@ export const KeycloakCard = ({
/> />
)} )}
</CardActions> </CardActions>
<CardTitle>{title}</CardTitle> <CardTitle data-cy="keycloak-card-title">{title}</CardTitle>
</CardHeader> </CardHeader>
<CardBody /> <CardBody />
<CardFooter> <CardFooter>

View file

@ -118,6 +118,7 @@ export const ViewHeader = ({
} }
isOpen={isDropdownOpen} isOpen={isDropdownOpen}
dropdownItems={dropdownItems} dropdownItems={dropdownItems}
data-cy="action-dropdown"
/> />
</ToolbarItem> </ToolbarItem>
)} )}

View file

@ -220,12 +220,24 @@ export const routes: RoutesFn = (t: TFunction) => [
breadcrumb: t("common:settings"), breadcrumb: t("common:settings"),
access: "view-realm", access: "view-realm",
}, },
{
path: "/:realm/user-federation/kerberos/new",
component: UserFederationKerberosSettings,
breadcrumb: t("common:settings"),
access: "view-realm",
},
{ {
path: "/:realm/user-federation/ldap/:id", path: "/:realm/user-federation/ldap/:id",
component: UserFederationLdapSettings, component: UserFederationLdapSettings,
breadcrumb: t("common:settings"), breadcrumb: t("common:settings"),
access: "view-realm", access: "view-realm",
}, },
{
path: "/:realm/user-federation/ldap/new",
component: UserFederationLdapSettings,
breadcrumb: t("common:settings"),
access: "view-realm",
},
{ {
path: "/:realm/", path: "/:realm/",
component: DashboardSection, component: DashboardSection,

View file

@ -37,6 +37,7 @@ const KerberosSettingsHeader = ({
toggleDeleteDialog, toggleDeleteDialog,
}: KerberosSettingsHeaderProps) => { }: KerberosSettingsHeaderProps) => {
const { t } = useTranslation("user-federation"); const { t } = useTranslation("user-federation");
const { id } = useParams<{ id: string }>();
const [toggleDisableDialog, DisableConfirm] = useConfirmDialog({ const [toggleDisableDialog, DisableConfirm] = useConfirmDialog({
titleKey: "user-federation:userFedDisableConfirmTitle", titleKey: "user-federation:userFedDisableConfirmTitle",
messageKey: "user-federation:userFedDisableConfirm", messageKey: "user-federation:userFedDisableConfirm",
@ -49,11 +50,18 @@ const KerberosSettingsHeader = ({
return ( return (
<> <>
<DisableConfirm /> <DisableConfirm />
{id === "new" ? (
<ViewHeader titleKey="Kerberos" subKey="" />
) : (
<ViewHeader <ViewHeader
titleKey="Kerberos" titleKey="Kerberos"
subKey="" subKey=""
dropdownItems={[ dropdownItems={[
<DropdownItem key="delete" onClick={() => toggleDeleteDialog()}> <DropdownItem
key="delete"
onClick={() => toggleDeleteDialog()}
data-cy="delete-provider-cmd"
>
{t("deleteProvider")} {t("deleteProvider")}
</DropdownItem>, </DropdownItem>,
]} ]}
@ -67,6 +75,7 @@ const KerberosSettingsHeader = ({
} }
}} }}
/> />
)}
</> </>
); );
}; };
@ -106,11 +115,23 @@ export const UserFederationKerberosSettings = () => {
const save = async (component: ComponentRepresentation) => { const save = async (component: ComponentRepresentation) => {
try { try {
if (id) {
if (id === "new") {
await adminClient.components.create(component);
} else {
await adminClient.components.update({ id }, component); await adminClient.components.update({ id }, component);
}
}
setupForm(component as ComponentRepresentation); setupForm(component as ComponentRepresentation);
addAlert(t("saveSuccess"), AlertVariant.success); addAlert(
t(id === "new" ? "createSuccess" : "saveSuccess"),
AlertVariant.success
);
} catch (error) { } catch (error) {
addAlert(`${t("saveError")} '${error}'`, AlertVariant.danger); addAlert(
`${t(id === "new" ? "createError" : "saveError")} '${error}'`,
AlertVariant.danger
);
} }
}; };
@ -135,7 +156,7 @@ export const UserFederationKerberosSettings = () => {
<DeleteConfirm /> <DeleteConfirm />
<Controller <Controller
name="config.enabled[0]" name="config.enabled[0]"
defaultValue={["true"]} defaultValue={["true"][0]}
control={form.control} control={form.control}
render={({ onChange, value }) => ( render={({ onChange, value }) => (
<KerberosSettingsHeader <KerberosSettingsHeader
@ -153,12 +174,13 @@ export const UserFederationKerberosSettings = () => {
<SettingsCache form={form} showSectionHeading /> <SettingsCache form={form} showSectionHeading />
<Form onSubmit={form.handleSubmit(save)}> <Form onSubmit={form.handleSubmit(save)}>
<ActionGroup> <ActionGroup>
<Button variant="primary" type="submit"> <Button variant="primary" type="submit" data-cy="kerberos-save">
{t("common:save")} {t("common:save")}
</Button> </Button>
<Button <Button
variant="link" variant="link"
onClick={() => history.push(`/${realm}/user-federation`)} onClick={() => history.push(`/${realm}/user-federation`)}
data-cy="kerberos-cancel"
> >
{t("common:cancel")} {t("common:cancel")}
</Button> </Button>

View file

@ -26,6 +26,8 @@ import { useAdminClient, asyncStateFetch } from "../context/auth/AdminClient";
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog"; import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
import "./user-federation.css"; import "./user-federation.css";
import { useHistory, useRouteMatch } from "react-router-dom";
export const UserFederationSection = () => { export const UserFederationSection = () => {
const [userFederations, setUserFederations] = useState< const [userFederations, setUserFederations] = useState<
ComponentRepresentation[] ComponentRepresentation[]
@ -37,12 +39,15 @@ export const UserFederationSection = () => {
const [key, setKey] = useState(0); const [key, setKey] = useState(0);
const refresh = () => setKey(new Date().getTime()); const refresh = () => setKey(new Date().getTime());
const { url } = useRouteMatch();
const history = useHistory();
useEffect(() => { useEffect(() => {
return asyncStateFetch( return asyncStateFetch(
() => { () => {
const testParams: { [name: string]: string | number } = { const testParams: { [name: string]: string | number } = {
parentId: realm, parentId: realm,
type: "org.keycloak.storage.UserStorageProvider", // MF note that this is providerType in the output, but API call is still type type: "org.keycloak.storage.UserStorageProvider",
}; };
return adminClient.components.find(testParams); return adminClient.components.find(testParams);
}, },
@ -53,8 +58,18 @@ export const UserFederationSection = () => {
}, [key]); }, [key]);
const ufAddProviderDropdownItems = [ const ufAddProviderDropdownItems = [
<DropdownItem key="itemLDAP">LDAP</DropdownItem>, <DropdownItem
<DropdownItem key="itemKerberos">Kerberos</DropdownItem>, key="itemLDAP"
onClick={() => history.push(`${url}/ldap/new`)}
>
LDAP
</DropdownItem>,
<DropdownItem
key="itemKerberos"
onClick={() => history.push(`${url}/kerberos/new`)}
>
Kerberos
</DropdownItem>,
]; ];
const learnMoreLinkProps = { const learnMoreLinkProps = {
@ -95,6 +110,7 @@ export const UserFederationSection = () => {
onClick={() => { onClick={() => {
toggleDeleteForCard(userFederation.id!); toggleDeleteForCard(userFederation.id!);
}} }}
data-cy="card-delete"
> >
{t("common:delete")} {t("common:delete")}
</DropdownItem>, </DropdownItem>,
@ -157,7 +173,11 @@ export const UserFederationSection = () => {
</TextContent> </TextContent>
<hr className="pf-u-mb-lg" /> <hr className="pf-u-mb-lg" />
<Gallery hasGutter> <Gallery hasGutter>
<Card isHoverable> <Card
isHoverable
onClick={() => history.push(`${url}/kerberos/new`)}
data-cy="kerberos-card"
>
<CardTitle> <CardTitle>
<Split hasGutter> <Split hasGutter>
<SplitItem> <SplitItem>

View file

@ -11,6 +11,7 @@ import { useTranslation } from "react-i18next";
import { UseFormMethods, Controller, useWatch } from "react-hook-form"; import { UseFormMethods, Controller, useWatch } from "react-hook-form";
import { FormAccess } from "../../components/form-access/FormAccess"; import { FormAccess } from "../../components/form-access/FormAccess";
import { useRealm } from "../../context/realm-context/RealmContext";
import { HelpItem } from "../../components/help-enabler/HelpItem"; import { HelpItem } from "../../components/help-enabler/HelpItem";
import _ from "lodash"; import _ from "lodash";
@ -29,6 +30,7 @@ export const KerberosSettingsRequired = ({
}: KerberosSettingsRequiredProps) => { }: KerberosSettingsRequiredProps) => {
const { t } = useTranslation("user-federation"); const { t } = useTranslation("user-federation");
const helpText = useTranslation("user-federation-help").t; const helpText = useTranslation("user-federation-help").t;
const { realm } = useRealm();
const [isEditModeDropdownOpen, setIsEditModeDropdownOpen] = useState(false); const [isEditModeDropdownOpen, setIsEditModeDropdownOpen] = useState(false);
@ -62,18 +64,19 @@ export const KerberosSettingsRequired = ({
isRequired isRequired
> >
{/* These hidden fields are required so data object written back matches data retrieved */} {/* These hidden fields are required so data object written back matches data retrieved */}
<TextInput {/* <TextInput
hidden hidden
type="text" type="text"
id="kc-console-id" id="kc-console-id"
name="id" name="id"
ref={form.register} ref={form.register}
/> /> */}
<TextInput <TextInput
hidden hidden
type="text" type="text"
id="kc-console-providerId" id="kc-console-providerId"
name="providerId" name="providerId"
defaultValue="kerberos"
ref={form.register} ref={form.register}
/> />
<TextInput <TextInput
@ -81,6 +84,7 @@ export const KerberosSettingsRequired = ({
type="text" type="text"
id="kc-console-providerType" id="kc-console-providerType"
name="providerType" name="providerType"
defaultValue="org.keycloak.storage.UserStorageProvider"
ref={form.register} ref={form.register}
/> />
<TextInput <TextInput
@ -88,6 +92,7 @@ export const KerberosSettingsRequired = ({
type="text" type="text"
id="kc-console-parentId" id="kc-console-parentId"
name="parentId" name="parentId"
defaultValue={realm}
ref={form.register} ref={form.register}
/> />
@ -102,6 +107,7 @@ export const KerberosSettingsRequired = ({
message: `${t("validateName")}`, message: `${t("validateName")}`,
}, },
})} })}
data-cy="kerberos-name"
/> />
{form.errors.name && ( {form.errors.name && (
<div className="error">{form.errors.name.message}</div> <div className="error">{form.errors.name.message}</div>
@ -131,6 +137,7 @@ export const KerberosSettingsRequired = ({
message: `${t("validateRealm")}`, message: `${t("validateRealm")}`,
}, },
})} })}
data-cy="kerberos-realm"
/> />
{form.errors.config && {form.errors.config &&
form.errors.config.kerberosRealm && form.errors.config.kerberosRealm &&
@ -164,6 +171,7 @@ export const KerberosSettingsRequired = ({
message: `${t("validateServerPrincipal")}`, message: `${t("validateServerPrincipal")}`,
}, },
})} })}
data-cy="kerberos-principal"
/> />
{form.errors.config && {form.errors.config &&
form.errors.config.serverPrincipal && form.errors.config.serverPrincipal &&
@ -197,6 +205,7 @@ export const KerberosSettingsRequired = ({
message: `${t("validateKeyTab")}`, message: `${t("validateKeyTab")}`,
}, },
})} })}
data-cy="kerberos-keytab"
/> />
{form.errors.config && {form.errors.config &&
form.errors.config.keyTab && form.errors.config.keyTab &&
@ -222,7 +231,7 @@ export const KerberosSettingsRequired = ({
{" "} {" "}
<Controller <Controller
name="config.debug" name="config.debug"
defaultValue={false} defaultValue={["false"]}
control={form.control} control={form.control}
render={({ onChange, value }) => ( render={({ onChange, value }) => (
<Switch <Switch
@ -251,7 +260,7 @@ export const KerberosSettingsRequired = ({
> >
<Controller <Controller
name="config.allowPasswordAuthentication" name="config.allowPasswordAuthentication"
defaultValue={false} defaultValue={["false"]}
control={form.control} control={form.control}
render={({ onChange, value }) => ( render={({ onChange, value }) => (
<Switch <Switch
@ -324,7 +333,7 @@ export const KerberosSettingsRequired = ({
> >
<Controller <Controller
name="config.updateProfileFirstLogin" name="config.updateProfileFirstLogin"
defaultValue={false} defaultValue={["false"]}
control={form.control} control={form.control}
render={({ onChange, value }) => ( render={({ onChange, value }) => (
<Switch <Switch

View file

@ -80,8 +80,10 @@
"oneLevel": "One Level", "oneLevel": "One Level",
"subtree": "Subtree", "subtree": "Subtree",
"saveSuccess": "User federation successfully saved", "saveSuccess": "User federation provider successfully saved",
"saveError": "User federation could not be saved: {error}", "saveError": "User federation provider could not be saved: {error}",
"createSuccess": "User federation provider successfully created",
"createError": "User federation provider could not be created: {error}",
"learnMore": "Learn more", "learnMore": "Learn more",
"addNewProvider": "Add new provider", "addNewProvider": "Add new provider",

View file

@ -126,6 +126,7 @@ export const SettingsCache = ({
}} }}
selections={value} selections={value}
variant={SelectVariant.single} variant={SelectVariant.single}
data-cy="kerberos-cache-policy"
> >
<SelectOption key={0} value={["DEFAULT"]} isPlaceholder /> <SelectOption key={0} value={["DEFAULT"]} isPlaceholder />
<SelectOption key={1} value={["EVICT_DAILY"]} /> <SelectOption key={1} value={["EVICT_DAILY"]} />
@ -155,6 +156,7 @@ export const SettingsCache = ({
control={form.control} control={form.control}
render={({ onChange, value }) => ( render={({ onChange, value }) => (
<Select <Select
data-cy="cache-day"
toggleId="kc-eviction-day" toggleId="kc-eviction-day"
required required
onToggle={() => onToggle={() =>
@ -292,6 +294,7 @@ export const SettingsCache = ({
id="kc-max-lifespan" id="kc-max-lifespan"
name="config.maxLifespan[0]" name="config.maxLifespan[0]"
ref={form.register} ref={form.register}
data-cy="kerberos-cache-lifespan"
/> />
</FormGroup> </FormGroup>
) : ( ) : (

View file

@ -0,0 +1,224 @@
import LoginPage from "../support/pages/LoginPage";
import SidebarPage from "../support/pages/admin_console/SidebarPage";
import CreateKerberosProviderPage from "../support/pages/admin_console/manage/providers/CreateKerberosProviderPage";
import Masthead from "../support/pages/admin_console/Masthead";
import ModalUtils from "../support/util/ModalUtils";
const loginPage = new LoginPage();
const masthead = new Masthead();
const sidebarPage = new SidebarPage();
const providersPage = new CreateKerberosProviderPage();
const modalUtils = new ModalUtils();
const firstKerberosName = "my-kerberos";
const firstKerberosRealm = "my-realm";
const firstKerberosPrincipal = "my-principal";
const firstKerberosKeytab = "my-keytab";
const secondKerberosName = `${firstKerberosName}-2`;
const secondKerberosRealm = `${firstKerberosRealm}-2`;
const secondKerberosPrincipal = `${firstKerberosPrincipal}-2`;
const secondKerberosKeytab = `${firstKerberosKeytab}-2`;
const defaultPolicy = "DEFAULT";
const newPolicy = "EVICT_WEEKLY";
const defaultKerberosDay = "Sunday";
const defaultKerberosHour = "00";
const defaultKerberosMinute = "00";
const newKerberosDay = "Wednesday";
const newKerberosHour = "15";
const newKerberosMinute = "55";
const createdSuccessMessage = "User federation provider successfully created";
const savedSuccessMessage = "User federation provider successfully saved";
const deletedSuccessMessage = "The user federation provider has been deleted.";
const deleteModalTitle = "Delete user federation provider?";
const disableModalTitle = "Disable user federation provider?";
describe("User Fed Kerberos tests", () => {
it("Create Kerberos provider from empty state", () => {
cy.visit("");
loginPage.logIn();
sidebarPage.goToUserFederation();
cy.get("[data-cy=kerberos-card]").click();
providersPage.fillKerberosRequiredData(
firstKerberosName,
firstKerberosRealm,
firstKerberosPrincipal,
firstKerberosKeytab
);
providersPage.save();
masthead.checkNotificationMessage(createdSuccessMessage);
sidebarPage.goToUserFederation();
});
it("Update an existing Kerberos provider and save", () => {
cy.visit("");
loginPage.logIn();
sidebarPage.goToUserFederation();
cy.get('[data-cy="keycloak-card-title"]')
.contains(firstKerberosName)
.click();
cy.wait(1000);
providersPage.selectCacheType(newPolicy);
cy.contains(defaultKerberosDay).click();
cy.contains(newKerberosDay).click();
cy.contains(defaultKerberosHour).click();
cy.contains(newKerberosHour).click();
cy.contains(defaultKerberosMinute).click();
cy.contains(newKerberosMinute).click();
providersPage.save();
masthead.checkNotificationMessage(savedSuccessMessage);
sidebarPage.goToUserFederation();
cy.wait(1000);
cy.get('[data-cy="keycloak-card-title"]')
.contains(firstKerberosName)
.click();
cy.wait(1000);
expect(cy.contains(newPolicy).should("exist"));
expect(cy.contains(defaultPolicy).should("not.exist"));
});
it("Change existing Kerberos provider and click button to cancel", () => {
cy.visit("");
loginPage.logIn();
sidebarPage.goToUserFederation();
cy.get('[data-cy="keycloak-card-title"]')
.contains(firstKerberosName)
.click();
cy.wait(1000);
providersPage.selectCacheType(newPolicy);
cy.contains(newKerberosDay).click();
cy.contains(defaultKerberosDay).click();
cy.contains(newKerberosHour).click();
cy.contains(defaultKerberosHour).click();
cy.contains(newKerberosMinute).click();
cy.contains(defaultKerberosMinute).click();
providersPage.cancel();
cy.wait(1000);
cy.get('[data-cy="keycloak-card-title"]')
.contains(firstKerberosName)
.click();
cy.wait(1000);
providersPage.selectCacheType(newPolicy);
expect(cy.contains(newKerberosDay).should("exist"));
expect(cy.contains(newKerberosHour).should("exist"));
expect(cy.contains(newKerberosMinute).should("exist"));
expect(cy.contains(defaultKerberosMinute).should("not.exist"));
sidebarPage.goToUserFederation();
});
it("Disable an existing Kerberos provider", () => {
cy.visit("");
loginPage.logIn();
sidebarPage.goToUserFederation();
cy.get('[data-cy="keycloak-card-title"]')
.contains(firstKerberosName)
.click();
cy.wait(1000);
providersPage.disableEnabledSwitch();
modalUtils.checkModalTitle(disableModalTitle).confirmModal();
masthead.checkNotificationMessage(savedSuccessMessage);
sidebarPage.goToUserFederation();
masthead.checkNotificationMessage(savedSuccessMessage);
sidebarPage.goToUserFederation();
cy.wait(1000);
expect(cy.contains("Disabled").should("exist"));
});
it("Enable an existing previously-disabled Kerberos provider", () => {
cy.visit("");
loginPage.logIn();
sidebarPage.goToUserFederation();
cy.get('[data-cy="keycloak-card-title"]')
.contains(firstKerberosName)
.click();
cy.wait(1000);
providersPage.enableEnabledSwitch();
masthead.checkNotificationMessage(savedSuccessMessage);
sidebarPage.goToUserFederation();
cy.wait(1000);
expect(cy.contains("Enabled").should("exist"));
});
it("Create new Kerberos provider using the New Provider dropdown", () => {
cy.visit("");
loginPage.logIn();
sidebarPage.goToUserFederation();
cy.contains("Add new provider").click();
cy.contains("Kerberos").click();
providersPage.fillKerberosRequiredData(
secondKerberosName,
secondKerberosRealm,
secondKerberosPrincipal,
secondKerberosKeytab
);
providersPage.save();
masthead.checkNotificationMessage(createdSuccessMessage);
sidebarPage.goToUserFederation();
});
it("Delete a Kerberos provider from card view using the card's menu", () => {
cy.visit("");
loginPage.logIn();
sidebarPage.goToUserFederation();
cy.get('[data-cy="my-kerberos-2-dropdown"]').click();
cy.get('[data-cy="card-delete"]').click();
modalUtils.checkModalTitle(deleteModalTitle).confirmModal();
masthead.checkNotificationMessage(deletedSuccessMessage);
});
it("Delete a Kerberos provider using the Settings view's Action menu", () => {
cy.visit("");
loginPage.logIn();
sidebarPage.goToUserFederation();
cy.get('[data-cy="keycloak-card-title"]')
.contains(firstKerberosName)
.click();
cy.wait(1000);
cy.get('[data-cy="action-dropdown"]').click();
cy.get('[data-cy="delete-provider-cmd"]').click();
modalUtils.checkModalTitle(deleteModalTitle).confirmModal();
masthead.checkNotificationMessage(deletedSuccessMessage);
});
});

View file

@ -0,0 +1,87 @@
export default class CreateKerberosProviderPage {
kerberosNameInput: string;
kerberosRealmInput: string;
kerberosPrincipalInput: string;
kerberosKeytabInput: string;
kerberosEnabledInput: string;
kerberosCacheDayInput: string;
kerberosCacheDayList: string;
kerberosCacheHourInput: string;
kerberosCacheHourList: string;
kerberosCacheMinuteInput: string;
kerberosCacheMinuteList: string;
kerberosCachePolicyInput: string;
kerberosCachePolicyList: string;
saveBtn: string;
cancelBtn: string;
constructor() {
this.kerberosNameInput = "data-cy=kerberos-name";
this.kerberosRealmInput = "data-cy=kerberos-realm";
this.kerberosPrincipalInput = "data-cy=kerberos-principal";
this.kerberosKeytabInput = "data-cy=kerberos-keytab";
this.kerberosEnabledInput = "#Kerberos-switch";
this.kerberosCacheDayInput = "#kc-eviction-day";
this.kerberosCacheDayList = "#kc-eviction-day + ul";
this.kerberosCacheHourInput = "#kc-eviction-hour";
this.kerberosCacheHourList = "#kc-eviction-hour + ul";
this.kerberosCacheMinuteInput = "#kc-eviction-minute";
this.kerberosCacheMinuteList = "#kc-eviction-minute + ul";
this.kerberosCachePolicyInput = "#kc-cache-policy";
this.kerberosCachePolicyList = "#kc-cache-policy + ul";
this.saveBtn = "data-cy=kerberos-save";
this.cancelBtn = "data-cy=kerberos-cancel";
}
//#region Required Settings
fillKerberosRequiredData(
name: string,
realm: string,
principal: string,
keytab: string
) {
if (name) {
cy.get(`[${this.kerberosNameInput}]`).type(name);
}
if (realm) {
cy.get(`[${this.kerberosRealmInput}]`).type(realm);
}
if (principal) {
cy.get(`[${this.kerberosPrincipalInput}]`).type(principal);
}
if (keytab) {
cy.get(`[${this.kerberosKeytabInput}]`).type(keytab);
}
return this;
}
selectCacheType(cacheType: string) {
cy.get(this.kerberosCachePolicyInput).click();
cy.get(this.kerberosCachePolicyList).contains(cacheType).click();
return this;
}
disableEnabledSwitch() {
cy.get(this.kerberosEnabledInput).uncheck({ force: true });
}
enableEnabledSwitch() {
cy.get(this.kerberosEnabledInput).check({ force: true });
}
save() {
cy.get(`[${this.saveBtn}]`).click();
return this;
}
cancel() {
cy.get(`[${this.cancelBtn}]`).click();
return this;
}
}

View file

@ -0,0 +1,10 @@
{
"compilerOptions": {
"target": "es5",
"lib": ["es5", "dom"],
"types": ["cypress"]
},
"include": [
"**/*.ts"
]
}