Skip to content

Commit ae4ffc8

Browse files
author
Greg Solomon
committed
Greg tweaks
1 parent d7799f1 commit ae4ffc8

10 files changed

Lines changed: 840 additions & 769 deletions

File tree

client-app/.yarn/releases/yarn-1.22.19.cjs

Lines changed: 631 additions & 631 deletions
Large diffs are not rendered by default.

playwright/fixtures/todo.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ export class TodoPage extends HoistPage {
88
await doAuthZeroLogin(this.page, 'todo');
99
}
1010

11-
override async init() {
12-
await super.init();
13-
await this.click('reset-button');
11+
override async initAsync() {
12+
await super.initAsync();
13+
await this.clickAsync('reset-button');
1414
}
1515

1616
get grid(): GridHelper {
@@ -21,7 +21,7 @@ export class TodoPage extends HoistPage {
2121
export const test = baseTest.extend<{todo: TodoPage}>({
2222
todo: async ({page, baseURL}, use) => {
2323
const todoPage = new TodoPage({page, baseURL});
24-
await todoPage.init();
24+
await todoPage.initAsync();
2525
await use(todoPage);
2626
}
2727
});

playwright/fixtures/toolbox.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export class TBoxPage extends HoistPage {
2828
}
2929

3030
async getTabHierarchy(): Promise<PlainObject[]> {
31-
const routes = await this.getRoutes();
31+
const routes = await this.getRoutesAsync();
3232
return routes[0].children.map(route => {
3333
return {
3434
id: route.name,
@@ -52,7 +52,7 @@ export class TBoxPage extends HoistPage {
5252
export const test = baseTest.extend<{tb: TBoxPage}>({
5353
tb: async ({page, baseURL}, use) => {
5454
const tbPage = new TBoxPage({page, baseURL});
55-
await tbPage.init();
55+
await tbPage.initAsync();
5656
await use(tbPage);
5757
}
5858
});

playwright/hoist/GridHelper.ts

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,10 @@ import {HoistPage} from './HoistPage';
33
import {PlainObject} from '@xh/hoist/core';
44

55
export class GridHelper {
6-
private readonly hoistPage: HoistPage;
7-
private readonly testId: string;
8-
9-
constructor(hoistPage: HoistPage, testId: string) {
10-
this.hoistPage = hoistPage;
11-
this.testId = testId;
12-
}
6+
constructor(
7+
private readonly hoistPage: HoistPage,
8+
private readonly testId: string
9+
) {}
1310

1411
get page(): Page {
1512
return this.hoistPage.page;

playwright/hoist/HoistPage.ts

Lines changed: 110 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,87 +1,113 @@
1-
import {ConsoleMessage, expect, Page} from '@playwright/test';
1+
import {ConsoleMessage, expect, Locator, Page, Route} from '@playwright/test';
22
import {XHApi} from '@xh/hoist/core/XH';
33
import {GridHelper} from './GridHelper';
44
import {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+
1518
export 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+
}

playwright/package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@
99
"@playwright/test": "^1.36.2",
1010
"@xh/hoist": "^59.0.1",
1111
"dotenv": "16.x",
12+
"lodash": "4.x",
1213
"prettier": "3.x"
14+
},
15+
"devDependencies": {
16+
"@types/lodash": "^4.14.199"
1317
}
1418
}

0 commit comments

Comments
 (0)