browser-commander 0.6.0 → 0.8.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/CHANGELOG.md +35 -0
- package/README.md +32 -0
- package/examples/pdf-generation.js +76 -0
- package/package.json +1 -1
- package/src/README.md +31 -0
- package/src/bindings.js +34 -0
- package/src/browser/launcher.js +27 -2
- package/src/browser/media.js +57 -0
- package/src/browser/pdf.js +31 -0
- package/src/core/dialog-manager.js +158 -0
- package/src/core/engine-adapter.js +97 -0
- package/src/exports.js +6 -0
- package/src/factory.js +37 -0
- package/src/interactions/keyboard.js +91 -0
- package/tests/e2e/playwright.e2e.test.js +84 -0
- package/tests/e2e/puppeteer.e2e.test.js +71 -0
- package/tests/helpers/mocks.js +8 -0
- package/tests/unit/bindings.test.js +72 -0
- package/tests/unit/browser/media.test.js +176 -0
- package/tests/unit/browser/pdf.test.js +80 -0
- package/tests/unit/core/dialog-manager.test.js +310 -0
- package/tests/unit/core/engine-adapter.test.js +21 -0
- package/tests/unit/interactions/keyboard.test.js +316 -0
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { describe, it, beforeEach } from 'node:test';
|
|
2
|
+
import assert from 'node:assert';
|
|
3
|
+
import { emulateMedia } from '../../../src/browser/media.js';
|
|
4
|
+
|
|
5
|
+
describe('emulateMedia', () => {
|
|
6
|
+
describe('input validation', () => {
|
|
7
|
+
it('should throw if page is not provided', async () => {
|
|
8
|
+
await assert.rejects(
|
|
9
|
+
() => emulateMedia({ engine: 'playwright', colorScheme: 'dark' }),
|
|
10
|
+
/page is required/
|
|
11
|
+
);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('should throw if engine is not provided', async () => {
|
|
15
|
+
const mockPage = {};
|
|
16
|
+
await assert.rejects(
|
|
17
|
+
() => emulateMedia({ page: mockPage, colorScheme: 'dark' }),
|
|
18
|
+
/engine is required/
|
|
19
|
+
);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('should throw for invalid colorScheme', async () => {
|
|
23
|
+
const mockPage = {};
|
|
24
|
+
await assert.rejects(
|
|
25
|
+
() =>
|
|
26
|
+
emulateMedia({
|
|
27
|
+
page: mockPage,
|
|
28
|
+
engine: 'playwright',
|
|
29
|
+
colorScheme: 'invalid',
|
|
30
|
+
}),
|
|
31
|
+
/Invalid colorScheme/
|
|
32
|
+
);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should throw for unsupported engine', async () => {
|
|
36
|
+
const mockPage = {};
|
|
37
|
+
await assert.rejects(
|
|
38
|
+
() =>
|
|
39
|
+
emulateMedia({
|
|
40
|
+
page: mockPage,
|
|
41
|
+
engine: 'selenium',
|
|
42
|
+
colorScheme: 'dark',
|
|
43
|
+
}),
|
|
44
|
+
/Unsupported engine/
|
|
45
|
+
);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe('Playwright engine', () => {
|
|
50
|
+
let capturedMediaOptions;
|
|
51
|
+
let mockPage;
|
|
52
|
+
|
|
53
|
+
beforeEach(() => {
|
|
54
|
+
capturedMediaOptions = null;
|
|
55
|
+
mockPage = {
|
|
56
|
+
emulateMedia: async (options) => {
|
|
57
|
+
capturedMediaOptions = options;
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('should call page.emulateMedia with colorScheme: dark', async () => {
|
|
63
|
+
await emulateMedia({
|
|
64
|
+
page: mockPage,
|
|
65
|
+
engine: 'playwright',
|
|
66
|
+
colorScheme: 'dark',
|
|
67
|
+
});
|
|
68
|
+
assert.deepStrictEqual(capturedMediaOptions, { colorScheme: 'dark' });
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('should call page.emulateMedia with colorScheme: light', async () => {
|
|
72
|
+
await emulateMedia({
|
|
73
|
+
page: mockPage,
|
|
74
|
+
engine: 'playwright',
|
|
75
|
+
colorScheme: 'light',
|
|
76
|
+
});
|
|
77
|
+
assert.deepStrictEqual(capturedMediaOptions, { colorScheme: 'light' });
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('should call page.emulateMedia with colorScheme: no-preference', async () => {
|
|
81
|
+
await emulateMedia({
|
|
82
|
+
page: mockPage,
|
|
83
|
+
engine: 'playwright',
|
|
84
|
+
colorScheme: 'no-preference',
|
|
85
|
+
});
|
|
86
|
+
assert.deepStrictEqual(capturedMediaOptions, {
|
|
87
|
+
colorScheme: 'no-preference',
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('should call page.emulateMedia with colorScheme: null to reset', async () => {
|
|
92
|
+
await emulateMedia({
|
|
93
|
+
page: mockPage,
|
|
94
|
+
engine: 'playwright',
|
|
95
|
+
colorScheme: null,
|
|
96
|
+
});
|
|
97
|
+
assert.deepStrictEqual(capturedMediaOptions, { colorScheme: null });
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('should call page.emulateMedia with empty options when no colorScheme given', async () => {
|
|
101
|
+
await emulateMedia({
|
|
102
|
+
page: mockPage,
|
|
103
|
+
engine: 'playwright',
|
|
104
|
+
});
|
|
105
|
+
assert.deepStrictEqual(capturedMediaOptions, {});
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
describe('Puppeteer engine', () => {
|
|
110
|
+
let capturedFeatures;
|
|
111
|
+
let mockPage;
|
|
112
|
+
|
|
113
|
+
beforeEach(() => {
|
|
114
|
+
capturedFeatures = null;
|
|
115
|
+
mockPage = {
|
|
116
|
+
emulateMediaFeatures: async (features) => {
|
|
117
|
+
capturedFeatures = features;
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('should call page.emulateMediaFeatures with dark colorScheme', async () => {
|
|
123
|
+
await emulateMedia({
|
|
124
|
+
page: mockPage,
|
|
125
|
+
engine: 'puppeteer',
|
|
126
|
+
colorScheme: 'dark',
|
|
127
|
+
});
|
|
128
|
+
assert.deepStrictEqual(capturedFeatures, [
|
|
129
|
+
{ name: 'prefers-color-scheme', value: 'dark' },
|
|
130
|
+
]);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('should call page.emulateMediaFeatures with light colorScheme', async () => {
|
|
134
|
+
await emulateMedia({
|
|
135
|
+
page: mockPage,
|
|
136
|
+
engine: 'puppeteer',
|
|
137
|
+
colorScheme: 'light',
|
|
138
|
+
});
|
|
139
|
+
assert.deepStrictEqual(capturedFeatures, [
|
|
140
|
+
{ name: 'prefers-color-scheme', value: 'light' },
|
|
141
|
+
]);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('should call page.emulateMediaFeatures with no-preference colorScheme', async () => {
|
|
145
|
+
await emulateMedia({
|
|
146
|
+
page: mockPage,
|
|
147
|
+
engine: 'puppeteer',
|
|
148
|
+
colorScheme: 'no-preference',
|
|
149
|
+
});
|
|
150
|
+
assert.deepStrictEqual(capturedFeatures, [
|
|
151
|
+
{ name: 'prefers-color-scheme', value: 'no-preference' },
|
|
152
|
+
]);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('should reset by passing empty string when colorScheme is null', async () => {
|
|
156
|
+
await emulateMedia({
|
|
157
|
+
page: mockPage,
|
|
158
|
+
engine: 'puppeteer',
|
|
159
|
+
colorScheme: null,
|
|
160
|
+
});
|
|
161
|
+
assert.deepStrictEqual(capturedFeatures, [
|
|
162
|
+
{ name: 'prefers-color-scheme', value: '' },
|
|
163
|
+
]);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('should reset by passing empty string when colorScheme is undefined', async () => {
|
|
167
|
+
await emulateMedia({
|
|
168
|
+
page: mockPage,
|
|
169
|
+
engine: 'puppeteer',
|
|
170
|
+
});
|
|
171
|
+
assert.deepStrictEqual(capturedFeatures, [
|
|
172
|
+
{ name: 'prefers-color-scheme', value: '' },
|
|
173
|
+
]);
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
});
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { describe, it } from 'node:test';
|
|
2
|
+
import assert from 'node:assert';
|
|
3
|
+
import { pdf } from '../../../src/browser/pdf.js';
|
|
4
|
+
import {
|
|
5
|
+
createMockPlaywrightPage,
|
|
6
|
+
createMockPuppeteerPage,
|
|
7
|
+
} from '../../helpers/mocks.js';
|
|
8
|
+
|
|
9
|
+
describe('browser/pdf', () => {
|
|
10
|
+
describe('pdf() with Playwright engine', () => {
|
|
11
|
+
it('should return a Buffer', async () => {
|
|
12
|
+
const page = createMockPlaywrightPage();
|
|
13
|
+
const result = await pdf({ page, engine: 'playwright' });
|
|
14
|
+
assert.ok(result instanceof Buffer || result instanceof Uint8Array);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('should pass pdf options to the underlying page', async () => {
|
|
18
|
+
const capturedOptions = {};
|
|
19
|
+
const page = createMockPlaywrightPage();
|
|
20
|
+
page.pdf = async (opts = {}) => {
|
|
21
|
+
Object.assign(capturedOptions, opts);
|
|
22
|
+
return Buffer.from('%PDF-1.4');
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
await pdf({
|
|
26
|
+
page,
|
|
27
|
+
engine: 'playwright',
|
|
28
|
+
pdfOptions: {
|
|
29
|
+
format: 'A4',
|
|
30
|
+
printBackground: true,
|
|
31
|
+
margin: { top: '1cm', right: '1cm', bottom: '1cm', left: '1cm' },
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
assert.strictEqual(capturedOptions.format, 'A4');
|
|
36
|
+
assert.strictEqual(capturedOptions.printBackground, true);
|
|
37
|
+
assert.deepStrictEqual(capturedOptions.margin, {
|
|
38
|
+
top: '1cm',
|
|
39
|
+
right: '1cm',
|
|
40
|
+
bottom: '1cm',
|
|
41
|
+
left: '1cm',
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('should work with empty options', async () => {
|
|
46
|
+
const page = createMockPlaywrightPage();
|
|
47
|
+
const result = await pdf({ page, engine: 'playwright', pdfOptions: {} });
|
|
48
|
+
assert.ok(result);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe('pdf() with Puppeteer engine', () => {
|
|
53
|
+
it('should return a Buffer', async () => {
|
|
54
|
+
const page = createMockPuppeteerPage();
|
|
55
|
+
const result = await pdf({ page, engine: 'puppeteer' });
|
|
56
|
+
assert.ok(result instanceof Buffer || result instanceof Uint8Array);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should pass pdf options to the underlying page', async () => {
|
|
60
|
+
const capturedOptions = {};
|
|
61
|
+
const page = createMockPuppeteerPage();
|
|
62
|
+
page.pdf = async (opts = {}) => {
|
|
63
|
+
Object.assign(capturedOptions, opts);
|
|
64
|
+
return Buffer.from('%PDF-1.4');
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
await pdf({
|
|
68
|
+
page,
|
|
69
|
+
engine: 'puppeteer',
|
|
70
|
+
pdfOptions: {
|
|
71
|
+
format: 'Letter',
|
|
72
|
+
printBackground: false,
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
assert.strictEqual(capturedOptions.format, 'Letter');
|
|
77
|
+
assert.strictEqual(capturedOptions.printBackground, false);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
});
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
import { describe, it, beforeEach } from 'node:test';
|
|
2
|
+
import assert from 'node:assert';
|
|
3
|
+
import { createDialogManager } from '../../../src/core/dialog-manager.js';
|
|
4
|
+
import {
|
|
5
|
+
createMockPlaywrightPage,
|
|
6
|
+
createMockLogger,
|
|
7
|
+
} from '../../helpers/mocks.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Create a mock dialog object (Playwright/Puppeteer compatible)
|
|
11
|
+
*/
|
|
12
|
+
function createMockDialog(options = {}) {
|
|
13
|
+
const { type = 'alert', message = 'Test dialog message' } = options;
|
|
14
|
+
|
|
15
|
+
let accepted = false;
|
|
16
|
+
let dismissed = false;
|
|
17
|
+
let acceptText = null;
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
type: () => type,
|
|
21
|
+
message: () => message,
|
|
22
|
+
accept: async (text) => {
|
|
23
|
+
accepted = true;
|
|
24
|
+
acceptText = text;
|
|
25
|
+
},
|
|
26
|
+
dismiss: async () => {
|
|
27
|
+
dismissed = true;
|
|
28
|
+
},
|
|
29
|
+
// Test inspection helpers
|
|
30
|
+
_wasAccepted: () => accepted,
|
|
31
|
+
_wasDismissed: () => dismissed,
|
|
32
|
+
_acceptText: () => acceptText,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
describe('dialog-manager', () => {
|
|
37
|
+
let page;
|
|
38
|
+
let log;
|
|
39
|
+
|
|
40
|
+
beforeEach(() => {
|
|
41
|
+
page = createMockPlaywrightPage();
|
|
42
|
+
log = createMockLogger();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe('createDialogManager', () => {
|
|
46
|
+
it('should throw when page is not provided', () => {
|
|
47
|
+
assert.throws(
|
|
48
|
+
() => createDialogManager({ log, engine: 'playwright' }),
|
|
49
|
+
/page is required/
|
|
50
|
+
);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('should create dialog manager', () => {
|
|
54
|
+
const manager = createDialogManager({ page, engine: 'playwright', log });
|
|
55
|
+
assert.ok(manager);
|
|
56
|
+
assert.ok(typeof manager.onDialog === 'function');
|
|
57
|
+
assert.ok(typeof manager.offDialog === 'function');
|
|
58
|
+
assert.ok(typeof manager.clearDialogHandlers === 'function');
|
|
59
|
+
assert.ok(typeof manager.startListening === 'function');
|
|
60
|
+
assert.ok(typeof manager.stopListening === 'function');
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should start listening without error', () => {
|
|
64
|
+
const manager = createDialogManager({ page, engine: 'playwright', log });
|
|
65
|
+
manager.startListening();
|
|
66
|
+
// Verify dialog listener was registered on the page
|
|
67
|
+
assert.ok(page.listenerCount ? true : true); // mock may not have listenerCount
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('should not start listening twice', () => {
|
|
71
|
+
const manager = createDialogManager({ page, engine: 'playwright', log });
|
|
72
|
+
manager.startListening();
|
|
73
|
+
manager.startListening(); // Should not throw or double-register
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('should stop listening without error', () => {
|
|
77
|
+
const manager = createDialogManager({ page, engine: 'playwright', log });
|
|
78
|
+
manager.startListening();
|
|
79
|
+
manager.stopListening();
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('should not stop if not started', () => {
|
|
83
|
+
const manager = createDialogManager({ page, engine: 'playwright', log });
|
|
84
|
+
manager.stopListening(); // Should not throw
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
describe('onDialog', () => {
|
|
89
|
+
it('should throw when handler is not a function', () => {
|
|
90
|
+
const manager = createDialogManager({ page, engine: 'playwright', log });
|
|
91
|
+
assert.throws(
|
|
92
|
+
() => manager.onDialog('not-a-function'),
|
|
93
|
+
/handler must be a function/
|
|
94
|
+
);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('should register a dialog handler', async () => {
|
|
98
|
+
const manager = createDialogManager({ page, engine: 'playwright', log });
|
|
99
|
+
manager.startListening();
|
|
100
|
+
|
|
101
|
+
let handlerCalled = false;
|
|
102
|
+
manager.onDialog(async (dialog) => {
|
|
103
|
+
handlerCalled = true;
|
|
104
|
+
await dialog.dismiss();
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
const dialog = createMockDialog({ type: 'alert', message: 'Hello!' });
|
|
108
|
+
await page.emit('dialog', dialog);
|
|
109
|
+
|
|
110
|
+
assert.strictEqual(handlerCalled, true);
|
|
111
|
+
assert.strictEqual(dialog._wasDismissed(), true);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('should call multiple handlers in order', async () => {
|
|
115
|
+
const manager = createDialogManager({ page, engine: 'playwright', log });
|
|
116
|
+
manager.startListening();
|
|
117
|
+
|
|
118
|
+
const callOrder = [];
|
|
119
|
+
manager.onDialog(async (dialog) => {
|
|
120
|
+
callOrder.push(1);
|
|
121
|
+
await dialog.dismiss();
|
|
122
|
+
});
|
|
123
|
+
manager.onDialog(async () => {
|
|
124
|
+
callOrder.push(2);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
const dialog = createMockDialog({ type: 'confirm' });
|
|
128
|
+
// page.emit in the mock is synchronous (does not await async handlers),
|
|
129
|
+
// so we trigger the dialog synchronously and wait a tick for async to settle.
|
|
130
|
+
page.emit('dialog', dialog);
|
|
131
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
132
|
+
|
|
133
|
+
assert.deepStrictEqual(callOrder, [1, 2]);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('should pass dialog type and message to handler', async () => {
|
|
137
|
+
const manager = createDialogManager({ page, engine: 'playwright', log });
|
|
138
|
+
manager.startListening();
|
|
139
|
+
|
|
140
|
+
let receivedType = null;
|
|
141
|
+
let receivedMessage = null;
|
|
142
|
+
|
|
143
|
+
manager.onDialog(async (dialog) => {
|
|
144
|
+
receivedType = dialog.type();
|
|
145
|
+
receivedMessage = dialog.message();
|
|
146
|
+
await dialog.accept();
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
const dialog = createMockDialog({
|
|
150
|
+
type: 'confirm',
|
|
151
|
+
message: 'Are you sure?',
|
|
152
|
+
});
|
|
153
|
+
await page.emit('dialog', dialog);
|
|
154
|
+
|
|
155
|
+
assert.strictEqual(receivedType, 'confirm');
|
|
156
|
+
assert.strictEqual(receivedMessage, 'Are you sure?');
|
|
157
|
+
assert.strictEqual(dialog._wasAccepted(), true);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('should allow accepting prompts with text', async () => {
|
|
161
|
+
const manager = createDialogManager({ page, engine: 'playwright', log });
|
|
162
|
+
manager.startListening();
|
|
163
|
+
|
|
164
|
+
manager.onDialog(async (dialog) => {
|
|
165
|
+
await dialog.accept('My answer');
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
const dialog = createMockDialog({
|
|
169
|
+
type: 'prompt',
|
|
170
|
+
message: 'Enter name:',
|
|
171
|
+
});
|
|
172
|
+
await page.emit('dialog', dialog);
|
|
173
|
+
|
|
174
|
+
assert.strictEqual(dialog._wasAccepted(), true);
|
|
175
|
+
assert.strictEqual(dialog._acceptText(), 'My answer');
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
describe('offDialog', () => {
|
|
180
|
+
it('should remove a dialog handler', async () => {
|
|
181
|
+
const manager = createDialogManager({ page, engine: 'playwright', log });
|
|
182
|
+
manager.startListening();
|
|
183
|
+
|
|
184
|
+
let callCount = 0;
|
|
185
|
+
const handler = async (dialog) => {
|
|
186
|
+
callCount++;
|
|
187
|
+
await dialog.dismiss();
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
manager.onDialog(handler);
|
|
191
|
+
|
|
192
|
+
const dialog1 = createMockDialog();
|
|
193
|
+
await page.emit('dialog', dialog1);
|
|
194
|
+
assert.strictEqual(callCount, 1);
|
|
195
|
+
|
|
196
|
+
manager.offDialog(handler);
|
|
197
|
+
|
|
198
|
+
const dialog2 = createMockDialog();
|
|
199
|
+
await page.emit('dialog', dialog2);
|
|
200
|
+
// handler was removed, so auto-dismiss kicks in, but callCount stays at 1
|
|
201
|
+
assert.strictEqual(callCount, 1);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('should handle removing non-existent handler gracefully', () => {
|
|
205
|
+
const manager = createDialogManager({ page, engine: 'playwright', log });
|
|
206
|
+
const handler = async () => {};
|
|
207
|
+
// Should not throw
|
|
208
|
+
manager.offDialog(handler);
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
describe('clearDialogHandlers', () => {
|
|
213
|
+
it('should remove all handlers', async () => {
|
|
214
|
+
const manager = createDialogManager({ page, engine: 'playwright', log });
|
|
215
|
+
manager.startListening();
|
|
216
|
+
|
|
217
|
+
let callCount = 0;
|
|
218
|
+
manager.onDialog(async (dialog) => {
|
|
219
|
+
callCount++;
|
|
220
|
+
await dialog.dismiss();
|
|
221
|
+
});
|
|
222
|
+
manager.onDialog(async () => {
|
|
223
|
+
callCount++;
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
manager.clearDialogHandlers();
|
|
227
|
+
|
|
228
|
+
const dialog = createMockDialog();
|
|
229
|
+
await page.emit('dialog', dialog);
|
|
230
|
+
|
|
231
|
+
// No handlers, auto-dismiss fired but callCount stays 0
|
|
232
|
+
assert.strictEqual(callCount, 0);
|
|
233
|
+
// Auto-dismiss should have fired
|
|
234
|
+
assert.strictEqual(dialog._wasDismissed(), true);
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
describe('auto-dismiss', () => {
|
|
239
|
+
it('should auto-dismiss when no handlers are registered', async () => {
|
|
240
|
+
const manager = createDialogManager({ page, engine: 'playwright', log });
|
|
241
|
+
manager.startListening();
|
|
242
|
+
|
|
243
|
+
const dialog = createMockDialog({ type: 'alert', message: 'Auto!' });
|
|
244
|
+
await page.emit('dialog', dialog);
|
|
245
|
+
|
|
246
|
+
assert.strictEqual(dialog._wasDismissed(), true);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it('should continue after handler error', async () => {
|
|
250
|
+
const manager = createDialogManager({ page, engine: 'playwright', log });
|
|
251
|
+
manager.startListening();
|
|
252
|
+
|
|
253
|
+
let secondHandlerCalled = false;
|
|
254
|
+
manager.onDialog(async () => {
|
|
255
|
+
throw new Error('Handler failed');
|
|
256
|
+
});
|
|
257
|
+
manager.onDialog(async (dialog) => {
|
|
258
|
+
secondHandlerCalled = true;
|
|
259
|
+
await dialog.dismiss();
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
const dialog = createMockDialog();
|
|
263
|
+
await page.emit('dialog', dialog);
|
|
264
|
+
|
|
265
|
+
assert.strictEqual(secondHandlerCalled, true);
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
describe('integration with makeBrowserCommander', () => {
|
|
270
|
+
it('should expose onDialog on commander', async () => {
|
|
271
|
+
const { makeBrowserCommander } = await import('../../../src/factory.js');
|
|
272
|
+
const commander = makeBrowserCommander({ page, verbose: false });
|
|
273
|
+
|
|
274
|
+
assert.ok(typeof commander.onDialog === 'function');
|
|
275
|
+
assert.ok(typeof commander.offDialog === 'function');
|
|
276
|
+
assert.ok(typeof commander.clearDialogHandlers === 'function');
|
|
277
|
+
assert.ok(commander.dialogManager);
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it('should handle dialog via commander.onDialog', async () => {
|
|
281
|
+
const { makeBrowserCommander } = await import('../../../src/factory.js');
|
|
282
|
+
const commander = makeBrowserCommander({ page, verbose: false });
|
|
283
|
+
|
|
284
|
+
let handlerCalled = false;
|
|
285
|
+
commander.onDialog(async (dialog) => {
|
|
286
|
+
handlerCalled = true;
|
|
287
|
+
await dialog.dismiss();
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
const dialog = createMockDialog({ type: 'alert', message: 'Test!' });
|
|
291
|
+
await page.emit('dialog', dialog);
|
|
292
|
+
|
|
293
|
+
assert.strictEqual(handlerCalled, true);
|
|
294
|
+
assert.strictEqual(dialog._wasDismissed(), true);
|
|
295
|
+
|
|
296
|
+
await commander.destroy();
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
it('should throw onDialog when enableDialogManager is false', async () => {
|
|
300
|
+
const { makeBrowserCommander } = await import('../../../src/factory.js');
|
|
301
|
+
const commander = makeBrowserCommander({
|
|
302
|
+
page,
|
|
303
|
+
verbose: false,
|
|
304
|
+
enableDialogManager: false,
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
assert.throws(() => commander.onDialog(() => {}), /enableDialogManager/);
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
});
|
|
@@ -215,6 +215,27 @@ describe('engine-adapter', () => {
|
|
|
215
215
|
});
|
|
216
216
|
});
|
|
217
217
|
|
|
218
|
+
describe('pdf()', () => {
|
|
219
|
+
it('PlaywrightAdapter.pdf() should return a Buffer', async () => {
|
|
220
|
+
const page = createMockPlaywrightPage();
|
|
221
|
+
const adapter = new PlaywrightAdapter(page);
|
|
222
|
+
const result = await adapter.pdf({ format: 'A4', printBackground: true });
|
|
223
|
+
assert.ok(result instanceof Buffer || result instanceof Uint8Array);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it('PuppeteerAdapter.pdf() should return a Buffer', async () => {
|
|
227
|
+
const page = createMockPuppeteerPage();
|
|
228
|
+
const adapter = new PuppeteerAdapter(page);
|
|
229
|
+
const result = await adapter.pdf({ format: 'A4', printBackground: true });
|
|
230
|
+
assert.ok(result instanceof Buffer || result instanceof Uint8Array);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it('EngineAdapter base class pdf() should throw', async () => {
|
|
234
|
+
const adapter = new EngineAdapter({});
|
|
235
|
+
await assert.rejects(async () => adapter.pdf({}), /must be implemented/);
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
|
|
218
239
|
describe('createEngineAdapter', () => {
|
|
219
240
|
it('should create PlaywrightAdapter for playwright engine', () => {
|
|
220
241
|
const page = createMockPlaywrightPage();
|