browser-commander 0.5.4 → 0.7.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.
@@ -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
+ });
@@ -170,5 +170,62 @@ describe('factory', () => {
170
170
  // Should not throw
171
171
  await commander.destroy();
172
172
  });
173
+
174
+ // Extensibility escape hatch tests (issue #39)
175
+ it('should expose the raw page object as commander.page (extensibility escape hatch)', () => {
176
+ const page = createMockPlaywrightPage();
177
+ page.locator = (sel) => ({
178
+ count: async () => 1,
179
+ waitFor: async () => {},
180
+ });
181
+ page.context = () => ({});
182
+
183
+ const commander = makeBrowserCommander({ page });
184
+
185
+ // commander.page must be the exact same object as the raw page passed in
186
+ // This is the official extensibility escape hatch for APIs not yet in browser-commander
187
+ assert.strictEqual(
188
+ commander.page,
189
+ page,
190
+ 'commander.page must be the raw engine page (not a wrapper)'
191
+ );
192
+ });
193
+
194
+ it('should allow using commander.page to call engine-specific APIs not in browser-commander', () => {
195
+ const page = createMockPlaywrightPage();
196
+ page.locator = (sel) => ({
197
+ count: async () => 1,
198
+ waitFor: async () => {},
199
+ });
200
+ page.context = () => ({});
201
+
202
+ // Add a custom method to simulate engine-specific API (e.g. page.pdf(), page.emulateMedia())
203
+ page.customEngineMethod = () => 'engine-specific-result';
204
+
205
+ const commander = makeBrowserCommander({ page });
206
+
207
+ // Users can call engine-specific APIs via commander.page without needing _page hacks
208
+ const result = commander.page.customEngineMethod();
209
+ assert.strictEqual(
210
+ result,
211
+ 'engine-specific-result',
212
+ 'commander.page should allow calling engine-specific APIs'
213
+ );
214
+ });
215
+
216
+ it('should expose raw page with Puppeteer page too (extensibility escape hatch)', () => {
217
+ const page = createMockPuppeteerPage();
218
+ delete page.locator;
219
+ delete page.context;
220
+ page.$eval = async () => {};
221
+
222
+ const commander = makeBrowserCommander({ page });
223
+
224
+ assert.strictEqual(
225
+ commander.page,
226
+ page,
227
+ 'commander.page must be the raw Puppeteer page (not a wrapper)'
228
+ );
229
+ });
173
230
  });
174
231
  });
