1
+ import { createClerkClient } from '@clerk/backend' ;
1
2
import type { Clerk , SignOutOptions } from '@clerk/types' ;
2
3
import type { Page } from '@playwright/test' ;
3
4
@@ -15,36 +16,55 @@ type PlaywrightClerkLoadedParams = {
15
16
page : Page ;
16
17
} ;
17
18
19
+ type PlaywrightClerkSignInParamsWithEmail = {
20
+ page : Page ;
21
+ emailAddress : string ;
22
+ setupClerkTestingTokenOptions ?: SetupClerkTestingTokenOptions ;
23
+ } ;
24
+
18
25
type ClerkHelperParams = {
19
26
/**
20
- * Signs in a user using Clerk. This helper supports only password, phone_code and email_code first factor strategies.
27
+ * Signs in a user using Clerk. This helper supports multiple sign-in strategies:
28
+ * 1. Using signInParams object (password, phone_code, email_code strategies)
29
+ * 2. Using emailAddress for automatic ticket-based sign-in
30
+ *
21
31
* Multi-factor is not supported.
22
32
* This helper is using the `setupClerkTestingToken` internally.
23
33
* It is required to call `page.goto` before calling this helper, and navigate to a not protected page that loads Clerk.
24
34
*
35
+ * For strategy-based sign-in:
25
36
* If the strategy is password, the helper will sign in the user using the provided password and identifier.
26
37
* If the strategy is phone_code, you are required to have a user with a test phone number as an identifier (e.g. +15555550100).
27
38
* If the strategy is email_code, you are required to have a user with a test email as an identifier (e.g. [email protected] ).
28
39
*
29
- * @param opts.signInParams.strategy - The sign in strategy. Supported strategies are 'password', 'phone_code' and 'email_code'.
30
- * @param opts.signInParams.identifier - The user's identifier. Could be a username, a phone number or an email.
31
- * @param opts.signInParams.password - The user's password. Required only if the strategy is 'password'.
32
- * @param opts.page - The Playwright page object.
33
- * @param opts.setupClerkTestingTokenOptions - The options for the `setupClerkTestingToken` function. Optional.
40
+ * For email-based sign-in:
41
+ * The helper finds the user by email, creates a sign-in token using Clerk's backend API, and uses the ticket strategy.
34
42
*
35
- * @example
43
+ * @example Strategy-based sign-in
36
44
* import { clerk } from "@clerk/testing/playwright";
37
45
*
38
- * test("sign in", async ({ page }) => {
46
+ * test("sign in with strategy ", async ({ page }) => {
39
47
* await page.goto("/");
40
48
* await clerk.signIn({
41
49
* page,
42
50
* signInParams: { strategy: 'phone_code', identifier: '+15555550100' },
43
51
* });
44
52
* await page.goto("/protected");
45
53
* });
54
+ *
55
+ * @example Email-based sign-in
56
+ * import { clerk } from "@clerk/testing/playwright";
57
+ *
58
+ * test("sign in with email", async ({ page }) => {
59
+ * await page.goto("/");
60
+ * await clerk.signIn({ emailAddress: "bryce@clerk .dev", page });
61
+ * await page.goto("/protected");
62
+ * });
46
63
*/
47
- signIn : ( opts : PlaywrightClerkSignInParams ) => Promise < void > ;
64
+ signIn : {
65
+ ( opts : PlaywrightClerkSignInParams ) : Promise < void > ;
66
+ ( opts : PlaywrightClerkSignInParamsWithEmail ) : Promise < void > ;
67
+ } ;
48
68
/**
49
69
* Signs out the current user using Clerk.
50
70
* It is required to call `page.goto` before calling this helper, and navigate to a page that loads Clerk.
@@ -87,16 +107,56 @@ type PlaywrightClerkSignInParams = {
87
107
setupClerkTestingTokenOptions ?: SetupClerkTestingTokenOptions ;
88
108
} ;
89
109
90
- const signIn = async ( { page , signInParams , setupClerkTestingTokenOptions } : PlaywrightClerkSignInParams ) => {
91
- const context = page . context ( ) ;
110
+ const signIn = async ( opts : PlaywrightClerkSignInParams | PlaywrightClerkSignInParamsWithEmail ) => {
111
+ const context = opts . page . context ( ) ;
92
112
if ( ! context ) {
93
113
throw new Error ( 'Page context is not available. Make sure the page is properly initialized.' ) ;
94
114
}
95
115
96
- await setupClerkTestingToken ( { context, options : setupClerkTestingTokenOptions } ) ;
97
- await loaded ( { page } ) ;
116
+ await setupClerkTestingToken ( {
117
+ context,
118
+ options : 'setupClerkTestingTokenOptions' in opts ? opts . setupClerkTestingTokenOptions : undefined ,
119
+ } ) ;
120
+ await loaded ( { page : opts . page } ) ;
121
+
122
+ if ( 'emailAddress' in opts ) {
123
+ // Email-based sign-in using ticket strategy
124
+ const { emailAddress, page } = opts ;
125
+
126
+ const secretKey = process . env . CLERK_SECRET_KEY ;
127
+ if ( ! secretKey ) {
128
+ throw new Error ( 'CLERK_SECRET_KEY environment variable is required for email-based sign-in' ) ;
129
+ }
130
+
131
+ const clerkClient = createClerkClient ( { secretKey } ) ;
98
132
99
- await page . evaluate ( signInHelper , { signInParams } ) ;
133
+ try {
134
+ // Find user by email
135
+ const userList = await clerkClient . users . getUserList ( { emailAddress : [ emailAddress ] } ) ;
136
+ if ( ! userList . data || userList . data . length === 0 ) {
137
+ throw new Error ( `No user found with email: ${ emailAddress } ` ) ;
138
+ }
139
+
140
+ const user = userList . data [ 0 ] ;
141
+
142
+ const signInToken = await clerkClient . signInTokens . createSignInToken ( {
143
+ userId : user . id ,
144
+ expiresInSeconds : 300 , // 5 minutes
145
+ } ) ;
146
+
147
+ await page . evaluate ( signInHelper , {
148
+ signInParams : { strategy : 'ticket' as const , ticket : signInToken . token } ,
149
+ } ) ;
150
+
151
+ await page . waitForFunction ( ( ) => window . Clerk ?. user !== null ) ;
152
+ } catch ( err : any ) {
153
+ throw new Error ( `Failed to sign in with email ${ emailAddress } : ${ err ?. message } ` ) ;
154
+ }
155
+ } else {
156
+ // Strategy-based sign-in: signIn(opts)
157
+ const { page, signInParams } = opts ;
158
+ await page . evaluate ( signInHelper , { signInParams } ) ;
159
+ }
100
160
} ;
101
161
102
162
type PlaywrightClerkSignOutParams = {
@@ -113,7 +173,7 @@ const signOut = async ({ page, signOutOptions }: PlaywrightClerkSignOutParams) =
113
173
} ;
114
174
115
175
export const clerk : ClerkHelperParams = {
116
- signIn,
176
+ signIn : signIn as ClerkHelperParams [ 'signIn' ] ,
117
177
signOut,
118
178
loaded,
119
179
} ;
0 commit comments