@@ -7,6 +7,10 @@ import type { Page } from '@playwright/test';
77import path from 'path' ;
88import fs from 'fs' ;
99import os from 'os' ;
10+ import { inviteAndAcceptMember } from './utils/members' ;
11+
12+ // Tests that invite + accept members need more time
13+ test . describe . configure ( { timeout : 60000 } ) ;
1014
1115async function goToMembersPage ( page : Page ) {
1216 await page . goto ( PLAYWRIGHT_BASE_URL + '/members' ) ;
@@ -19,41 +23,45 @@ async function openInviteMemberModal(page: Page) {
1923 ] ) ;
2024}
2125
22- test ( 'test that new manager can be invited' , async ( { page } ) => {
26+ test ( 'test that new manager can be invited and accepted' , async ( { page, browser } ) => {
27+ const memberId = Math . round ( Math . random ( ) * 100000 ) ;
28+ const memberEmail = `manager+${ memberId } @invite.test` ;
29+
30+ await inviteAndAcceptMember ( page , browser , 'Invited Mgr' , memberEmail , 'Manager' ) ;
31+
32+ // Verify the member appears in the members table with the correct role
2333 await goToMembersPage ( page ) ;
24- await openInviteMemberModal ( page ) ;
25- const editorId = Math . round ( Math . random ( ) * 10000 ) ;
26- await page . getByLabel ( 'Email' ) . fill ( `new+${ editorId } @editor.test` ) ;
27- await page . getByRole ( 'button' , { name : 'Manager' } ) . click ( ) ;
28- await Promise . all ( [
29- page . getByRole ( 'button' , { name : 'Invite Member' , exact : true } ) . click ( ) ,
30- expect ( page . getByRole ( 'main' ) ) . toContainText ( `new+${ editorId } @editor.test` ) ,
31- ] ) ;
34+ const memberRow = page . getByRole ( 'row' ) . filter ( { hasText : 'Invited Mgr' } ) ;
35+ await expect ( memberRow ) . toBeVisible ( ) ;
36+ await expect ( memberRow . getByText ( 'Manager' , { exact : true } ) ) . toBeVisible ( ) ;
3237} ) ;
3338
34- test ( 'test that new employee can be invited' , async ( { page } ) => {
39+ test ( 'test that new employee can be invited and accepted' , async ( { page, browser } ) => {
40+ const memberId = Math . round ( Math . random ( ) * 100000 ) ;
41+ const memberEmail = `employee+${ memberId } @invite.test` ;
42+
43+ await inviteAndAcceptMember ( page , browser , 'Invited Emp' , memberEmail , 'Employee' ) ;
44+
45+ // Verify the member appears in the members table with the correct role
3546 await goToMembersPage ( page ) ;
36- await openInviteMemberModal ( page ) ;
37- const editorId = Math . round ( Math . random ( ) * 10000 ) ;
38- await page . getByLabel ( 'Email' ) . fill ( `new+${ editorId } @editor.test` ) ;
39- await page . getByRole ( 'button' , { name : 'Employee' } ) . click ( ) ;
40- await Promise . all ( [
41- page . getByRole ( 'button' , { name : 'Invite Member' , exact : true } ) . click ( ) ,
42- expect ( page . getByRole ( 'main' ) ) . toContainText ( `new+${ editorId } @editor.test` ) ,
43- ] ) ;
47+ const memberRow = page . getByRole ( 'row' ) . filter ( { hasText : 'Invited Emp' } ) ;
48+ await expect ( memberRow ) . toBeVisible ( ) ;
49+ await expect ( memberRow . getByText ( 'Employee' , { exact : true } ) ) . toBeVisible ( ) ;
4450} ) ;
4551
46- test ( 'test that new admin can be invited' , async ( { page } ) => {
52+ test ( 'test that new admin can be invited and accepted' , async ( { page, browser } ) => {
53+ const memberId = Math . round ( Math . random ( ) * 100000 ) ;
54+ const memberEmail = `admin+${ memberId } @invite.test` ;
55+
56+ await inviteAndAcceptMember ( page , browser , 'Invited Adm' , memberEmail , 'Administrator' ) ;
57+
58+ // Verify the member appears in the members table with the correct role
4759 await goToMembersPage ( page ) ;
48- await openInviteMemberModal ( page ) ;
49- const adminId = Math . round ( Math . random ( ) * 10000 ) ;
50- await page . getByLabel ( 'Email' ) . fill ( `new+${ adminId } @admin.test` ) ;
51- await page . getByRole ( 'button' , { name : 'Administrator' } ) . click ( ) ;
52- await Promise . all ( [
53- page . getByRole ( 'button' , { name : 'Invite Member' , exact : true } ) . click ( ) ,
54- expect ( page . getByRole ( 'main' ) ) . toContainText ( `new+${ adminId } @admin.test` ) ,
55- ] ) ;
60+ const memberRow = page . getByRole ( 'row' ) . filter ( { hasText : 'Invited Adm' } ) ;
61+ await expect ( memberRow ) . toBeVisible ( ) ;
62+ await expect ( memberRow . getByText ( 'Admin' , { exact : true } ) ) . toBeVisible ( ) ;
5663} ) ;
64+
5765test ( 'test that error shows if no role is selected' , async ( { page } ) => {
5866 await goToMembersPage ( page ) ;
5967 await openInviteMemberModal ( page ) ;
@@ -131,7 +139,7 @@ async function createPlaceholderMemberViaImport(page: Page, placeholderName: str
131139 fs . unlinkSync ( tmpFile ) ;
132140}
133141
134- test ( 'test that changing member role updates the role in the member table ' , async ( { page } ) => {
142+ test ( 'test that changing role of placeholder member is rejected ' , async ( { page } ) => {
135143 const placeholderName = 'RoleChange ' + Math . floor ( Math . random ( ) * 10000 ) ;
136144
137145 // Create a placeholder member via import
@@ -141,7 +149,7 @@ test('test that changing member role updates the role in the member table', asyn
141149 await goToMembersPage ( page ) ;
142150 const memberRow = page . getByRole ( 'row' ) . filter ( { hasText : placeholderName } ) ;
143151 await expect ( memberRow ) . toBeVisible ( ) ;
144- await expect ( memberRow . getByText ( 'Placeholder' ) ) . toBeVisible ( ) ;
152+ await expect ( memberRow . getByText ( 'Placeholder' , { exact : true } ) ) . toBeVisible ( ) ;
145153
146154 // Open the edit modal for the placeholder member
147155 await memberRow . getByRole ( 'button' ) . click ( ) ;
@@ -152,7 +160,53 @@ test('test that changing member role updates the role in the member table', asyn
152160 // Change role to Employee
153161 const roleSelect = page . getByRole ( 'dialog' ) . getByRole ( 'combobox' ) . first ( ) ;
154162 await roleSelect . click ( ) ;
163+ await expect ( page . getByRole ( 'option' , { name : 'Employee' } ) ) . toBeVisible ( ) ;
155164 await page . getByRole ( 'option' , { name : 'Employee' } ) . click ( ) ;
165+ await expect ( roleSelect ) . toContainText ( 'Employee' ) ;
166+
167+ // Submit the change - the API should reject it with 400
168+ await Promise . all ( [
169+ page . getByRole ( 'button' , { name : 'Update Member' } ) . click ( ) ,
170+ page . waitForResponse (
171+ ( response ) =>
172+ response . url ( ) . includes ( '/members/' ) &&
173+ response . request ( ) . method ( ) === 'PUT' &&
174+ response . status ( ) === 400
175+ ) ,
176+ ] ) ;
177+
178+ // Verify error notification is shown
179+ await expect ( page . getByText ( 'Failed to update member' ) ) . toBeVisible ( ) ;
180+ } ) ;
181+
182+ test ( 'test that changing member role updates the role in the member table' , async ( {
183+ page,
184+ browser,
185+ } ) => {
186+ const memberId = Math . floor ( Math . random ( ) * 100000 ) ;
187+ const memberEmail = `member+${ memberId } @rolechange.test` ;
188+
189+ // Invite and accept a new Employee member
190+ await inviteAndAcceptMember ( page , browser , 'Jane Smith' , memberEmail , 'Employee' ) ;
191+
192+ // Verify the new member appears with the Employee role
193+ await goToMembersPage ( page ) ;
194+ const memberRow = page . getByRole ( 'row' ) . filter ( { hasText : 'Jane Smith' } ) ;
195+ await expect ( memberRow ) . toBeVisible ( ) ;
196+ await expect ( memberRow . getByText ( 'Employee' , { exact : true } ) ) . toBeVisible ( ) ;
197+
198+ // Open the edit modal
199+ await memberRow . getByRole ( 'button' ) . click ( ) ;
200+ await page . getByRole ( 'menuitem' ) . getByText ( 'Edit' ) . click ( ) ;
201+ await expect ( page . getByRole ( 'dialog' ) ) . toBeVisible ( ) ;
202+ await expect ( page . getByRole ( 'heading' , { name : 'Update Member' } ) ) . toBeVisible ( ) ;
203+
204+ // Change role to Manager
205+ const roleSelect = page . getByRole ( 'dialog' ) . getByRole ( 'combobox' ) . first ( ) ;
206+ await roleSelect . click ( ) ;
207+ await expect ( page . getByRole ( 'option' , { name : 'Manager' } ) ) . toBeVisible ( ) ;
208+ await page . getByRole ( 'option' , { name : 'Manager' } ) . click ( ) ;
209+ await expect ( roleSelect ) . toContainText ( 'Manager' ) ;
156210
157211 // Submit the change and verify the API call succeeds
158212 await Promise . all ( [
@@ -169,7 +223,7 @@ test('test that changing member role updates the role in the member table', asyn
169223 await expect ( page . getByRole ( 'dialog' ) ) . not . toBeVisible ( ) ;
170224
171225 // Verify the role updated in the table
172- await expect ( memberRow . getByText ( 'Employee' ) ) . toBeVisible ( ) ;
226+ await expect ( memberRow . getByText ( 'Manager' , { exact : true } ) ) . toBeVisible ( ) ;
173227} ) ;
174228
175229test ( 'test that merging a placeholder member works' , async ( { page } ) => {
@@ -192,8 +246,8 @@ test('test that merging a placeholder member works', async ({ page }) => {
192246 await expect ( page . getByRole ( 'heading' , { name : 'Merge Member' } ) ) . toBeVisible ( ) ;
193247
194248 // Select the current user (the owner) as merge target via MemberCombobox
195- const combobox = page . getByRole ( 'dialog' ) . getByRole ( 'combobox' ) ;
196- await combobox . click ( ) ;
249+ // The MemberCombobox renders a Button as trigger; clicking it opens the popover with the combobox input
250+ await page . getByRole ( 'dialog' ) . getByRole ( 'button' , { name : 'Select a member...' } ) . click ( ) ;
197251
198252 // Wait for dropdown options to load
199253 const firstOption = page . getByRole ( 'option' ) . first ( ) ;
0 commit comments