1+ import { Browser , BrowserContext , Page } from '@playwright/test' ;
2+ import { RequestUtils } from '@wordpress/e2e-test-utils-playwright' ;
3+ import { WordPressApiClient } from './wordpress-api-client' ;
4+
5+ export interface TestUser {
6+ username : string ;
7+ email : string ;
8+ password : string ;
9+ role : 'administrator' | 'customer' | 'shop_manager' ;
10+ displayName : string ;
11+ firstName : string ;
12+ lastName : string ;
13+ country : string ;
14+ state ?: string ;
15+ city : string ;
16+ address : string ;
17+ postcode : string ;
18+ phone : string ;
19+ }
20+
21+ export const TEST_USERS : Record < string , TestUser > = {
22+ ADMIN : {
23+ username : 'admin' ,
24+ email : 'admin@test.local' ,
25+ password : process . env . WP_ADMIN_PASS || 'admin' ,
26+ role : 'administrator' ,
27+ displayName : 'Test Admin' ,
28+ firstName : 'Test' ,
29+ lastName : 'Admin' ,
30+ country : 'ES' ,
31+ state : 'Barcelona' ,
32+ city : 'Barcelona' ,
33+ address : 'Test Street 123' ,
34+ postcode : '08001' ,
35+ phone : '+34666777888'
36+ } ,
37+ ES_CUSTOMER : {
38+ username : 'es_customer' ,
39+ email : 'customer@test.local' ,
40+ password : 'customer123' ,
41+ role : 'customer' ,
42+ displayName : 'Spanish Customer' ,
43+ firstName : 'Carlos' ,
44+ lastName : 'García' ,
45+ country : 'ES' ,
46+ state : 'Madrid' ,
47+ city : 'Madrid' ,
48+ address : 'Calle Mayor 1' ,
49+ postcode : '28001' ,
50+ phone : '+34600123456'
51+ } ,
52+ PT_CUSTOMER : {
53+ username : 'pt_customer' ,
54+ email : 'portugal@test.local' ,
55+ password : 'customer123' ,
56+ role : 'customer' ,
57+ displayName : 'Portuguese Customer' ,
58+ firstName : 'João' ,
59+ lastName : 'Silva' ,
60+ country : 'PT' ,
61+ city : 'Lisboa' ,
62+ address : 'Rua da Liberdade 1' ,
63+ postcode : '1250-096' ,
64+ phone : '+351911234567'
65+ } ,
66+ US_CUSTOMER : {
67+ username : 'us_customer' ,
68+ email : 'usa@test.local' ,
69+ password : 'customer123' ,
70+ role : 'customer' ,
71+ displayName : 'US Customer' ,
72+ firstName : 'John' ,
73+ lastName : 'Smith' ,
74+ country : 'US' ,
75+ state : 'CA' ,
76+ city : 'Los Angeles' ,
77+ address : '123 Main St' ,
78+ postcode : '90210' ,
79+ phone : '+1555123456'
80+ }
81+ } ;
82+
83+ export class UserAuthenticationManager {
84+ private apiClient : WordPressApiClient ;
85+ private requestUtils : RequestUtils | null = null ;
86+ private baseUrl : string ;
87+
88+ constructor ( apiClient : WordPressApiClient , baseUrl : string ) {
89+ this . apiClient = apiClient ;
90+ this . baseUrl = baseUrl ;
91+ }
92+
93+ async initializeRequestUtils ( requestContext : any ) : Promise < void > {
94+ this . requestUtils = new RequestUtils ( requestContext , {
95+ storageStatePath : 'tests/auth/admin-state.json'
96+ } ) ;
97+ await this . requestUtils . setupRest ( ) ;
98+ }
99+
100+ /**
101+ * Setup all test users during global setup
102+ */
103+ async setupTestUsers ( ) : Promise < void > {
104+ console . log ( '🧑💼 Setting up test users...' ) ;
105+
106+ for ( const [ userKey , userData ] of Object . entries ( TEST_USERS ) ) {
107+ if ( userKey === 'ADMIN' ) {
108+ // Skip admin as it should already exist
109+ console . log ( ` ✅ Admin user exists: ${ userData . username } ` ) ;
110+ continue ;
111+ }
112+
113+ await this . ensureUserExists ( userData ) ;
114+ }
115+
116+ console . log ( '✅ Test users setup complete' ) ;
117+ }
118+
119+ /**
120+ * Create user if it doesn't exist
121+ */
122+ private async ensureUserExists ( userData : TestUser ) : Promise < void > {
123+ try {
124+ // Check if user exists via API
125+ const existingUser = await this . getUserByUsername ( userData . username ) ;
126+
127+ if ( ! existingUser ) {
128+ console . log ( ` 🆕 Creating user: ${ userData . username } ` ) ;
129+ await this . createUser ( userData ) ;
130+ } else {
131+ console . log ( ` ✅ User exists: ${ userData . username } (ID: ${ existingUser . id } )` ) ;
132+ // Optionally update user data
133+ await this . updateUserBillingInfo ( existingUser . id , userData ) ;
134+ }
135+ } catch ( error ) {
136+ console . error ( ` ❌ Failed to setup user ${ userData . username } :` , error . message ) ;
137+ }
138+ }
139+
140+ /**
141+ * Get user by username via WordPress API
142+ */
143+ private async getUserByUsername ( username : string ) : Promise < any | null > {
144+ try {
145+ const response = await fetch ( `${ this . baseUrl } /wp-json/wp/v2/users?search=${ username } ` , {
146+ headers : {
147+ 'Authorization' : `Basic ${ Buffer . from ( `${ process . env . WORDPRESS_ADMIN_USER } :${ process . env . WP_API_APP_PASSWORD || process . env . WORDPRESS_ADMIN_PASSWORD } ` ) . toString ( 'base64' ) } ` ,
148+ 'Content-Type' : 'application/json'
149+ }
150+ } ) ;
151+
152+ if ( response . ok ) {
153+ const users = await response . json ( ) ;
154+ return users . find ( ( user : any ) => user . slug === username ) || null ;
155+ }
156+ return null ;
157+ } catch ( error ) {
158+ console . warn ( `Could not fetch user ${ username } :` , error . message ) ;
159+ return null ;
160+ }
161+ }
162+
163+ /**
164+ * Create new user via WordPress API
165+ */
166+ private async createUser ( userData : TestUser ) : Promise < any > {
167+ const userPayload = {
168+ username : userData . username ,
169+ email : userData . email ,
170+ password : userData . password ,
171+ roles : [ userData . role ] ,
172+ first_name : userData . firstName ,
173+ last_name : userData . lastName ,
174+ name : userData . displayName
175+ } ;
176+
177+ const response = await fetch ( `${ this . baseUrl } /wp-json/wp/v2/users` , {
178+ method : 'POST' ,
179+ headers : {
180+ 'Authorization' : `Basic ${ Buffer . from ( `${ process . env . WORDPRESS_ADMIN_USER } :${ process . env . WP_API_APP_PASSWORD || process . env . WORDPRESS_ADMIN_PASSWORD } ` ) . toString ( 'base64' ) } ` ,
181+ 'Content-Type' : 'application/json'
182+ } ,
183+ body : JSON . stringify ( userPayload )
184+ } ) ;
185+
186+ if ( ! response . ok ) {
187+ const errorText = await response . text ( ) ;
188+ throw new Error ( `Failed to create user ${ userData . username } : ${ errorText } ` ) ;
189+ }
190+
191+ const user = await response . json ( ) ;
192+ console . log ( ` ✅ User created: ${ user . name } (ID: ${ user . id } )` ) ;
193+
194+ // Set up billing information for WooCommerce
195+ await this . updateUserBillingInfo ( user . id , userData ) ;
196+
197+ return user ;
198+ }
199+
200+ /**
201+ * Update user's WooCommerce billing information
202+ */
203+ private async updateUserBillingInfo ( userId : number , userData : TestUser ) : Promise < void > {
204+ try {
205+ const customerData = {
206+ billing : {
207+ first_name : userData . firstName ,
208+ last_name : userData . lastName ,
209+ email : userData . email ,
210+ phone : userData . phone ,
211+ country : userData . country ,
212+ state : userData . state || '' ,
213+ city : userData . city ,
214+ address_1 : userData . address ,
215+ postcode : userData . postcode
216+ } ,
217+ shipping : {
218+ first_name : userData . firstName ,
219+ last_name : userData . lastName ,
220+ country : userData . country ,
221+ state : userData . state || '' ,
222+ city : userData . city ,
223+ address_1 : userData . address ,
224+ postcode : userData . postcode
225+ }
226+ } ;
227+
228+ // Use WooCommerce API to update customer
229+ const response = await this . apiClient . wooCommerce . put ( `customers/${ userId } ` , customerData ) ;
230+ console . log ( ` 💳 Billing info updated for user ${ userData . username } ` ) ;
231+ } catch ( error ) {
232+ console . warn ( ` ⚠️ Could not update billing info for ${ userData . username } :` , error . message ) ;
233+ }
234+ }
235+
236+ /**
237+ * Authenticate user and save storage state
238+ */
239+ async authenticateUser ( browser : Browser , userKey : string ) : Promise < string > {
240+ const userData = TEST_USERS [ userKey ] ;
241+ if ( ! userData ) {
242+ throw new Error ( `User ${ userKey } not found in TEST_USERS` ) ;
243+ }
244+
245+ console . log ( `🔐 Authenticating user: ${ userData . username } ` ) ;
246+
247+ const context = await browser . newContext ( {
248+ baseURL : this . baseUrl
249+ } ) ;
250+ const page = await context . newPage ( ) ;
251+
252+ try {
253+ // Go to login page
254+ await page . goto ( '/wp-login.php' ) ;
255+
256+ // Fill login form
257+ await page . fill ( '#user_login' , userData . username ) ;
258+ await page . fill ( '#user_pass' , userData . password ) ;
259+ await page . click ( '#wp-submit' ) ;
260+
261+ // Wait for successful login (redirect to dashboard or profile)
262+ await page . waitForURL ( url =>
263+ url . pathname . includes ( '/wp-admin/' ) ||
264+ url . pathname . includes ( '/my-account/' ) ||
265+ url . pathname === '/'
266+ ) ;
267+
268+ // Save authentication state
269+ const storageStatePath = `tests/auth/${ userKey . toLowerCase ( ) } -state.json` ;
270+ await context . storageState ( { path : storageStatePath } ) ;
271+
272+ console . log ( ` ✅ Authentication saved: ${ storageStatePath } ` ) ;
273+ return storageStatePath ;
274+
275+ } catch ( error ) {
276+ throw new Error ( `Failed to authenticate user ${ userData . username } : ${ error . message } ` ) ;
277+ } finally {
278+ await context . close ( ) ;
279+ }
280+ }
281+
282+ /**
283+ * Create all authentication states during global setup
284+ */
285+ async createAllAuthStates ( browser : Browser ) : Promise < Record < string , string > > {
286+ const authStates : Record < string , string > = { } ;
287+
288+ // Create guest state (no authentication)
289+ const guestContext = await browser . newContext ( { baseURL : this . baseUrl } ) ;
290+ const guestPage = await guestContext . newPage ( ) ;
291+ await guestPage . goto ( '/' ) ;
292+ const guestStatePath = 'tests/auth/guest-state.json' ;
293+ await guestContext . storageState ( { path : guestStatePath } ) ;
294+ await guestContext . close ( ) ;
295+ authStates [ 'GUEST' ] = guestStatePath ;
296+ console . log ( ' ✅ Guest state saved' ) ;
297+
298+ // Create authenticated states for each user
299+ for ( const userKey of Object . keys ( TEST_USERS ) ) {
300+ try {
301+ const statePath = await this . authenticateUser ( browser , userKey ) ;
302+ authStates [ userKey ] = statePath ;
303+ } catch ( error ) {
304+ console . error ( ` ❌ Failed to create auth state for ${ userKey } :` , error . message ) ;
305+ // Use guest state as fallback
306+ authStates [ userKey ] = guestStatePath ;
307+ }
308+ }
309+
310+ return authStates ;
311+ }
312+ }
0 commit comments