browser-commander 0.2.0
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/.changeset/README.md +8 -0
- package/.changeset/config.json +11 -0
- package/.github/workflows/release.yml +296 -0
- package/.husky/pre-commit +1 -0
- package/.jscpd.json +20 -0
- package/.prettierignore +7 -0
- package/.prettierrc +10 -0
- package/CHANGELOG.md +32 -0
- package/LICENSE +24 -0
- package/README.md +320 -0
- package/bunfig.toml +3 -0
- package/deno.json +7 -0
- package/eslint.config.js +125 -0
- package/examples/react-test-app/index.html +25 -0
- package/examples/react-test-app/package.json +19 -0
- package/examples/react-test-app/src/App.jsx +473 -0
- package/examples/react-test-app/src/main.jsx +10 -0
- package/examples/react-test-app/src/styles.css +323 -0
- package/examples/react-test-app/vite.config.js +9 -0
- package/package.json +89 -0
- package/scripts/changeset-version.mjs +38 -0
- package/scripts/create-github-release.mjs +93 -0
- package/scripts/create-manual-changeset.mjs +86 -0
- package/scripts/format-github-release.mjs +83 -0
- package/scripts/format-release-notes.mjs +216 -0
- package/scripts/instant-version-bump.mjs +121 -0
- package/scripts/merge-changesets.mjs +260 -0
- package/scripts/publish-to-npm.mjs +126 -0
- package/scripts/setup-npm.mjs +37 -0
- package/scripts/validate-changeset.mjs +262 -0
- package/scripts/version-and-commit.mjs +237 -0
- package/src/ARCHITECTURE.md +270 -0
- package/src/README.md +517 -0
- package/src/bindings.js +298 -0
- package/src/browser/launcher.js +93 -0
- package/src/browser/navigation.js +513 -0
- package/src/core/constants.js +24 -0
- package/src/core/engine-adapter.js +466 -0
- package/src/core/engine-detection.js +49 -0
- package/src/core/logger.js +21 -0
- package/src/core/navigation-manager.js +503 -0
- package/src/core/navigation-safety.js +160 -0
- package/src/core/network-tracker.js +373 -0
- package/src/core/page-session.js +299 -0
- package/src/core/page-trigger-manager.js +564 -0
- package/src/core/preferences.js +46 -0
- package/src/elements/content.js +197 -0
- package/src/elements/locators.js +243 -0
- package/src/elements/selectors.js +360 -0
- package/src/elements/visibility.js +166 -0
- package/src/exports.js +121 -0
- package/src/factory.js +192 -0
- package/src/high-level/universal-logic.js +206 -0
- package/src/index.js +17 -0
- package/src/interactions/click.js +684 -0
- package/src/interactions/fill.js +383 -0
- package/src/interactions/scroll.js +341 -0
- package/src/utilities/url.js +33 -0
- package/src/utilities/wait.js +135 -0
- package/tests/e2e/playwright.e2e.test.js +442 -0
- package/tests/e2e/puppeteer.e2e.test.js +408 -0
- package/tests/helpers/mocks.js +542 -0
- package/tests/unit/bindings.test.js +218 -0
- package/tests/unit/browser/navigation.test.js +345 -0
- package/tests/unit/core/constants.test.js +72 -0
- package/tests/unit/core/engine-adapter.test.js +170 -0
- package/tests/unit/core/engine-detection.test.js +81 -0
- package/tests/unit/core/logger.test.js +80 -0
- package/tests/unit/core/navigation-safety.test.js +202 -0
- package/tests/unit/core/network-tracker.test.js +198 -0
- package/tests/unit/core/page-trigger-manager.test.js +358 -0
- package/tests/unit/elements/content.test.js +318 -0
- package/tests/unit/elements/locators.test.js +236 -0
- package/tests/unit/elements/selectors.test.js +302 -0
- package/tests/unit/elements/visibility.test.js +234 -0
- package/tests/unit/factory.test.js +174 -0
- package/tests/unit/high-level/universal-logic.test.js +299 -0
- package/tests/unit/interactions/click.test.js +340 -0
- package/tests/unit/interactions/fill.test.js +378 -0
- package/tests/unit/interactions/scroll.test.js +330 -0
- package/tests/unit/utilities/url.test.js +63 -0
- package/tests/unit/utilities/wait.test.js +207 -0
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
import { describe, it } from 'node:test';
|
|
2
|
+
import assert from 'node:assert';
|
|
3
|
+
import {
|
|
4
|
+
textContent,
|
|
5
|
+
inputValue,
|
|
6
|
+
getAttribute,
|
|
7
|
+
getInputValue,
|
|
8
|
+
logElementInfo,
|
|
9
|
+
} from '../../../src/elements/content.js';
|
|
10
|
+
import {
|
|
11
|
+
createMockPlaywrightPage,
|
|
12
|
+
createMockPuppeteerPage,
|
|
13
|
+
createMockLogger,
|
|
14
|
+
} from '../../helpers/mocks.js';
|
|
15
|
+
|
|
16
|
+
describe('content', () => {
|
|
17
|
+
describe('textContent', () => {
|
|
18
|
+
it('should throw when selector is not provided', async () => {
|
|
19
|
+
const page = createMockPlaywrightPage();
|
|
20
|
+
await assert.rejects(
|
|
21
|
+
() => textContent({ page, engine: 'playwright' }),
|
|
22
|
+
/selector is required/
|
|
23
|
+
);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('should return text content for Playwright', async () => {
|
|
27
|
+
const page = createMockPlaywrightPage({
|
|
28
|
+
elements: { div: { textContent: 'Hello World', count: 1 } },
|
|
29
|
+
});
|
|
30
|
+
const text = await textContent({
|
|
31
|
+
page,
|
|
32
|
+
engine: 'playwright',
|
|
33
|
+
selector: 'div',
|
|
34
|
+
});
|
|
35
|
+
assert.strictEqual(text, 'Hello World');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should return text content for Puppeteer', async () => {
|
|
39
|
+
const page = createMockPuppeteerPage({
|
|
40
|
+
elements: { div: { textContent: 'Hello World', count: 1 } },
|
|
41
|
+
});
|
|
42
|
+
// Override evaluate to properly return textContent for this test
|
|
43
|
+
page.evaluate = async (fn, el) =>
|
|
44
|
+
// The fn is like: el => el.textContent
|
|
45
|
+
// Return the configured text content
|
|
46
|
+
'Hello World';
|
|
47
|
+
const text = await textContent({
|
|
48
|
+
page,
|
|
49
|
+
engine: 'puppeteer',
|
|
50
|
+
selector: 'div',
|
|
51
|
+
});
|
|
52
|
+
assert.strictEqual(text, 'Hello World');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('should return null when element not found', async () => {
|
|
56
|
+
const page = createMockPuppeteerPage({
|
|
57
|
+
elements: { div: { count: 0 } },
|
|
58
|
+
});
|
|
59
|
+
const text = await textContent({
|
|
60
|
+
page,
|
|
61
|
+
engine: 'puppeteer',
|
|
62
|
+
selector: 'div',
|
|
63
|
+
});
|
|
64
|
+
assert.strictEqual(text, null);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('should handle navigation errors', async () => {
|
|
68
|
+
const page = createMockPlaywrightPage();
|
|
69
|
+
page.locator = () => ({
|
|
70
|
+
textContent: async () => {
|
|
71
|
+
throw new Error('Execution context was destroyed');
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
const text = await textContent({
|
|
75
|
+
page,
|
|
76
|
+
engine: 'playwright',
|
|
77
|
+
selector: 'div',
|
|
78
|
+
});
|
|
79
|
+
assert.strictEqual(text, null);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe('inputValue', () => {
|
|
84
|
+
it('should throw when selector is not provided', async () => {
|
|
85
|
+
const page = createMockPlaywrightPage();
|
|
86
|
+
await assert.rejects(
|
|
87
|
+
() => inputValue({ page, engine: 'playwright' }),
|
|
88
|
+
/selector is required/
|
|
89
|
+
);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('should return input value for Playwright', async () => {
|
|
93
|
+
const page = createMockPlaywrightPage({
|
|
94
|
+
elements: { input: { value: 'test value', count: 1 } },
|
|
95
|
+
});
|
|
96
|
+
const value = await inputValue({
|
|
97
|
+
page,
|
|
98
|
+
engine: 'playwright',
|
|
99
|
+
selector: 'input',
|
|
100
|
+
});
|
|
101
|
+
assert.strictEqual(value, 'test value');
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('should return input value for Puppeteer', async () => {
|
|
105
|
+
const page = createMockPuppeteerPage({
|
|
106
|
+
elements: { input: { value: 'test value', count: 1 } },
|
|
107
|
+
});
|
|
108
|
+
// Override evaluate to properly return value for this test
|
|
109
|
+
page.evaluate = async (fn, el) =>
|
|
110
|
+
// The fn is like: el => el.value
|
|
111
|
+
// Return the configured value
|
|
112
|
+
'test value';
|
|
113
|
+
const value = await inputValue({
|
|
114
|
+
page,
|
|
115
|
+
engine: 'puppeteer',
|
|
116
|
+
selector: 'input',
|
|
117
|
+
});
|
|
118
|
+
assert.strictEqual(value, 'test value');
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('should return empty string when element not found', async () => {
|
|
122
|
+
const page = createMockPuppeteerPage({
|
|
123
|
+
elements: { input: { count: 0 } },
|
|
124
|
+
});
|
|
125
|
+
const value = await inputValue({
|
|
126
|
+
page,
|
|
127
|
+
engine: 'puppeteer',
|
|
128
|
+
selector: 'input',
|
|
129
|
+
});
|
|
130
|
+
assert.strictEqual(value, '');
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('should handle navigation errors', async () => {
|
|
134
|
+
const page = createMockPlaywrightPage();
|
|
135
|
+
page.locator = () => ({
|
|
136
|
+
inputValue: async () => {
|
|
137
|
+
throw new Error('Execution context was destroyed');
|
|
138
|
+
},
|
|
139
|
+
});
|
|
140
|
+
const value = await inputValue({
|
|
141
|
+
page,
|
|
142
|
+
engine: 'playwright',
|
|
143
|
+
selector: 'input',
|
|
144
|
+
});
|
|
145
|
+
assert.strictEqual(value, '');
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
describe('getAttribute', () => {
|
|
150
|
+
it('should throw when selector is not provided', async () => {
|
|
151
|
+
const page = createMockPlaywrightPage();
|
|
152
|
+
await assert.rejects(
|
|
153
|
+
() => getAttribute({ page, engine: 'playwright', attribute: 'href' }),
|
|
154
|
+
/selector and attribute are required/
|
|
155
|
+
);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('should throw when attribute is not provided', async () => {
|
|
159
|
+
const page = createMockPlaywrightPage();
|
|
160
|
+
await assert.rejects(
|
|
161
|
+
() => getAttribute({ page, engine: 'playwright', selector: 'a' }),
|
|
162
|
+
/selector and attribute are required/
|
|
163
|
+
);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('should return attribute value for Playwright', async () => {
|
|
167
|
+
const page = createMockPlaywrightPage({
|
|
168
|
+
elements: { a: { href: 'https://example.com', count: 1 } },
|
|
169
|
+
});
|
|
170
|
+
const value = await getAttribute({
|
|
171
|
+
page,
|
|
172
|
+
engine: 'playwright',
|
|
173
|
+
selector: 'a',
|
|
174
|
+
attribute: 'href',
|
|
175
|
+
});
|
|
176
|
+
assert.strictEqual(value, 'https://example.com');
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it('should return null when attribute not found', async () => {
|
|
180
|
+
const page = createMockPlaywrightPage({
|
|
181
|
+
elements: { a: { count: 1 } },
|
|
182
|
+
});
|
|
183
|
+
const value = await getAttribute({
|
|
184
|
+
page,
|
|
185
|
+
engine: 'playwright',
|
|
186
|
+
selector: 'a',
|
|
187
|
+
attribute: 'nonexistent',
|
|
188
|
+
});
|
|
189
|
+
assert.strictEqual(value, null);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it('should return null when element not found', async () => {
|
|
193
|
+
const page = createMockPuppeteerPage({
|
|
194
|
+
elements: { a: { count: 0 } },
|
|
195
|
+
});
|
|
196
|
+
const value = await getAttribute({
|
|
197
|
+
page,
|
|
198
|
+
engine: 'puppeteer',
|
|
199
|
+
selector: 'a',
|
|
200
|
+
attribute: 'href',
|
|
201
|
+
});
|
|
202
|
+
assert.strictEqual(value, null);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it('should handle navigation errors', async () => {
|
|
206
|
+
const page = createMockPlaywrightPage();
|
|
207
|
+
page.locator = () => ({
|
|
208
|
+
getAttribute: async () => {
|
|
209
|
+
throw new Error('Execution context was destroyed');
|
|
210
|
+
},
|
|
211
|
+
});
|
|
212
|
+
const value = await getAttribute({
|
|
213
|
+
page,
|
|
214
|
+
engine: 'playwright',
|
|
215
|
+
selector: 'a',
|
|
216
|
+
attribute: 'href',
|
|
217
|
+
});
|
|
218
|
+
assert.strictEqual(value, null);
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
describe('getInputValue', () => {
|
|
223
|
+
it('should throw when locatorOrElement is not provided', async () => {
|
|
224
|
+
const page = createMockPlaywrightPage();
|
|
225
|
+
await assert.rejects(
|
|
226
|
+
() => getInputValue({ page, engine: 'playwright' }),
|
|
227
|
+
/locatorOrElement is required/
|
|
228
|
+
);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it('should return value from locator', async () => {
|
|
232
|
+
const page = createMockPlaywrightPage();
|
|
233
|
+
const mockLocator = {
|
|
234
|
+
inputValue: async () => 'locator value',
|
|
235
|
+
};
|
|
236
|
+
const adapter = {
|
|
237
|
+
getInputValue: async () => 'adapter value',
|
|
238
|
+
};
|
|
239
|
+
const value = await getInputValue({
|
|
240
|
+
page,
|
|
241
|
+
engine: 'playwright',
|
|
242
|
+
locatorOrElement: mockLocator,
|
|
243
|
+
adapter,
|
|
244
|
+
});
|
|
245
|
+
assert.strictEqual(value, 'adapter value');
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it('should handle navigation errors', async () => {
|
|
249
|
+
const page = createMockPlaywrightPage();
|
|
250
|
+
const mockLocator = {};
|
|
251
|
+
const adapter = {
|
|
252
|
+
getInputValue: async () => {
|
|
253
|
+
throw new Error('Execution context was destroyed');
|
|
254
|
+
},
|
|
255
|
+
};
|
|
256
|
+
const value = await getInputValue({
|
|
257
|
+
page,
|
|
258
|
+
engine: 'playwright',
|
|
259
|
+
locatorOrElement: mockLocator,
|
|
260
|
+
adapter,
|
|
261
|
+
});
|
|
262
|
+
assert.strictEqual(value, '');
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
describe('logElementInfo', () => {
|
|
267
|
+
it('should not throw when locatorOrElement is not provided', async () => {
|
|
268
|
+
const page = createMockPlaywrightPage();
|
|
269
|
+
const log = createMockLogger();
|
|
270
|
+
await logElementInfo({ page, engine: 'playwright', log });
|
|
271
|
+
// Should not throw
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
it('should log element information', async () => {
|
|
275
|
+
const page = createMockPlaywrightPage({
|
|
276
|
+
elements: { button: { textContent: 'Click me', count: 1 } },
|
|
277
|
+
});
|
|
278
|
+
const log = createMockLogger({ collectLogs: true });
|
|
279
|
+
const mockLocator = {
|
|
280
|
+
evaluate: async (fn) => 'BUTTON',
|
|
281
|
+
textContent: async () => 'Click me',
|
|
282
|
+
};
|
|
283
|
+
const adapter = {
|
|
284
|
+
evaluateOnElement: async () => 'BUTTON',
|
|
285
|
+
getTextContent: async () => 'Click me',
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
await logElementInfo({
|
|
289
|
+
page,
|
|
290
|
+
engine: 'playwright',
|
|
291
|
+
log,
|
|
292
|
+
locatorOrElement: mockLocator,
|
|
293
|
+
adapter,
|
|
294
|
+
});
|
|
295
|
+
// Should log without error
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
it('should handle navigation errors', async () => {
|
|
299
|
+
const page = createMockPlaywrightPage();
|
|
300
|
+
const log = createMockLogger();
|
|
301
|
+
const mockLocator = {};
|
|
302
|
+
const adapter = {
|
|
303
|
+
evaluateOnElement: async () => {
|
|
304
|
+
throw new Error('Execution context was destroyed');
|
|
305
|
+
},
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
await logElementInfo({
|
|
309
|
+
page,
|
|
310
|
+
engine: 'playwright',
|
|
311
|
+
log,
|
|
312
|
+
locatorOrElement: mockLocator,
|
|
313
|
+
adapter,
|
|
314
|
+
});
|
|
315
|
+
// Should not throw
|
|
316
|
+
});
|
|
317
|
+
});
|
|
318
|
+
});
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { describe, it } from 'node:test';
|
|
2
|
+
import assert from 'node:assert';
|
|
3
|
+
import {
|
|
4
|
+
createPlaywrightLocator,
|
|
5
|
+
getLocatorOrElement,
|
|
6
|
+
waitForLocatorOrElement,
|
|
7
|
+
waitForVisible,
|
|
8
|
+
locator,
|
|
9
|
+
} from '../../../src/elements/locators.js';
|
|
10
|
+
import {
|
|
11
|
+
createMockPlaywrightPage,
|
|
12
|
+
createMockPuppeteerPage,
|
|
13
|
+
} from '../../helpers/mocks.js';
|
|
14
|
+
|
|
15
|
+
describe('locators', () => {
|
|
16
|
+
describe('createPlaywrightLocator', () => {
|
|
17
|
+
it('should throw when selector is not provided', () => {
|
|
18
|
+
const page = createMockPlaywrightPage();
|
|
19
|
+
assert.throws(
|
|
20
|
+
() => createPlaywrightLocator({ page }),
|
|
21
|
+
/selector is required/
|
|
22
|
+
);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('should create locator from simple selector', () => {
|
|
26
|
+
const page = createMockPlaywrightPage();
|
|
27
|
+
const loc = createPlaywrightLocator({ page, selector: 'button' });
|
|
28
|
+
assert.ok(loc);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should handle :nth-of-type selector', () => {
|
|
32
|
+
const page = createMockPlaywrightPage();
|
|
33
|
+
const loc = createPlaywrightLocator({
|
|
34
|
+
page,
|
|
35
|
+
selector: 'button:nth-of-type(2)',
|
|
36
|
+
});
|
|
37
|
+
assert.ok(loc);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should create locator for complex selector', () => {
|
|
41
|
+
const page = createMockPlaywrightPage();
|
|
42
|
+
const loc = createPlaywrightLocator({
|
|
43
|
+
page,
|
|
44
|
+
selector: 'div.class > span[data-id="test"]',
|
|
45
|
+
});
|
|
46
|
+
assert.ok(loc);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe('getLocatorOrElement', () => {
|
|
51
|
+
it('should throw when selector is not provided', async () => {
|
|
52
|
+
const page = createMockPlaywrightPage();
|
|
53
|
+
await assert.rejects(
|
|
54
|
+
() => getLocatorOrElement({ page, engine: 'playwright' }),
|
|
55
|
+
/selector is required/
|
|
56
|
+
);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should return locator for Playwright', async () => {
|
|
60
|
+
const page = createMockPlaywrightPage();
|
|
61
|
+
const loc = await getLocatorOrElement({
|
|
62
|
+
page,
|
|
63
|
+
engine: 'playwright',
|
|
64
|
+
selector: 'button',
|
|
65
|
+
});
|
|
66
|
+
assert.ok(loc);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('should return element for Puppeteer', async () => {
|
|
70
|
+
const page = createMockPuppeteerPage();
|
|
71
|
+
const el = await getLocatorOrElement({
|
|
72
|
+
page,
|
|
73
|
+
engine: 'puppeteer',
|
|
74
|
+
selector: 'button',
|
|
75
|
+
});
|
|
76
|
+
assert.ok(el);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('should return existing locator/element unchanged', async () => {
|
|
80
|
+
const page = createMockPlaywrightPage();
|
|
81
|
+
const existingLocator = { isLocator: true };
|
|
82
|
+
const result = await getLocatorOrElement({
|
|
83
|
+
page,
|
|
84
|
+
engine: 'playwright',
|
|
85
|
+
selector: existingLocator,
|
|
86
|
+
});
|
|
87
|
+
assert.strictEqual(result, existingLocator);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('should return null for Puppeteer when element not found', async () => {
|
|
91
|
+
const page = createMockPuppeteerPage({
|
|
92
|
+
elements: { missing: { count: 0 } },
|
|
93
|
+
});
|
|
94
|
+
const el = await getLocatorOrElement({
|
|
95
|
+
page,
|
|
96
|
+
engine: 'puppeteer',
|
|
97
|
+
selector: 'missing',
|
|
98
|
+
});
|
|
99
|
+
assert.strictEqual(el, null);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe('waitForLocatorOrElement', () => {
|
|
104
|
+
it('should throw when selector is not provided', async () => {
|
|
105
|
+
const page = createMockPlaywrightPage();
|
|
106
|
+
await assert.rejects(
|
|
107
|
+
() => waitForLocatorOrElement({ page, engine: 'playwright' }),
|
|
108
|
+
/selector is required/
|
|
109
|
+
);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('should wait for Playwright locator to be visible', async () => {
|
|
113
|
+
const page = createMockPlaywrightPage({
|
|
114
|
+
elements: { button: { visible: true } },
|
|
115
|
+
});
|
|
116
|
+
const loc = await waitForLocatorOrElement({
|
|
117
|
+
page,
|
|
118
|
+
engine: 'playwright',
|
|
119
|
+
selector: 'button',
|
|
120
|
+
timeout: 1000,
|
|
121
|
+
});
|
|
122
|
+
assert.ok(loc);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('should wait for Puppeteer element to be visible', async () => {
|
|
126
|
+
const page = createMockPuppeteerPage({
|
|
127
|
+
elements: { button: { visible: true } },
|
|
128
|
+
});
|
|
129
|
+
const el = await waitForLocatorOrElement({
|
|
130
|
+
page,
|
|
131
|
+
engine: 'puppeteer',
|
|
132
|
+
selector: 'button',
|
|
133
|
+
timeout: 1000,
|
|
134
|
+
});
|
|
135
|
+
assert.ok(el);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('should handle navigation errors with throwOnNavigation false', async () => {
|
|
139
|
+
const page = createMockPlaywrightPage();
|
|
140
|
+
page.locator = () => ({
|
|
141
|
+
first: () => ({
|
|
142
|
+
waitFor: async () => {
|
|
143
|
+
throw new Error('Execution context was destroyed');
|
|
144
|
+
},
|
|
145
|
+
}),
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
const result = await waitForLocatorOrElement({
|
|
149
|
+
page,
|
|
150
|
+
engine: 'playwright',
|
|
151
|
+
selector: 'button',
|
|
152
|
+
throwOnNavigation: false,
|
|
153
|
+
});
|
|
154
|
+
assert.strictEqual(result, null);
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
describe('waitForVisible', () => {
|
|
159
|
+
it('should throw when locatorOrElement is not provided', async () => {
|
|
160
|
+
await assert.rejects(
|
|
161
|
+
() => waitForVisible({ engine: 'playwright' }),
|
|
162
|
+
/locatorOrElement is required/
|
|
163
|
+
);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('should wait for Playwright locator visibility', async () => {
|
|
167
|
+
const mockLocator = {
|
|
168
|
+
waitFor: async () => {},
|
|
169
|
+
};
|
|
170
|
+
await waitForVisible({
|
|
171
|
+
engine: 'playwright',
|
|
172
|
+
locatorOrElement: mockLocator,
|
|
173
|
+
timeout: 1000,
|
|
174
|
+
});
|
|
175
|
+
// Should not throw
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it('should handle Puppeteer elements (no-op since already fetched)', async () => {
|
|
179
|
+
const mockElement = { click: async () => {} };
|
|
180
|
+
await waitForVisible({
|
|
181
|
+
engine: 'puppeteer',
|
|
182
|
+
locatorOrElement: mockElement,
|
|
183
|
+
timeout: 1000,
|
|
184
|
+
});
|
|
185
|
+
// Should not throw
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('should throw for Puppeteer when element is null', async () => {
|
|
189
|
+
await assert.rejects(
|
|
190
|
+
() =>
|
|
191
|
+
waitForVisible({
|
|
192
|
+
engine: 'puppeteer',
|
|
193
|
+
locatorOrElement: null,
|
|
194
|
+
}),
|
|
195
|
+
/locatorOrElement is required/
|
|
196
|
+
);
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
describe('locator', () => {
|
|
201
|
+
it('should throw when selector is not provided', () => {
|
|
202
|
+
const page = createMockPlaywrightPage();
|
|
203
|
+
assert.throws(
|
|
204
|
+
() => locator({ page, engine: 'playwright' }),
|
|
205
|
+
/selector is required/
|
|
206
|
+
);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it('should create Playwright locator', () => {
|
|
210
|
+
const page = createMockPlaywrightPage();
|
|
211
|
+
const loc = locator({ page, engine: 'playwright', selector: 'button' });
|
|
212
|
+
assert.ok(loc);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it('should create Puppeteer locator wrapper', () => {
|
|
216
|
+
const page = createMockPuppeteerPage();
|
|
217
|
+
const loc = locator({ page, engine: 'puppeteer', selector: 'button' });
|
|
218
|
+
assert.ok(loc);
|
|
219
|
+
assert.ok(typeof loc.count === 'function');
|
|
220
|
+
assert.ok(typeof loc.click === 'function');
|
|
221
|
+
assert.ok(typeof loc.fill === 'function');
|
|
222
|
+
assert.ok(typeof loc.textContent === 'function');
|
|
223
|
+
assert.ok(typeof loc.nth === 'function');
|
|
224
|
+
assert.ok(typeof loc.first === 'function');
|
|
225
|
+
assert.ok(typeof loc.last === 'function');
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it('should allow chained nth calls on Puppeteer wrapper', () => {
|
|
229
|
+
const page = createMockPuppeteerPage();
|
|
230
|
+
const loc = locator({ page, engine: 'puppeteer', selector: 'button' });
|
|
231
|
+
const nthLoc = loc.nth(2);
|
|
232
|
+
assert.ok(nthLoc);
|
|
233
|
+
assert.ok(typeof nthLoc.click === 'function');
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
});
|