1- import { expect , Locator , Page } from '@playwright/test' ;
1+ import { expect , Locator , Page , test } from '@playwright/test' ;
22import type { ElementHandle } from 'playwright' ;
33
44interface VisibilityOptions {
@@ -25,6 +25,29 @@ interface ClickWithTextOptions {
2525 parentSelector ?: string ;
2626 isTargetChanged ?: boolean ;
2727 index ?: number ;
28+ wait ?: number ;
29+ }
30+
31+ interface BrowserAlertOptions {
32+ selector : string ;
33+ alertMessage ?: string ;
34+ isEqual ?: boolean ;
35+ index ?: number ;
36+ parentSelector ?: string ;
37+ wait ?: number ;
38+ }
39+
40+ interface BrowserAlertForMultipleHostsOptions extends BrowserAlertOptions {
41+ host : number ;
42+ }
43+
44+ interface CompareHostsOptions {
45+ selector : string ;
46+ extraHost : number ;
47+ isEqual ?: boolean ;
48+ index ?: number ;
49+ clickSelector ?: string ;
50+ wait ?: number ;
2851}
2952
3053interface ElementContainTextOptions {
@@ -68,8 +91,16 @@ export class BaseMethods {
6891 constructor ( protected readonly page : Page ) { }
6992
7093 private resolveLocator ( selector : string , options : { parentSelector ?: string ; text ?: string ; index ?: number } = { } ) : Locator {
94+ return this . resolveLocatorForPage ( this . page , selector , options ) ;
95+ }
96+
97+ private resolveLocatorForPage (
98+ page : Page ,
99+ selector : string ,
100+ options : { parentSelector ?: string ; text ?: string ; index ?: number } = { } ,
101+ ) : Locator {
71102 const { parentSelector, text, index } = options ;
72- let locator = parentSelector ? this . page . locator ( parentSelector ) . locator ( selector ) : this . page . locator ( selector ) ;
103+ let locator = parentSelector ? page . locator ( parentSelector ) . locator ( selector ) : page . locator ( selector ) ;
73104
74105 if ( text ) {
75106 locator = locator . filter ( { hasText : text } ) ;
@@ -200,7 +231,7 @@ export class BaseMethods {
200231 await expect ( locator ) . not . toHaveCount ( 0 ) ;
201232 }
202233
203- async clickElementWithText ( { selector, text, parentSelector, isTargetChanged = false , index } : ClickWithTextOptions ) : Promise < void > {
234+ async clickElementWithText ( { selector, text, parentSelector, isTargetChanged = false , index, wait } : ClickWithTextOptions ) : Promise < void > {
204235 const locator = this . resolveLocator ( selector , { parentSelector, text, index } ) ;
205236 const element = locator . first ( ) ;
206237
@@ -209,6 +240,10 @@ export class BaseMethods {
209240 }
210241
211242 await element . click ( ) ;
243+
244+ if ( wait && wait > 0 ) {
245+ await this . page . waitForTimeout ( wait ) ;
246+ }
212247 }
213248
214249 async checkElementContainText ( { selector, text, isContain = true , index, parentSelector } : ElementContainTextOptions ) : Promise < void > {
@@ -364,30 +399,190 @@ export class BaseMethods {
364399 await poller . not . toContain ( urlPart ) ;
365400 }
366401
402+ skipTestByCondition ( condition : unknown , reason : string = 'Skipped by condition' ) : void {
403+ if ( condition ) {
404+ test . info ( ) . skip ( reason ) ;
405+ }
406+ }
407+
408+ async checkBrowserAlertByText ( {
409+ selector,
410+ alertMessage,
411+ isEqual = true ,
412+ index = 0 ,
413+ parentSelector,
414+ wait = 0 ,
415+ } : BrowserAlertOptions ) : Promise < void > {
416+ const locator = this . resolveLocator ( selector , { parentSelector, index } ) ;
417+
418+ if ( wait > 0 ) {
419+ await this . page . waitForTimeout ( wait ) ;
420+ }
421+
422+ const message = await this . captureDialogMessage ( this . page , locator . first ( ) ) ;
423+
424+ if ( alertMessage !== undefined ) {
425+ if ( isEqual ) {
426+ expect ( message ) . toBe ( alertMessage ) ;
427+ } else {
428+ expect ( message ) . not . toBe ( alertMessage ) ;
429+ }
430+ }
431+ }
432+
433+ async checkBrowserAlertForMultipleHosts ( {
434+ selector,
435+ alertMessage,
436+ isEqual = true ,
437+ index = 0 ,
438+ parentSelector,
439+ host,
440+ wait = 0 ,
441+ } : BrowserAlertForMultipleHostsOptions ) : Promise < void > {
442+ const baseGroup = this . resolveLocator ( selector , { parentSelector } ) ;
443+ const baseCount = await baseGroup . count ( ) ;
444+
445+ if ( baseCount === 0 ) {
446+ throw new Error ( `No elements found for selector "${ selector } " on the base page.` ) ;
447+ }
448+
449+ const targetIndex = Math . min ( index , baseCount - 1 ) ;
450+
451+ if ( wait > 0 ) {
452+ await this . page . waitForTimeout ( wait ) ;
453+ }
454+
455+ const baseMessage = await this . captureDialogMessage ( this . page , baseGroup . nth ( targetIndex ) ) ;
456+
457+ if ( alertMessage !== undefined ) {
458+ if ( isEqual ) {
459+ expect ( baseMessage ) . toBe ( alertMessage ) ;
460+ } else {
461+ expect ( baseMessage ) . not . toBe ( alertMessage ) ;
462+ }
463+ }
464+
465+ const remotePage = await this . page . context ( ) . newPage ( ) ;
466+
467+ try {
468+ await remotePage . goto ( `http://localhost:${ host } /` , { waitUntil : 'networkidle' } ) ;
469+
470+ const remoteGroup = this . resolveLocatorForPage ( remotePage , selector , { parentSelector } ) ;
471+ const remoteCount = await remoteGroup . count ( ) ;
472+
473+ if ( remoteCount === 0 ) {
474+ throw new Error ( `No elements found for selector "${ selector } " on host ${ host } .` ) ;
475+ }
476+
477+ const remoteIndex = Math . min ( targetIndex , remoteCount - 1 ) ;
478+ const remoteMessage = await this . captureDialogMessage ( remotePage , remoteGroup . nth ( remoteIndex ) ) ;
479+
480+ if ( wait > 0 ) {
481+ await remotePage . waitForTimeout ( wait ) ;
482+ }
483+
484+ if ( isEqual ) {
485+ if ( alertMessage !== undefined ) {
486+ expect ( remoteMessage ) . toBe ( alertMessage ) ;
487+ }
488+
489+ expect ( remoteMessage ) . toBe ( baseMessage ) ;
490+ } else {
491+ if ( alertMessage !== undefined ) {
492+ expect ( remoteMessage ) . not . toBe ( alertMessage ) ;
493+ }
494+
495+ expect ( remoteMessage ) . not . toBe ( baseMessage ) ;
496+ }
497+ } finally {
498+ await remotePage . close ( ) ;
499+ }
500+ }
501+
367502 async compareInfoBetweenHosts (
368- selector : string ,
369- extraHost : number ,
370- isEqual : boolean = true ,
371- index : number = 0 ,
372- clickSelector ?: string ,
373- wait : number = 0 ,
503+ selectorOrOptions : string | CompareHostsOptions ,
504+ extraHostArg ? : number ,
505+ isEqualArg : boolean = true ,
506+ indexArg : number = 0 ,
507+ clickSelectorArg ?: string ,
508+ waitArg : number = 0 ,
374509 ) : Promise < void > {
375- const baseLocator = this . page . locator ( selector ) . nth ( index ) ;
376- const baseText = ( await baseLocator . innerText ( ) ) . trim ( ) ;
510+ let selector : string ;
511+ let extraHost : number ;
512+ let isEqual : boolean ;
513+ let index : number ;
514+ let clickSelector : string | undefined ;
515+ let wait : number ;
516+
517+ if ( typeof selectorOrOptions === 'string' ) {
518+ selector = selectorOrOptions ;
519+ if ( typeof extraHostArg !== 'number' ) {
520+ throw new Error ( 'The "extraHost" parameter must be provided when using the positional signature.' ) ;
521+ }
522+ extraHost = extraHostArg ;
523+ isEqual = isEqualArg ;
524+ index = indexArg ;
525+ clickSelector = clickSelectorArg ;
526+ wait = waitArg ;
527+ } else {
528+ ( { selector, extraHost, isEqual = true , index = 0 , clickSelector, wait = 0 } = selectorOrOptions ) ;
529+ }
530+
531+ const baseGroup = this . page . locator ( selector ) ;
532+ const baseCount = await baseGroup . count ( ) ;
533+
534+ if ( baseCount === 0 ) {
535+ throw new Error ( `No elements found for selector "${ selector } " on the base page.` ) ;
536+ }
537+
538+ const targetIndex = Math . min ( index , baseCount - 1 ) ;
539+
540+ if ( wait > 0 ) {
541+ await this . page . waitForTimeout ( wait ) ;
542+ }
543+
544+ const baseText = ( await baseGroup . nth ( targetIndex ) . innerText ( ) ) . trim ( ) ;
377545
378546 const remotePage = await this . page . context ( ) . newPage ( ) ;
379547
380548 try {
381549 await remotePage . goto ( `http://localhost:${ extraHost } /` , { waitUntil : 'networkidle' } ) ;
382550
383551 if ( clickSelector ) {
384- await remotePage . locator ( clickSelector ) . click ( ) ;
552+ const remoteClickGroup = remotePage . locator ( clickSelector ) ;
553+ const remoteClickCount = await remoteClickGroup . count ( ) ;
554+
555+ if ( remoteClickCount === 0 ) {
556+ throw new Error ( `No elements found for selector "${ clickSelector } " on host ${ extraHost } .` ) ;
557+ }
558+
559+ const clickIndex = Math . min ( targetIndex , remoteClickCount - 1 ) ;
560+
561+ try {
562+ await this . captureDialogMessage ( remotePage , remoteClickGroup . nth ( clickIndex ) ) ;
563+ } catch ( error ) {
564+ const isTimeoutError = error instanceof Error && / T i m e o u t / . test ( error . message ) ;
565+ if ( isTimeoutError ) {
566+ await remoteClickGroup . nth ( clickIndex ) . click ( ) ;
567+ } else {
568+ throw error ;
569+ }
570+ }
571+
385572 if ( wait > 0 ) {
386573 await remotePage . waitForTimeout ( wait ) ;
387574 }
388575 }
389576
390- const remoteText = ( await remotePage . locator ( selector ) . nth ( index ) . innerText ( ) ) . trim ( ) ;
577+ const remoteGroup = remotePage . locator ( selector ) ;
578+ const remoteCount = await remoteGroup . count ( ) ;
579+
580+ if ( remoteCount === 0 ) {
581+ throw new Error ( `No elements found for selector "${ selector } " on host ${ extraHost } .` ) ;
582+ }
583+
584+ const remoteIndex = Math . min ( targetIndex , remoteCount - 1 ) ;
585+ const remoteText = ( await remoteGroup . nth ( remoteIndex ) . innerText ( ) ) . trim ( ) ;
391586
392587 if ( isEqual ) {
393588 expect ( remoteText ) . toBe ( baseText ) ;
@@ -414,4 +609,16 @@ export class BaseMethods {
414609 return style . getPropertyValue ( property as string ) ;
415610 } , prop ) ;
416611 }
612+
613+ private async captureDialogMessage ( page : Page , locator : Locator ) : Promise < string > {
614+ const [ dialog ] = await Promise . all ( [
615+ page . waitForEvent ( 'dialog' , { timeout : 5_000 } ) ,
616+ locator . click ( ) ,
617+ ] ) ;
618+
619+ const message = dialog . message ( ) ;
620+ await dialog . accept ( ) ;
621+
622+ return message ;
623+ }
417624}
0 commit comments