11import { test , expect } from '@playwright/experimental-ct-react'
2- import { shouldSkipTurnstile , waitForTurnstileToken } from './turnstile'
2+ import { shouldSkipTurnstile , waitForTurnstileToken , type TurnstileApi , type TurnstileRenderOptions } from './turnstile'
33
44const container = { } as HTMLElement
55
6+ function mockTurnstile ( overrides : Partial < TurnstileApi > = { } ) : { api : TurnstileApi ; options : TurnstileRenderOptions [ ] } {
7+ const rendered : TurnstileRenderOptions [ ] = [ ]
8+ const api : TurnstileApi = {
9+ ready : ( callback ) => callback ( ) ,
10+ render : ( _container , options ) => {
11+ rendered . push ( options )
12+ return 'widget-1'
13+ } ,
14+ execute : ( ) => {
15+ rendered . at ( - 1 ) ?. callback ?.( 'tok' )
16+ } ,
17+ reset : ( ) => undefined ,
18+ remove : ( ) => undefined ,
19+ ...overrides ,
20+ }
21+ return { api, options : rendered }
22+ }
23+
624test ( 'skips Turnstile on CI-style loopback hosts' , ( ) => {
725 expect ( shouldSkipTurnstile ( '127.0.0.1' ) ) . toBe ( true )
826 expect ( shouldSkipTurnstile ( 'localhost' ) ) . toBe ( true )
@@ -19,41 +37,93 @@ test('returns empty when the Turnstile API is missing', async () => {
1937 expect ( token ) . toBe ( '' )
2038} )
2139
22- test ( 'does not hang when execute throws' , async ( ) => {
23- const token = await waitForTurnstileToken ( {
24- turnstile : {
25- execute : ( ) => {
26- throw new Error ( 'no widget' )
27- } ,
40+ test ( 'does not hang when render throws' , async ( ) => {
41+ const { api } = mockTurnstile ( {
42+ render : ( ) => {
43+ throw new Error ( 'no widget' )
2844 } ,
45+ } )
46+ const token = await waitForTurnstileToken ( {
47+ turnstile : api ,
2948 container,
3049 sitekey : 'sitekey' ,
3150 hostname : 'try.playwright.tech' ,
3251 } )
3352 expect ( token ) . toBe ( '' )
3453} )
3554
36- test ( 'resolves the success token' , async ( ) => {
55+ test ( 'renders with execution=execute then execute(widgetId)' , async ( ) => {
56+ const { api, options } = mockTurnstile ( )
57+ let executedWith : unknown
58+ let resetWith : unknown
59+ api . execute = ( target ) => {
60+ executedWith = target
61+ options . at ( - 1 ) ?. callback ?.( 'tok' )
62+ }
63+ api . reset = ( target ) => {
64+ resetWith = target
65+ }
66+
67+ const widgetIdRef = { current : null as string | null }
3768 const token = await waitForTurnstileToken ( {
38- turnstile : {
39- execute : ( _el , options ) => {
40- ; ( options . callback as ( value : string ) => void ) ( 'tok' )
41- } ,
42- } ,
69+ turnstile : api ,
4370 container,
4471 sitekey : 'sitekey' ,
4572 hostname : 'try.playwright.tech' ,
73+ widgetIdRef,
4674 } )
75+
4776 expect ( token ) . toBe ( 'tok' )
77+ expect ( widgetIdRef . current ) . toBe ( 'widget-1' )
78+ expect ( executedWith ) . toBe ( 'widget-1' )
79+ expect ( resetWith ) . toBe ( 'widget-1' )
80+ expect ( options [ 0 ] ) . toMatchObject ( {
81+ sitekey : 'sitekey' ,
82+ execution : 'execute' ,
83+ appearance : 'interaction-only' ,
84+ } )
85+ } )
86+
87+ test ( 'reuses the rendered widget on a later execute' , async ( ) => {
88+ let renderCount = 0
89+ const { api, options } = mockTurnstile ( {
90+ render : ( _container , renderOptions ) => {
91+ renderCount += 1
92+ options . push ( renderOptions )
93+ return 'widget-1'
94+ } ,
95+ } )
96+ api . execute = ( ) => {
97+ options . at ( - 1 ) ?. callback ?.( `tok-${ renderCount } ` )
98+ }
99+ const widgetIdRef = { current : null as string | null }
100+
101+ await waitForTurnstileToken ( {
102+ turnstile : api ,
103+ container,
104+ sitekey : 'sitekey' ,
105+ hostname : 'try.playwright.tech' ,
106+ widgetIdRef,
107+ } )
108+ const token = await waitForTurnstileToken ( {
109+ turnstile : api ,
110+ container,
111+ sitekey : 'sitekey' ,
112+ hostname : 'try.playwright.tech' ,
113+ widgetIdRef,
114+ } )
115+
116+ expect ( renderCount ) . toBe ( 1 )
117+ expect ( token ) . toBe ( 'tok-1' )
48118} )
49119
50120test ( 'resolves empty on error-callback' , async ( ) => {
121+ const { api, options } = mockTurnstile ( )
122+ api . execute = ( ) => {
123+ options . at ( - 1 ) ?. [ 'error-callback' ] ?.( )
124+ }
51125 const token = await waitForTurnstileToken ( {
52- turnstile : {
53- execute : ( _el , options ) => {
54- ; ( options [ 'error-callback' ] as ( ) => void ) ( )
55- } ,
56- } ,
126+ turnstile : api ,
57127 container,
58128 sitekey : 'sitekey' ,
59129 hostname : 'try.playwright.tech' ,
@@ -62,12 +132,11 @@ test('resolves empty on error-callback', async () => {
62132} )
63133
64134test ( 'times out when neither callback fires (interactive / bot challenge)' , async ( ) => {
135+ const { api } = mockTurnstile ( {
136+ execute : ( ) => undefined ,
137+ } )
65138 const token = await waitForTurnstileToken ( {
66- turnstile : {
67- execute : ( ) => {
68- // never calls back — same as Cloudflare dummy interactive key in Playwright
69- } ,
70- } ,
139+ turnstile : api ,
71140 container,
72141 sitekey : 'sitekey' ,
73142 hostname : 'try.playwright.tech' ,
@@ -76,19 +145,26 @@ test('times out when neither callback fires (interactive / bot challenge)', asyn
76145 expect ( token ) . toBe ( '' )
77146} )
78147
79- test ( 'skips execute entirely on 127.0.0.1' , async ( ) => {
148+ test ( 'skips render and execute entirely on 127.0.0.1' , async ( ) => {
149+ let rendered = false
80150 let executed = false
81- const token = await waitForTurnstileToken ( {
82- turnstile : {
83- execute : ( ) => {
84- executed = true
85- } ,
151+ const { api } = mockTurnstile ( {
152+ render : ( ) => {
153+ rendered = true
154+ return 'widget-1'
155+ } ,
156+ execute : ( ) => {
157+ executed = true
86158 } ,
159+ } )
160+ const token = await waitForTurnstileToken ( {
161+ turnstile : api ,
87162 container,
88163 sitekey : 'sitekey' ,
89164 hostname : '127.0.0.1' ,
90165 timeoutMs : 50 ,
91166 } )
167+ expect ( rendered ) . toBe ( false )
92168 expect ( executed ) . toBe ( false )
93169 expect ( token ) . toBe ( '' )
94170} )
0 commit comments