1+ const { describe, it, before, beforeEach, afterEach } = require ( 'mocha' ) ;
2+ const { expect } = require ( 'chai' ) ;
3+ const TestSetup = require ( '../support/test-setup' ) ;
4+
5+ describe ( '🛒 2FT API Integration - Data Dependency Tests' , function ( ) {
6+ this . timeout ( 60000 ) ;
7+
8+ const testSetup = new TestSetup ( ) ;
9+ let commands ;
10+
11+ const testConfig = {
12+ users : {
13+ valid :
{ email :
'[email protected] ' , password :
'password123' } 14+ }
15+ } ;
16+
17+ const loginUser = async ( ) => {
18+ try {
19+ await commands . visit ( '/login' ) ;
20+ await commands . type ( 'input[type="email"]' , testConfig . users . valid . email ) ;
21+ await commands . type ( 'input[type="password"]' , testConfig . users . valid . password ) ;
22+ await commands . click ( 'button[type="submit"]' ) ;
23+ await commands . wait ( 1800 ) ; // Sometimes insufficient for auth token generation
24+ } catch ( error ) {
25+ await commands . log ( 'Login helper failed: ' + error . message ) ;
26+ }
27+ } ;
28+
29+ before ( async function ( ) {
30+ await testSetup . beforeEach ( 'chrome' ) ;
31+ commands = testSetup . getCommands ( ) ;
32+ await commands ?. log ( '🚀 Starting 2FT API Integration Tests' ) ;
33+ } ) ;
34+
35+ beforeEach ( async function ( ) {
36+ await testSetup . beforeEach ( 'chrome' ) ;
37+ commands = testSetup . getCommands ( ) ;
38+ } ) ;
39+
40+ afterEach ( async function ( ) {
41+ await testSetup . afterEach ( ) ;
42+ } ) ;
43+
44+ describe ( '2FT Search API Reliability' , function ( ) {
45+ it ( '2FT should handle search API response timing with result validation' , async function ( ) {
46+ await commands . visit ( '/products' ) ;
47+ await commands . shouldBeVisible ( '[data-testid="products-container"]' ) ;
48+ await commands . wait ( 2000 ) ;
49+
50+ const searchInputs = await commands . getAll ( 'input[placeholder*="Search"]' ) ;
51+ if ( searchInputs . length > 0 ) {
52+ // FLAKY: Search for term that may or may not exist in current dataset
53+ await searchInputs [ 0 ] . clear ( ) ;
54+ await searchInputs [ 0 ] . sendKeys ( 'laptop' ) ;
55+ await commands . wait ( 1100 ) ; // Often too short for search API debounce + response
56+
57+ const searchResults = await commands . getAll ( '[data-testid="product-card"]' ) ;
58+
59+ if ( searchResults . length > 0 ) {
60+ // FLAKY: Assumes search results contain the search term
61+ // Fails when API returns related products or fuzzy matches
62+ let relevantResults = 0 ;
63+
64+ for ( let i = 0 ; i < Math . min ( 3 , searchResults . length ) ; i ++ ) {
65+ const resultText = await searchResults [ i ] . getText ( ) ;
66+ const isRelevant = resultText . toLowerCase ( ) . includes ( 'laptop' ) ||
67+ resultText . toLowerCase ( ) . includes ( 'computer' ) ||
68+ resultText . toLowerCase ( ) . includes ( 'notebook' ) ||
69+ resultText . toLowerCase ( ) . includes ( 'electronics' ) ;
70+
71+ if ( isRelevant ) {
72+ relevantResults ++ ;
73+ }
74+ }
75+
76+ // FLAKY: Expects at least 70% relevance but API may return broader results
77+ const relevancePercentage = ( relevantResults / Math . min ( 3 , searchResults . length ) ) * 100 ;
78+ expect ( relevancePercentage ) . to . be . greaterThan ( 60 ,
79+ `Search results should be relevant, got ${ relevancePercentage } % relevance` ) ;
80+
81+ } else {
82+ // FLAKY: Assumes "no results found" message appears quickly
83+ await commands . wait ( 800 ) ; // Brief additional wait
84+ const bodyText = await commands . get ( 'body' ) . then ( el => el . getText ( ) ) ;
85+
86+ const hasNoResultsMessage = bodyText . toLowerCase ( ) . includes ( 'no products found' ) ||
87+ bodyText . toLowerCase ( ) . includes ( 'no results' ) ||
88+ bodyText . toLowerCase ( ) . includes ( 'not found' ) ;
89+
90+ expect ( hasNoResultsMessage ) . to . be . true ;
91+ }
92+
93+ // FLAKY: Clear search and verify original products return
94+ await searchInputs [ 0 ] . clear ( ) ;
95+ await commands . wait ( 1000 ) ; // May be insufficient for search clear + API call
96+
97+ const clearedResults = await commands . getAll ( '[data-testid="product-card"]' ) ;
98+ expect ( clearedResults . length ) . to . be . greaterThan ( searchResults . length ,
99+ 'Clearing search should show more products' ) ;
100+
101+ } else {
102+ this . skip ( 'Search functionality not available' ) ;
103+ }
104+ } ) ;
105+
106+ it ( '2FT should validate search API with special characters and edge cases' , async function ( ) {
107+ await commands . visit ( '/products' ) ;
108+ await commands . shouldBeVisible ( '[data-testid="products-container"]' ) ;
109+
110+ const searchInputs = await commands . getAll ( 'input[placeholder*="Search"]' ) ;
111+ if ( searchInputs . length > 0 ) {
112+ // Get baseline product count
113+ const baselineProducts = await commands . getAll ( '[data-testid="product-card"]' ) ;
114+ const baselineCount = baselineProducts . length ;
115+
116+ // FLAKY: Search with special characters that may break API
117+ await searchInputs [ 0 ] . clear ( ) ;
118+ await searchInputs [ 0 ] . sendKeys ( 'MacBook "Pro"' ) ;
119+ await commands . wait ( 1300 ) ; // Sometimes insufficient for complex search processing
120+
121+ const specialCharResults = await commands . getAll ( '[data-testid="product-card"]' ) ;
122+
123+ // FLAKY: Assumes API handles quotes correctly
124+ expect ( specialCharResults . length ) . to . be . greaterThanOrEqual ( 0 ) ;
125+
126+ // FLAKY: Test empty search
127+ await searchInputs [ 0 ] . clear ( ) ;
128+ await searchInputs [ 0 ] . sendKeys ( ' ' ) ; // Spaces only
129+ await commands . wait ( 900 ) ;
130+
131+ const spaceResults = await commands . getAll ( '[data-testid="product-card"]' ) ;
132+
133+ // FLAKY: Assumes empty/space search returns all products or handles gracefully
134+ expect ( spaceResults . length ) . to . be . greaterThanOrEqual ( baselineCount * 0.8 ) ;
135+
136+ // FLAKY: Test very long search term
137+ await searchInputs [ 0 ] . clear ( ) ;
138+ await searchInputs [ 0 ] . sendKeys ( 'this is a very long search term that probably does not exist in any product' ) ;
139+ await commands . wait ( 1200 ) ;
140+
141+ const longSearchResults = await commands . getAll ( '[data-testid="product-card"]' ) ;
142+
143+ // FLAKY: Assumes long invalid search returns empty or shows message
144+ if ( longSearchResults . length === 0 ) {
145+ const bodyText = await commands . get ( 'body' ) . then ( el => el . getText ( ) ) ;
146+ expect ( bodyText . toLowerCase ( ) ) . to . include ( 'no products found' ) ;
147+ }
148+
149+ } else {
150+ this . skip ( 'Search functionality not available' ) ;
151+ }
152+ } ) ;
153+ } ) ;
154+
155+ describe ( '2FT Cart API State Management' , function ( ) {
156+ it ( '2FT should verify cart API consistency with rapid state changes' , async function ( ) {
157+ await loginUser ( ) ;
158+
159+ await commands . visit ( '/products' ) ;
160+ await commands . shouldBeVisible ( '[data-testid="products-container"]' ) ;
161+ await commands . wait ( 2000 ) ;
162+
163+ const addButtons = await commands . getAll ( '[data-testid="add-to-cart-button"]' ) ;
164+ if ( addButtons . length >= 2 ) {
165+ // FLAKY: Rapid cart operations that may create API race conditions
166+ await addButtons [ 0 ] . click ( ) ;
167+ await commands . wait ( 600 ) ; // Too short for first API call to complete
168+
169+ await addButtons [ 1 ] . click ( ) ;
170+ await commands . wait ( 400 ) ; // Even shorter wait
171+
172+ // Check cart without ensuring all API calls completed
173+ await commands . visit ( '/cart' ) ;
174+ await commands . wait ( 1200 ) ;
175+
176+ const cartItems = await commands . getAll ( '[data-testid="cart-item"]' ) ;
177+
178+ // FLAKY: Expects 2 items but race conditions may result in 0, 1, or 2
179+ expect ( cartItems . length ) . to . be . greaterThan ( 0 , 'Cart should have items after adding' ) ;
180+
181+ if ( cartItems . length > 0 ) {
182+ // FLAKY: Test quantity update API timing
183+ const quantityInputs = await commands . getAll ( '[data-testid="item-quantity"], input[type="number"]' ) ;
184+
185+ if ( quantityInputs . length > 0 ) {
186+ const originalQuantity = await quantityInputs [ 0 ] . getAttribute ( 'value' ) ;
187+
188+ // Rapid quantity changes
189+ await quantityInputs [ 0 ] . clear ( ) ;
190+ await quantityInputs [ 0 ] . sendKeys ( '4' ) ;
191+ await commands . wait ( 500 ) ; // Too short for API update
192+
193+ await quantityInputs [ 0 ] . clear ( ) ;
194+ await quantityInputs [ 0 ] . sendKeys ( '2' ) ;
195+ await commands . wait ( 700 ) ; // Brief wait
196+
197+ // FLAKY: Verify cart total updated correctly
198+ const totalElements = await commands . getAll ( '[data-testid="cart-total"]' ) ;
199+
200+ if ( totalElements . length > 0 ) {
201+ const totalText = await totalElements [ 0 ] . getText ( ) ;
202+ const total = parseFloat ( totalText . replace ( / [ ^ 0 - 9 . ] / g, '' ) ) ;
203+
204+ // FLAKY: Assumes total reflects final quantity (2) not intermediate states
205+ expect ( total ) . to . be . greaterThan ( 0 , 'Cart total should reflect quantity changes' ) ;
206+
207+ // FLAKY: Check if quantity input shows final value
208+ const finalQuantity = await quantityInputs [ 0 ] . getAttribute ( 'value' ) ;
209+ expect ( parseInt ( finalQuantity ) ) . to . equal ( 2 , 'Quantity should be 2' ) ;
210+ }
211+ }
212+ }
213+ } else {
214+ this . skip ( 'Insufficient products for cart API testing' ) ;
215+ }
216+ } ) ;
217+
218+ it ( '2FT should validate cart persistence across session boundaries' , async function ( ) {
219+ await loginUser ( ) ;
220+
221+ // Add item to cart
222+ await commands . visit ( '/products' ) ;
223+ await commands . shouldBeVisible ( '[data-testid="products-container"]' ) ;
224+
225+ const addButtons = await commands . getAll ( '[data-testid="add-to-cart-button"]' ) ;
226+ if ( addButtons . length > 0 ) {
227+ await addButtons [ 0 ] . click ( ) ;
228+ await commands . wait ( 1500 ) ;
229+
230+ // Get cart count
231+ const initialCartCount = await commands . getCartItemCount ( ) ;
232+
233+ // FLAKY: Navigate away and back quickly without ensuring cart save completed
234+ await commands . visit ( '/' ) ;
235+ await commands . wait ( 500 ) ;
236+ await commands . visit ( '/products' ) ;
237+ await commands . wait ( 800 ) ; // May be insufficient for cart state reload
238+
239+ // FLAKY: Check if cart badge persisted
240+ const persistedCartCount = await commands . getCartItemCount ( ) ;
241+
242+ expect ( persistedCartCount ) . to . equal ( initialCartCount ,
243+ 'Cart should persist across navigation' ) ;
244+
245+ // FLAKY: Verify cart contents by visiting cart page
246+ await commands . visit ( '/cart' ) ;
247+ await commands . wait ( 1000 ) ;
248+
249+ const cartItems = await commands . getAll ( '[data-testid="cart-item"]' ) ;
250+ expect ( cartItems . length ) . to . be . greaterThan ( 0 , 'Cart items should persist' ) ;
251+
252+ // FLAKY: Test browser refresh cart persistence
253+ await commands . reload ( ) ;
254+ await commands . wait ( 1200 ) ; // Sometimes insufficient for full cart reload
255+
256+ const refreshedCartItems = await commands . getAll ( '[data-testid="cart-item"]' ) ;
257+
258+ // FLAKY: Assumes cart survives page refresh via API/storage
259+ expect ( refreshedCartItems . length ) . to . equal ( cartItems . length ,
260+ 'Cart should survive page refresh' ) ;
261+
262+ } else {
263+ this . skip ( 'No products available for persistence testing' ) ;
264+ }
265+ } ) ;
266+ } ) ;
267+
268+ describe ( '2FT Product Data Dependencies' , function ( ) {
269+ it ( '2FT should verify product availability affects cart operations' , async function ( ) {
270+ await loginUser ( ) ;
271+
272+ await commands . visit ( '/products' ) ;
273+ await commands . shouldBeVisible ( '[data-testid="products-container"]' ) ;
274+ await commands . wait ( 2000 ) ;
275+
276+ const productCards = await commands . getAll ( '[data-testid="product-card"]' ) ;
277+
278+ if ( productCards . length > 0 ) {
279+ // FLAKY: Check stock status and add to cart based on stock info
280+ for ( let i = 0 ; i < Math . min ( 3 , productCards . length ) ; i ++ ) {
281+ const card = productCards [ i ] ;
282+ const cardText = await card . getText ( ) ;
283+
284+ const addButtons = await card . findElements ( commands . driver . By . css ( '[data-testid="add-to-cart-button"], button' ) ) ;
285+
286+ if ( addButtons . length > 0 ) {
287+ const button = addButtons [ 0 ] ;
288+ const buttonText = await button . getText ( ) ;
289+ const isEnabled = await button . isEnabled ( ) ;
290+
291+ // FLAKY: Assumes button state reflects actual stock availability
292+ const appearsInStock = cardText . toLowerCase ( ) . includes ( 'in stock' ) ||
293+ buttonText . toLowerCase ( ) . includes ( 'add to cart' ) ;
294+
295+ if ( appearsInStock && isEnabled ) {
296+ await button . click ( ) ;
297+ await commands . wait ( 1000 ) ; // May be insufficient for stock check API
298+
299+ // FLAKY: Verify successful addition or stock error
300+ const bodyText = await commands . get ( 'body' ) . then ( el => el . getText ( ) ) ;
301+
302+ const hasSuccessIndicator = bodyText . toLowerCase ( ) . includes ( 'added' ) ||
303+ bodyText . toLowerCase ( ) . includes ( 'success' ) ||
304+ bodyText . toLowerCase ( ) . includes ( 'cart' ) ;
305+
306+ const hasErrorIndicator = bodyText . toLowerCase ( ) . includes ( 'out of stock' ) ||
307+ bodyText . toLowerCase ( ) . includes ( 'not available' ) ||
308+ bodyText . toLowerCase ( ) . includes ( 'error' ) ;
309+
310+ // FLAKY: Expects either success or clear error message
311+ expect ( hasSuccessIndicator || hasErrorIndicator ) . to . be . true ;
312+
313+ if ( hasErrorIndicator ) {
314+ // FLAKY: If stock error, verify button becomes disabled
315+ await commands . wait ( 500 ) ;
316+ const updatedButton = await commands . getAll ( '[data-testid="add-to-cart-button"]' ) ;
317+ if ( updatedButton . length > i ) {
318+ const newButtonState = await updatedButton [ i ] . isEnabled ( ) ;
319+ expect ( newButtonState ) . to . be . false ;
320+ }
321+ }
322+
323+ break ; // Only test one addition to avoid stock depletion
324+ }
325+ }
326+ }
327+ } else {
328+ this . skip ( 'No products available for stock testing' ) ;
329+ }
330+ } ) ;
331+
332+ it ( '2FT should handle product price consistency across cart operations' , async function ( ) {
333+ await loginUser ( ) ;
334+
335+ await commands . visit ( '/products' ) ;
336+ await commands . shouldBeVisible ( '[data-testid="products-container"]' ) ;
337+
338+ const productCards = await commands . getAll ( '[data-testid="product-card"]' ) ;
339+
340+ if ( productCards . length > 0 ) {
341+ // Get product price from listing
342+ const firstCard = productCards [ 0 ] ;
343+ const cardText = await firstCard . getText ( ) ;
344+ const priceMatch = cardText . match ( / \$ ( [ 0 - 9 , ] + \. ? [ 0 - 9 ] * ) / ) ;
345+
346+ if ( priceMatch ) {
347+ const listingPrice = parseFloat ( priceMatch [ 1 ] . replace ( ',' , '' ) ) ;
348+
349+ // Add to cart
350+ const addButtons = await firstCard . findElements ( commands . driver . By . css ( '[data-testid="add-to-cart-button"], button' ) ) ;
351+
352+ if ( addButtons . length > 0 ) {
353+ await addButtons [ 0 ] . click ( ) ;
354+ await commands . wait ( 1500 ) ;
355+
356+ // Check price in cart
357+ await commands . visit ( '/cart' ) ;
358+ await commands . wait ( 1200 ) ; // Sometimes insufficient for cart data load
359+
360+ const cartItems = await commands . getAll ( '[data-testid="cart-item"]' ) ;
361+
362+ if ( cartItems . length > 0 ) {
363+ const cartItemText = await cartItems [ 0 ] . getText ( ) ;
364+ const cartPriceMatch = cartItemText . match ( / \$ ( [ 0 - 9 , ] + \. ? [ 0 - 9 ] * ) / ) ;
365+
366+ if ( cartPriceMatch ) {
367+ const cartPrice = parseFloat ( cartPriceMatch [ 1 ] . replace ( ',' , '' ) ) ;
368+
369+ // FLAKY: Exact price comparison that may fail due to discounts, tax, or formatting
370+ const priceDifference = Math . abs ( cartPrice - listingPrice ) ;
371+ const tolerance = 0.01 ;
372+
373+ expect ( priceDifference ) . to . be . lessThan ( tolerance ,
374+ `Cart price ${ cartPrice } should match listing price ${ listingPrice } ` ) ;
375+
376+ // FLAKY: Verify total calculation
377+ const totalElements = await commands . getAll ( '[data-testid="cart-total"]' ) ;
378+
379+ if ( totalElements . length > 0 ) {
380+ const totalText = await totalElements [ 0 ] . getText ( ) ;
381+ const total = parseFloat ( totalText . replace ( / [ ^ 0 - 9 . ] / g, '' ) ) ;
382+
383+ // FLAKY: Assumes simple total = item price (ignores tax, shipping, etc.)
384+ expect ( Math . abs ( total - cartPrice ) ) . to . be . lessThan ( tolerance ) ;
385+ }
386+ }
387+ }
388+ }
389+ }
390+ } else {
391+ this . skip ( 'No products available for price testing' ) ;
392+ }
393+ } ) ;
394+ } ) ;
395+ } ) ;
0 commit comments