@@ -0,0 +1,316 @@
1
+ import { describe, it } from 'node:test';
2
+ import assert from 'node:assert';
3
+ import {
4
+ pressKey,
5
+ typeText,
6
+ keyDown,
7
+ keyUp,
8
+ } from '../../../src/interactions/keyboard.js';
9
+ import {
10
+ PlaywrightAdapter,
11
+ PuppeteerAdapter,
12
+ } from '../../../src/core/engine-adapter.js';
13
+ import {
14
+ createMockPlaywrightPage,
15
+ createMockPuppeteerPage,
16
+ } from '../../helpers/mocks.js';
17
+
18
+ describe('keyboard interactions', () => {
19
+ // ---------------------------------------------------------------------------
20
+ // pressKey
21
+ // ---------------------------------------------------------------------------
22
+ describe('pressKey', () => {
23
+ it('should press a key using Playwright adapter', async () => {
24
+ const pressedKeys = [];
25
+ const page = createMockPlaywrightPage();
26
+ page.keyboard.press = async (key) => {
27
+ pressedKeys.push(key);
28
+ };
29
+
30
+ await pressKey({ page, engine: 'playwright', key: 'Escape' });
31
+
32
+ assert.deepStrictEqual(pressedKeys, ['Escape']);
33
+ });
34
+
35
+ it('should press a key using Puppeteer adapter', async () => {
36
+ const pressedKeys = [];
37
+ const page = createMockPuppeteerPage();
38
+ page.keyboard.press = async (key) => {
39
+ pressedKeys.push(key);
40
+ };
41
+
42
+ await pressKey({ page, engine: 'puppeteer', key: 'Enter' });
43
+
44
+ assert.deepStrictEqual(pressedKeys, ['Enter']);
45
+ });
46
+
47
+ it('should accept a pre-created adapter', async () => {
48
+ const pressedKeys = [];
49
+ const adapter = {
50
+ keyboardPress: async (key) => {
51
+ pressedKeys.push(key);
52
+ },
53
+ };
54
+
55
+ await pressKey({ key: 'Tab', adapter });
56
+
57
+ assert.deepStrictEqual(pressedKeys, ['Tab']);
58
+ });
59
+
60
+ it('should throw when key is not provided', async () => {
61
+ const page = createMockPlaywrightPage();
62
+ await assert.rejects(
63
+ () => pressKey({ page, engine: 'playwright' }),
64
+ /key is required/
65
+ );
66
+ });
67
+ });
68
+
69
+ // ---------------------------------------------------------------------------
70
+ // typeText
71
+ // ---------------------------------------------------------------------------
72
+ describe('typeText', () => {
73
+ it('should type text using Playwright adapter', async () => {
74
+ const typedTexts = [];
75
+ const page = createMockPlaywrightPage();
76
+ page.keyboard.type = async (text) => {
77
+ typedTexts.push(text);
78
+ };
79
+
80
+ await typeText({ page, engine: 'playwright', text: 'Hello World' });
81
+
82
+ assert.deepStrictEqual(typedTexts, ['Hello World']);
83
+ });
84
+
85
+ it('should type text using Puppeteer adapter', async () => {
86
+ const typedTexts = [];
87
+ const page = createMockPuppeteerPage();
88
+ page.keyboard.type = async (text) => {
89
+ typedTexts.push(text);
90
+ };
91
+
92
+ await typeText({ page, engine: 'puppeteer', text: 'Hello World' });
93
+
94
+ assert.deepStrictEqual(typedTexts, ['Hello World']);
95
+ });
96
+
97
+ it('should accept a pre-created adapter', async () => {
98
+ const typedTexts = [];
99
+ const adapter = {
100
+ keyboardType: async (text) => {
101
+ typedTexts.push(text);
102
+ },
103
+ };
104
+
105
+ await typeText({ text: 'test input', adapter });
106
+
107
+ assert.deepStrictEqual(typedTexts, ['test input']);
108
+ });
109
+
110
+ it('should throw when text is not provided', async () => {
111
+ const page = createMockPlaywrightPage();
112
+ await assert.rejects(
113
+ () => typeText({ page, engine: 'playwright' }),
114
+ /text is required/
115
+ );
116
+ });
117
+ });
118
+
119
+ // ---------------------------------------------------------------------------
120
+ // keyDown
121
+ // ---------------------------------------------------------------------------
122
+ describe('keyDown', () => {
123
+ it('should hold a key down using Playwright adapter', async () => {
124
+ const downKeys = [];
125
+ const page = createMockPlaywrightPage();
126
+ page.keyboard.down = async (key) => {
127
+ downKeys.push(key);
128
+ };
129
+
130
+ await keyDown({ page, engine: 'playwright', key: 'Control' });
131
+
132
+ assert.deepStrictEqual(downKeys, ['Control']);
133
+ });
134
+
135
+ it('should hold a key down using Puppeteer adapter', async () => {
136
+ const downKeys = [];
137
+ const page = createMockPuppeteerPage();
138
+ page.keyboard.down = async (key) => {
139
+ downKeys.push(key);
140
+ };
141
+
142
+ await keyDown({ page, engine: 'puppeteer', key: 'Shift' });
143
+
144
+ assert.deepStrictEqual(downKeys, ['Shift']);
145
+ });
146
+
147
+ it('should accept a pre-created adapter', async () => {
148
+ const downKeys = [];
149
+ const adapter = {
150
+ keyboardDown: async (key) => {
151
+ downKeys.push(key);
152
+ },
153
+ };
154
+
155
+ await keyDown({ key: 'Alt', adapter });
156
+
157
+ assert.deepStrictEqual(downKeys, ['Alt']);
158
+ });
159
+
160
+ it('should throw when key is not provided', async () => {
161
+ const page = createMockPlaywrightPage();
162
+ await assert.rejects(
163
+ () => keyDown({ page, engine: 'playwright' }),
164
+ /key is required/
165
+ );
166
+ });
167
+ });
168
+
169
+ // ---------------------------------------------------------------------------
170
+ // keyUp
171
+ // ---------------------------------------------------------------------------
172
+ describe('keyUp', () => {
173
+ it('should release a key using Playwright adapter', async () => {
174
+ const upKeys = [];
175
+ const page = createMockPlaywrightPage();
176
+ page.keyboard.up = async (key) => {
177
+ upKeys.push(key);
178
+ };
179
+
180
+ await keyUp({ page, engine: 'playwright', key: 'Control' });
181
+
182
+ assert.deepStrictEqual(upKeys, ['Control']);
183
+ });
184
+
185
+ it('should release a key using Puppeteer adapter', async () => {
186
+ const upKeys = [];
187
+ const page = createMockPuppeteerPage();
188
+ page.keyboard.up = async (key) => {
189
+ upKeys.push(key);
190
+ };
191
+
192
+ await keyUp({ page, engine: 'puppeteer', key: 'Shift' });
193
+
194
+ assert.deepStrictEqual(upKeys, ['Shift']);
195
+ });
196
+
197
+ it('should accept a pre-created adapter', async () => {
198
+ const upKeys = [];
199
+ const adapter = {
200
+ keyboardUp: async (key) => {
201
+ upKeys.push(key);
202
+ },
203
+ };
204
+
205
+ await keyUp({ key: 'Meta', adapter });
206
+
207
+ assert.deepStrictEqual(upKeys, ['Meta']);
208
+ });
209
+
210
+ it('should throw when key is not provided', async () => {
211
+ const page = createMockPlaywrightPage();
212
+ await assert.rejects(
213
+ () => keyUp({ page, engine: 'playwright' }),
214
+ /key is required/
215
+ );
216
+ });
217
+ });
218
+
219
+ // ---------------------------------------------------------------------------
220
+ // PlaywrightAdapter keyboard methods
221
+ // ---------------------------------------------------------------------------
222
+ describe('PlaywrightAdapter keyboard methods', () => {
223
+ it('should delegate keyboardPress to page.keyboard.press', async () => {
224
+ const pressedKeys = [];
225
+ const page = createMockPlaywrightPage();
226
+ page.keyboard.press = async (key) => pressedKeys.push(key);
227
+
228
+ const adapter = new PlaywrightAdapter(page);
229
+ await adapter.keyboardPress('Escape');
230
+
231
+ assert.deepStrictEqual(pressedKeys, ['Escape']);
232
+ });
233
+
234
+ it('should delegate keyboardType to page.keyboard.type', async () => {
235
+ const typedTexts = [];
236
+ const page = createMockPlaywrightPage();
237
+ page.keyboard.type = async (text) => typedTexts.push(text);
238
+
239
+ const adapter = new PlaywrightAdapter(page);
240
+ await adapter.keyboardType('hello');
241
+
242
+ assert.deepStrictEqual(typedTexts, ['hello']);
243
+ });
244
+
245
+ it('should delegate keyboardDown to page.keyboard.down', async () => {
246
+ const downKeys = [];
247
+ const page = createMockPlaywrightPage();
248
+ page.keyboard.down = async (key) => downKeys.push(key);
249
+
250
+ const adapter = new PlaywrightAdapter(page);
251
+ await adapter.keyboardDown('Control');
252
+
253
+ assert.deepStrictEqual(downKeys, ['Control']);
254
+ });
255
+
256
+ it('should delegate keyboardUp to page.keyboard.up', async () => {
257
+ const upKeys = [];
258
+ const page = createMockPlaywrightPage();
259
+ page.keyboard.up = async (key) => upKeys.push(key);
260
+
261
+ const adapter = new PlaywrightAdapter(page);
262
+ await adapter.keyboardUp('Control');
263
+
264
+ assert.deepStrictEqual(upKeys, ['Control']);
265
+ });
266
+ });
267
+
268
+ // ---------------------------------------------------------------------------
269
+ // PuppeteerAdapter keyboard methods
270
+ // ---------------------------------------------------------------------------
271
+ describe('PuppeteerAdapter keyboard methods', () => {
272
+ it('should delegate keyboardPress to page.keyboard.press', async () => {
273
+ const pressedKeys = [];
274
+ const page = createMockPuppeteerPage();
275
+ page.keyboard.press = async (key) => pressedKeys.push(key);
276
+
277
+ const adapter = new PuppeteerAdapter(page);
278
+ await adapter.keyboardPress('Enter');
279
+
280
+ assert.deepStrictEqual(pressedKeys, ['Enter']);
281
+ });
282
+
283
+ it('should delegate keyboardType to page.keyboard.type', async () => {
284
+ const typedTexts = [];
285
+ const page = createMockPuppeteerPage();
286
+ page.keyboard.type = async (text) => typedTexts.push(text);
287
+
288
+ const adapter = new PuppeteerAdapter(page);
289
+ await adapter.keyboardType('world');
290
+
291
+ assert.deepStrictEqual(typedTexts, ['world']);
292
+ });
293
+
294
+ it('should delegate keyboardDown to page.keyboard.down', async () => {
295
+ const downKeys = [];
296
+ const page = createMockPuppeteerPage();
297
+ page.keyboard.down = async (key) => downKeys.push(key);
298
+
299
+ const adapter = new PuppeteerAdapter(page);
300
+ await adapter.keyboardDown('Shift');
301
+
302
+ assert.deepStrictEqual(downKeys, ['Shift']);
303
+ });
304
+
305
+ it('should delegate keyboardUp to page.keyboard.up', async () => {
306
+ const upKeys = [];
307
+ const page = createMockPuppeteerPage();
308
+ page.keyboard.up = async (key) => upKeys.push(key);
309
+
310
+ const adapter = new PuppeteerAdapter(page);
311
+ await adapter.keyboardUp('Shift');
312
+
313
+ assert.deepStrictEqual(upKeys, ['Shift']);
314
+ });
315
+ });
316
+ });