Skip to content

Commit c70d269

Browse files
committed
Fix browser tests and improve Playwright configuration
- Replace arbitrary waitForTimeout calls with requestAnimationFrame - Add proper timeouts: 60s for tests, 3s/10s for selectors (local/CI) - Add explicit return types to all async methods - Update lint script to include browser-tests - Add Playwright artifacts to .gitignore
1 parent e325b90 commit c70d269

File tree

7 files changed

+77
-131
lines changed

7 files changed

+77
-131
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ build
33
node_modules
44
*.log
55

6+
# Playwright
7+
browser-tests/playwright-report/
8+
browser-tests/test-results/
9+
610
# useful file for debugging a mocha unit test from cloud9, file contents: var Mocha = require("mocha");var mocha = new Mocha();mocha.addFile("test/createDom");mocha.run();
711
sandbox.js
812

browser-tests/playwright-report/index.html

Lines changed: 0 additions & 85 deletions
This file was deleted.

browser-tests/playwright.config.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,13 @@ export default defineConfig({
88
// Test files location
99
testDir: "./tests",
1010

11-
// Timeout for each test - 10s on CI, 3s locally
12-
timeout: process.env.CI ? 10000 : 3000,
11+
// Timeout for each test - 60 seconds
12+
timeout: 60000,
13+
14+
// Timeout for selectors/assertions - 3s locally, 10s on CI
15+
expect: {
16+
timeout: process.env.CI ? 10000 : 3000,
17+
},
1318

1419
// Run tests in files in parallel
1520
fullyParallel: true,
@@ -32,6 +37,9 @@ export default defineConfig({
3237
// Using 127.0.0.1 instead of localhost to avoid IPv6 resolution issues
3338
baseURL: "http://127.0.0.1:8080",
3439

40+
// Timeout for actions (click, fill, etc.) - 3s locally, 10s on CI
41+
actionTimeout: process.env.CI ? 10000 : 3000,
42+
3543
// Collect trace when retrying the failed test
3644
trace: "on-first-retry",
3745

browser-tests/test-results/.last-run.json

Lines changed: 0 additions & 4 deletions
This file was deleted.

browser-tests/tests/TodoPage.ts

Lines changed: 60 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -35,19 +35,33 @@ export class TodoPage {
3535
this.filterLinks = page.locator("#filters a");
3636
}
3737

38+
/**
39+
* Wait for one animation frame to allow maquette to render
40+
*/
41+
async waitForAnimationFrame(): Promise<void> {
42+
await this.page.evaluate(
43+
() => new Promise((resolve) => requestAnimationFrame(() => resolve(undefined)))
44+
);
45+
}
46+
3847
/**
3948
* Navigate to the TodoMVC application
4049
*/
41-
async goto() {
50+
async goto(): Promise<void> {
4251
await this.page.goto("/examples/todomvc/index.html");
52+
// Clear any existing data and reload for a fresh state
53+
await this.page.evaluate(() => {
54+
window.localStorage.removeItem("todomvc-maquette");
55+
});
56+
await this.page.reload();
4357
// Wait for the app to be ready
4458
await this.newTodoInput.waitFor();
4559
}
4660

4761
/**
4862
* Clear localStorage to reset application state
4963
*/
50-
async clearStorage() {
64+
async clearStorage(): Promise<void> {
5165
await this.page.evaluate(() => {
5266
window.localStorage.setItem("todomvc-maquette", "");
5367
});
@@ -56,17 +70,17 @@ export class TodoPage {
5670
/**
5771
* Add a new todo item
5872
*/
59-
async addTodo(text: string) {
73+
async addTodo(text: string): Promise<void> {
6074
await this.newTodoInput.fill(text);
6175
await this.newTodoInput.press("Enter");
62-
// Wait for the UI to update
63-
await this.page.waitForTimeout(50);
76+
// Wait for maquette to render
77+
await this.waitForAnimationFrame();
6478
}
6579

6680
/**
6781
* Add multiple todo items
6882
*/
69-
async addTodos(...texts: string[]) {
83+
async addTodos(...texts: string[]): Promise<void> {
7084
for (const text of texts) {
7185
await this.addTodo(text);
7286
}
@@ -82,23 +96,25 @@ export class TodoPage {
8296
/**
8397
* Toggle a todo item at the given index
8498
*/
85-
async toggleTodoAt(index: number) {
99+
async toggleTodoAt(index: number): Promise<void> {
86100
await this.todoItems.nth(index).locator(".toggle").click();
87-
await this.page.waitForTimeout(50);
101+
// Wait for maquette to render
102+
await this.waitForAnimationFrame();
88103
}
89104

90105
/**
91106
* Double-click a todo item to edit it
92107
*/
93-
async doubleClickTodoAt(index: number) {
108+
async doubleClickTodoAt(index: number): Promise<void> {
94109
await this.todoItems.nth(index).locator("label").dblclick();
95-
await this.page.waitForTimeout(50);
110+
// Wait for maquette to render
111+
await this.waitForAnimationFrame();
96112
}
97113

98114
/**
99115
* Edit the currently focused todo item
100116
*/
101-
async editTodo(text: string, options?: { submit?: "enter" | "escape" | "blur" }) {
117+
async editTodo(text: string, options?: { submit?: "enter" | "escape" | "blur" }): Promise<void> {
102118
const editInput = this.todoList.locator("input.edit");
103119
await editInput.clear();
104120
await editInput.fill(text);
@@ -112,84 +128,91 @@ export class TodoPage {
112128
// Click somewhere else to blur
113129
await this.toggleAll.focus();
114130
}
115-
await this.page.waitForTimeout(50);
131+
// Wait for maquette to render
132+
await this.waitForAnimationFrame();
116133
}
117134

118135
/**
119136
* Click the "Mark all as completed" checkbox
120137
*/
121-
async toggleAll_click() {
138+
async toggleAll_click(): Promise<void> {
122139
await this.toggleAll.click();
123-
await this.page.waitForTimeout(50);
140+
// Wait for maquette to render
141+
await this.waitForAnimationFrame();
124142
}
125143

126144
/**
127145
* Click the "Clear completed" button
128146
*/
129-
async clickClearCompleted() {
147+
async clickClearCompleted(): Promise<void> {
130148
await this.clearCompletedButton.click();
131-
await this.page.waitForTimeout(50);
149+
// Wait for maquette to render
150+
await this.waitForAnimationFrame();
132151
}
133152

134153
/**
135154
* Filter by "All" items
136155
*/
137-
async filterAll() {
156+
async filterAll(): Promise<void> {
138157
await this.filterLinks.nth(0).click();
139-
await this.page.waitForTimeout(50);
158+
// Wait for maquette to render
159+
await this.waitForAnimationFrame();
140160
}
141161

142162
/**
143163
* Filter by "Active" items
144164
*/
145-
async filterActive() {
165+
async filterActive(): Promise<void> {
146166
await this.filterLinks.nth(1).click();
147-
await this.page.waitForTimeout(50);
167+
// Wait for maquette to render
168+
await this.waitForAnimationFrame();
148169
}
149170

150171
/**
151172
* Filter by "Completed" items
152173
*/
153-
async filterCompleted() {
174+
async filterCompleted(): Promise<void> {
154175
await this.filterLinks.nth(2).click();
155-
await this.page.waitForTimeout(50);
176+
// Wait for maquette to render
177+
await this.waitForAnimationFrame();
156178
}
157179

158180
/**
159181
* Go back in browser history
160182
*/
161-
async goBack() {
183+
async goBack(): Promise<void> {
162184
await this.page.goBack();
163-
await this.page.waitForTimeout(50);
185+
// Wait for maquette to render after navigation
186+
await this.waitForAnimationFrame();
164187
}
165188

166189
// ==================== Assertions ====================
167190

168191
/**
169192
* Assert the new todo input is focused
170193
*/
171-
async assertInputFocused() {
194+
async assertInputFocused(): Promise<void> {
172195
await expect(this.newTodoInput).toBeFocused();
173196
}
174197

175198
/**
176199
* Assert the todo items match the expected texts
177200
*/
178-
async assertTodos(expected: string[]) {
201+
async assertTodos(expected: string[]): Promise<void> {
179202
await expect(this.todoItems.locator("label")).toHaveText(expected);
180203
}
181204

182205
/**
183206
* Assert the new todo input is empty
184207
*/
185-
async assertInputEmpty() {
208+
async assertInputEmpty(): Promise<void> {
186209
await expect(this.newTodoInput).toHaveValue("");
187210
}
188211

189212
/**
190213
* Assert the main section visibility
191214
*/
192-
async assertMainSectionVisible(visible: boolean) {
215+
async assertMainSectionVisible(visible: boolean): Promise<void> {
193216
if (visible) {
194217
await expect(this.mainSection).toBeVisible();
195218
} else {
@@ -200,7 +223,7 @@ export class TodoPage {
200223
/**
201224
* Assert the footer visibility
202225
*/
203-
async assertFooterVisible(visible: boolean) {
226+
async assertFooterVisible(visible: boolean): Promise<void> {
204227
if (visible) {
205228
await expect(this.footer).toBeVisible();
206229
} else {
@@ -211,7 +234,7 @@ export class TodoPage {
211234
/**
212235
* Assert which items are completed
213236
*/
214-
async assertCompletedStates(expectedStates: boolean[]) {
237+
async assertCompletedStates(expectedStates: boolean[]): Promise<void> {
215238
const count = await this.todoItems.count();
216239
expect(count).toBe(expectedStates.length);
217240

@@ -228,7 +251,7 @@ export class TodoPage {
228251
/**
229252
* Assert the "mark all" checkbox is checked
230253
*/
231-
async assertToggleAllChecked(checked: boolean) {
254+
async assertToggleAllChecked(checked: boolean): Promise<void> {
232255
if (checked) {
233256
await expect(this.toggleAll).toBeChecked();
234257
} else {
@@ -239,21 +262,21 @@ export class TodoPage {
239262
/**
240263
* Assert the todo count text
241264
*/
242-
async assertTodoCount(expected: string) {
265+
async assertTodoCount(expected: string): Promise<void> {
243266
await expect(this.todoCount).toHaveText(expected);
244267
}
245268

246269
/**
247270
* Assert the clear completed button text
248271
*/
249-
async assertClearCompletedText(expected: string) {
272+
async assertClearCompletedText(expected: string): Promise<void> {
250273
await expect(this.clearCompletedButton).toHaveText(expected);
251274
}
252275

253276
/**
254277
* Assert the clear completed button visibility
255278
*/
256-
async assertClearCompletedVisible(visible: boolean) {
279+
async assertClearCompletedVisible(visible: boolean): Promise<void> {
257280
if (visible) {
258281
await expect(this.clearCompletedButton).toBeVisible();
259282
} else {
@@ -264,21 +287,21 @@ export class TodoPage {
264287
/**
265288
* Assert which filter is currently selected
266289
*/
267-
async assertFilterSelected(index: number) {
290+
async assertFilterSelected(index: number): Promise<void> {
268291
await expect(this.filterLinks.nth(index)).toHaveClass("selected");
269292
}
270293

271294
/**
272295
* Assert the toggle checkbox is hidden for item at index (during editing)
273296
*/
274-
async assertItemToggleHidden(index: number) {
297+
async assertItemToggleHidden(index: number): Promise<void> {
275298
await expect(this.todoItems.nth(index).locator(".toggle")).toBeHidden();
276299
}
277300

278301
/**
279302
* Assert the label is hidden for item at index (during editing)
280303
*/
281-
async assertItemLabelHidden(index: number) {
304+
async assertItemLabelHidden(index: number): Promise<void> {
282305
await expect(this.todoItems.nth(index).locator("label")).toBeHidden();
283306
}
284307
}

browser-tests/tests/todomvc.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ test.describe("TodoMVC - Maquette", () => {
5252
});
5353

5454
test("should trim text input", async () => {
55-
await todoPage.addTodo(" " + TODO_ITEM_ONE + " ");
55+
await todoPage.addTodo(` ${TODO_ITEM_ONE} `);
5656
await todoPage.assertTodos([TODO_ITEM_ONE]);
5757
});
5858

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@
2626
"prepare": "husky",
2727
"prepublishOnly": "npm run clean && npm run dist",
2828
"release": "echo 'You can make a release by updating the version number in package.json and push the changes'",
29-
"lint": "eslint src test tools",
30-
"lint:fix": "eslint src test tools --fix",
29+
"lint": "eslint src test tools browser-tests",
30+
"lint:fix": "eslint src test tools browser-tests --fix",
3131
"format": "prettier --check \"src/**/*.ts\" \"test/**/*.ts\" \"tools/**/*.ts\"",
3232
"format:fix": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\" \"tools/**/*.ts\"",
3333
"clean": "rimraf dist build",

0 commit comments

Comments
 (0)