diff --git a/cypress/e2e/po/components/resource-table.po.ts b/cypress/e2e/po/components/resource-table.po.ts index 460a489e6c8..60098adf180 100644 --- a/cypress/e2e/po/components/resource-table.po.ts +++ b/cypress/e2e/po/components/resource-table.po.ts @@ -5,4 +5,8 @@ export default class ResourceTablePo extends ComponentPo { sortableTable() { return new SortableTablePo(this.self()); } + + downloadYamlButton() { + return cy.getId('sortable-table-download'); + } } diff --git a/cypress/e2e/po/detail/management.cattle.io.globalrole.po.ts b/cypress/e2e/po/detail/management.cattle.io.globalrole.po.ts new file mode 100644 index 00000000000..3ef2aec8848 --- /dev/null +++ b/cypress/e2e/po/detail/management.cattle.io.globalrole.po.ts @@ -0,0 +1,19 @@ +import RoleDetailPo from '@/cypress/e2e/po/detail/role.po'; +import GlobalRoleEditPo from '@/cypress/e2e/po/edit/management.cattle.io.globalrole.po'; +import ResourceDetailPo from '@/cypress/e2e/po/edit/resource-detail.po'; + +class GlobalRoleDetailComponentPo extends ResourceDetailPo { + userCreateEditView(clusterId: string, userId?: string ) { + return new GlobalRoleEditPo(clusterId, userId); + } +} + +export default class GlobalRoleDetailPo extends RoleDetailPo { + constructor(clusterId = '_', roleId: string) { + super(clusterId, 'management.cattle.io.globalrole', roleId); + } + + detail() { + return new GlobalRoleDetailComponentPo(this.self()); + } +} diff --git a/cypress/e2e/po/detail/management.cattle.io.roletemplate.po.ts b/cypress/e2e/po/detail/management.cattle.io.roletemplate.po.ts new file mode 100644 index 00000000000..1a7fc951775 --- /dev/null +++ b/cypress/e2e/po/detail/management.cattle.io.roletemplate.po.ts @@ -0,0 +1,19 @@ +import RoleDetailPo from '@/cypress/e2e/po/detail/role.po'; +import RoleTemplateEditPo from '@/cypress/e2e/po/edit/management.cattle.io.roletemplate.po'; +import ResourceDetailPo from '@/cypress/e2e/po/edit/resource-detail.po'; + +class RoleTemplateDetailComponentPo extends ResourceDetailPo { + userCreateEditView(clusterId: string, userId?: string ) { + return new RoleTemplateEditPo(clusterId, userId); + } +} + +export default class RoleTemplateDetailPo extends RoleDetailPo { + constructor(clusterId = '_', roleId: string) { + super(clusterId, 'management.cattle.io.roletemplate', roleId); + } + + detail() { + return new RoleTemplateDetailComponentPo(this.self()); + } +} diff --git a/cypress/e2e/po/detail/management.cattle.io.user.po.ts b/cypress/e2e/po/detail/management.cattle.io.user.po.ts new file mode 100644 index 00000000000..ae92dcae24f --- /dev/null +++ b/cypress/e2e/po/detail/management.cattle.io.user.po.ts @@ -0,0 +1,27 @@ +import MgmtUserEditPo from '@/cypress/e2e/po/edit/management.cattle.io.user.po'; +import ResourceDetailPo from '@/cypress/e2e/po/edit/resource-detail.po'; +import PagePo from '@/cypress/e2e/po/pages/page.po'; + +class MgmtUserResourceDetailComponentPo extends ResourceDetailPo { + userCreateEditView(clusterId: string, userId?: string ) { + return new MgmtUserEditPo(clusterId, userId); + } +} + +export default class MgmtUserResourceDetailPo extends PagePo { + private static createPath(clusterId: string, userId: string ) { + return `/c/${ clusterId }/auth/management.cattle.io.user/${ userId }`; + } + + static goTo(path: string): Cypress.Chainable { + throw new Error('invalid'); + } + + constructor(clusterId: string, userId: string) { + super(MgmtUserResourceDetailPo.createPath(clusterId, userId)); + } + + detail() { + return new MgmtUserResourceDetailComponentPo(this.self()); + } +} diff --git a/cypress/e2e/po/detail/role.po.ts b/cypress/e2e/po/detail/role.po.ts new file mode 100644 index 00000000000..d671917c766 --- /dev/null +++ b/cypress/e2e/po/detail/role.po.ts @@ -0,0 +1,15 @@ +import PagePo from '@/cypress/e2e/po/pages/page.po'; + +export default abstract class RoleDetailPo extends PagePo { + private static createPath(clusterId: string, resource: string, roleId: string) { + return `/c/${ clusterId }/auth/roles/${ resource }/${ roleId }`; + } + + static goTo(path: string): Cypress.Chainable { + throw new Error('invalid'); + } + + constructor(clusterId = '_', resource: string, roleId: string) { + super(RoleDetailPo.createPath(clusterId, resource, roleId)); + } +} diff --git a/cypress/e2e/po/edit/management.cattle.io.globalrole.po.ts b/cypress/e2e/po/edit/management.cattle.io.globalrole.po.ts new file mode 100644 index 00000000000..e2edadad9a1 --- /dev/null +++ b/cypress/e2e/po/edit/management.cattle.io.globalrole.po.ts @@ -0,0 +1,7 @@ +import RoleEditPo from '@/cypress/e2e/po/edit/role.po'; + +export default class GlobalRoleEditPo extends RoleEditPo { + constructor(clusterId = '_', roleId?: string) { + super(clusterId, 'management.cattle.io.globalrole', roleId); + } +} diff --git a/cypress/e2e/po/edit/management.cattle.io.roletemplate.po.ts b/cypress/e2e/po/edit/management.cattle.io.roletemplate.po.ts new file mode 100644 index 00000000000..d346558bd08 --- /dev/null +++ b/cypress/e2e/po/edit/management.cattle.io.roletemplate.po.ts @@ -0,0 +1,7 @@ +import RoleEditPo from '@/cypress/e2e/po/edit/role.po'; + +export default class RoleTemplateEditPo extends RoleEditPo { + constructor(clusterId = '_', roleId?: string) { + super(clusterId, 'management.cattle.io.roletemplate', roleId); + } +} diff --git a/cypress/e2e/po/edit/management.cattle.io.user.po.ts b/cypress/e2e/po/edit/management.cattle.io.user.po.ts new file mode 100644 index 00000000000..8efb26aed6f --- /dev/null +++ b/cypress/e2e/po/edit/management.cattle.io.user.po.ts @@ -0,0 +1,56 @@ +import PagePo from '@/cypress/e2e/po/pages/page.po'; +import LabeledInputPo from '@/cypress/e2e/po/components/labeled-input.po'; +import AsyncButtonPo from '@/cypress/e2e/po/components/async-button.po'; +import CheckboxInputPo from '@/cypress/e2e/po/components/checkbox-input.po'; +import { CypressChainable } from '@/cypress/e2e/po/po.types'; + +export default class MgmtUserEditPo extends PagePo { + private static createPath(clusterId: string, userId?: string ) { + const root = `/c/${ clusterId }/auth/management.cattle.io.user`; + + return userId ? `${ root }/${ userId }?mode=edit` : `${ root }/create`; + } + + static goTo(path: string): Cypress.Chainable { + throw new Error('invalid'); + } + + constructor(clusterId = '_', userId?: string) { + super(MgmtUserEditPo.createPath(clusterId, userId)); + } + + name(): LabeledInputPo { + return LabeledInputPo.byLabel(this.self(), 'Name'); + } + + username(): LabeledInputPo { + return LabeledInputPo.byLabel(this.self(), 'Username'); + } + + description(): LabeledInputPo { + return LabeledInputPo.byLabel(this.self(), 'Description'); + } + + newPass(): LabeledInputPo { + return LabeledInputPo.byLabel(this.self(), 'New Password'); + } + + confirmNewPass(): LabeledInputPo { + return LabeledInputPo.byLabel(this.self(), 'Confirm Password'); + } + + selectCheckbox(label:string): CheckboxInputPo { + return CheckboxInputPo.byLabel(this.self(), label); + } + + saveCreateForm(): AsyncButtonPo { + return new AsyncButtonPo('[data-testid="form-save"]', this.self()); + } + + saveAndWaitForRequests(method: string, url: any, multipleCalls?: boolean): CypressChainable { + cy.intercept(method, url).as('request'); + this.saveCreateForm().click(); + + return (multipleCalls ? cy.wait(['@request', '@request'], { timeout: 10000 }) : cy.wait('@request', { timeout: 10000 })); + } +} diff --git a/cypress/e2e/po/edit/provisioning.cattle.io.cluster/create/cluster-create-rke1-custom.po.ts b/cypress/e2e/po/edit/provisioning.cattle.io.cluster/create/cluster-create-rke1-custom.po.ts index 4b8f1420015..560fa319898 100644 --- a/cypress/e2e/po/edit/provisioning.cattle.io.cluster/create/cluster-create-rke1-custom.po.ts +++ b/cypress/e2e/po/edit/provisioning.cattle.io.cluster/create/cluster-create-rke1-custom.po.ts @@ -3,7 +3,7 @@ import ClusterManagerCreatePagePo from '@/cypress/e2e/po/edit/provisioning.cattl import EmberInputPo from '@/cypress/e2e/po/components/ember/ember-input.po'; import ClusterManagerCreateRKE1PagePo from '@/cypress/e2e/po/edit/provisioning.cattle.io.cluster/create/cluster-create-rke1.po'; import EmberAccordionPo from '@/cypress/e2e/po/components/ember/ember-accordion.po'; -import EmberFormMembersPo from '~/cypress/e2e/po/components/ember/ember-form-members.po'; +import EmberFormMembersPo from '@/cypress/e2e/po/components/ember/ember-form-members.po'; /** * Create page for an RKE1 custom cluster diff --git a/cypress/e2e/po/edit/role.po.ts b/cypress/e2e/po/edit/role.po.ts new file mode 100644 index 00000000000..fbd61da1f06 --- /dev/null +++ b/cypress/e2e/po/edit/role.po.ts @@ -0,0 +1,73 @@ +import PagePo from '@/cypress/e2e/po/pages/page.po'; + +import LabeledInputPo from '@/cypress/e2e/po/components/labeled-input.po'; +import LabeledSelectPo from '@/cypress/e2e/po/components/labeled-select.po'; +import AsyncButtonPo from '@/cypress/e2e/po/components/async-button.po'; +import CheckboxInputPo from '@/cypress/e2e/po/components/checkbox-input.po'; +import { CypressChainable } from '@/cypress/e2e/po/po.types'; +import RadioGroupInputPo from '@/cypress/e2e/po/components/radio-group-input.po'; + +export default abstract class RoleEditPo extends PagePo { + private static createPath(clusterId: string, resource: string, roleId?: string) { + const root = `/c/${ clusterId }/auth/roles/${ resource }`; + + return roleId ? `${ root }/${ roleId }?mode=edit` : `${ root }/create`; + } + + static goTo(path: string): Cypress.Chainable { + throw new Error('invalid'); + } + + constructor(clusterId = '_', resource: string, roleId?: string) { + super(RoleEditPo.createPath(clusterId, resource, roleId)); + } + + name(): LabeledInputPo { + return LabeledInputPo.byLabel(this.self(), 'Name'); + } + + description(): LabeledInputPo { + return LabeledInputPo.byLabel(this.self(), 'Description'); + } + + selectCheckbox(label:string): CheckboxInputPo { + return CheckboxInputPo.byLabel(this.self(), label); + } + + saveCreateForm(): AsyncButtonPo { + return new AsyncButtonPo('[data-testid="form-save"]', this.self()); + } + + saveAndWaitForRequests(method: string, url: any): CypressChainable { + cy.intercept(method, url).as('request'); + this.saveCreateForm().click(); + + return cy.wait('@request', { timeout: 10000 }); + } + + selectVerbs(itemRow: number, optionIndex: number) { + const selectVerb = new LabeledSelectPo(`[data-testid="grant-resources-verbs${ itemRow }"]`, this.self()); + + selectVerb.toggle(); + selectVerb.clickOption(optionIndex); + } + + selectResourcesByLabelValue(itemRow: number, label: string) { + const selectResources = new LabeledSelectPo(`[data-testid="grant-resources-resources${ itemRow }"]`, this.self()); + + selectResources.toggle(); + selectResources.clickOptionWithLabel(label); + } + + selectCreatorDefaultRadioBtn(optionIndex: number): CypressChainable { + const selectRadio = new RadioGroupInputPo('[data-testid="roletemplate-creator-default-options"] div > .radio-container', this.self()); + + return selectRadio.set(optionIndex); + } + + selectLockedRadioBtn(optionIndex: number): CypressChainable { + const selectRadio = new RadioGroupInputPo('[data-testid="roletemplate-locked-options"] div > .radio-container', this.self()); + + return selectRadio.set(optionIndex); + } +} diff --git a/cypress/e2e/po/lists/home-cluster-list.po.ts b/cypress/e2e/po/lists/home-cluster-list.po.ts index 7d620bd79e4..b55dbbce0a0 100644 --- a/cypress/e2e/po/lists/home-cluster-list.po.ts +++ b/cypress/e2e/po/lists/home-cluster-list.po.ts @@ -1,4 +1,4 @@ -import BaseResourceList from '~/cypress/e2e/po/lists/base-resource-list.po'; +import BaseResourceList from '@/cypress/e2e/po/lists/base-resource-list.po'; /** * List component for home page cluster resources diff --git a/cypress/e2e/po/lists/management.cattle.io.user.po.ts b/cypress/e2e/po/lists/management.cattle.io.user.po.ts new file mode 100644 index 00000000000..05878f15d72 --- /dev/null +++ b/cypress/e2e/po/lists/management.cattle.io.user.po.ts @@ -0,0 +1,52 @@ +import BaseResourceList from '@/cypress/e2e/po/lists/base-resource-list.po'; +import AsyncButtonPo from '@/cypress/e2e/po/components/async-button.po'; + +/** + * List component for api key resources + */ +export default class MgmtUsersListPo extends BaseResourceList { + create() { + return this.masthead().actions().eq(0).click(); + } + + refreshGroupMembership(): AsyncButtonPo { + return new AsyncButtonPo('[data-testid="action-button-async-button"]', this.self()); + } + + deactivate() { + return cy.getId('sortable-table-deactivate'); + } + + activate() { + return cy.getId('sortable-table-activate'); + } + + openBulkActionDropdown() { + return this.resourceTable().sortableTable().bulkActionDropDownOpen(); + } + + bulkActionButton(name: string) { + return this.resourceTable().sortableTable().bulkActionDropDownButton(name); + } + + selectAll() { + return this.resourceTable().sortableTable().selectAllCheckbox(); + } + + elements() { + return this.resourceTable().sortableTable().rowElements(); + } + + elementWithName(name: string) { + return this.resourceTable().sortableTable().rowElementWithName(name); + } + + details(name: string, index: number) { + return this.resourceTable().sortableTable().rowWithName(name).column(index); + } + + clickRowActionMenuItem(name: string, itemLabel:string) { + return this.resourceTable().sortableTable().rowActionMenuOpen(name, 7).getMenuItem(itemLabel) + .click(); + } +} diff --git a/cypress/e2e/po/lists/role-list.po.ts b/cypress/e2e/po/lists/role-list.po.ts new file mode 100644 index 00000000000..6d4d51c6405 --- /dev/null +++ b/cypress/e2e/po/lists/role-list.po.ts @@ -0,0 +1,23 @@ +import BaseResourceList from '@/cypress/e2e/po/lists/base-resource-list.po'; + +export default class RoleListPo extends BaseResourceList { + downloadYaml() { + return this.resourceTable().downloadYamlButton().first(); + } + + delete() { + return this.resourceTable().sortableTable().deleteButton().first(); + } + + elements() { + return this.resourceTable().sortableTable().rowElements(); + } + + elementWithName(name: string) { + return this.resourceTable().sortableTable().rowElementWithName(name); + } + + details(name: string, index: number) { + return this.resourceTable().sortableTable().rowWithName(name).column(index); + } +} diff --git a/cypress/e2e/po/pages/explorer/api-services.po.ts b/cypress/e2e/po/pages/explorer/api-services.po.ts index c2786e1261e..ccd7dca114c 100644 --- a/cypress/e2e/po/pages/explorer/api-services.po.ts +++ b/cypress/e2e/po/pages/explorer/api-services.po.ts @@ -1,5 +1,5 @@ import PagePo from '@/cypress/e2e/po/pages/page.po'; -import BaseResourceList from '~/cypress/e2e/po/lists/base-resource-list.po'; +import BaseResourceList from '@/cypress/e2e/po/lists/base-resource-list.po'; export class APIServicesPagePo extends PagePo { private static createPath(clusterId: string) { diff --git a/cypress/e2e/po/pages/explorer/workloads/workloads.po.ts b/cypress/e2e/po/pages/explorer/workloads/workloads.po.ts index f907d9ba096..957cb0569f4 100644 --- a/cypress/e2e/po/pages/explorer/workloads/workloads.po.ts +++ b/cypress/e2e/po/pages/explorer/workloads/workloads.po.ts @@ -4,7 +4,7 @@ import LabeledInputPo from '@/cypress/e2e/po/components/labeled-input.po'; import AsyncButtonPo from '@/cypress/e2e/po/components/async-button.po'; import LabeledSelectPo from '@/cypress/e2e/po/components/labeled-select.po'; import WorkloadPagePo from '@/cypress/e2e/po/pages/explorer/workloads.po'; -import PromptRemove from '~/cypress/e2e/po/prompts/promptRemove.po'; +import PromptRemove from '@/cypress/e2e/po/prompts/promptRemove.po'; import { WorkloadType } from '@shell/types/fleet'; export class workloadDetailsPageBasePo extends PagePo { static url: string; diff --git a/cypress/e2e/po/pages/extensions.po.ts b/cypress/e2e/po/pages/extensions.po.ts index 90eaf716709..128a3437bd6 100644 --- a/cypress/e2e/po/pages/extensions.po.ts +++ b/cypress/e2e/po/pages/extensions.po.ts @@ -6,7 +6,7 @@ import ActionMenuPo from '@/cypress/e2e/po/components/action-menu.po'; import NameNsDescriptionPo from '@/cypress/e2e/po/components/name-ns-description.po'; import ReposListPagePo from '@/cypress/e2e/po/pages/repositories.po'; import AppClusterRepoEditPo from '@/cypress/e2e/po/edit/catalog.cattle.io.clusterrepo.po'; -import BannersPo from '~/cypress/e2e/po/components/banners.po'; +import BannersPo from '@/cypress/e2e/po/components/banners.po'; export default class ExtensionsPagePo extends PagePo { static url = '/c/local/uiplugins' diff --git a/cypress/e2e/po/pages/home.po.ts b/cypress/e2e/po/pages/home.po.ts index b1f92b7d81e..1ce98dc3930 100644 --- a/cypress/e2e/po/pages/home.po.ts +++ b/cypress/e2e/po/pages/home.po.ts @@ -1,9 +1,9 @@ import PagePo from '@/cypress/e2e/po/pages/page.po'; import PageActions from '@/cypress/e2e/po/side-bars/page-actions.po'; -import BannerGraphicPo from '~/cypress/e2e/po/components/banner-graphic.po'; -import BannersPo from '~/cypress/e2e/po/components/banners.po'; -import SimpleBoxPo from '~/cypress/e2e/po/components/simple-box.po'; -import HomeClusterListPo from '~/cypress/e2e/po/lists/home-cluster-list.po'; +import BannerGraphicPo from '@/cypress/e2e/po/components/banner-graphic.po'; +import BannersPo from '@/cypress/e2e/po/components/banners.po'; +import SimpleBoxPo from '@/cypress/e2e/po/components/simple-box.po'; +import HomeClusterListPo from '@/cypress/e2e/po/lists/home-cluster-list.po'; export default class HomePagePo extends PagePo { static url = '/home' diff --git a/cypress/e2e/po/pages/page.po.ts b/cypress/e2e/po/pages/page.po.ts index 912385d76e5..179b82bae53 100644 --- a/cypress/e2e/po/pages/page.po.ts +++ b/cypress/e2e/po/pages/page.po.ts @@ -1,6 +1,6 @@ import ComponentPo from '@/cypress/e2e/po/components/component.po'; -import BurgerMenuPo from '~/cypress/e2e/po/side-bars/burger-side-menu.po'; -import ProductNavPo from '~/cypress/e2e/po/side-bars/product-side-nav.po'; +import BurgerMenuPo from '@/cypress/e2e/po/side-bars/burger-side-menu.po'; +import ProductNavPo from '@/cypress/e2e/po/side-bars/product-side-nav.po'; export default class PagePo extends ComponentPo { constructor(protected path: string, selector = '.dashboard-root') { @@ -34,8 +34,8 @@ export default class PagePo extends ComponentPo { } } - goTo(): Cypress.Chainable { - return PagePo.goTo(this.path); + goTo(params?: string, fragment?: string): Cypress.Chainable { + return PagePo.goTo(`${ this.path }${ !!params ? `?${ params }` : '' }${ !!fragment ? `#${ fragment }` : '' }`); } waitForPage(params?: string, fragment?: string) { diff --git a/cypress/e2e/po/pages/users-and-auth/roles.po.ts b/cypress/e2e/po/pages/users-and-auth/roles.po.ts new file mode 100644 index 00000000000..a347a65879f --- /dev/null +++ b/cypress/e2e/po/pages/users-and-auth/roles.po.ts @@ -0,0 +1,53 @@ +import PagePo from '@/cypress/e2e/po/pages/page.po'; +import BaseResourceList from '@/cypress/e2e/po/lists/base-resource-list.po'; +import GlobalRoleEditPo from '@/cypress/e2e/po/edit/management.cattle.io.globalrole.po'; +import RoleTemplateEditPo from '@/cypress/e2e/po/edit/management.cattle.io.roletemplate.po'; +import GlobalRoleDetailPo from '@/cypress/e2e/po/detail/management.cattle.io.globalrole.po'; +import RoleTemplateDetailPo from '@/cypress/e2e/po/detail/management.cattle.io.roletemplate.po'; +import RoleListPo from '@/cypress/e2e/po/lists/role-list.po'; + +export default class RolesPo extends PagePo { + private static createPath(clusterId: string) { + // return (roleId ? `/c/${ clusterId }/auth/roles/${ resource }/${ roleId }` : `/c/${ clusterId }/auth/roles${ resource }`); + return `/c/${ clusterId }/auth/roles`; + } + + static goTo(path: string): Cypress.Chainable { + throw new Error('invalid'); + } + + constructor(private clusterId = '_') { + super(RolesPo.createPath(clusterId)); + } + + waitForRequests() { + RolesPo.goToAndWaitForGet(this.goTo.bind(this), ['/v1/management.cattle.io.roletemplates?exclude=metadata.managedFields']); + } + + createGlobal(userId?: string) { + return new GlobalRoleEditPo(this.clusterId, userId); + } + + detailGlobal(roleId: string) { + return new GlobalRoleDetailPo(this.clusterId, roleId); + } + + createRole(userId?: string) { + return new RoleTemplateEditPo(this.clusterId, userId); + } + + detailRole(roleId: string) { + return new RoleTemplateDetailPo(this.clusterId, roleId); + } + + listCreate(label: string) { + const baseResourceList = new BaseResourceList(this.self()); + + return baseResourceList.masthead().actions().contains(label) + .click(); + } + + list() { + return new RoleListPo(this.self()); + } +} diff --git a/cypress/e2e/po/pages/users-and-auth/users-and-auth.po.ts b/cypress/e2e/po/pages/users-and-auth/users-and-auth.po.ts deleted file mode 100644 index 01718d201d7..00000000000 --- a/cypress/e2e/po/pages/users-and-auth/users-and-auth.po.ts +++ /dev/null @@ -1,83 +0,0 @@ -import PagePo from '@/cypress/e2e/po/pages/page.po'; -import BaseResourceList from '@/cypress/e2e/po/lists/base-resource-list.po'; -import LabeledInputPo from '@/cypress/e2e/po/components/labeled-input.po'; -import LabeledSelectPo from '@/cypress/e2e/po/components/labeled-select.po'; -import AsyncButtonPo from '@/cypress/e2e/po/components/async-button.po'; -import CheckboxInputPo from '~/cypress/e2e/po/components/checkbox-input.po'; -import ResourceListMastheadPo from '@/cypress/e2e/po/components/ResourceList/resource-list-masthead.po'; - -export default class UsersAndAuthPo extends PagePo { - private static createPath(clusterId: string, resource: string) { - return `/c/${ clusterId }/auth/${ resource }`; - } - - static goTo(path: string): Cypress.Chainable { - throw new Error('invalid'); - } - - constructor(clusterId = '_', resource = 'management.cattle.io.user') { - super(UsersAndAuthPo.createPath(clusterId, resource)); - } - - listCreate() { - const baseResourceList = new BaseResourceList(this.self()); - - return baseResourceList.masthead().actions().eq(0).click(); - } - - listElements() { - const baseResourceList = new BaseResourceList(this.self()); - - return baseResourceList.resourceTable().sortableTable().rowElements(); - } - - listElementWithName(name: string) { - const baseResourceList = new BaseResourceList(this.self()); - - return baseResourceList.resourceTable().sortableTable().rowElementWithName(name); - } - - listTitle() { - const resourceListMasthead = new ResourceListMastheadPo(this.self()); - - return resourceListMasthead.title(); - } - - name(): LabeledInputPo { - return LabeledInputPo.byLabel(this.self(), 'Name'); - } - - username(): LabeledInputPo { - return LabeledInputPo.byLabel(this.self(), 'Username'); - } - - newPass(): LabeledInputPo { - return LabeledInputPo.byLabel(this.self(), 'New Password'); - } - - confirmNewPass(): LabeledInputPo { - return LabeledInputPo.byLabel(this.self(), 'Confirm Password'); - } - - selectCheckbox(label:string): CheckboxInputPo { - return CheckboxInputPo.byLabel(this.self(), label); - } - - saveCreateForm(): AsyncButtonPo { - return new AsyncButtonPo('[data-testid="form-save"]', this.self()); - } - - selectVerbs(itemRow: number, optionIndex: number) { - const selectVerb = new LabeledSelectPo(`[data-testid="grant-resources-verbs${ itemRow }"]`, this.self()); - - selectVerb.toggle(); - selectVerb.clickOption(optionIndex); - } - - selectResourcesByLabelValue(itemRow: number, label: string) { - const selectResources = new LabeledSelectPo(`[data-testid="grant-resources-resources${ itemRow }"]`, this.self()); - - selectResources.toggle(); - selectResources.clickOptionWithLabel(label); - } -} diff --git a/cypress/e2e/po/pages/users-and-auth/users.po.ts b/cypress/e2e/po/pages/users-and-auth/users.po.ts new file mode 100644 index 00000000000..1eb54924e02 --- /dev/null +++ b/cypress/e2e/po/pages/users-and-auth/users.po.ts @@ -0,0 +1,34 @@ +import PagePo from '@/cypress/e2e/po/pages/page.po'; +import MgmtUsersListPo from '@/cypress/e2e/po/lists/management.cattle.io.user.po'; +import MgmtUserEditPo from '@/cypress/e2e/po/edit/management.cattle.io.user.po'; +import MgmtUserResourceDetailPo from '@/cypress/e2e/po/detail/management.cattle.io.user.po'; + +export default class UsersPo extends PagePo { + private static createPath(clusterId: string) { + return `/c/${ clusterId }/auth/management.cattle.io.user`; + } + + static goTo(path: string): Cypress.Chainable { + throw new Error('invalid'); + } + + constructor(private clusterId = '_') { + super(UsersPo.createPath(clusterId)); + } + + waitForRequests() { + UsersPo.goToAndWaitForGet(this.goTo.bind(this), ['/v3/users?limit=0']); + } + + list() { + return new MgmtUsersListPo(this.self()); + } + + createEdit(userId?: string) { + return new MgmtUserEditPo(this.clusterId, userId); + } + + detail(userId: string) { + return new MgmtUserResourceDetailPo(this.clusterId, userId); + } +} diff --git a/cypress/e2e/tests/pages/explorer/cluster-project-members.spec.ts b/cypress/e2e/tests/pages/explorer/cluster-project-members.spec.ts index bd44dee9c2c..8a3018137d3 100644 --- a/cypress/e2e/tests/pages/explorer/cluster-project-members.spec.ts +++ b/cypress/e2e/tests/pages/explorer/cluster-project-members.spec.ts @@ -1,5 +1,5 @@ -import UsersAndAuthPo from '~/cypress/e2e/po/pages/users-and-auth/users-and-auth.po'; -import ClusterProjectMembersPo from '~/cypress/e2e/po/pages/explorer/cluster-project-members.po'; +import UsersPo from '@/cypress/e2e/po/pages/users-and-auth/users.po'; +import ClusterProjectMembersPo from '@/cypress/e2e/po/pages/explorer/cluster-project-members.po'; const runTimestamp = +new Date(); const runPrefix = `e2e-test-${ runTimestamp }`; @@ -9,19 +9,20 @@ const standardPassword = 'standard-password'; describe('Cluster Project and Members', { tags: ['@adminUser'] }, () => { it('Members added to both Cluster Membership should not show "Loading..." next to their names', () => { - const usersAdmin = new UsersAndAuthPo('_', 'management.cattle.io.user'); + const usersAdmin = new UsersPo('_'); + const userCreate = usersAdmin.createEdit(); // this will login as admin cy.login(); // create a standard user usersAdmin.goTo(); - usersAdmin.listCreate(); + usersAdmin.list().create(); - usersAdmin.username().set(username); - usersAdmin.newPass().set(standardPassword); - usersAdmin.confirmNewPass().set(standardPassword); - usersAdmin.saveCreateForm().click(); + userCreate.username().set(username); + userCreate.newPass().set(standardPassword); + userCreate.confirmNewPass().set(standardPassword); + userCreate.saveCreateForm().click(); usersAdmin.waitForPageWithExactUrl(); // add user to Cluster membership diff --git a/cypress/e2e/tests/pages/extensions.spec.ts b/cypress/e2e/tests/pages/extensions.spec.ts index c1d07045c29..2b5f7d382a8 100644 --- a/cypress/e2e/tests/pages/extensions.spec.ts +++ b/cypress/e2e/tests/pages/extensions.spec.ts @@ -1,6 +1,6 @@ import ExtensionsPagePo from '@/cypress/e2e/po/pages/extensions.po'; import ReposListPagePo from '@/cypress/e2e/po/pages/repositories.po'; -import PromptRemove from '~/cypress/e2e/po/prompts/promptRemove.po'; +import PromptRemove from '@/cypress/e2e/po/prompts/promptRemove.po'; const EXTENSION_NAME = 'clock'; const UI_PLUGINS_PARTNERS_REPO_URL = 'https://github.com/rancher/partner-extensions'; diff --git a/cypress/e2e/tests/pages/home.spec.ts b/cypress/e2e/tests/pages/home.spec.ts index 364bbea727e..5c765c72017 100644 --- a/cypress/e2e/tests/pages/home.spec.ts +++ b/cypress/e2e/tests/pages/home.spec.ts @@ -1,7 +1,7 @@ import HomePagePo from '@/cypress/e2e/po/pages/home.po'; import PreferencesPagePo from '@/cypress/e2e/po/pages/preferences.po'; -import ClusterManagerListPagePo from '~/cypress/e2e/po/pages/cluster-manager/cluster-manager-list.po'; -import ClusterManagerImportGenericPagePo from '~/cypress/e2e/po/edit/provisioning.cattle.io.cluster/import/cluster-import.generic.po'; +import ClusterManagerListPagePo from '@/cypress/e2e/po/pages/cluster-manager/cluster-manager-list.po'; +import ClusterManagerImportGenericPagePo from '@/cypress/e2e/po/edit/provisioning.cattle.io.cluster/import/cluster-import.generic.po'; const homePage = new HomePagePo(); const homeClusterList = homePage.list(); diff --git a/cypress/e2e/tests/pages/users-and-auth/roles.spec.ts b/cypress/e2e/tests/pages/users-and-auth/roles.spec.ts new file mode 100644 index 00000000000..493f22b1724 --- /dev/null +++ b/cypress/e2e/tests/pages/users-and-auth/roles.spec.ts @@ -0,0 +1,192 @@ +import UsersPo from '@/cypress/e2e/po/pages/users-and-auth/users.po'; +import RolesPo from '@/cypress/e2e/po/pages/users-and-auth/roles.po'; +import PromptRemove from '@/cypress/e2e/po/prompts/promptRemove.po'; +import * as path from 'path'; + +const roles = new RolesPo('_'); +const usersPo = new UsersPo('_'); +const userCreate = usersPo.createEdit(); + +const runTimestamp = +new Date(); +const runPrefix = `e2e-test-${ runTimestamp }`; +const downloadsFolder = Cypress.config('downloadsFolder'); +const globalRoleName = `${ runPrefix }-my-global-role`; + +describe('Roles', { tags: '@adminUser' }, () => { + beforeEach(() => { + cy.login(); + }); + + it('can create a Global Role', () => { + // create global role + const fragment = 'GLOBAL'; + + roles.goTo(undefined, fragment); + roles.waitForPage(undefined, fragment); + roles.listCreate('Create Global Role'); + + const createGlobalRole = roles.createGlobal(); + + createGlobalRole.waitForPage('roleContext=GLOBAL', 'grant-resources'); + createGlobalRole.name().set(globalRoleName); + createGlobalRole.description().set('e2e-description'); + createGlobalRole.selectCreatorDefaultRadioBtn(0); + createGlobalRole.selectVerbs(0, 3); + createGlobalRole.selectVerbs(0, 4); + createGlobalRole.selectResourcesByLabelValue(0, 'GlobalRoles'); + createGlobalRole.saveAndWaitForRequests('POST', '/v3/globalroles').then((res) => { + const globalRoleId = res.response?.body.id; + + // view role details + roles.waitForPage(undefined, fragment); + roles.list().details(globalRoleName, 2).find('a').click(); + + const globalRoleDetails = roles.detailGlobal(globalRoleId); + + globalRoleDetails.waitForPage(); + globalRoleDetails.mastheadTitle().should('include', `${ globalRoleName }`); + }); + }); + + it('can create a Cluster Role', () => { + const clusterRoleName = `${ runPrefix }-my-cluster-role`; + const fragment = 'CLUSTER'; + + // create cluster role + roles.goTo(undefined, fragment); + + roles.waitForPage(undefined, fragment); + roles.listCreate('Create Cluster Role'); + + const createClusterRole = roles.createRole(); + + createClusterRole.waitForPage('roleContext=CLUSTER', 'grant-resources'); + createClusterRole.name().set(clusterRoleName); + createClusterRole.description().set('e2e-description'); + createClusterRole.selectCreatorDefaultRadioBtn(0); + createClusterRole.selectLockedRadioBtn(0); + createClusterRole.selectVerbs(0, 3); + createClusterRole.selectVerbs(0, 4); + createClusterRole.selectResourcesByLabelValue(0, 'ClusterRoles'); + createClusterRole.saveAndWaitForRequests('POST', '/v3/roletemplates').then((res) => { + const clusterRoleId = res.response?.body.id; + + // view role details + roles.waitForPage(undefined, fragment); + roles.list().details(clusterRoleName, 2).find('a').click(); + + const clusterRoleDetails = roles.detailRole(clusterRoleId); + + clusterRoleDetails.waitForPage(); + cy.contains(`Cluster - ${ clusterRoleName }`); + }); + }); + + it('can create a Project/Namespaces', () => { + const fragment = 'NAMESPACE'; + const projectRoleName = `${ runPrefix }-my-project-role`; + + // create project role + roles.goTo(undefined, fragment); + roles.waitForPage(undefined, fragment); + roles.listCreate('Create Project/Namespaces Role'); + + const createProjectRole = roles.createRole(); + + createProjectRole.waitForPage('roleContext=NAMESPACE', 'grant-resources'); + createProjectRole.name().set(projectRoleName); + createProjectRole.description().set('e2e-description'); + createProjectRole.selectCreatorDefaultRadioBtn(0); + createProjectRole.selectLockedRadioBtn(0); + createProjectRole.selectVerbs(0, 3); + createProjectRole.selectVerbs(0, 4); + createProjectRole.selectResourcesByLabelValue(0, 'Namespaces'); + createProjectRole.saveAndWaitForRequests('POST', '/v3/roletemplates').then((res) => { + const projectRoleId = res.response?.body.id; + + // view role details + roles.waitForPage(undefined, fragment); + roles.list().details(projectRoleName, 2).find('a').click(); + + const projectRoleDetails = roles.detailRole(projectRoleId); + + projectRoleDetails.waitForPage(); + cy.contains(`Project/Namespaces - ${ projectRoleName }`); + }); + }); + + it('can Download YAML', () => { + // Download YAML and verify file exists + + roles.waitForRequests(); + roles.list().elementWithName(globalRoleName).click(); + cy.intercept('GET', '/v1/management.cattle.io.globalroles/*').as('downloadYaml'); + roles.list().downloadYaml().click(); + cy.wait('@downloadYaml', { timeout: 10000 }).its('response.statusCode').should('eq', 200); + const downloadedFilename = path.join(downloadsFolder, `${ globalRoleName }.yaml`); + + cy.readFile(downloadedFilename).should('exist'); + }); + + it('can delete a role', () => { + // Delete role and verify role is removed from list + roles.waitForRequests(); + roles.list().elementWithName(globalRoleName).click(); + roles.list().delete().click(); + const promptRemove = new PromptRemove(); + + cy.intercept('DELETE', '/v3/globalRoles/*').as('deleteRole'); + promptRemove.remove(); + cy.wait('@deleteRole').its('response.statusCode').should('be.lessThan', 300); // Can sometimes be 204 + roles.list().elementWithName(globalRoleName).should('not.exist'); + }); + + it('Standard user with List, Get & Resources: Global Roles should be able to list users in Users and Auth', () => { + const customRoleName = `${ runPrefix }-my-custom-role`; + const standardUsername = `${ runPrefix }-standard-user-e2e`; + const standardPassword = 'standard-password'; + + // create global role + roles.goTo(); + roles.listCreate('Create Global Role'); + + const createGlobal = roles.createGlobal(); + + createGlobal.name().set(customRoleName); + createGlobal.selectVerbs(0, 3); + createGlobal.selectVerbs(0, 4); + createGlobal.selectResourcesByLabelValue(0, 'GlobalRoles'); + createGlobal.saveAndWaitForRequests('POST', '/v3/globalroles'); + + // create standard user + usersPo.goTo(); + usersPo.list().create(); + + userCreate.username().set(standardUsername); + userCreate.newPass().set(standardPassword); + userCreate.confirmNewPass().set(standardPassword); + userCreate.selectCheckbox(customRoleName).set(); + userCreate.saveAndWaitForRequests('POST', '/v3/globalrolebindings', true); + + // let's just check that the user is on the list view before attempting to login + usersPo.goTo(); + usersPo.waitForPage(); + usersPo.list().elementWithName(standardUsername).should('exist'); + + // logout admin + cy.logout(); + + // login as standard user + cy.login(standardUsername, standardPassword); + + // navigate to the roles page and make sure user can see it + roles.goTo(); + roles.list().masthead().title().should('contain', 'Role Templates'); + roles.list().elements().should('have.length.of.at.least', 1); + + // navigate to the users page and make sure user can see it + usersPo.waitForRequests(); + usersPo.list().masthead().title().should('contain', 'Users'); + usersPo.list().elements().should('have.length', 1); + }); +}); diff --git a/cypress/e2e/tests/pages/users-and-auth/users-and-auth.spec.ts b/cypress/e2e/tests/pages/users-and-auth/users-and-auth.spec.ts deleted file mode 100644 index 0e1ec55f01a..00000000000 --- a/cypress/e2e/tests/pages/users-and-auth/users-and-auth.spec.ts +++ /dev/null @@ -1,75 +0,0 @@ -import UserMenuPo from '@/cypress/e2e/po/side-bars/user-menu.po'; -import { LoginPagePo } from '~/cypress/e2e/po/pages/login-page.po'; -import UsersAndAuthPo from '~/cypress/e2e/po/pages/users-and-auth/users-and-auth.po'; - -const userMenu = new UserMenuPo(); -const loginPage = new LoginPagePo(); - -const runTimestamp = +new Date(); -const runPrefix = `e2e-test-${ runTimestamp }`; - -const customRoleName = `${ runPrefix }-my-custom-role`; -const standardUsername = `${ runPrefix }-standard-user`; -const standardPassword = 'standard-password'; - -describe('Users and Authentication', { tags: '@adminUser' }, () => { - it('Standard user with List, Get & Resources: Global Roles should be able to list users in Users and Auth', () => { - const userRoles = new UsersAndAuthPo('_', 'roles'); - const usersAdmin = new UsersAndAuthPo('_', 'management.cattle.io.user'); - - // last API call on the user creation process (with global role assignment) - cy.intercept('GET', '/v1/management.cattle.io.globalrolebindings?exclude=metadata.managedFields').as('globalRoleBindingCreated'); - - // this will login as admin - cy.login(); - - // create global role - userRoles.goTo(); - userRoles.listCreate(); - - userRoles.name().set(customRoleName); - userRoles.selectVerbs(0, 3); - userRoles.selectVerbs(0, 4); - userRoles.selectResourcesByLabelValue(0, 'GlobalRoles'); - userRoles.saveCreateForm().click(); - - // create standard user - usersAdmin.goTo(); - usersAdmin.listCreate(); - - usersAdmin.username().set(standardUsername); - usersAdmin.newPass().set(standardPassword); - usersAdmin.confirmNewPass().set(standardPassword); - usersAdmin.selectCheckbox(customRoleName).set(); - usersAdmin.saveCreateForm().click(); - - // we need to wait for that last API call to be finished to make sure we are ready for login - // with that new user - cy.wait('@globalRoleBindingCreated').then((res) => { - expect(res.response?.statusCode).to.equal(200); - - // let's just check that the user is on the list view before attempting to login - usersAdmin.goTo(); - usersAdmin.waitForPage(); - usersAdmin.listElementWithName(standardUsername).should('exist'); - - // logout admin - userMenu.clickMenuItem('Log Out'); - loginPage.waitForPage(); - loginPage.username().checkVisible(); - - // login as standard user - cy.login(standardUsername, standardPassword); - - // navigate to the roles page and make sure user can see it - userRoles.goTo(); - userRoles.listTitle().should('contain', 'Role Templates'); - userRoles.listElements().should('have.length.of.at.least', 1); - - // navigate to the users page and make sure user can see it - usersAdmin.goTo(); - usersAdmin.listTitle().should('contain', 'Users'); - usersAdmin.listElements().should('have.length.of.at.least', 1); - }); - }); -}); diff --git a/cypress/e2e/tests/pages/users-and-auth/users.spec.ts b/cypress/e2e/tests/pages/users-and-auth/users.spec.ts new file mode 100644 index 00000000000..e910d401a9f --- /dev/null +++ b/cypress/e2e/tests/pages/users-and-auth/users.spec.ts @@ -0,0 +1,234 @@ +import UsersPo from '@/cypress/e2e/po/pages/users-and-auth/users.po'; +import PromptRemove from '@/cypress/e2e/po/prompts/promptRemove.po'; +import * as path from 'path'; +import * as jsyaml from 'js-yaml'; +import MgmtUserEditPo from '@/cypress/e2e/po/edit/management.cattle.io.user.po'; + +const usersPo = new UsersPo('_'); +const userCreate = usersPo.createEdit(); + +const runTimestamp = +new Date(); +const runPrefix = `e2e-test-${ runTimestamp }`; +const downloadsFolder = Cypress.config('downloadsFolder'); +const standardUsername = `${ runPrefix }-standard-user`; +const standardPassword = 'standardUser-password'; +const userBaseUsername = `${ runPrefix }-userBase-user`; + +let userId: string; + +describe('Users', { tags: '@adminUser' }, () => { + beforeEach(() => { + cy.login(); + }); + + it('can create Admin', () => { + const adminUsername = `${ runPrefix }-admin-user`; + const adminPassword = 'admin-password'; + + usersPo.goTo(); + usersPo.list().create(); + + userCreate.waitForPage(); + userCreate.username().set(adminUsername); + userCreate.newPass().set(adminPassword); + userCreate.confirmNewPass().set(adminPassword); + userCreate.selectCheckbox('Administrator').set(); + userCreate.saveAndWaitForRequests('POST', '/v3/globalrolebindings'); + }); + + it('can create Restricted Admin', () => { + const restrictedAdminUsername = `${ runPrefix }-restrictedAdmin-user`; + const restrictedAdminPassword = 'restrictedAdmin-password'; + + usersPo.goTo(); + usersPo.list().create(); + + userCreate.waitForPage(); + userCreate.username().set(restrictedAdminUsername); + userCreate.newPass().set(restrictedAdminPassword); + userCreate.confirmNewPass().set(restrictedAdminPassword); + userCreate.selectCheckbox('Restricted Administrator').set(); + userCreate.saveAndWaitForRequests('POST', '/v3/globalrolebindings'); + }); + + it('can create User-Base', () => { + const userBasePassword = 'userBase-password'; + + usersPo.goTo(); + usersPo.list().create(); + + userCreate.waitForPage(); + userCreate.username().set(userBaseUsername); + userCreate.newPass().set(userBasePassword); + userCreate.confirmNewPass().set(userBasePassword); + userCreate.selectCheckbox('User-Base').set(); + userCreate.saveAndWaitForRequests('POST', '/v3/globalrolebindings'); + + // usersPo.goTo(); + usersPo.waitForPage(); + usersPo.list().elementWithName(userBaseUsername).should('be.visible'); + }); + + it('can create Standard User and view their details', () => { + usersPo.goTo(); + usersPo.list().create(); + + userCreate.username().set(standardUsername); + userCreate.newPass().set(standardPassword); + userCreate.confirmNewPass().set(standardPassword); + + // verify standard user checkbox selected by default + userCreate.selectCheckbox('Standard User').isChecked(); + + userCreate.saveAndWaitForRequests('POST', '/v3/globalrolebindings').then((res) => { + userId = res.response?.body.userId; + + usersPo.waitForPage(); + usersPo.list().elementWithName(standardUsername).should('be.visible'); + + // view user's details + usersPo.list().details(standardUsername, 2).find('a').click(); + + const userDetails = usersPo.detail(userId); + + userDetails.waitForPage(); + userDetails.mastheadTitle().should('contain', standardUsername); + }); + }); + + it('can Refresh Group Memberships', () => { + // Refresh Group Membership and verify request is made + usersPo.goTo(); + cy.intercept('POST', '/v3/users?action=refreshauthprovideraccess').as('refreshGroup'); + usersPo.list().refreshGroupMembership().click(); + cy.wait('@refreshGroup').its('response.statusCode').should('eq', 200); + }); + + describe('Action Menu', () => { + it('can Deactivate and Activate user', () => { + // Deactivate user and check state is Inactive + usersPo.goTo(); + usersPo.list().clickRowActionMenuItem(standardUsername, 'Deactivate'); + usersPo.list().details(standardUsername, 1).should('include.text', 'Inactive'); + + // Activate user and check state is Active + usersPo.list().clickRowActionMenuItem(standardUsername, 'Activate'); + usersPo.list().details(standardUsername, 1).should('include.text', 'Active'); + }); + + it('can Refresh Group Memberships', () => { + // Refresh Group Membership and verify request is made + cy.intercept('POST', `/v3/users/${ userId }?action=refreshauthprovideraccess`).as('refreshGroup'); + usersPo.waitForRequests(); + usersPo.list().clickRowActionMenuItem(standardUsername, 'Refresh Group Memberships'); + cy.wait('@refreshGroup').its('response.statusCode').should('eq', 200); + }); + + it('can Edit Config', () => { + // Edit user and make sure edit is saved + const userEdit = usersPo.createEdit(userId); + + usersPo.waitForRequests(); + usersPo.list().clickRowActionMenuItem(standardUsername, 'Edit Config'); + userEdit.waitForPage(); + userEdit.mastheadTitle().should('contain', standardUsername); + const mgmtUserEditPo = new MgmtUserEditPo(); + + mgmtUserEditPo.description().set('e2e_test'); + mgmtUserEditPo.saveAndWaitForRequests('PUT', `/v3/users/${ userId }`).then((res) => { + expect(res.response?.statusCode).to.equal(200); + expect(res.response?.body.description).to.equal('e2e_test'); + }); + }); + + it('can View YAML', () => { + // View YAML and verify user lands on correct page + + // We don't have a good pattern for the view/edit yaml page yet + const viewYaml = usersPo.createEdit(userId); + + usersPo.goTo(); + usersPo.list().clickRowActionMenuItem(standardUsername, 'View YAML'); + cy.url().should('include', `?mode=view&as=yaml`); + viewYaml.mastheadTitle().should('contain', standardUsername); + }); + + it('can Download YAML', () => { + // Download YAML and verify file exists + const downloadedFilename = path.join(downloadsFolder, `${ standardUsername }.yaml`); + + usersPo.goTo(); + usersPo.list().clickRowActionMenuItem(standardUsername, 'Download YAML'); + cy.readFile(downloadedFilename).should('exist').then((buffer) => { + const obj: any = jsyaml.load(buffer); + + // Basic checks on the downloaded YAML + expect(obj.username).to.equal(standardUsername); + expect(obj.apiVersion).to.equal('management.cattle.io/v3'); + expect(obj.kind).to.equal('User'); + }); + }); + + it('can Delete user', () => { + // Delete user and verify user is removed from list + usersPo.goTo(); + usersPo.list().clickRowActionMenuItem(standardUsername, 'Delete'); + + const promptRemove = new PromptRemove(); + + cy.intercept('DELETE', '/v3/users/*').as('deleteUser'); + promptRemove.confirm(standardUsername); + promptRemove.remove(); + cy.wait('@deleteUser').its('response.statusCode').should('eq', 200); + usersPo.list().elementWithName(standardUsername).should('not.exist'); + }); + }); + + describe('Bulk Actions', () => { + it('can Deactivate and Activate users', () => { + // Deactivate user and check state is Inactive + cy.intercept('PUT', '/v3/users/*').as('updateUsers'); + usersPo.waitForRequests(); + usersPo.list().selectAll().set(); + usersPo.list().deactivate().click(); + cy.wait('@updateUsers'); + cy.contains('Inactive'); + usersPo.list().details('admin', 1).should('include.text', 'Active'); + usersPo.list().details(userBaseUsername, 1).should('include.text', 'Inactive'); + + // Activate user and check state is Active + usersPo.list().activate().click(); + cy.wait('@updateUsers'); + usersPo.list().details(userBaseUsername, 1).should('include.text', 'Active'); + }); + + it('can Download YAML', () => { + // Download YAML and verify file exists + usersPo.waitForRequests(); + usersPo.list().selectAll().set(); + usersPo.list().openBulkActionDropdown(); + + cy.intercept('GET', '/v1/management.cattle.io.users/*').as('downloadYaml'); + usersPo.list().bulkActionButton('Download YAML').click(); + cy.wait('@downloadYaml', { timeout: 10000 }).its('response.statusCode').should('eq', 200); + const downloadedFilename = path.join(downloadsFolder, 'resources.zip'); + + cy.readFile(downloadedFilename).should('exist'); + }); + + it('can Delete user', () => { + // Delete user and verify user is removed from list + usersPo.waitForRequests(); + usersPo.list().elementWithName(userBaseUsername).click(); + usersPo.list().openBulkActionDropdown(); + usersPo.list().bulkActionButton('Delete').click(); + const promptRemove = new PromptRemove(); + + cy.intercept('DELETE', '/v3/users/*').as('deleteUser'); + promptRemove.confirm(userBaseUsername); + promptRemove.remove(); + cy.wait('@deleteUser').its('response.statusCode').should('eq', 200); + usersPo.list().elementWithName(userBaseUsername).should('not.exist'); + }); + }); +}); diff --git a/cypress/globals.d.ts b/cypress/globals.d.ts index 9cdd8c2d069..abe7555deb6 100644 --- a/cypress/globals.d.ts +++ b/cypress/globals.d.ts @@ -26,6 +26,7 @@ declare namespace Cypress { state(state: any): any; login(username?: string, password?: string, cacheSession?: boolean): Chainable; + logout(): Chainable; byLabel(label: string): Chainable; createUser(params: CreateUserParams): Chainable; diff --git a/cypress/support/commands/commands.ts b/cypress/support/commands/commands.ts index 23884eae0dd..f42b4615f96 100644 --- a/cypress/support/commands/commands.ts +++ b/cypress/support/commands/commands.ts @@ -43,6 +43,15 @@ Cypress.Commands.add('interceptAllRequests', (method = '/GET/POST/PUT/PATCH/', u return cy.wrap(interceptedUrls); }); +/** + * Logout of Rancher + */ +Cypress.Commands.add('logout', () => { + cy.intercept('POST', '/v3/tokens?action=logout').as('loggedOut'); + cy.visit('/auth/logout?logged-out=true'); + cy.wait('@loggedOut').its('response.statusCode').should('eq', 200); +}); + Cypress.Commands.add('iFrame', () => { return cy .get('[data-testid="ember-iframe"]', { log: false }) diff --git a/shell/components/auth/RoleDetailEdit.vue b/shell/components/auth/RoleDetailEdit.vue index d8371375836..2517a8fbda4 100644 --- a/shell/components/auth/RoleDetailEdit.vue +++ b/shell/components/auth/RoleDetailEdit.vue @@ -579,6 +579,7 @@ export default { name="storageSource" :label="defaultLabel" class="mb-10" + data-testid="roletemplate-creator-default-options" :options="newUserDefaultOptions" :mode="mode" /> @@ -592,6 +593,7 @@ export default { name="storageSource" :label="t('rbac.roletemplate.locked.label')" class="mb-10" + data-testid="roletemplate-locked-options" :options="lockedOptions" :mode="mode" />