@mechris3/glassbox 0.1.4 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +92 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -102,7 +102,26 @@ await fetch('http://...', { headers: { Authorization: `Bearer ${apiKey}` } });
|
|
|
102
102
|
|
|
103
103
|
### Page Objects
|
|
104
104
|
|
|
105
|
-
Extend `BasePage` — no constructor needed
|
|
105
|
+
Extend `BasePage` — no constructor needed. The following methods are available via `this`:
|
|
106
|
+
|
|
107
|
+
| Method | Description |
|
|
108
|
+
|--------|-------------|
|
|
109
|
+
| `click(selector)` | Click an element |
|
|
110
|
+
| `fill(selector, value)` | Clear and set an input's value |
|
|
111
|
+
| `type(selector, value, {delay?})` | Type character by character (for autocomplete etc.) |
|
|
112
|
+
| `getText(selector)` | Get an element's text content |
|
|
113
|
+
| `getInputValue(selector)` | Get an input's current value |
|
|
114
|
+
| `getAttribute(selector, attr)` | Get an element's attribute value |
|
|
115
|
+
| `countElements(selector)` | Count matching elements |
|
|
116
|
+
| `isVisible(selector)` | Check if element is visible |
|
|
117
|
+
| `isDisabled(selector)` | Check if element is disabled |
|
|
118
|
+
| `waitForSelector(selector, {timeout?})` | Wait for element to appear (default 5s) |
|
|
119
|
+
| `waitForHidden(selector, {timeout?})` | Wait for element to disappear |
|
|
120
|
+
| `goto(url)` | Navigate (relative URLs resolve against target URL) |
|
|
121
|
+
| `evaluate(script)` | Execute arbitrary JS in the page |
|
|
122
|
+
| `clearSession(origin?)` | Clear cookies and storage |
|
|
123
|
+
|
|
124
|
+
Example:
|
|
106
125
|
|
|
107
126
|
```typescript
|
|
108
127
|
// page-objects/login.page.ts
|
|
@@ -179,7 +198,78 @@ export default defineConfig({
|
|
|
179
198
|
});
|
|
180
199
|
```
|
|
181
200
|
|
|
182
|
-
###
|
|
201
|
+
### Best Practices
|
|
202
|
+
|
|
203
|
+
### Journeys are pure orchestration
|
|
204
|
+
|
|
205
|
+
A journey's `execute()` method should contain **only** calls to page object methods — no conditionals, no assertions, no direct selectors:
|
|
206
|
+
|
|
207
|
+
```typescript
|
|
208
|
+
// Good — reads like a user story
|
|
209
|
+
async execute() {
|
|
210
|
+
await this.loginPage.loginAs('alice@company.com', 'alice123');
|
|
211
|
+
await this.dashboardPage.verifyLoaded();
|
|
212
|
+
await this.dashboardPage.navigateToProjects();
|
|
213
|
+
await this.projectsPage.verifyProjectCount(6);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Bad — logic leaking into journey
|
|
217
|
+
async execute() {
|
|
218
|
+
await this.fill('[data-testid="email"]', 'alice@company.com');
|
|
219
|
+
const count = await this.getText('[data-testid="count"]');
|
|
220
|
+
if (parseInt(count) !== 6) throw new Error('wrong');
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Page objects own everything
|
|
225
|
+
|
|
226
|
+
- **Selectors** — store in a private `selectors` object, never expose them
|
|
227
|
+
- **Interaction** — `loginAs()`, `addTask()`, `filterByStatus()` — intention-revealing names
|
|
228
|
+
- **Validation** — `verifyLoaded()`, `verifyErrorMessage()`, `verifyTaskCount()` — throw descriptive errors on failure
|
|
229
|
+
|
|
230
|
+
```typescript
|
|
231
|
+
export class ProjectsPage extends BasePage {
|
|
232
|
+
private selectors = {
|
|
233
|
+
projectCard: '[data-testid="project-card"]',
|
|
234
|
+
filterActive: '[data-testid="filter-active"]',
|
|
235
|
+
count: '[data-testid="project-count"]',
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
async filterByActive() {
|
|
239
|
+
await this.click(this.selectors.filterActive);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
async verifyProjectCount(expected: number) {
|
|
243
|
+
const count = await this.getText(this.selectors.count);
|
|
244
|
+
if (count !== String(expected)) {
|
|
245
|
+
throw new Error(`Expected ${expected} projects, got ${count}`);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### Selector strategy
|
|
252
|
+
|
|
253
|
+
- Always use `[data-testid="..."]` — never CSS classes, tag names, or DOM structure
|
|
254
|
+
- For repeated items, include an identifier: `[data-testid="task-item-${id}"]`
|
|
255
|
+
- If an element doesn't have a `data-testid`, add one to your app first
|
|
256
|
+
|
|
257
|
+
### File organisation
|
|
258
|
+
|
|
259
|
+
```
|
|
260
|
+
glassbox/
|
|
261
|
+
├── page-objects/
|
|
262
|
+
│ ├── login.page.ts ← one class per file
|
|
263
|
+
│ ├── dashboard.page.ts
|
|
264
|
+
│ └── projects.page.ts
|
|
265
|
+
├── journeys/
|
|
266
|
+
│ ├── login-and-browse.journey.ts
|
|
267
|
+
│ └── create-task.journey.ts
|
|
268
|
+
└── helpers/
|
|
269
|
+
└── before-each.ts
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
## Lifecycle Hooks
|
|
183
273
|
|
|
184
274
|
Run code before/after journeys for test data setup and cleanup:
|
|
185
275
|
|
package/package.json
CHANGED