browser-commander 0.22.0 → 0.24.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/.jscpd-baseline.json +47 -29
- package/CHANGELOG.md +19 -0
- package/README.md +12 -3
- package/eslint.config.js +1 -1
- package/examples/react-test-app/package-lock.json +890 -0
- package/examples/react-test-app/package.json +4 -4
- package/package.json +17 -17
- package/src/browser/browser-cookies.js +37 -5
- package/src/browser/browser-profiles.js +18 -0
- package/src/browser/browser-sources.json +30 -0
- package/src/browser/connector.js +46 -4
- package/src/browser/launcher.js +26 -1
- package/src/browser/migration/index.js +33 -0
- package/src/browser/real-browser.js +8 -2
- package/src/browser/safari-cookies.js +173 -0
- package/src/browser/storage-state.js +27 -0
- package/src/browser/webdriver.js +18 -2
- package/src/cli/args.js +5 -0
- package/src/cli/commands.js +10 -6
- package/src/cli/handle-methods.js +19 -2
- package/src/cli/high-level-methods.js +20 -3
- package/src/cli/modules.js +5 -1
- package/src/cli/sessions.js +14 -2
- package/src/tests/index.js +1 -0
- package/tests/e2e/engine-matrix.e2e.test.js +107 -0
- package/tests/e2e/playwright.e2e.test.js +11 -17
- package/tests/e2e/puppeteer.e2e.test.js +13 -17
- package/tests/unit/browser/safari-cookies.test.js +246 -0
- package/tests/unit/cli/dispatcher.test.js +2 -2
- package/tests/unit/cli/selenium-parity.test.js +186 -0
- package/tests/unit/tests/index.test.js +2 -1
- package/tests/unit/traces/links.test.js +2 -2
package/src/browser/webdriver.js
CHANGED
|
@@ -302,11 +302,20 @@ export function buildBrowserOptions({
|
|
|
302
302
|
args,
|
|
303
303
|
automationParity,
|
|
304
304
|
bidi,
|
|
305
|
+
debuggerAddress,
|
|
305
306
|
}) {
|
|
306
307
|
let options;
|
|
307
308
|
let browserArgs;
|
|
308
309
|
if (browser === 'chrome') {
|
|
309
310
|
options = new selenium.chrome.Options();
|
|
311
|
+
if (debuggerAddress) {
|
|
312
|
+
options.debuggerAddress(debuggerAddress);
|
|
313
|
+
if (bidi) {
|
|
314
|
+
options.set('webSocketUrl', true);
|
|
315
|
+
options.set('unhandledPromptBehavior', 'ignore');
|
|
316
|
+
}
|
|
317
|
+
return { options, args: [] };
|
|
318
|
+
}
|
|
310
319
|
browserArgs = buildRealBrowserArgs({
|
|
311
320
|
userDataDir,
|
|
312
321
|
remoteDebuggingPort,
|
|
@@ -450,8 +459,12 @@ export async function launchWebDriver(options = {}, dependencies = {}) {
|
|
|
450
459
|
env,
|
|
451
460
|
startupTimeout = 30_000,
|
|
452
461
|
verbose = false,
|
|
462
|
+
debuggerAddress,
|
|
453
463
|
} = options;
|
|
454
464
|
assertBrowser(browser);
|
|
465
|
+
if (debuggerAddress && browser !== 'chrome') {
|
|
466
|
+
throw new Error('debuggerAddress is only supported by Chrome WebDriver');
|
|
467
|
+
}
|
|
455
468
|
if (browser === 'firefox' && restrictions.length > 0) {
|
|
456
469
|
throw new Error(
|
|
457
470
|
'Launch restrictions are Chrome switches; not available for firefox'
|
|
@@ -476,8 +489,10 @@ export async function launchWebDriver(options = {}, dependencies = {}) {
|
|
|
476
489
|
...(dependencies.platform ? { platform: dependencies.platform } : {}),
|
|
477
490
|
});
|
|
478
491
|
|
|
479
|
-
const temporaryProfile = !requestedUserDataDir;
|
|
480
|
-
const userDataDir =
|
|
492
|
+
const temporaryProfile = !debuggerAddress && !requestedUserDataDir;
|
|
493
|
+
const userDataDir = debuggerAddress
|
|
494
|
+
? undefined
|
|
495
|
+
: await resolveProfile(browser, requestedUserDataDir);
|
|
481
496
|
const childEnv = driverEnvironment(restrictions, env);
|
|
482
497
|
|
|
483
498
|
let driverProcess;
|
|
@@ -513,6 +528,7 @@ export async function launchWebDriver(options = {}, dependencies = {}) {
|
|
|
513
528
|
args,
|
|
514
529
|
automationParity,
|
|
515
530
|
bidi,
|
|
531
|
+
debuggerAddress,
|
|
516
532
|
});
|
|
517
533
|
driver = await buildSession({
|
|
518
534
|
selenium,
|
package/src/cli/args.js
CHANGED
|
@@ -31,12 +31,15 @@ const BROWSER_OPTIONS = Object.freeze({
|
|
|
31
31
|
'local-state': STRINGS,
|
|
32
32
|
'default-browser-check': FLAG,
|
|
33
33
|
'first-run': FLAG,
|
|
34
|
+
'driver-path': STRING,
|
|
35
|
+
bidi: FLAG,
|
|
34
36
|
});
|
|
35
37
|
|
|
36
38
|
/** Options accepted by every page command. */
|
|
37
39
|
const PAGE_OPTIONS = Object.freeze({
|
|
38
40
|
...BROWSER_OPTIONS,
|
|
39
41
|
'cdp-endpoint': STRING,
|
|
42
|
+
'server-url': STRING,
|
|
40
43
|
url: STRING,
|
|
41
44
|
});
|
|
42
45
|
|
|
@@ -248,6 +251,8 @@ export function launchParams(options) {
|
|
|
248
251
|
localState: settings(options.localState, '--local-state'),
|
|
249
252
|
defaultBrowserCheck: options.defaultBrowserCheck,
|
|
250
253
|
firstRun: options.firstRun,
|
|
254
|
+
driverPath: options.driverPath,
|
|
255
|
+
bidi: options.bidi,
|
|
251
256
|
};
|
|
252
257
|
return Object.fromEntries(
|
|
253
258
|
Object.entries(params).filter(([, value]) => value !== undefined)
|
package/src/cli/commands.js
CHANGED
|
@@ -75,12 +75,16 @@ async function withPageSession(parsed, io, work, { skipUrl = false } = {}) {
|
|
|
75
75
|
const { options } = parsed;
|
|
76
76
|
const dispatcher = createDispatcher({ dependencies: io.dependencies });
|
|
77
77
|
try {
|
|
78
|
-
const opened =
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
78
|
+
const opened =
|
|
79
|
+
options.cdpEndpoint || options.serverUrl
|
|
80
|
+
? await dispatcher.dispatch('session.connect', {
|
|
81
|
+
cdpEndpoint: options.cdpEndpoint,
|
|
82
|
+
engine: options.engine,
|
|
83
|
+
...(options.serverUrl ? { serverUrl: options.serverUrl } : {}),
|
|
84
|
+
...(options.driverPath ? { driverPath: options.driverPath } : {}),
|
|
85
|
+
...(options.bidi !== undefined ? { bidi: options.bidi } : {}),
|
|
86
|
+
})
|
|
87
|
+
: await dispatcher.dispatch('session.launch', launchParams(options));
|
|
84
88
|
if (options.url && !skipUrl) {
|
|
85
89
|
await dispatcher.dispatch('page.goto', {
|
|
86
90
|
session: opened.session,
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
import { decodeValue, describeObject, encodeValue } from './handles.js';
|
|
10
10
|
import { invalidParams, requireString } from './rpc-error.js';
|
|
11
11
|
|
|
12
|
-
const ENGINE_ROOTS = Object.freeze(['playwright', 'puppeteer']);
|
|
12
|
+
const ENGINE_ROOTS = Object.freeze(['playwright', 'puppeteer', 'selenium']);
|
|
13
13
|
|
|
14
14
|
function targetOf(state, params) {
|
|
15
15
|
const handle = params.handle;
|
|
@@ -36,11 +36,14 @@ async function root(state, params) {
|
|
|
36
36
|
browser: handleOrNull(state, session.browser),
|
|
37
37
|
context: handleOrNull(state, session.context),
|
|
38
38
|
page: handleOrNull(state, session.page),
|
|
39
|
+
...(session.engine === 'selenium'
|
|
40
|
+
? { driver: handleOrNull(state, session.driver) }
|
|
41
|
+
: {}),
|
|
39
42
|
};
|
|
40
43
|
}
|
|
41
44
|
if (!ENGINE_ROOTS.includes(name)) {
|
|
42
45
|
throw invalidParams(
|
|
43
|
-
`Unknown root "${name}"; expected playwright, puppeteer or session:<id>`
|
|
46
|
+
`Unknown root "${name}"; expected playwright, puppeteer, selenium or session:<id>`
|
|
44
47
|
);
|
|
45
48
|
}
|
|
46
49
|
const module = await state.dependencies.loadEngine(name);
|
|
@@ -71,6 +74,19 @@ async function get(state, params) {
|
|
|
71
74
|
);
|
|
72
75
|
}
|
|
73
76
|
|
|
77
|
+
/** Instantiate an engine constructor obtained through `handle.get`. */
|
|
78
|
+
function construct(state, params) {
|
|
79
|
+
const target = targetOf(state, params);
|
|
80
|
+
const args = params.args ?? [];
|
|
81
|
+
if (typeof target !== 'function' || !Array.isArray(args)) {
|
|
82
|
+
throw invalidParams(
|
|
83
|
+
'handle.construct requires a constructor handle and an args array'
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
const result = Reflect.construct(target, decodeValue(args, state.handles));
|
|
87
|
+
return encodeValue(result, state.handles);
|
|
88
|
+
}
|
|
89
|
+
|
|
74
90
|
function dispose(state, params) {
|
|
75
91
|
const id =
|
|
76
92
|
typeof params.handle === 'object' && params.handle
|
|
@@ -129,6 +145,7 @@ export const HANDLE_METHODS = Object.freeze({
|
|
|
129
145
|
'handle.root': root,
|
|
130
146
|
'handle.call': call,
|
|
131
147
|
'handle.get': get,
|
|
148
|
+
'handle.construct': construct,
|
|
132
149
|
'handle.dispose': dispose,
|
|
133
150
|
'handle.describe': describe,
|
|
134
151
|
'events.subscribe': subscribe,
|
|
@@ -37,14 +37,27 @@ async function launchSession(state, params) {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
async function connectSession(state, params) {
|
|
40
|
-
const cdpEndpoint = requireString(params, 'cdpEndpoint');
|
|
41
40
|
const engine = resolveEngine(params.engine);
|
|
41
|
+
const endpoint =
|
|
42
|
+
engine === 'selenium' && params.serverUrl !== undefined
|
|
43
|
+
? {
|
|
44
|
+
serverUrl: requireString(params, 'serverUrl'),
|
|
45
|
+
capabilities: params.capabilities,
|
|
46
|
+
}
|
|
47
|
+
: { cdpEndpoint: requireString(params, 'cdpEndpoint') };
|
|
42
48
|
const connected = await state.dependencies.connectBrowser({
|
|
43
49
|
engine,
|
|
44
|
-
|
|
50
|
+
...endpoint,
|
|
51
|
+
...(params.driverPath ? { driverPath: params.driverPath } : {}),
|
|
52
|
+
...(params.bidi !== undefined ? { bidi: params.bidi } : {}),
|
|
45
53
|
});
|
|
46
54
|
const session = state.sessions.add(sessionFromConnect(engine, connected));
|
|
47
|
-
return {
|
|
55
|
+
return {
|
|
56
|
+
session,
|
|
57
|
+
...(endpoint.serverUrl
|
|
58
|
+
? { serverUrl: endpoint.serverUrl }
|
|
59
|
+
: { cdpEndpoint: endpoint.cdpEndpoint }),
|
|
60
|
+
};
|
|
48
61
|
}
|
|
49
62
|
|
|
50
63
|
async function closeSession(state, params) {
|
|
@@ -70,6 +83,10 @@ async function fill(state, params) {
|
|
|
70
83
|
const { engine, page } = sessionOf(state, params);
|
|
71
84
|
if (engine === 'playwright') {
|
|
72
85
|
await page.fill(selector, value);
|
|
86
|
+
} else if (engine === 'selenium') {
|
|
87
|
+
const element = await page.waitForSelector(selector, { visible: true });
|
|
88
|
+
await element.clear();
|
|
89
|
+
await element.sendKeys(value);
|
|
73
90
|
} else {
|
|
74
91
|
await page.locator(selector).fill(value);
|
|
75
92
|
}
|
package/src/cli/modules.js
CHANGED
|
@@ -70,7 +70,11 @@ export const DEFAULT_DEPENDENCIES = Object.freeze({
|
|
|
70
70
|
(await import('../browser/browser-profiles.js')).listBrowserProfiles(
|
|
71
71
|
options
|
|
72
72
|
),
|
|
73
|
-
loadEngine: (name) =>
|
|
73
|
+
loadEngine: (name) =>
|
|
74
|
+
importOptional(
|
|
75
|
+
name === 'selenium' ? 'selenium-webdriver' : name,
|
|
76
|
+
`The ${name} engine`
|
|
77
|
+
),
|
|
74
78
|
openInUserBrowser: async (url) =>
|
|
75
79
|
(
|
|
76
80
|
await importOptional(
|
package/src/cli/sessions.js
CHANGED
|
@@ -14,7 +14,7 @@ export const BROWSER_CHANNELS = Object.freeze({
|
|
|
14
14
|
chromium: 'chromium',
|
|
15
15
|
});
|
|
16
16
|
|
|
17
|
-
const ENGINES = Object.freeze(['playwright', 'puppeteer']);
|
|
17
|
+
const ENGINES = Object.freeze(['playwright', 'puppeteer', 'selenium']);
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
20
|
* Launch options that are forwarded to `launchBrowser()` as they are, so the
|
|
@@ -38,6 +38,8 @@ const LAUNCH_PASSTHROUGH = Object.freeze([
|
|
|
38
38
|
'localState',
|
|
39
39
|
'defaultBrowserCheck',
|
|
40
40
|
'firstRun',
|
|
41
|
+
'driverPath',
|
|
42
|
+
'bidi',
|
|
41
43
|
]);
|
|
42
44
|
|
|
43
45
|
/** Validate an engine name, defaulting to Playwright. */
|
|
@@ -85,13 +87,17 @@ export function buildLaunchOptions(params = {}) {
|
|
|
85
87
|
engine: resolveEngine(params.engine),
|
|
86
88
|
headless: params.headless === true,
|
|
87
89
|
};
|
|
88
|
-
const channel =
|
|
90
|
+
const channel =
|
|
91
|
+
options.engine === 'selenium' ? undefined : resolveChannel(params.browser);
|
|
89
92
|
const defined = {
|
|
90
93
|
launch: params.launch,
|
|
91
94
|
executablePath: params.executablePath,
|
|
92
95
|
userDataDir: params.userDataDir,
|
|
93
96
|
restrictions: params.restrictions,
|
|
94
97
|
channel,
|
|
98
|
+
...(options.engine === 'selenium' && params.browser !== undefined
|
|
99
|
+
? { browser: params.browser }
|
|
100
|
+
: {}),
|
|
95
101
|
};
|
|
96
102
|
for (const name of LAUNCH_PASSTHROUGH) {
|
|
97
103
|
defined[name] ??= params[name];
|
|
@@ -121,6 +127,7 @@ export function sessionFromLaunch(engine, launched) {
|
|
|
121
127
|
: handle,
|
|
122
128
|
context: playwright ? handle : (handle?.defaultBrowserContext?.() ?? null),
|
|
123
129
|
page: launched.page,
|
|
130
|
+
driver: launched.driver ?? (engine === 'selenium' ? handle : null),
|
|
124
131
|
args: launched.args ?? [],
|
|
125
132
|
close: () => launched.close(),
|
|
126
133
|
connected: false,
|
|
@@ -141,10 +148,15 @@ export function sessionFromConnect(engine, connected) {
|
|
|
141
148
|
? (page?.context?.() ?? null)
|
|
142
149
|
: (browser?.defaultBrowserContext?.() ?? null),
|
|
143
150
|
page,
|
|
151
|
+
driver: connected.driver ?? null,
|
|
144
152
|
// Playwright's close() on a connectOverCDP browser disconnects;
|
|
145
153
|
// Puppeteer's close() would quit the browser, disconnect() does not.
|
|
146
154
|
close: async () => {
|
|
147
155
|
await connected.downloads?.dispose?.();
|
|
156
|
+
if (engine === 'selenium') {
|
|
157
|
+
await connected.close();
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
148
160
|
await (playwright ? browser.close() : browser.disconnect());
|
|
149
161
|
},
|
|
150
162
|
connected: true,
|
package/src/tests/index.js
CHANGED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/** Native common launch and commander contracts for every JS engine (#124). */
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { existsSync } from 'node:fs';
|
|
4
|
+
import { createServer } from 'node:http';
|
|
5
|
+
import { describe, it } from 'node:test';
|
|
6
|
+
import { launchBrowser } from '../../src/browser/launcher.js';
|
|
7
|
+
import { makeBrowserCommander } from '../../src/factory.js';
|
|
8
|
+
import {
|
|
9
|
+
CHROME_LAUNCH_OPTIONS,
|
|
10
|
+
PARITY_CHROME,
|
|
11
|
+
} from '../helpers/e2e-browser.js';
|
|
12
|
+
|
|
13
|
+
// feature-parity: engines.webdriver@native-typed engines.cli-matrix@native-typed
|
|
14
|
+
describe('Common native engine API', { skip: !process.env.RUN_E2E }, () => {
|
|
15
|
+
it(
|
|
16
|
+
'selenium restores portable cookies and BiDi origin state',
|
|
17
|
+
{ timeout: 60000 },
|
|
18
|
+
async () => {
|
|
19
|
+
const server = createServer((_request, response) => {
|
|
20
|
+
response.end('<title>Portable state</title>');
|
|
21
|
+
});
|
|
22
|
+
await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
|
|
23
|
+
const origin = `http://127.0.0.1:${server.address().port}`;
|
|
24
|
+
let session;
|
|
25
|
+
try {
|
|
26
|
+
session = await launchBrowser({
|
|
27
|
+
engine: 'selenium',
|
|
28
|
+
launch: 'engine',
|
|
29
|
+
bidi: true,
|
|
30
|
+
headless: true,
|
|
31
|
+
executablePath: PARITY_CHROME,
|
|
32
|
+
...CHROME_LAUNCH_OPTIONS,
|
|
33
|
+
storageState: {
|
|
34
|
+
cookies: [{ name: 'session', value: 'restored', url: origin }],
|
|
35
|
+
origins: [
|
|
36
|
+
{ origin, localStorage: [{ name: 'theme', value: 'dark' }] },
|
|
37
|
+
],
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
await session.page.goto(origin);
|
|
41
|
+
assert.equal(
|
|
42
|
+
await session.page.evaluate(() =>
|
|
43
|
+
globalThis.localStorage.getItem('theme')
|
|
44
|
+
),
|
|
45
|
+
'dark'
|
|
46
|
+
);
|
|
47
|
+
assert.match(
|
|
48
|
+
await session.page.evaluate(() => document.cookie),
|
|
49
|
+
/session=restored/u
|
|
50
|
+
);
|
|
51
|
+
} finally {
|
|
52
|
+
await session?.close();
|
|
53
|
+
await new Promise((resolve) => server.close(resolve));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
);
|
|
57
|
+
for (const engine of ['playwright', 'puppeteer', 'selenium']) {
|
|
58
|
+
for (const launch of ['real', 'engine']) {
|
|
59
|
+
it(
|
|
60
|
+
`${engine}/${launch}: input, click, native handles, PDF, cleanup`,
|
|
61
|
+
{ timeout: 120000 },
|
|
62
|
+
async () => {
|
|
63
|
+
const session = await launchBrowser({
|
|
64
|
+
engine,
|
|
65
|
+
launch,
|
|
66
|
+
headless: true,
|
|
67
|
+
executablePath: PARITY_CHROME,
|
|
68
|
+
...CHROME_LAUNCH_OPTIONS,
|
|
69
|
+
});
|
|
70
|
+
const commander = makeBrowserCommander({
|
|
71
|
+
page: session.page,
|
|
72
|
+
enableNetworkTracking: false,
|
|
73
|
+
enableNavigationManager: false,
|
|
74
|
+
});
|
|
75
|
+
const profile = session.userDataDir;
|
|
76
|
+
try {
|
|
77
|
+
assert.equal(commander.engine, engine);
|
|
78
|
+
await commander.goto({
|
|
79
|
+
url: `data:text/html,${encodeURIComponent('<input id="name"><button id="go" onclick="document.title=document.querySelector(\'#name\').value">Go</button>')}`,
|
|
80
|
+
});
|
|
81
|
+
await commander.fill({ selector: '#name', text: 'Ada' });
|
|
82
|
+
assert.equal(
|
|
83
|
+
await commander.inputValue({ selector: '#name' }),
|
|
84
|
+
'Ada'
|
|
85
|
+
);
|
|
86
|
+
await commander.click({ selector: '#go' });
|
|
87
|
+
assert.equal(await session.page.title(), 'Ada');
|
|
88
|
+
assert.ok(
|
|
89
|
+
Buffer.from(await session.page.pdf({ format: 'A4' }))
|
|
90
|
+
.subarray(0, 5)
|
|
91
|
+
.equals(Buffer.from('%PDF-'))
|
|
92
|
+
);
|
|
93
|
+
if (engine === 'selenium') {
|
|
94
|
+
assert.equal(await session.driver.getTitle(), 'Ada');
|
|
95
|
+
}
|
|
96
|
+
} finally {
|
|
97
|
+
await commander.destroy();
|
|
98
|
+
await session.close();
|
|
99
|
+
}
|
|
100
|
+
if (session.temporaryProfile) {
|
|
101
|
+
assert.equal(existsSync(profile), false);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
});
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import { describe, it, before, after } from 'node:test';
|
|
11
11
|
import assert from 'node:assert';
|
|
12
|
+
import { CHROME_LAUNCH_OPTIONS } from '../helpers/e2e-browser.js';
|
|
12
13
|
|
|
13
14
|
// Dynamic import for playwright since it may not be installed
|
|
14
15
|
let playwright;
|
|
@@ -25,23 +26,16 @@ describe(
|
|
|
25
26
|
const BASE_URL = process.env.TEST_URL || 'http://localhost:3000';
|
|
26
27
|
|
|
27
28
|
before(async () => {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
commander = createCommander({ page, verbose: true });
|
|
39
|
-
} catch (error) {
|
|
40
|
-
console.log(
|
|
41
|
-
'Skipping E2E tests - playwright not available or test app not running'
|
|
42
|
-
);
|
|
43
|
-
console.log('Error:', error.message);
|
|
44
|
-
}
|
|
29
|
+
playwright = await import('playwright');
|
|
30
|
+
const module = await import('../../src/index.js');
|
|
31
|
+
createCommander = module.createCommander;
|
|
32
|
+
browser = await playwright.chromium.launch({
|
|
33
|
+
headless: process.env.HEADLESS !== 'false',
|
|
34
|
+
...CHROME_LAUNCH_OPTIONS,
|
|
35
|
+
});
|
|
36
|
+
const context = await browser.newContext();
|
|
37
|
+
page = await context.newPage();
|
|
38
|
+
commander = createCommander({ page, verbose: true });
|
|
45
39
|
});
|
|
46
40
|
|
|
47
41
|
after(async () => {
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import { describe, it, before, after } from 'node:test';
|
|
11
11
|
import assert from 'node:assert';
|
|
12
|
+
import { CHROME_LAUNCH_OPTIONS } from '../helpers/e2e-browser.js';
|
|
12
13
|
|
|
13
14
|
// Dynamic import for puppeteer since it may not be installed
|
|
14
15
|
let puppeteer;
|
|
@@ -22,22 +23,15 @@ describe('E2E Tests - Puppeteer Engine', { skip: !process.env.RUN_E2E }, () => {
|
|
|
22
23
|
const BASE_URL = process.env.TEST_URL || 'http://localhost:3000';
|
|
23
24
|
|
|
24
25
|
before(async () => {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
commander = createCommander({ page, verbose: true });
|
|
35
|
-
} catch (error) {
|
|
36
|
-
console.log(
|
|
37
|
-
'Skipping E2E tests - puppeteer not available or test app not running'
|
|
38
|
-
);
|
|
39
|
-
console.log('Error:', error.message);
|
|
40
|
-
}
|
|
26
|
+
puppeteer = await import('puppeteer');
|
|
27
|
+
const module = await import('../../src/index.js');
|
|
28
|
+
createCommander = module.createCommander;
|
|
29
|
+
browser = await puppeteer.default.launch({
|
|
30
|
+
headless: process.env.HEADLESS !== 'false',
|
|
31
|
+
...CHROME_LAUNCH_OPTIONS,
|
|
32
|
+
});
|
|
33
|
+
page = await browser.newPage();
|
|
34
|
+
commander = createCommander({ page, verbose: true });
|
|
41
35
|
});
|
|
42
36
|
|
|
43
37
|
after(async () => {
|
|
@@ -352,7 +346,9 @@ describe('E2E Tests - Puppeteer Engine', { skip: !process.env.RUN_E2E }, () => {
|
|
|
352
346
|
}
|
|
353
347
|
|
|
354
348
|
// Scroll to specific item
|
|
355
|
-
await commander.
|
|
349
|
+
await commander.scrollIntoView({
|
|
350
|
+
locatorOrElement: await page.$('[data-testid="scroll-item-15"]'),
|
|
351
|
+
});
|
|
356
352
|
await commander.wait({ ms: 500 });
|
|
357
353
|
|
|
358
354
|
// Target should be visible
|