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,302 @@
|
|
|
1
|
+
import { describe, it } from 'node:test';
|
|
2
|
+
import assert from 'node:assert';
|
|
3
|
+
import {
|
|
4
|
+
querySelector,
|
|
5
|
+
querySelectorAll,
|
|
6
|
+
findByText,
|
|
7
|
+
normalizeSelector,
|
|
8
|
+
withTextSelectorSupport,
|
|
9
|
+
waitForSelector,
|
|
10
|
+
} from '../../../src/elements/selectors.js';
|
|
11
|
+
import {
|
|
12
|
+
createMockPlaywrightPage,
|
|
13
|
+
createMockPuppeteerPage,
|
|
14
|
+
} from '../../helpers/mocks.js';
|
|
15
|
+
|
|
16
|
+
describe('selectors', () => {
|
|
17
|
+
describe('querySelector', () => {
|
|
18
|
+
it('should throw when selector is not provided', async () => {
|
|
19
|
+
const page = createMockPlaywrightPage();
|
|
20
|
+
await assert.rejects(
|
|
21
|
+
() => querySelector({ page, engine: 'playwright' }),
|
|
22
|
+
/selector is required/
|
|
23
|
+
);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('should find element with Playwright', async () => {
|
|
27
|
+
const page = createMockPlaywrightPage({
|
|
28
|
+
elements: { button: { count: 1 } },
|
|
29
|
+
});
|
|
30
|
+
const el = await querySelector({
|
|
31
|
+
page,
|
|
32
|
+
engine: 'playwright',
|
|
33
|
+
selector: 'button',
|
|
34
|
+
});
|
|
35
|
+
assert.ok(el);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should find element with Puppeteer', async () => {
|
|
39
|
+
const page = createMockPuppeteerPage({
|
|
40
|
+
elements: { button: { count: 1 } },
|
|
41
|
+
});
|
|
42
|
+
const el = await querySelector({
|
|
43
|
+
page,
|
|
44
|
+
engine: 'puppeteer',
|
|
45
|
+
selector: 'button',
|
|
46
|
+
});
|
|
47
|
+
assert.ok(el);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should return null when element not found with Playwright', async () => {
|
|
51
|
+
const page = createMockPlaywrightPage({
|
|
52
|
+
elements: { button: { count: 0 } },
|
|
53
|
+
});
|
|
54
|
+
const el = await querySelector({
|
|
55
|
+
page,
|
|
56
|
+
engine: 'playwright',
|
|
57
|
+
selector: 'button',
|
|
58
|
+
});
|
|
59
|
+
assert.strictEqual(el, null);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('should handle navigation errors', async () => {
|
|
63
|
+
const page = createMockPlaywrightPage();
|
|
64
|
+
page.locator = () => ({
|
|
65
|
+
first: () => ({
|
|
66
|
+
count: async () => {
|
|
67
|
+
throw new Error('Execution context was destroyed');
|
|
68
|
+
},
|
|
69
|
+
}),
|
|
70
|
+
});
|
|
71
|
+
const el = await querySelector({
|
|
72
|
+
page,
|
|
73
|
+
engine: 'playwright',
|
|
74
|
+
selector: 'button',
|
|
75
|
+
});
|
|
76
|
+
assert.strictEqual(el, null);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
describe('querySelectorAll', () => {
|
|
81
|
+
it('should throw when selector is not provided', async () => {
|
|
82
|
+
const page = createMockPlaywrightPage();
|
|
83
|
+
await assert.rejects(
|
|
84
|
+
() => querySelectorAll({ page, engine: 'playwright' }),
|
|
85
|
+
/selector is required/
|
|
86
|
+
);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('should find all elements with Playwright', async () => {
|
|
90
|
+
const page = createMockPlaywrightPage({
|
|
91
|
+
elements: { button: { count: 3 } },
|
|
92
|
+
});
|
|
93
|
+
const elements = await querySelectorAll({
|
|
94
|
+
page,
|
|
95
|
+
engine: 'playwright',
|
|
96
|
+
selector: 'button',
|
|
97
|
+
});
|
|
98
|
+
assert.ok(Array.isArray(elements));
|
|
99
|
+
assert.strictEqual(elements.length, 3);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('should find all elements with Puppeteer', async () => {
|
|
103
|
+
const page = createMockPuppeteerPage({
|
|
104
|
+
elements: { button: { count: 3 } },
|
|
105
|
+
});
|
|
106
|
+
const elements = await querySelectorAll({
|
|
107
|
+
page,
|
|
108
|
+
engine: 'puppeteer',
|
|
109
|
+
selector: 'button',
|
|
110
|
+
});
|
|
111
|
+
assert.ok(Array.isArray(elements));
|
|
112
|
+
assert.strictEqual(elements.length, 3);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('should return empty array when no elements found', async () => {
|
|
116
|
+
const page = createMockPlaywrightPage({
|
|
117
|
+
elements: { button: { count: 0 } },
|
|
118
|
+
});
|
|
119
|
+
const elements = await querySelectorAll({
|
|
120
|
+
page,
|
|
121
|
+
engine: 'playwright',
|
|
122
|
+
selector: 'button',
|
|
123
|
+
});
|
|
124
|
+
assert.ok(Array.isArray(elements));
|
|
125
|
+
assert.strictEqual(elements.length, 0);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('should handle navigation errors', async () => {
|
|
129
|
+
const page = createMockPlaywrightPage();
|
|
130
|
+
page.locator = () => ({
|
|
131
|
+
count: async () => {
|
|
132
|
+
throw new Error('Execution context was destroyed');
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
const elements = await querySelectorAll({
|
|
136
|
+
page,
|
|
137
|
+
engine: 'playwright',
|
|
138
|
+
selector: 'button',
|
|
139
|
+
});
|
|
140
|
+
assert.ok(Array.isArray(elements));
|
|
141
|
+
assert.strictEqual(elements.length, 0);
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
describe('findByText', () => {
|
|
146
|
+
it('should throw when text is not provided', async () => {
|
|
147
|
+
await assert.rejects(
|
|
148
|
+
() => findByText({ engine: 'playwright' }),
|
|
149
|
+
/text is required/
|
|
150
|
+
);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('should return Playwright text selector', async () => {
|
|
154
|
+
const selector = await findByText({
|
|
155
|
+
engine: 'playwright',
|
|
156
|
+
text: 'Click me',
|
|
157
|
+
selector: 'button',
|
|
158
|
+
});
|
|
159
|
+
assert.ok(selector.includes('has-text'));
|
|
160
|
+
assert.ok(selector.includes('Click me'));
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('should return Playwright exact text selector', async () => {
|
|
164
|
+
const selector = await findByText({
|
|
165
|
+
engine: 'playwright',
|
|
166
|
+
text: 'Click me',
|
|
167
|
+
selector: 'button',
|
|
168
|
+
exact: true,
|
|
169
|
+
});
|
|
170
|
+
assert.ok(selector.includes('text-is'));
|
|
171
|
+
assert.ok(selector.includes('Click me'));
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('should return Puppeteer text selector object', async () => {
|
|
175
|
+
const selector = await findByText({
|
|
176
|
+
engine: 'puppeteer',
|
|
177
|
+
text: 'Click me',
|
|
178
|
+
selector: 'button',
|
|
179
|
+
});
|
|
180
|
+
assert.ok(typeof selector === 'object');
|
|
181
|
+
assert.strictEqual(selector._isPuppeteerTextSelector, true);
|
|
182
|
+
assert.strictEqual(selector.text, 'Click me');
|
|
183
|
+
assert.strictEqual(selector.baseSelector, 'button');
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('should use default selector when not provided', async () => {
|
|
187
|
+
const selector = await findByText({
|
|
188
|
+
engine: 'playwright',
|
|
189
|
+
text: 'Hello',
|
|
190
|
+
});
|
|
191
|
+
assert.ok(selector.includes('*'));
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
describe('normalizeSelector', () => {
|
|
196
|
+
it('should throw when selector is not provided', async () => {
|
|
197
|
+
const page = createMockPlaywrightPage();
|
|
198
|
+
await assert.rejects(
|
|
199
|
+
() => normalizeSelector({ page }),
|
|
200
|
+
/selector is required/
|
|
201
|
+
);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('should return string selector unchanged', async () => {
|
|
205
|
+
const page = createMockPlaywrightPage();
|
|
206
|
+
const result = await normalizeSelector({ page, selector: 'button' });
|
|
207
|
+
assert.strictEqual(result, 'button');
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('should return non-text-selector object unchanged', async () => {
|
|
211
|
+
const page = createMockPlaywrightPage();
|
|
212
|
+
const obj = { someKey: 'value' };
|
|
213
|
+
const result = await normalizeSelector({ page, selector: obj });
|
|
214
|
+
assert.strictEqual(result, obj);
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
describe('withTextSelectorSupport', () => {
|
|
219
|
+
it('should wrap function and pass through options', async () => {
|
|
220
|
+
const page = createMockPuppeteerPage();
|
|
221
|
+
let receivedOptions = null;
|
|
222
|
+
const fn = async (options) => {
|
|
223
|
+
receivedOptions = options;
|
|
224
|
+
return 'result';
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
const wrapped = withTextSelectorSupport(fn, 'puppeteer', page);
|
|
228
|
+
const result = await wrapped({ selector: 'button', otherOption: true });
|
|
229
|
+
|
|
230
|
+
assert.strictEqual(result, 'result');
|
|
231
|
+
assert.ok(receivedOptions);
|
|
232
|
+
assert.strictEqual(receivedOptions.selector, 'button');
|
|
233
|
+
assert.strictEqual(receivedOptions.otherOption, true);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it('should pass through string selectors unchanged for Puppeteer', async () => {
|
|
237
|
+
const page = createMockPuppeteerPage();
|
|
238
|
+
let receivedSelector = null;
|
|
239
|
+
const fn = async (options) => {
|
|
240
|
+
receivedSelector = options.selector;
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
const wrapped = withTextSelectorSupport(fn, 'puppeteer', page);
|
|
244
|
+
await wrapped({ selector: 'button' });
|
|
245
|
+
|
|
246
|
+
assert.strictEqual(receivedSelector, 'button');
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
describe('waitForSelector', () => {
|
|
251
|
+
it('should throw when selector is not provided', async () => {
|
|
252
|
+
const page = createMockPlaywrightPage();
|
|
253
|
+
await assert.rejects(
|
|
254
|
+
() => waitForSelector({ page, engine: 'playwright' }),
|
|
255
|
+
/selector is required/
|
|
256
|
+
);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it('should wait for selector with Playwright', async () => {
|
|
260
|
+
const page = createMockPlaywrightPage({
|
|
261
|
+
elements: { button: { visible: true } },
|
|
262
|
+
});
|
|
263
|
+
const result = await waitForSelector({
|
|
264
|
+
page,
|
|
265
|
+
engine: 'playwright',
|
|
266
|
+
selector: 'button',
|
|
267
|
+
timeout: 1000,
|
|
268
|
+
});
|
|
269
|
+
assert.strictEqual(result, true);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it('should wait for selector with Puppeteer', async () => {
|
|
273
|
+
const page = createMockPuppeteerPage({
|
|
274
|
+
elements: { button: { visible: true } },
|
|
275
|
+
});
|
|
276
|
+
const result = await waitForSelector({
|
|
277
|
+
page,
|
|
278
|
+
engine: 'puppeteer',
|
|
279
|
+
selector: 'button',
|
|
280
|
+
timeout: 1000,
|
|
281
|
+
});
|
|
282
|
+
assert.strictEqual(result, true);
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it('should handle navigation errors with throwOnNavigation false', async () => {
|
|
286
|
+
const page = createMockPlaywrightPage();
|
|
287
|
+
page.locator = () => ({
|
|
288
|
+
waitFor: async () => {
|
|
289
|
+
throw new Error('Execution context was destroyed');
|
|
290
|
+
},
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
const result = await waitForSelector({
|
|
294
|
+
page,
|
|
295
|
+
engine: 'playwright',
|
|
296
|
+
selector: 'button',
|
|
297
|
+
throwOnNavigation: false,
|
|
298
|
+
});
|
|
299
|
+
assert.strictEqual(result, false);
|
|
300
|
+
});
|
|
301
|
+
});
|
|
302
|
+
});
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { describe, it } from 'node:test';
|
|
2
|
+
import assert from 'node:assert';
|
|
3
|
+
import {
|
|
4
|
+
isVisible,
|
|
5
|
+
isEnabled,
|
|
6
|
+
count,
|
|
7
|
+
} from '../../../src/elements/visibility.js';
|
|
8
|
+
import {
|
|
9
|
+
createMockPlaywrightPage,
|
|
10
|
+
createMockPuppeteerPage,
|
|
11
|
+
} from '../../helpers/mocks.js';
|
|
12
|
+
|
|
13
|
+
describe('visibility', () => {
|
|
14
|
+
describe('isVisible', () => {
|
|
15
|
+
it('should throw when selector is not provided', async () => {
|
|
16
|
+
const page = createMockPlaywrightPage();
|
|
17
|
+
await assert.rejects(
|
|
18
|
+
() => isVisible({ page, engine: 'playwright' }),
|
|
19
|
+
/selector is required/
|
|
20
|
+
);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('should return true for visible element with Playwright', async () => {
|
|
24
|
+
const page = createMockPlaywrightPage({
|
|
25
|
+
elements: { button: { visible: true, count: 1 } },
|
|
26
|
+
});
|
|
27
|
+
const visible = await isVisible({
|
|
28
|
+
page,
|
|
29
|
+
engine: 'playwright',
|
|
30
|
+
selector: 'button',
|
|
31
|
+
});
|
|
32
|
+
assert.strictEqual(visible, true);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should return false for non-visible element with Playwright', async () => {
|
|
36
|
+
const page = createMockPlaywrightPage({
|
|
37
|
+
elements: { button: { visible: false, count: 1 } },
|
|
38
|
+
});
|
|
39
|
+
const visible = await isVisible({
|
|
40
|
+
page,
|
|
41
|
+
engine: 'playwright',
|
|
42
|
+
selector: 'button',
|
|
43
|
+
});
|
|
44
|
+
assert.strictEqual(visible, false);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('should return true for visible element with Puppeteer', async () => {
|
|
48
|
+
const page = createMockPuppeteerPage({
|
|
49
|
+
elements: { button: { visible: true, count: 1 } },
|
|
50
|
+
});
|
|
51
|
+
const visible = await isVisible({
|
|
52
|
+
page,
|
|
53
|
+
engine: 'puppeteer',
|
|
54
|
+
selector: 'button',
|
|
55
|
+
});
|
|
56
|
+
assert.strictEqual(visible, true);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should return false when element not found with Puppeteer', async () => {
|
|
60
|
+
const page = createMockPuppeteerPage({
|
|
61
|
+
elements: { button: { count: 0 } },
|
|
62
|
+
});
|
|
63
|
+
const visible = await isVisible({
|
|
64
|
+
page,
|
|
65
|
+
engine: 'puppeteer',
|
|
66
|
+
selector: 'button',
|
|
67
|
+
});
|
|
68
|
+
assert.strictEqual(visible, false);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('should handle navigation errors', async () => {
|
|
72
|
+
const page = createMockPlaywrightPage();
|
|
73
|
+
page.locator = () => ({
|
|
74
|
+
waitFor: async () => {
|
|
75
|
+
throw new Error('Execution context was destroyed');
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
const visible = await isVisible({
|
|
79
|
+
page,
|
|
80
|
+
engine: 'playwright',
|
|
81
|
+
selector: 'button',
|
|
82
|
+
});
|
|
83
|
+
assert.strictEqual(visible, false);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
describe('isEnabled', () => {
|
|
88
|
+
it('should throw when selector is not provided', async () => {
|
|
89
|
+
const page = createMockPlaywrightPage();
|
|
90
|
+
await assert.rejects(
|
|
91
|
+
() => isEnabled({ page, engine: 'playwright' }),
|
|
92
|
+
/selector is required/
|
|
93
|
+
);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('should return true for enabled element with Playwright', async () => {
|
|
97
|
+
const page = createMockPlaywrightPage({
|
|
98
|
+
elements: { button: { enabled: true, count: 1 } },
|
|
99
|
+
});
|
|
100
|
+
const enabled = await isEnabled({
|
|
101
|
+
page,
|
|
102
|
+
engine: 'playwright',
|
|
103
|
+
selector: 'button',
|
|
104
|
+
});
|
|
105
|
+
assert.strictEqual(enabled, true);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('should return false for disabled element with Playwright', async () => {
|
|
109
|
+
const page = createMockPlaywrightPage({
|
|
110
|
+
elements: { button: { enabled: false, disabled: true, count: 1 } },
|
|
111
|
+
});
|
|
112
|
+
const enabled = await isEnabled({
|
|
113
|
+
page,
|
|
114
|
+
engine: 'playwright',
|
|
115
|
+
selector: 'button',
|
|
116
|
+
});
|
|
117
|
+
assert.strictEqual(enabled, false);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('should check for custom disabled classes', async () => {
|
|
121
|
+
const page = createMockPlaywrightPage({
|
|
122
|
+
elements: {
|
|
123
|
+
button: {
|
|
124
|
+
enabled: true,
|
|
125
|
+
className: 'magritte-button_loading',
|
|
126
|
+
count: 1,
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
const enabled = await isEnabled({
|
|
131
|
+
page,
|
|
132
|
+
engine: 'playwright',
|
|
133
|
+
selector: 'button',
|
|
134
|
+
disabledClasses: ['magritte-button_loading'],
|
|
135
|
+
});
|
|
136
|
+
assert.strictEqual(enabled, false);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('should handle navigation errors', async () => {
|
|
140
|
+
const page = createMockPlaywrightPage();
|
|
141
|
+
page.locator = () => ({
|
|
142
|
+
first: () => ({
|
|
143
|
+
evaluate: async () => {
|
|
144
|
+
throw new Error('Execution context was destroyed');
|
|
145
|
+
},
|
|
146
|
+
}),
|
|
147
|
+
});
|
|
148
|
+
const enabled = await isEnabled({
|
|
149
|
+
page,
|
|
150
|
+
engine: 'playwright',
|
|
151
|
+
selector: 'button',
|
|
152
|
+
});
|
|
153
|
+
assert.strictEqual(enabled, false);
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
describe('count', () => {
|
|
158
|
+
it('should throw when selector is not provided', async () => {
|
|
159
|
+
const page = createMockPlaywrightPage();
|
|
160
|
+
await assert.rejects(
|
|
161
|
+
() => count({ page, engine: 'playwright' }),
|
|
162
|
+
/selector is required/
|
|
163
|
+
);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('should return element count with Playwright', async () => {
|
|
167
|
+
const page = createMockPlaywrightPage({
|
|
168
|
+
elements: { button: { count: 5 } },
|
|
169
|
+
});
|
|
170
|
+
const c = await count({
|
|
171
|
+
page,
|
|
172
|
+
engine: 'playwright',
|
|
173
|
+
selector: 'button',
|
|
174
|
+
});
|
|
175
|
+
assert.strictEqual(c, 5);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it('should return element count with Puppeteer', async () => {
|
|
179
|
+
const page = createMockPuppeteerPage({
|
|
180
|
+
elements: { button: { count: 3 } },
|
|
181
|
+
});
|
|
182
|
+
const c = await count({
|
|
183
|
+
page,
|
|
184
|
+
engine: 'puppeteer',
|
|
185
|
+
selector: 'button',
|
|
186
|
+
});
|
|
187
|
+
assert.strictEqual(c, 3);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('should return 0 when no elements found', async () => {
|
|
191
|
+
const page = createMockPlaywrightPage({
|
|
192
|
+
elements: { button: { count: 0 } },
|
|
193
|
+
});
|
|
194
|
+
const c = await count({
|
|
195
|
+
page,
|
|
196
|
+
engine: 'playwright',
|
|
197
|
+
selector: 'button',
|
|
198
|
+
});
|
|
199
|
+
assert.strictEqual(c, 0);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('should handle Puppeteer text selector objects', async () => {
|
|
203
|
+
const page = createMockPuppeteerPage();
|
|
204
|
+
page.evaluate = async () => 2; // Mock count result
|
|
205
|
+
|
|
206
|
+
const c = await count({
|
|
207
|
+
page,
|
|
208
|
+
engine: 'puppeteer',
|
|
209
|
+
selector: {
|
|
210
|
+
_isPuppeteerTextSelector: true,
|
|
211
|
+
baseSelector: 'button',
|
|
212
|
+
text: 'Submit',
|
|
213
|
+
exact: false,
|
|
214
|
+
},
|
|
215
|
+
});
|
|
216
|
+
assert.strictEqual(c, 2);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it('should handle navigation errors', async () => {
|
|
220
|
+
const page = createMockPlaywrightPage();
|
|
221
|
+
page.locator = () => ({
|
|
222
|
+
count: async () => {
|
|
223
|
+
throw new Error('Execution context was destroyed');
|
|
224
|
+
},
|
|
225
|
+
});
|
|
226
|
+
const c = await count({
|
|
227
|
+
page,
|
|
228
|
+
engine: 'playwright',
|
|
229
|
+
selector: 'button',
|
|
230
|
+
});
|
|
231
|
+
assert.strictEqual(c, 0);
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
});
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { describe, it } from 'node:test';
|
|
2
|
+
import assert from 'node:assert';
|
|
3
|
+
import { makeBrowserCommander } from '../../src/factory.js';
|
|
4
|
+
import {
|
|
5
|
+
createMockPlaywrightPage,
|
|
6
|
+
createMockPuppeteerPage,
|
|
7
|
+
createMockLogger,
|
|
8
|
+
} from '../helpers/mocks.js';
|
|
9
|
+
|
|
10
|
+
describe('factory', () => {
|
|
11
|
+
describe('makeBrowserCommander', () => {
|
|
12
|
+
it('should throw when page is not provided', () => {
|
|
13
|
+
assert.throws(() => makeBrowserCommander({}), /page is required/);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('should create commander with Playwright page', () => {
|
|
17
|
+
const page = createMockPlaywrightPage();
|
|
18
|
+
page.locator = (sel) => ({
|
|
19
|
+
count: async () => 1,
|
|
20
|
+
waitFor: async () => {},
|
|
21
|
+
});
|
|
22
|
+
page.context = () => ({});
|
|
23
|
+
|
|
24
|
+
const commander = makeBrowserCommander({ page });
|
|
25
|
+
|
|
26
|
+
assert.ok(commander);
|
|
27
|
+
assert.ok(typeof commander.clickButton === 'function');
|
|
28
|
+
assert.ok(typeof commander.fillTextArea === 'function');
|
|
29
|
+
assert.ok(typeof commander.goto === 'function');
|
|
30
|
+
assert.ok(typeof commander.wait === 'function');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('should create commander with Puppeteer page', () => {
|
|
34
|
+
const page = createMockPuppeteerPage();
|
|
35
|
+
// Make sure it doesn't have Playwright-specific methods
|
|
36
|
+
delete page.locator;
|
|
37
|
+
delete page.context;
|
|
38
|
+
page.$eval = async () => {};
|
|
39
|
+
|
|
40
|
+
const commander = makeBrowserCommander({ page });
|
|
41
|
+
|
|
42
|
+
assert.ok(commander);
|
|
43
|
+
assert.ok(typeof commander.clickButton === 'function');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('should accept verbose option', () => {
|
|
47
|
+
const page = createMockPlaywrightPage();
|
|
48
|
+
page.locator = (sel) => ({
|
|
49
|
+
count: async () => 1,
|
|
50
|
+
waitFor: async () => {},
|
|
51
|
+
});
|
|
52
|
+
page.context = () => ({});
|
|
53
|
+
|
|
54
|
+
const commander = makeBrowserCommander({ page, verbose: true });
|
|
55
|
+
|
|
56
|
+
assert.ok(commander);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should have all expected methods', () => {
|
|
60
|
+
const page = createMockPlaywrightPage();
|
|
61
|
+
page.locator = (sel) => ({
|
|
62
|
+
count: async () => 1,
|
|
63
|
+
waitFor: async () => {},
|
|
64
|
+
});
|
|
65
|
+
page.context = () => ({});
|
|
66
|
+
|
|
67
|
+
const commander = makeBrowserCommander({ page });
|
|
68
|
+
|
|
69
|
+
// Core navigation
|
|
70
|
+
assert.ok(typeof commander.goto === 'function');
|
|
71
|
+
assert.ok(typeof commander.getUrl === 'function');
|
|
72
|
+
|
|
73
|
+
// Element selection
|
|
74
|
+
assert.ok(typeof commander.querySelector === 'function');
|
|
75
|
+
assert.ok(typeof commander.querySelectorAll === 'function');
|
|
76
|
+
assert.ok(typeof commander.findByText === 'function');
|
|
77
|
+
|
|
78
|
+
// Interactions
|
|
79
|
+
assert.ok(typeof commander.clickButton === 'function');
|
|
80
|
+
assert.ok(typeof commander.fillTextArea === 'function');
|
|
81
|
+
|
|
82
|
+
// Element state
|
|
83
|
+
assert.ok(typeof commander.isVisible === 'function');
|
|
84
|
+
assert.ok(typeof commander.isEnabled === 'function');
|
|
85
|
+
assert.ok(typeof commander.count === 'function');
|
|
86
|
+
|
|
87
|
+
// Content
|
|
88
|
+
assert.ok(typeof commander.textContent === 'function');
|
|
89
|
+
assert.ok(typeof commander.inputValue === 'function');
|
|
90
|
+
assert.ok(typeof commander.getAttribute === 'function');
|
|
91
|
+
|
|
92
|
+
// Utilities
|
|
93
|
+
assert.ok(typeof commander.wait === 'function');
|
|
94
|
+
assert.ok(typeof commander.evaluate === 'function');
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('should return engine type', () => {
|
|
98
|
+
const page = createMockPlaywrightPage();
|
|
99
|
+
page.locator = (sel) => ({
|
|
100
|
+
count: async () => 1,
|
|
101
|
+
waitFor: async () => {},
|
|
102
|
+
});
|
|
103
|
+
page.context = () => ({});
|
|
104
|
+
|
|
105
|
+
const commander = makeBrowserCommander({ page });
|
|
106
|
+
|
|
107
|
+
assert.ok(
|
|
108
|
+
commander.engine === 'playwright' || commander.engine === 'puppeteer'
|
|
109
|
+
);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('should expose page object', () => {
|
|
113
|
+
const page = createMockPlaywrightPage();
|
|
114
|
+
page.locator = (sel) => ({
|
|
115
|
+
count: async () => 1,
|
|
116
|
+
waitFor: async () => {},
|
|
117
|
+
});
|
|
118
|
+
page.context = () => ({});
|
|
119
|
+
|
|
120
|
+
const commander = makeBrowserCommander({ page });
|
|
121
|
+
|
|
122
|
+
assert.strictEqual(commander.page, page);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('should allow disabling network tracking', () => {
|
|
126
|
+
const page = createMockPlaywrightPage();
|
|
127
|
+
page.locator = (sel) => ({
|
|
128
|
+
count: async () => 1,
|
|
129
|
+
waitFor: async () => {},
|
|
130
|
+
});
|
|
131
|
+
page.context = () => ({});
|
|
132
|
+
|
|
133
|
+
const commander = makeBrowserCommander({
|
|
134
|
+
page,
|
|
135
|
+
enableNetworkTracking: false,
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
assert.ok(commander);
|
|
139
|
+
assert.strictEqual(commander.networkTracker, null);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('should allow disabling navigation manager', () => {
|
|
143
|
+
const page = createMockPlaywrightPage();
|
|
144
|
+
page.locator = (sel) => ({
|
|
145
|
+
count: async () => 1,
|
|
146
|
+
waitFor: async () => {},
|
|
147
|
+
});
|
|
148
|
+
page.context = () => ({});
|
|
149
|
+
|
|
150
|
+
const commander = makeBrowserCommander({
|
|
151
|
+
page,
|
|
152
|
+
enableNavigationManager: false,
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
assert.ok(commander);
|
|
156
|
+
assert.strictEqual(commander.navigationManager, null);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('should have destroy method', async () => {
|
|
160
|
+
const page = createMockPlaywrightPage();
|
|
161
|
+
page.locator = (sel) => ({
|
|
162
|
+
count: async () => 1,
|
|
163
|
+
waitFor: async () => {},
|
|
164
|
+
});
|
|
165
|
+
page.context = () => ({});
|
|
166
|
+
|
|
167
|
+
const commander = makeBrowserCommander({ page });
|
|
168
|
+
|
|
169
|
+
assert.ok(typeof commander.destroy === 'function');
|
|
170
|
+
// Should not throw
|
|
171
|
+
await commander.destroy();
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
});
|