Skip to content

Commit 416ae5f

Browse files
committed
docs: Add synchronization and wait strategies guide for Playwright, covering WaitIUtils and advanced waits.
1 parent fa06c25 commit 416ae5f

File tree

3 files changed

+200
-0
lines changed

3 files changed

+200
-0
lines changed
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
# Playwright Synchronization & Wait Strategies
2+
3+
This guide demonstrates synchronization techniques in Playwright, from basic to advanced, and documents the enhanced `WaitIUtils` utility for robust, maintainable tests.
4+
5+
---
6+
7+
## Why Synchronization Matters
8+
- Web UIs are dynamic: elements may appear/disappear, load asynchronously, or change state.
9+
- Proper waits prevent flaky tests and ensure reliability.
10+
11+
---
12+
13+
## 1. Static Waits (Not Recommended)
14+
```js
15+
await page.waitForTimeout(2000); // Hard wait for 2 seconds
16+
```
17+
- Use only for debugging or as a last resort.
18+
19+
---
20+
21+
## 2. Dynamic Waits for Element State
22+
- **Visible:**
23+
```js
24+
await page.waitForSelector('.selector', { state: 'visible' });
25+
```
26+
- **Attached:**
27+
```js
28+
await page.waitForSelector('.selector', { state: 'attached' });
29+
```
30+
- **Hidden:**
31+
```js
32+
await page.waitForSelector('.selector', { state: 'hidden' });
33+
```
34+
- **Detached:**
35+
```js
36+
await page.waitForSelector('.selector', { state: 'detached' });
37+
```
38+
39+
---
40+
41+
## 3. Wait for Text Content
42+
```js
43+
await page.waitForFunction(
44+
sel => document.querySelector(sel)?.textContent.includes('expected'),
45+
'.selector'
46+
);
47+
```
48+
49+
---
50+
51+
## 4. Wait for Custom Condition
52+
```js
53+
await page.waitForFunction(() => document.querySelectorAll('.item').length >= 2);
54+
```
55+
56+
---
57+
58+
## 5. Wait for Network Idle
59+
```js
60+
await page.waitForLoadState('networkidle');
61+
```
62+
63+
---
64+
65+
## 6. Using the Enhanced WaitIUtils Utility
66+
67+
`utils/WaitIUtils.js` provides reusable, readable wait helpers:
68+
69+
```js
70+
const WaitIUtils = require('../utils/WaitIUtils');
71+
const waitUtil = new WaitIUtils(page);
72+
73+
await waitUtil.wait(1000); // Static wait
74+
await waitUtil.waitForVisible('.selector');
75+
await waitUtil.waitForAttached('.selector');
76+
await waitUtil.waitForHidden('.selector');
77+
await waitUtil.waitForDetached('.selector');
78+
await waitUtil.waitForText('.selector', 'expected text');
79+
await waitUtil.waitForFunction(() => ...); // Custom JS condition
80+
await waitUtil.waitForNetworkIdle();
81+
```
82+
83+
---
84+
85+
## 7. Example: SynchronizationTechniques.spec.js
86+
See `tests/sync/SynchronizationTechniques.spec.js` for:
87+
- Static waits
88+
- Waiting for visible/attached/hidden/detached
89+
- Waiting for text
90+
- Waiting for custom conditions
91+
- Waiting for network idle
92+
- Advanced: hover to reveal hidden elements before interaction
93+
94+
---
95+
96+
## Best Practices
97+
- Prefer dynamic waits over static waits.
98+
- Use utility methods for readability and maintainability.
99+
- Always wait for the expected state before interacting with elements.
100+
- Use custom conditions for complex scenarios.
101+
102+
---
103+
104+
**For more, see Playwright docs:** https://playwright.dev/docs/waiting
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
const { test, expect } = require('@playwright/test');
2+
const WaitIUtils = require('../../utils/WaitIUtils');
3+
4+
// Demo page with dynamic elements (replace with your own if needed)
5+
const DEMO_URL = 'https://demo.playwright.dev/todomvc';
6+
7+
test.describe('Synchronization Techniques: Basics to Advanced', () => {
8+
let waitUtil;
9+
10+
test.beforeEach(async ({ page }) => {
11+
waitUtil = new WaitIUtils(page);
12+
await page.goto(DEMO_URL);
13+
});
14+
15+
test('Static wait (not recommended for real tests)', async ({ page }) => {
16+
await waitUtil.wait(2000); // Hard wait for 2 seconds
17+
expect(await page.title()).toContain('React • TodoMVC');
18+
});
19+
20+
test('Wait for element to be visible', async ({ page }) => {
21+
await waitUtil.waitForVisible('.new-todo');
22+
await page.fill('.new-todo', 'Learn Playwright');
23+
await page.keyboard.press('Enter');
24+
await waitUtil.waitForVisible('.todo-list li');
25+
expect(await page.isVisible('.todo-list li')).toBe(true);
26+
});
27+
28+
test('Wait for element to be attached to DOM', async ({ page }) => {
29+
await waitUtil.waitForAttached('.new-todo');
30+
await page.fill('.new-todo', 'Wait for attach');
31+
await page.keyboard.press('Enter');
32+
await waitUtil.waitForAttached('.todo-list li');
33+
expect(await page.isVisible('.todo-list li')).toBe(true);
34+
});
35+
36+
test('Wait for element to be hidden', async ({ page }) => {
37+
await waitUtil.waitForVisible('.new-todo');
38+
await page.fill('.new-todo', 'Hide me');
39+
await page.keyboard.press('Enter');
40+
await waitUtil.waitForVisible('.todo-list li');
41+
// Hover over the todo item to reveal the .destroy button
42+
await page.hover('.todo-list li');
43+
await page.click('.destroy');
44+
await waitUtil.waitForHidden('.todo-list li');
45+
expect(await page.isVisible('.todo-list li')).toBe(false);
46+
});
47+
48+
test('Advanced: Wait for network idle', async ({ page }) => {
49+
// Reload and wait for network to be idle
50+
await Promise.all([
51+
page.waitForLoadState('networkidle'),
52+
page.reload(),
53+
]);
54+
expect(await page.title()).toContain('React • TodoMVC');
55+
});
56+
57+
test('Advanced: Wait for custom condition', async ({ page }) => {
58+
// Wait until there are at least 2 todos
59+
await waitUtil.waitForVisible('.new-todo');
60+
await page.fill('.new-todo', 'First');
61+
await page.keyboard.press('Enter');
62+
await page.fill('.new-todo', 'Second');
63+
await page.keyboard.press('Enter');
64+
await page.waitForFunction(() => document.querySelectorAll('.todo-list li').length >= 2);
65+
const count = await page.locator('.todo-list li').count();
66+
expect(count).toBeGreaterThanOrEqual(2);
67+
});
68+
});

utils/WaitIUtils.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,34 @@ class WaitIUtils {
2323
async waitForHidden(selector, timeout = 5000) {
2424
await this.page.waitForSelector(selector, { state: 'hidden', timeout });
2525
}
26+
27+
// Wait for selector to be detached from DOM
28+
async waitForDetached(selector, timeout = 5000) {
29+
await this.page.waitForSelector(selector, { state: 'detached', timeout });
30+
}
31+
32+
// Wait for element to contain specific text
33+
async waitForText(selector, text, timeout = 5000) {
34+
await this.page.waitForFunction(
35+
(sel, txt) => {
36+
const el = document.querySelector(sel);
37+
return el && el.textContent.includes(txt);
38+
},
39+
selector,
40+
text,
41+
{ timeout }
42+
);
43+
}
44+
45+
// Wait for a custom function/condition
46+
async waitForFunction(fn, arg, timeout = 5000) {
47+
await this.page.waitForFunction(fn, arg, { timeout });
48+
}
49+
50+
// Wait for network to be idle
51+
async waitForNetworkIdle(timeout = 5000) {
52+
await this.page.waitForLoadState('networkidle', { timeout });
53+
}
2654
}
2755

2856
module.exports = WaitIUtils;

0 commit comments

Comments
 (0)