From c76e3c278c238fc519995f6ddc2da8bc70b5ebef Mon Sep 17 00:00:00 2001 From: Steve Larson <9larsons@gmail.com> Date: Mon, 10 Aug 2026 09:20:41 -0500 Subject: [PATCH 1/9] Added per-role first-login smoke tests for Ghost Admin (#29815) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit no ref - New e2e spec `e2e/tests/admin/staff-role-smoke.test.ts`: every staff role performs a real first login in a fresh browser context and the test asserts the role's landing view plus the navigation it should (and should not) have: - **Administrator** → `/analytics`, full sidebar (Analytics, View site, Posts, Pages, Tags, Members, Settings) - **Editor** → `/site`, sidebar with Posts, Pages, Tags, Settings (no Analytics/View site/Members) - **Super Editor** → `/site`, Editor set plus Members - **Author** → `/site`, Posts and Pages only - **Contributor** → `/posts`, no sidebar at all — floating avatar menu with Posts / View site / Your profile - New Playwright fixtures `ghostAccountEditor`, `ghostAccountSuperEditor`, `ghostAccountAdministrator`, reusing the existing StaffAccountFactory invite + MailPit flow (Author/Contributor already existed). - New `ContributorUserMenu` page object backed by new selector constants in `@tryghost/test-data`. - Corrected the stale `NAV_ITEMS` role-visibility metadata in the sidebar page object (it claimed Editors see View site and Members, contradicting the shipped gating) and added `Super Editor` to its `UserRole` union. --- .../admin/sidebar/contributor-user-menu.ts | 26 +++++ e2e/helpers/pages/admin/sidebar/index.ts | 1 + .../pages/admin/sidebar/sidebar-page.ts | 21 ++-- e2e/helpers/playwright/fixture.ts | 15 +++ e2e/tests/admin/staff-role-smoke.test.ts | 97 +++++++++++++++++++ .../test-data/src/selectors/sidebar.ts | 4 + 6 files changed, 158 insertions(+), 6 deletions(-) create mode 100644 e2e/helpers/pages/admin/sidebar/contributor-user-menu.ts create mode 100644 e2e/tests/admin/staff-role-smoke.test.ts diff --git a/e2e/helpers/pages/admin/sidebar/contributor-user-menu.ts b/e2e/helpers/pages/admin/sidebar/contributor-user-menu.ts new file mode 100644 index 00000000000..7faaf94fb79 --- /dev/null +++ b/e2e/helpers/pages/admin/sidebar/contributor-user-menu.ts @@ -0,0 +1,26 @@ +import * as sidebarSel from '@tryghost/test-data/selectors/sidebar'; +import {AdminPage} from '@/admin-pages'; +import {Locator, Page} from '@playwright/test'; + +/** + * Contributors get a floating avatar menu instead of the admin sidebar + * (apps/admin/src/layout/admin-layout.tsx). + */ +export class ContributorUserMenu extends AdminPage { + public readonly trigger: Locator; + public readonly postsMenuItem: Locator; + public readonly viewSiteMenuItem: Locator; + public readonly profileMenuItem: Locator; + + constructor(page: Page) { + super(page); + this.trigger = page.getByRole('button', {name: sidebarSel.contributorMenuTrigger}); + this.postsMenuItem = page.getByRole('menuitem', {name: sidebarSel.contributorPostsMenuItem}); + this.viewSiteMenuItem = page.getByRole('menuitem', {name: sidebarSel.contributorViewSiteMenuItem}); + this.profileMenuItem = page.getByRole('menuitem', {name: sidebarSel.profileMenuItem}); + } + + async open(): Promise { + await this.trigger.click(); + } +} diff --git a/e2e/helpers/pages/admin/sidebar/index.ts b/e2e/helpers/pages/admin/sidebar/index.ts index d96ef6afe35..ca9558e1703 100644 --- a/e2e/helpers/pages/admin/sidebar/index.ts +++ b/e2e/helpers/pages/admin/sidebar/index.ts @@ -1 +1,2 @@ +export * from './contributor-user-menu'; export * from './sidebar-page'; diff --git a/e2e/helpers/pages/admin/sidebar/sidebar-page.ts b/e2e/helpers/pages/admin/sidebar/sidebar-page.ts index 6d1fb51f871..226c9c0d9be 100644 --- a/e2e/helpers/pages/admin/sidebar/sidebar-page.ts +++ b/e2e/helpers/pages/admin/sidebar/sidebar-page.ts @@ -3,7 +3,7 @@ import {AdminPage} from '@/admin-pages'; import {Locator, Page} from '@playwright/test'; import {whatsNewMenuItem} from '@tryghost/test-data/selectors/whats-new'; -export type UserRole = 'Administrator' | 'Editor' | 'Author' | 'Contributor'; +export type UserRole = 'Administrator' | 'Editor' | 'Super Editor' | 'Author' | 'Contributor'; export interface NavItem { name: string; @@ -15,15 +15,20 @@ export interface NavItem { /** * Navigation items in the sidebar with their expected paths and role visibility. * Used for navigation tests and force upgrade redirect validation. + * + * `roles` = roles whose sidebar shows the item (Owner sees everything + * Administrator does; Contributors have no sidebar at all — they get a + * floating user menu instead). Gating source: + * apps/admin/src/layout/app-sidebar/{nav-main,nav-content}.tsx. */ export const NAV_ITEMS: NavItem[] = [ {name: 'Analytics', path: /\/ghost\/#\/analytics\/?$/, directUrl: '/ghost/#/analytics', roles: ['Administrator']}, {name: 'Network', path: /\/ghost\/#\/(network|activitypub)\/?/, directUrl: '/ghost/#/activitypub', roles: ['Administrator']}, - {name: 'View site', path: /\/ghost\/#\/site\/?$/, directUrl: '/ghost/#/site', roles: ['Administrator', 'Editor']}, - {name: 'Posts', path: /\/ghost\/#\/posts\/?$/, directUrl: '/ghost/#/posts', roles: ['Administrator', 'Editor', 'Author', 'Contributor']}, - {name: 'Pages', path: /\/ghost\/#\/pages\/?$/, directUrl: '/ghost/#/pages', roles: ['Administrator', 'Editor']}, - {name: 'Tags', path: /\/ghost\/#\/tags\/?$/, directUrl: '/ghost/#/tags', roles: ['Administrator', 'Editor']}, - {name: 'Members', path: /\/ghost\/#\/members\/?$/, directUrl: '/ghost/#/members', roles: ['Administrator', 'Editor']} + {name: 'View site', path: /\/ghost\/#\/site\/?$/, directUrl: '/ghost/#/site', roles: ['Administrator']}, + {name: 'Posts', path: /\/ghost\/#\/posts\/?$/, directUrl: '/ghost/#/posts', roles: ['Administrator', 'Editor', 'Super Editor', 'Author']}, + {name: 'Pages', path: /\/ghost\/#\/pages\/?$/, directUrl: '/ghost/#/pages', roles: ['Administrator', 'Editor', 'Super Editor', 'Author']}, + {name: 'Tags', path: /\/ghost\/#\/tags\/?$/, directUrl: '/ghost/#/tags', roles: ['Administrator', 'Editor', 'Super Editor']}, + {name: 'Members', path: /\/ghost\/#\/members\/?$/, directUrl: '/ghost/#/members', roles: ['Administrator', 'Super Editor']} ]; /** @@ -36,6 +41,7 @@ export const NAV_ITEMS: NavItem[] = [ */ export class SidebarPage extends AdminPage { public readonly sidebar: Locator; + public readonly adminSidebar: Locator; public readonly postsToggle: Locator; public readonly userDropdownTrigger: Locator; public readonly appearanceMenuItem: Locator; @@ -57,6 +63,9 @@ export class SidebarPage extends AdminPage { // carry a breadcrumb