1- import { ConsoleMessage , expect , Page } from '@playwright/test' ;
1+ import { ConsoleMessage , expect , Locator , Page , Route } from '@playwright/test' ;
22import { XHApi } from '@xh/hoist/core/XH' ;
33import { GridHelper } from './GridHelper' ;
44import { FilterSelectQuery } from './Types' ;
5+ import { isString } from 'lodash' ;
56
6- declare global {
7- interface Window {
8- XH : XHApi ;
9- }
7+ interface HoistPageCfg {
8+ baseUrl : string ;
9+ page : Page ;
1010}
11+ type Predicate = TestId | { text : string } ;
12+ type TestId = string ;
1113
1214/**
1315 * Base fixture for testing Hoist applications.
1416 */
17+
1518export class HoistPage {
1619 readonly page : Page ;
17- readonly baseURL : string ;
20+ private readonly baseUrl : string ;
1821
19- get maskLocator ( ) {
20- return this . page . locator ( '.xh-mask' ) ;
21- }
22-
23- constructor ( { page, baseURL} : { page : Page ; baseURL : string } ) {
22+ constructor ( { baseUrl, page} : HoistPageCfg ) {
23+ this . baseUrl = baseUrl ;
2424 this . page = page ;
25- this . baseURL = baseURL ;
2625 }
2726
28- async init ( ) {
27+ // -------------------------------
28+ // Lifecycle
29+ // -------------------------------
30+
31+ async initAsync ( ) : Promise < void > {
32+ await this . authAsync ( ) ;
33+
2934 this . page . on ( 'console' , msg => {
3035 if ( msg . type ( ) === 'error' ) this . onConsoleError ( msg ) ;
3136 } ) ;
3237
33- await this . authAsync ( ) ;
3438 await this . waitForAppToBeRunning ( ) ;
3539 }
3640
37- createGridHelper ( testId : string ) : GridHelper {
38- return new GridHelper ( this , testId ) ;
41+ onConsoleError ( msg : ConsoleMessage ) : void {
42+ throw new Error ( msg . text ( ) ) ;
43+ }
44+
45+ // -------------------------------
46+ // Lookups
47+ // -------------------------------
48+
49+ get ( q : Predicate ) : Locator {
50+ return isString ( q ) ? this . page . getByTestId ( q ) : this . page . getByText ( q . text ) ;
3951 }
4052
41- get ( testId : string ) {
42- return this . page . getByTestId ( testId ) ;
53+ async getInputAsync ( q : Predicate ) : Promise < Locator > {
54+ const elem = this . get ( q ) ,
55+ possibleInputs = [
56+ elem . locator ( 'input' ) ,
57+ elem . getByRole ( 'textbox' ) ,
58+ elem . locator ( 'textarea' ) ,
59+ this . page . locator ( 'label' , { has : elem } )
60+ ] ;
61+
62+ for ( let input of possibleInputs ) {
63+ if ( await input . isVisible ( ) ) return input ;
64+ }
65+ return elem ;
4366 }
4467
45- getByText ( text : string ) {
46- return this . page . getByText ( text ) ;
68+ getMask ( ) : Locator {
69+ return this . page . locator ( '.xh-mask' ) ;
4770 }
4871
49- async click ( testId : string ) {
50- await this . get ( testId ) . click ( ) ;
72+ // todo - understand this
73+ async getRoutesAsync ( ) : Promise < Route [ ] > {
74+ return this . page . evaluate ( ( ) => {
75+ window . XH . appModel . getRoutes ( ) ;
76+ } ) ;
5177 }
5278
53- async fill ( testId : string , value : string ) {
54- const elem = this . get ( testId ) ;
55- if ( await elem . locator ( 'input' ) . isVisible ( ) ) return elem . locator ( 'input' ) . fill ( value ) ;
56- if ( await elem . getByRole ( 'textbox' ) . isVisible ( ) )
57- return elem . getByRole ( 'textbox' ) . fill ( value ) ;
58- if ( await elem . locator ( 'textarea' ) . isVisible ( ) ) return elem . locator ( 'textarea' ) . fill ( value ) ;
79+ // -------------------------------
80+ // Actions
81+ // -------------------------------
82+
83+ async clickAsync ( q : Predicate ) : Promise < void > {
84+ return this . get ( q ) . click ( ) ;
85+ }
5986
60- return elem . fill ( value ) ;
87+ async fillAsync ( q : Predicate , value : string ) : Promise < void > {
88+ const input = await this . getInputAsync ( q ) ;
89+ return input . fill ( value ) ;
6190 }
6291
63- async clear ( testId : string ) {
64- const elem = this . get ( testId ) ;
65- if ( await elem . locator ( 'input' ) . isVisible ( ) ) return elem . locator ( 'input' ) . clear ( ) ;
66- if ( await elem . locator ( 'textarea' ) . isVisible ( ) )
67- return await elem . locator ( 'textarea' ) . clear ( ) ;
68- if ( await elem . locator ( testId ) . getByRole ( 'textbox' ) . isVisible ( ) )
69- return elem . getByRole ( 'textbox' ) . clear ( ) ;
70- await elem . clear ( ) ;
92+ async clearAsync ( q : Predicate ) : Promise < void > {
93+ const input = await this . getInputAsync ( q ) ;
94+ return input . clear ( ) ;
7195 }
7296
73- async select ( testId : string , selectionText : string ) {
97+ // todo - cleanup
98+ async selectAsync ( testId : string , selectionText : string ) : Promise < void > {
7499 await this . page . getByTestId ( testId ) . locator ( 'svg' ) . click ( ) ;
75100 await this . get ( `${ testId } -menu` ) . getByText ( selectionText ) . click ( ) ;
76101 }
77102
78- async filterThenClickSelectOption ( {
103+ // todo - cleanup
104+ async filterThenClickSelectOptionAsync ( {
79105 testId,
80106 filterText,
81107 selectionText,
82108 asyncOptionUrl
83109 } : FilterSelectQuery ) {
84- await this . fill ( testId , filterText ) ;
110+ await this . fillAsync ( testId , filterText ) ;
85111 const menu = this . get ( `${ testId } -menu` ) ;
86112 if ( asyncOptionUrl ) this . page . waitForResponse ( resp => resp . url ( ) . includes ( asyncOptionUrl ) ) ;
87113 selectionText
@@ -91,48 +117,41 @@ export class HoistPage {
91117
92118 // Checkboxes switches and radio inputs
93119 // Looks for and toggles the label that has the input that matches the given testId
94- async toggle ( testId : string ) {
95- await this . page . locator ( 'label' , { has : this . page . getByTestId ( testId ) } ) . click ( ) ;
120+ async toggleAsync ( q : Predicate ) : Promise < void > {
121+ return ( await this . getInputAsync ( q ) ) . click ( ) ;
96122 }
97123
98- async check ( testId : string ) {
99- await this . page . locator ( 'label' , { has : this . page . getByTestId ( testId ) } ) . check ( ) ;
124+ async checkAsync ( q : Predicate ) : Promise < void > {
125+ return ( await this . getInputAsync ( q ) ) . check ( ) ;
100126 }
101127
102- async uncheck ( testId : string ) {
103- await this . page . locator ( 'label' , { has : this . page . getByTestId ( testId ) } ) . uncheck ( ) ;
128+ async uncheckAsync ( q : Predicate ) : Promise < void > {
129+ return ( await this . getInputAsync ( q ) ) . uncheck ( ) ;
104130 }
105131
106- onConsoleError ( msg : ConsoleMessage ) {
107- throw new Error ( msg . text ( ) ) ;
108- }
109-
110- async getRoutes ( ) {
111- return this . page . evaluate ( ( ) => {
112- window . XH . appModel . getRoutes ( ) ;
113- } ) ;
114- }
115-
116- async toggleTheme ( ) {
132+ async toggleThemeAsync ( ) : Promise < void > {
117133 return this . page . evaluate ( ( ) => {
118134 window . XH . toggleTheme ( ) ;
119135 } ) ;
120136 }
121137
122- async waitForMaskToClear ( ) {
123- await expect ( this . maskLocator ) . toHaveCount ( 0 , { timeout : 10000 } ) ;
124- }
138+ // -------------------------------
139+ // Assertions
140+ // -------------------------------
125141
126- //Expect
127- async expectText ( testId : string , text : string ) {
128- await expect ( this . get ( testId ) ) . toHaveText ( text ) ;
142+ async expectTextAsync ( q : Predicate , text : string ) : Promise < void > {
143+ await expect ( this . get ( q ) ) . toHaveText ( text ) ;
129144 }
130145
131- async expectTextVisible ( text : string ) {
132- await expect ( this . getByText ( text ) ) . toBeVisible ( { timeout : 10000 } ) ;
146+ async expectVisibleAsync ( q : Predicate ) : Promise < void > {
147+ await expect ( this . get ( q ) ) . toBeVisible ( { timeout : 10000 } ) ;
133148 }
134149
135- async waitForAppToBeRunning ( ) {
150+ // -------------------------------
151+ // Utils
152+ // -------------------------------
153+
154+ async waitForAppToBeRunning ( ) : Promise < void > {
136155 const runHandle = async ( ) => {
137156 return this . page . evaluate ( ( ) => {
138157 return window . XH . appIsRunning ;
@@ -142,9 +161,32 @@ export class HoistPage {
142161 await expect . poll ( runHandle ) . toBeTruthy ( ) ;
143162 }
144163
145- //------------------------
164+ async waitForMaskToClear ( ) : Promise < void > {
165+ await expect ( this . getMask ( ) ) . toHaveCount ( 0 , { timeout : 10000 } ) ;
166+ }
167+
168+ // -------------------------------
169+ // Helper Factories
170+ // -------------------------------
171+
172+ // todo
173+ // createFormHelper(testId: string): FormHelper {
174+ // return new FormHelper(this, testId);
175+ // }
176+
177+ createGridHelper ( testId : string ) : GridHelper {
178+ return new GridHelper ( this , testId ) ;
179+ }
180+
181+ // -------------------------------
146182 // Implementation
147- //------------------------
183+ // ------- ------------------------
148184
149185 protected async authAsync ( ) { }
150186}
187+
188+ declare global {
189+ interface Window {
190+ XH : XHApi ;
191+ }
192+ }
0 commit comments