craftdriver 0.1.0 → 0.2.1
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/CHANGELOG.md +111 -0
- package/README.md +157 -45
- package/bin/craftdriver.mjs +26 -0
- package/dist/cli/client.d.ts +17 -0
- package/dist/cli/client.d.ts.map +1 -0
- package/dist/cli/client.js +88 -0
- package/dist/cli/client.js.map +1 -0
- package/dist/cli/daemon.d.ts +15 -0
- package/dist/cli/daemon.d.ts.map +1 -0
- package/dist/cli/daemon.js +145 -0
- package/dist/cli/daemon.js.map +1 -0
- package/dist/cli/defaults.d.ts +10 -0
- package/dist/cli/defaults.d.ts.map +1 -0
- package/dist/cli/defaults.js +42 -0
- package/dist/cli/defaults.js.map +1 -0
- package/dist/cli/dispatcher.d.ts +28 -0
- package/dist/cli/dispatcher.d.ts.map +1 -0
- package/dist/cli/dispatcher.js +319 -0
- package/dist/cli/dispatcher.js.map +1 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +404 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/init.d.ts +24 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/init.js +192 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/mcp/artifacts.d.ts +43 -0
- package/dist/cli/mcp/artifacts.d.ts.map +1 -0
- package/dist/cli/mcp/artifacts.js +104 -0
- package/dist/cli/mcp/artifacts.js.map +1 -0
- package/dist/cli/mcp/server.d.ts +34 -0
- package/dist/cli/mcp/server.d.ts.map +1 -0
- package/dist/cli/mcp/server.js +214 -0
- package/dist/cli/mcp/server.js.map +1 -0
- package/dist/cli/mcp/tools.d.ts +34 -0
- package/dist/cli/mcp/tools.d.ts.map +1 -0
- package/dist/cli/mcp/tools.js +236 -0
- package/dist/cli/mcp/tools.js.map +1 -0
- package/dist/cli/parseArgs.d.ts +37 -0
- package/dist/cli/parseArgs.d.ts.map +1 -0
- package/dist/cli/parseArgs.js +215 -0
- package/dist/cli/parseArgs.js.map +1 -0
- package/dist/cli/protocol.d.ts +34 -0
- package/dist/cli/protocol.d.ts.map +1 -0
- package/dist/cli/protocol.js +2 -0
- package/dist/cli/protocol.js.map +1 -0
- package/dist/cli/selector.d.ts +29 -0
- package/dist/cli/selector.d.ts.map +1 -0
- package/dist/cli/selector.js +100 -0
- package/dist/cli/selector.js.map +1 -0
- package/dist/cli/snapshot.d.ts +48 -0
- package/dist/cli/snapshot.d.ts.map +1 -0
- package/dist/cli/snapshot.js +179 -0
- package/dist/cli/snapshot.js.map +1 -0
- package/dist/index.d.ts +14 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/a11y.d.ts +81 -0
- package/dist/lib/a11y.d.ts.map +1 -0
- package/dist/lib/a11y.js +171 -0
- package/dist/lib/a11y.js.map +1 -0
- package/dist/lib/bidi/index.d.ts +26 -1
- package/dist/lib/bidi/index.d.ts.map +1 -1
- package/dist/lib/bidi/index.js +49 -0
- package/dist/lib/bidi/index.js.map +1 -1
- package/dist/lib/bidi/logs.d.ts +2 -2
- package/dist/lib/bidi/logs.d.ts.map +1 -1
- package/dist/lib/bidi/logs.js +2 -2
- package/dist/lib/bidi/logs.js.map +1 -1
- package/dist/lib/bidi/network.d.ts +74 -1
- package/dist/lib/bidi/network.d.ts.map +1 -1
- package/dist/lib/bidi/network.js +222 -4
- package/dist/lib/bidi/network.js.map +1 -1
- package/dist/lib/bidi/storage.d.ts +5 -3
- package/dist/lib/bidi/storage.d.ts.map +1 -1
- package/dist/lib/bidi/storage.js +14 -7
- package/dist/lib/bidi/storage.js.map +1 -1
- package/dist/lib/bidi/types.d.ts +27 -6
- package/dist/lib/bidi/types.d.ts.map +1 -1
- package/dist/lib/browser.d.ts +637 -16
- package/dist/lib/browser.d.ts.map +1 -1
- package/dist/lib/browser.js +1428 -102
- package/dist/lib/browser.js.map +1 -1
- package/dist/lib/browserContext.d.ts +486 -0
- package/dist/lib/browserContext.d.ts.map +1 -0
- package/dist/lib/browserContext.js +937 -0
- package/dist/lib/browserContext.js.map +1 -0
- package/dist/lib/builder.d.ts +3 -0
- package/dist/lib/builder.d.ts.map +1 -1
- package/dist/lib/builder.js +31 -4
- package/dist/lib/builder.js.map +1 -1
- package/dist/lib/by.d.ts +18 -2
- package/dist/lib/by.d.ts.map +1 -1
- package/dist/lib/by.js +20 -2
- package/dist/lib/by.js.map +1 -1
- package/dist/lib/chrome.d.ts +13 -1
- package/dist/lib/chrome.d.ts.map +1 -1
- package/dist/lib/chrome.js +13 -17
- package/dist/lib/chrome.js.map +1 -1
- package/dist/lib/clock.d.ts +115 -0
- package/dist/lib/clock.d.ts.map +1 -0
- package/dist/lib/clock.js +407 -0
- package/dist/lib/clock.js.map +1 -0
- package/dist/lib/driver.d.ts +43 -0
- package/dist/lib/driver.d.ts.map +1 -1
- package/dist/lib/driver.js +120 -0
- package/dist/lib/driver.js.map +1 -1
- package/dist/lib/driverManager.d.ts +25 -0
- package/dist/lib/driverManager.d.ts.map +1 -0
- package/dist/lib/driverManager.js +399 -0
- package/dist/lib/driverManager.js.map +1 -0
- package/dist/lib/elementHandle.d.ts +67 -3
- package/dist/lib/elementHandle.d.ts.map +1 -1
- package/dist/lib/elementHandle.js +209 -67
- package/dist/lib/elementHandle.js.map +1 -1
- package/dist/lib/errors.d.ts +71 -0
- package/dist/lib/errors.d.ts.map +1 -0
- package/dist/lib/errors.js +73 -0
- package/dist/lib/errors.js.map +1 -0
- package/dist/lib/expect.d.ts +6 -1
- package/dist/lib/expect.d.ts.map +1 -1
- package/dist/lib/expect.js +55 -37
- package/dist/lib/expect.js.map +1 -1
- package/dist/lib/firefox.d.ts +18 -0
- package/dist/lib/firefox.d.ts.map +1 -0
- package/dist/lib/firefox.js +33 -0
- package/dist/lib/firefox.js.map +1 -0
- package/dist/lib/frame.d.ts +57 -0
- package/dist/lib/frame.d.ts.map +1 -0
- package/dist/lib/frame.js +277 -0
- package/dist/lib/frame.js.map +1 -0
- package/dist/lib/keyboard.d.ts +1 -1
- package/dist/lib/keyboard.d.ts.map +1 -1
- package/dist/lib/keyboard.js +1 -1
- package/dist/lib/keyboard.js.map +1 -1
- package/dist/lib/locator.d.ts +71 -2
- package/dist/lib/locator.d.ts.map +1 -1
- package/dist/lib/locator.js +289 -31
- package/dist/lib/locator.js.map +1 -1
- package/dist/lib/mouse.d.ts +1 -1
- package/dist/lib/mouse.d.ts.map +1 -1
- package/dist/lib/mouse.js +1 -1
- package/dist/lib/mouse.js.map +1 -1
- package/dist/lib/page.d.ts +107 -0
- package/dist/lib/page.d.ts.map +1 -0
- package/dist/lib/page.js +388 -0
- package/dist/lib/page.js.map +1 -0
- package/dist/lib/tracing.d.ts +128 -0
- package/dist/lib/tracing.d.ts.map +1 -0
- package/dist/lib/tracing.js +272 -0
- package/dist/lib/tracing.js.map +1 -0
- package/dist/lib/wait.d.ts.map +1 -1
- package/dist/lib/wait.js +14 -1
- package/dist/lib/wait.js.map +1 -1
- package/dist/lib/webelement.d.ts +2 -0
- package/dist/lib/webelement.d.ts.map +1 -1
- package/dist/lib/webelement.js +18 -0
- package/dist/lib/webelement.js.map +1 -1
- package/docs/accessibility.md +177 -0
- package/docs/api-reference.md +71 -0
- package/docs/assertions.md +182 -0
- package/docs/bidi-features.md +462 -0
- package/docs/browser-api.md +673 -0
- package/docs/browser-context.md +522 -0
- package/docs/cli.md +233 -0
- package/docs/clock.md +245 -0
- package/docs/dialogs.md +143 -0
- package/docs/driver-configuration.md +64 -0
- package/docs/element-api.md +219 -0
- package/docs/emulation.md +155 -0
- package/docs/error-codes.md +67 -0
- package/docs/getting-started.md +168 -0
- package/docs/keyboard-mouse.md +250 -0
- package/docs/mcp.md +250 -0
- package/docs/mobile-emulation.md +194 -0
- package/docs/screenshots.md +146 -0
- package/docs/selectors.md +382 -0
- package/docs/session-management.md +229 -0
- package/docs/tracing.md +282 -0
- package/package.json +26 -8
- package/skills/craftdriver/SKILL.md +84 -0
- package/skills/craftdriver/cheatsheet.md +194 -0
- package/skills/craftdriver/cli.md +104 -0
- package/skills/craftdriver/patterns.md +139 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# Screenshots
|
|
2
|
+
|
|
3
|
+
craftdriver exposes a single options-bag `screenshot()` method on both
|
|
4
|
+
`Browser` and `ElementHandle`. PNG bytes are always returned as a
|
|
5
|
+
`Buffer`; pass `path` to also write the file in one step.
|
|
6
|
+
|
|
7
|
+
## Page screenshots
|
|
8
|
+
|
|
9
|
+
### `browser.screenshot(opts?)`
|
|
10
|
+
|
|
11
|
+
| Option | Type | Default | Notes |
|
|
12
|
+
| ---------- | ------------------- | ------------ | ------------------------------------------------ |
|
|
13
|
+
| `path` | `string` | — | Write the PNG to this file path. |
|
|
14
|
+
| `selector` | `string \| By` | full viewport| If set, capture only the matching element. |
|
|
15
|
+
| `fullPage` | `boolean` | `false` | Capture the entire scrollable document. Requires BiDi. |
|
|
16
|
+
| `timeout` | `number` (ms) | default | Wait timeout when locating `selector`. |
|
|
17
|
+
|
|
18
|
+
`fullPage` and `selector` are mutually exclusive.
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
// Viewport only (default), buffer
|
|
22
|
+
const buffer = await browser.screenshot();
|
|
23
|
+
|
|
24
|
+
// Viewport, written to disk
|
|
25
|
+
await browser.screenshot({ path: 'screenshots/homepage.png' });
|
|
26
|
+
|
|
27
|
+
// Whole scrollable document (BiDi `browsingContext.captureScreenshot`
|
|
28
|
+
// with `origin: "document"`).
|
|
29
|
+
await browser.screenshot({ fullPage: true, path: 'screenshots/full.png' });
|
|
30
|
+
|
|
31
|
+
// One element by CSS selector
|
|
32
|
+
const chart = await browser.screenshot({ selector: '#sales-chart' });
|
|
33
|
+
|
|
34
|
+
// One element, written to disk
|
|
35
|
+
await browser.screenshot({
|
|
36
|
+
selector: '#sales-chart',
|
|
37
|
+
path: 'screenshots/chart.png',
|
|
38
|
+
});
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
#### Full-page vs viewport
|
|
42
|
+
|
|
43
|
+
Viewport capture (`fullPage: false`, the default) uses the W3C Classic
|
|
44
|
+
`Take Screenshot` endpoint and is bounded by the visible viewport.
|
|
45
|
+
Full-page capture uses BiDi `browsingContext.captureScreenshot` with
|
|
46
|
+
`origin: "document"` and produces a PNG sized to the full scrollable
|
|
47
|
+
content — useful for visual diffs of long pages and for archiving the
|
|
48
|
+
complete rendered output. Full-page capture requires `enableBiDi: true`
|
|
49
|
+
(the default); on Classic-only sessions it throws.
|
|
50
|
+
|
|
51
|
+
## Element screenshots
|
|
52
|
+
|
|
53
|
+
### `element.screenshot(opts?)`
|
|
54
|
+
|
|
55
|
+
| Option | Type | Default | Notes |
|
|
56
|
+
| --------- | ----------- | ------- | ---------------------------------- |
|
|
57
|
+
| `path` | `string` | — | Write the PNG to this file path. |
|
|
58
|
+
| `timeout` | `number` | default | Element resolution timeout. |
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
const buffer = await browser.find('#product-image').screenshot();
|
|
62
|
+
await browser.find('#logo').screenshot({ path: 'logo.png' });
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Use cases
|
|
66
|
+
|
|
67
|
+
### Test failure documentation
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
afterEach(async (context) => {
|
|
71
|
+
if (context.task.result?.state === 'fail') {
|
|
72
|
+
const name = context.task.name.replace(/\s+/g, '-');
|
|
73
|
+
await browser.screenshot({ path: `screenshots/${name}.png` });
|
|
74
|
+
}
|
|
75
|
+
await browser.quit();
|
|
76
|
+
});
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Visual comparison baseline
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
await browser.navigateTo('https://example.com');
|
|
83
|
+
await browser.screenshot({ path: 'baseline/homepage.png' });
|
|
84
|
+
|
|
85
|
+
// Later, capture the candidate and diff with an image library:
|
|
86
|
+
await browser.navigateTo('https://example.com');
|
|
87
|
+
await browser.screenshot({ path: 'current/homepage.png' });
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Component screenshots
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
await browser.screenshot({ selector: 'header', path: 'components/header.png' });
|
|
94
|
+
await browser.screenshot({ selector: '.sidebar', path: 'components/sidebar.png' });
|
|
95
|
+
await browser.screenshot({ selector: 'footer', path: 'components/footer.png' });
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Before / after an action
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
await browser.screenshot({ selector: '#order-form', path: 'before-submit.png' });
|
|
102
|
+
await browser.find('#submit-order').click();
|
|
103
|
+
await browser.expect('#confirmation').toBeVisible();
|
|
104
|
+
await browser.screenshot({ selector: '#confirmation', path: 'after-submit.png' });
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Tips
|
|
108
|
+
|
|
109
|
+
### Create the screenshot directory
|
|
110
|
+
|
|
111
|
+
`screenshot({ path })` writes through `fs.writeFile`; the parent
|
|
112
|
+
directory must already exist:
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
import { mkdir } from 'fs/promises';
|
|
116
|
+
await mkdir('screenshots', { recursive: true });
|
|
117
|
+
await browser.screenshot({ path: 'screenshots/test.png' });
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Timestamped filenames
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
const ts = new Date().toISOString().replace(/[:.]/g, '-');
|
|
124
|
+
await browser.screenshot({ path: `screenshots/page-${ts}.png` });
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Wait for stability
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
await browser.navigateTo('https://example.com');
|
|
131
|
+
await browser.expect('#main-content').toBeVisible();
|
|
132
|
+
await browser.screenshot({ path: 'stable-page.png' });
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Buffer usage
|
|
136
|
+
|
|
137
|
+
When you only need bytes (uploading, image diffing), omit `path`:
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
const buffer = await browser.screenshot();
|
|
141
|
+
|
|
142
|
+
import { writeFile } from 'fs/promises';
|
|
143
|
+
await writeFile('screenshot.png', buffer);
|
|
144
|
+
|
|
145
|
+
await uploadToS3(buffer, 'screenshots/latest.png');
|
|
146
|
+
```
|
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
# Selectors & Locators
|
|
2
|
+
|
|
3
|
+
CraftDriver provides multiple ways to locate elements on the page.
|
|
4
|
+
|
|
5
|
+
## CSS Selectors
|
|
6
|
+
|
|
7
|
+
The most common way to find elements is using CSS selectors as strings:
|
|
8
|
+
|
|
9
|
+
> String selectors are always parsed as CSS selectors. Craftdriver does not
|
|
10
|
+
> support Playwright-style selector strings such as `text=Save` or `role=button`.
|
|
11
|
+
> For semantic queries, use `By.*` or `browser.getBy*()`.
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
// By ID
|
|
15
|
+
await browser.click('#submit-button');
|
|
16
|
+
|
|
17
|
+
// By class
|
|
18
|
+
await browser.click('.btn-primary');
|
|
19
|
+
|
|
20
|
+
// By tag and attribute
|
|
21
|
+
await browser.click('input[type="email"]');
|
|
22
|
+
|
|
23
|
+
// Complex selectors
|
|
24
|
+
await browser.click('form.login #username');
|
|
25
|
+
await browser.click('ul.nav > li:first-child a');
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## By Locators
|
|
29
|
+
|
|
30
|
+
The `By` helper provides semantic locator strategies:
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
import { By } from 'craftdriver';
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### By.css(selector)
|
|
37
|
+
|
|
38
|
+
Locate by CSS selector (equivalent to passing a string).
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
browser.find(By.css('#my-button'));
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### By.id(id)
|
|
45
|
+
|
|
46
|
+
Locate by element ID.
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
browser.find(By.id('username'));
|
|
50
|
+
// Equivalent to: browser.find('#username')
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### By.className(name)
|
|
54
|
+
|
|
55
|
+
Locate by CSS class name.
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
browser.find(By.className('btn-primary'));
|
|
59
|
+
// Equivalent to: browser.find('.btn-primary')
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### By.name(name)
|
|
63
|
+
|
|
64
|
+
Locate by the `name` attribute (matches the canonical Selenium
|
|
65
|
+
`By.name` locator).
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
browser.find(By.name('email'));
|
|
69
|
+
// Finds: <input name="email">
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### By.tagName(tag)
|
|
73
|
+
|
|
74
|
+
Locate by HTML tag name.
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
browser.find(By.tagName('h1'));
|
|
78
|
+
// Finds the first <h1> element
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### By.attr(name, value)
|
|
82
|
+
|
|
83
|
+
Locate by an arbitrary attribute and value.
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
browser.find(By.attr('role', 'dialog'));
|
|
87
|
+
// Equivalent to: browser.find('[role="dialog"]')
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### By.dataAttr(name, value)
|
|
91
|
+
|
|
92
|
+
Locate by a `data-*` attribute. Pass the suffix only — `data-` is added
|
|
93
|
+
for you.
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
browser.find(By.dataAttr('state', 'open'));
|
|
97
|
+
// Equivalent to: browser.find('[data-state="open"]')
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### By.aria(name, value)
|
|
101
|
+
|
|
102
|
+
Locate by an `aria-*` attribute. Pass the suffix only — `aria-` is added
|
|
103
|
+
for you. Use `By.role()` / `getByRole()` for role + accessible-name
|
|
104
|
+
matching; use this helper when you need a specific ARIA state or
|
|
105
|
+
property.
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
browser.find(By.aria('expanded', 'true'));
|
|
109
|
+
// Equivalent to: browser.find('[aria-expanded="true"]')
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### By.testId(value)
|
|
113
|
+
|
|
114
|
+
Locate by `data-testid` attribute.
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
browser.find(By.testId('submit-button'));
|
|
118
|
+
// Equivalent to: browser.find('[data-testid="submit-button"]')
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### By.text(text, options?)
|
|
122
|
+
|
|
123
|
+
Locate by visible text content. Defaults: `exact: true`,
|
|
124
|
+
`caseSensitive: true`, `trim: true`.
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
// Exact match (whitespace-normalised)
|
|
128
|
+
browser.find(By.text('Submit Order'));
|
|
129
|
+
|
|
130
|
+
// Substring match — equivalent to By.partialText('Submit')
|
|
131
|
+
browser.find(By.text('Submit', { exact: false }));
|
|
132
|
+
|
|
133
|
+
// Case-insensitive
|
|
134
|
+
browser.find(By.text('submit order', { caseSensitive: false }));
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
> **`By.text` and `getByText` are the same vocabulary.**
|
|
138
|
+
> `getByText(text, opts)` is just `By.text(text, opts)`. Pass
|
|
139
|
+
> `{ exact: false }` on either to get substring matching;
|
|
140
|
+
> `By.partialText` remains as the lower-level entry point if you prefer
|
|
141
|
+
> to be explicit.
|
|
142
|
+
|
|
143
|
+
### By.partialText(substring, options?)
|
|
144
|
+
|
|
145
|
+
Locate the innermost element whose visible text contains `substring`.
|
|
146
|
+
Equivalent to `By.text(substring, { exact: false })`. Defaults:
|
|
147
|
+
`caseSensitive: true`, `trim: true`.
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
browser.find(By.partialText('Submit'));
|
|
151
|
+
browser.find(By.partialText('error', { caseSensitive: false }));
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### By.altText(text, options?)
|
|
155
|
+
|
|
156
|
+
Locate `<img>`, `<area>`, or `<input type="image">` by `alt` text.
|
|
157
|
+
Defaults: `exact: true`.
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
browser.find(By.altText('Company logo'));
|
|
161
|
+
browser.find(By.altText('logo', { exact: false }));
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### By.title(text, options?)
|
|
165
|
+
|
|
166
|
+
Locate by the `title` attribute (browser tooltip text). Defaults:
|
|
167
|
+
`exact: true`.
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
browser.find(By.title('Open in new tab'));
|
|
171
|
+
browser.find(By.title('Open', { exact: false }));
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### By.xpath(expression)
|
|
175
|
+
|
|
176
|
+
Locate using XPath expression.
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
browser.find(By.xpath('//button[@data-testid="submit"]'));
|
|
180
|
+
browser.find(By.xpath('//div[contains(@class, "error")]'));
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## Playwright-Style Locators
|
|
184
|
+
|
|
185
|
+
CraftDriver also supports Playwright-style semantic locators directly on the browser:
|
|
186
|
+
|
|
187
|
+
### getByRole(role, options?)
|
|
188
|
+
|
|
189
|
+
Locate by ARIA role.
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
browser.getByRole('button', { name: 'Submit' });
|
|
193
|
+
browser.getByRole('textbox', { name: 'Email' });
|
|
194
|
+
browser.getByRole('checkbox', { name: 'Remember me' });
|
|
195
|
+
browser.getByRole('link', { name: 'Learn more' });
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Options:
|
|
199
|
+
|
|
200
|
+
| Option | Type | Default | Description |
|
|
201
|
+
| --------------- | --------- | ------- | ----------------------------------------------------- |
|
|
202
|
+
| `name` | `string` | — | Accessible name (`aria-label` or visible text). |
|
|
203
|
+
| `exact` | `boolean` | `true` | If `false`, substring-match the accessible name. |
|
|
204
|
+
| `includeHidden` | `boolean` | `false` | Match elements with `hidden` or `aria-hidden="true"`. |
|
|
205
|
+
|
|
206
|
+
```typescript
|
|
207
|
+
// Default: skip elements marked hidden / aria-hidden
|
|
208
|
+
browser.getByRole('button', { name: 'Close' });
|
|
209
|
+
|
|
210
|
+
// Include hidden elements (e.g., off-canvas menus, collapsed sections)
|
|
211
|
+
browser.getByRole('button', { name: 'Close', includeHidden: true });
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### getByText(text, options?)
|
|
215
|
+
|
|
216
|
+
Locate by visible text.
|
|
217
|
+
|
|
218
|
+
```typescript
|
|
219
|
+
// Exact match (default)
|
|
220
|
+
browser.getByText('Welcome to our site');
|
|
221
|
+
|
|
222
|
+
// Partial match
|
|
223
|
+
browser.getByText('Welcome', { exact: false });
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
> **Heads-up for Playwright users.** Craftdriver's `getByText` defaults
|
|
227
|
+
> to **exact** matching, while Playwright's defaults to substring. Pass
|
|
228
|
+
> `{ exact: false }` for substring behaviour.
|
|
229
|
+
|
|
230
|
+
### getByLabel(text, options?)
|
|
231
|
+
|
|
232
|
+
Locate form controls by their associated label.
|
|
233
|
+
|
|
234
|
+
```typescript
|
|
235
|
+
browser.getByLabel('Email address');
|
|
236
|
+
browser.getByLabel('Password');
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### getByPlaceholder(text, options?)
|
|
240
|
+
|
|
241
|
+
Locate inputs by placeholder text.
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
browser.getByPlaceholder('Enter your email');
|
|
245
|
+
browser.getByPlaceholder('Search...');
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### getByTestId(testId)
|
|
249
|
+
|
|
250
|
+
Locate by `data-testid` attribute.
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
browser.getByTestId('submit-button');
|
|
254
|
+
browser.getByTestId('user-profile');
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
## Selector Best Practices
|
|
258
|
+
|
|
259
|
+
### Prefer Stable Selectors
|
|
260
|
+
|
|
261
|
+
```typescript
|
|
262
|
+
// ✅ Good - specific and stable
|
|
263
|
+
browser.find('#login-button');
|
|
264
|
+
browser.find('[data-testid="submit"]');
|
|
265
|
+
browser.getByRole('button', { name: 'Login' });
|
|
266
|
+
|
|
267
|
+
// ❌ Avoid - fragile, depends on structure
|
|
268
|
+
browser.find('div > div > button:nth-child(3)');
|
|
269
|
+
browser.find('.btn.mt-4.px-6'); // CSS utility classes change
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### Use Test IDs for Complex UIs
|
|
273
|
+
|
|
274
|
+
Add `data-testid` attributes to elements that are hard to select:
|
|
275
|
+
|
|
276
|
+
```html
|
|
277
|
+
<button class="btn btn-primary mx-2" data-testid="checkout-button">Proceed to Checkout</button>
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
```typescript
|
|
281
|
+
await browser.find('[data-testid="checkout-button"]').click();
|
|
282
|
+
// or
|
|
283
|
+
await browser.getByTestId('checkout-button').click();
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
### Semantic Locators for Accessibility
|
|
287
|
+
|
|
288
|
+
Using role-based locators ensures your UI is accessible:
|
|
289
|
+
|
|
290
|
+
```typescript
|
|
291
|
+
// This only works if the button is properly labeled
|
|
292
|
+
browser.getByRole('button', { name: 'Add to cart' });
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
## Examples
|
|
296
|
+
|
|
297
|
+
### Finding Multiple Elements
|
|
298
|
+
|
|
299
|
+
```typescript
|
|
300
|
+
// Click the first matching element
|
|
301
|
+
await browser.find('.product-card .add-to-cart').click();
|
|
302
|
+
|
|
303
|
+
// For multiple elements, use specific selectors
|
|
304
|
+
await browser.find('.product-card:nth-child(1) .add-to-cart').click();
|
|
305
|
+
await browser.find('.product-card:nth-child(2) .add-to-cart').click();
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
## Locators (lazy & chainable)
|
|
309
|
+
|
|
310
|
+
`browser.locator(selector)` returns a `Locator` — a lazy, re-resolving handle that
|
|
311
|
+
supports composition, filtering, and indexed access.
|
|
312
|
+
|
|
313
|
+
Use `find()` for a single one-shot element, `findAll()` for the simple array case,
|
|
314
|
+
and `locator()` when you need composition or want to pick from a list.
|
|
315
|
+
|
|
316
|
+
```typescript
|
|
317
|
+
import { Browser } from 'craftdriver';
|
|
318
|
+
|
|
319
|
+
// Count matching elements
|
|
320
|
+
const n = await browser.locator('.product').count();
|
|
321
|
+
|
|
322
|
+
// Pick by index (0-based)
|
|
323
|
+
await browser.locator('.buy-btn').first().click();
|
|
324
|
+
await browser.locator('.buy-btn').last().click();
|
|
325
|
+
await browser.locator('.buy-btn').nth(2).click();
|
|
326
|
+
|
|
327
|
+
// Filter by text content
|
|
328
|
+
await browser.locator('.product').filter({ hasText: 'Pro' }).locator('.buy-btn').click();
|
|
329
|
+
|
|
330
|
+
// Filter by presence of a child locator
|
|
331
|
+
const hasPromo = browser.locator('.badge');
|
|
332
|
+
const promoCards = browser.locator('.card').filter({ has: hasPromo });
|
|
333
|
+
await promoCards.first().click();
|
|
334
|
+
|
|
335
|
+
// Chain into a child element
|
|
336
|
+
await browser.locator('.card').nth(0).locator('button').click();
|
|
337
|
+
|
|
338
|
+
// Get all matching elements as snapshot handles
|
|
339
|
+
const handles = await browser.locator('.product').all();
|
|
340
|
+
const texts = await Promise.all(handles.map(h => h.text()));
|
|
341
|
+
|
|
342
|
+
// Simple array shortcut (no filtering)
|
|
343
|
+
const allButtons = await browser.findAll('.buy-btn');
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
### Assertions on locators
|
|
347
|
+
|
|
348
|
+
```typescript
|
|
349
|
+
await browser.locator('#result').expect().toHaveText('Done');
|
|
350
|
+
await browser.locator('.error').expect().not.toBeVisible();
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
### Form Controls
|
|
355
|
+
|
|
356
|
+
```typescript
|
|
357
|
+
// By label
|
|
358
|
+
await browser.getByLabel('Email').fill('test@example.com');
|
|
359
|
+
await browser.getByLabel('Password').fill('secret');
|
|
360
|
+
|
|
361
|
+
// By placeholder
|
|
362
|
+
await browser.getByPlaceholder('Search products...').fill('laptop');
|
|
363
|
+
|
|
364
|
+
// By role
|
|
365
|
+
await browser.getByRole('button', { name: 'Search' }).click();
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
### Navigation Links
|
|
369
|
+
|
|
370
|
+
```typescript
|
|
371
|
+
await browser.getByRole('link', { name: 'Products' }).click();
|
|
372
|
+
await browser.getByText('Contact Us').click();
|
|
373
|
+
await browser.find('nav a[href="/about"]').click();
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
### Buttons by Text
|
|
377
|
+
|
|
378
|
+
```typescript
|
|
379
|
+
await browser.getByRole('button', { name: 'Submit' }).click();
|
|
380
|
+
await browser.getByText('Cancel').click();
|
|
381
|
+
await browser.find('button:contains("Save")').click();
|
|
382
|
+
```
|