@playwright/mcp 0.0.10 → 0.0.14
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/README.md +133 -32
- package/index.d.ts +37 -24
- package/lib/context.js +162 -76
- package/lib/index.js +63 -11
- package/lib/javascript.js +54 -0
- package/lib/manualPromise.js +116 -0
- package/lib/program.js +9 -55
- package/lib/server.js +15 -4
- package/lib/tools/common.js +40 -12
- package/lib/{resources → tools}/console.js +23 -12
- package/lib/tools/dialogs.js +54 -0
- package/lib/tools/files.js +16 -7
- package/lib/tools/install.js +3 -4
- package/lib/tools/keyboard.js +11 -5
- package/lib/tools/navigate.js +34 -20
- package/lib/tools/pdf.js +43 -6
- package/lib/tools/screen.js +108 -19
- package/lib/tools/snapshot.js +154 -42
- package/lib/tools/tabs.js +36 -18
- package/lib/tools/utils.js +2 -2
- package/package.json +5 -1
package/lib/context.js
CHANGED
|
@@ -51,25 +51,48 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
51
51
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
52
52
|
};
|
|
53
53
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
54
|
-
exports.Context = void 0;
|
|
54
|
+
exports.Tab = exports.Context = void 0;
|
|
55
|
+
exports.generateLocator = generateLocator;
|
|
55
56
|
const playwright = __importStar(require("playwright"));
|
|
56
57
|
const yaml_1 = __importDefault(require("yaml"));
|
|
57
58
|
const utils_1 = require("./tools/utils");
|
|
59
|
+
const manualPromise_1 = require("./manualPromise");
|
|
58
60
|
class Context {
|
|
61
|
+
tools;
|
|
59
62
|
options;
|
|
60
63
|
_browser;
|
|
61
64
|
_browserContext;
|
|
62
65
|
_tabs = [];
|
|
63
66
|
_currentTab;
|
|
64
|
-
|
|
67
|
+
_modalStates = [];
|
|
68
|
+
_pendingAction;
|
|
69
|
+
constructor(tools, options) {
|
|
70
|
+
this.tools = tools;
|
|
65
71
|
this.options = options;
|
|
66
72
|
}
|
|
73
|
+
modalStates() {
|
|
74
|
+
return this._modalStates;
|
|
75
|
+
}
|
|
76
|
+
setModalState(modalState, inTab) {
|
|
77
|
+
this._modalStates.push({ ...modalState, tab: inTab });
|
|
78
|
+
}
|
|
79
|
+
clearModalState(modalState) {
|
|
80
|
+
this._modalStates = this._modalStates.filter(state => state !== modalState);
|
|
81
|
+
}
|
|
82
|
+
modalStatesMarkdown() {
|
|
83
|
+
const result = ['### Modal state'];
|
|
84
|
+
for (const state of this._modalStates) {
|
|
85
|
+
const tool = this.tools.find(tool => tool.clearsModalState === state.type);
|
|
86
|
+
result.push(`- [${state.description}]: can be handled by the "${tool?.schema.name}" tool`);
|
|
87
|
+
}
|
|
88
|
+
return result;
|
|
89
|
+
}
|
|
67
90
|
tabs() {
|
|
68
91
|
return this._tabs;
|
|
69
92
|
}
|
|
70
|
-
|
|
93
|
+
currentTabOrDie() {
|
|
71
94
|
if (!this._currentTab)
|
|
72
|
-
throw new Error('
|
|
95
|
+
throw new Error('No current snapshot available. Capture a snapshot of navigate to a new location first.');
|
|
73
96
|
return this._currentTab;
|
|
74
97
|
}
|
|
75
98
|
async newTab() {
|
|
@@ -88,10 +111,10 @@ class Context {
|
|
|
88
111
|
await context.newPage();
|
|
89
112
|
return this._currentTab;
|
|
90
113
|
}
|
|
91
|
-
async
|
|
114
|
+
async listTabsMarkdown() {
|
|
92
115
|
if (!this._tabs.length)
|
|
93
|
-
return 'No tabs open';
|
|
94
|
-
const lines = ['Open tabs
|
|
116
|
+
return '### No tabs open';
|
|
117
|
+
const lines = ['### Open tabs'];
|
|
95
118
|
for (let i = 0; i < this._tabs.length; i++) {
|
|
96
119
|
const tab = this._tabs[i];
|
|
97
120
|
const title = await tab.page.title();
|
|
@@ -102,9 +125,103 @@ class Context {
|
|
|
102
125
|
return lines.join('\n');
|
|
103
126
|
}
|
|
104
127
|
async closeTab(index) {
|
|
105
|
-
const tab = index === undefined ? this.
|
|
106
|
-
await tab
|
|
107
|
-
return await this.
|
|
128
|
+
const tab = index === undefined ? this._currentTab : this._tabs[index - 1];
|
|
129
|
+
await tab?.page.close();
|
|
130
|
+
return await this.listTabsMarkdown();
|
|
131
|
+
}
|
|
132
|
+
async run(tool, params) {
|
|
133
|
+
// Tab management is done outside of the action() call.
|
|
134
|
+
const toolResult = await tool.handle(this, params);
|
|
135
|
+
const { code, action, waitForNetwork, captureSnapshot, resultOverride } = toolResult;
|
|
136
|
+
const racingAction = action ? () => this._raceAgainstModalDialogs(action) : undefined;
|
|
137
|
+
if (resultOverride)
|
|
138
|
+
return resultOverride;
|
|
139
|
+
if (!this._currentTab) {
|
|
140
|
+
return {
|
|
141
|
+
content: [{
|
|
142
|
+
type: 'text',
|
|
143
|
+
text: 'No open pages available. Use the "browser_navigate" tool to navigate to a page first.',
|
|
144
|
+
}],
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
const tab = this.currentTabOrDie();
|
|
148
|
+
// TODO: race against modal dialogs to resolve clicks.
|
|
149
|
+
let actionResult;
|
|
150
|
+
try {
|
|
151
|
+
if (waitForNetwork)
|
|
152
|
+
actionResult = await (0, utils_1.waitForCompletion)(this, tab.page, async () => racingAction?.()) ?? undefined;
|
|
153
|
+
else
|
|
154
|
+
actionResult = await racingAction?.() ?? undefined;
|
|
155
|
+
}
|
|
156
|
+
finally {
|
|
157
|
+
if (captureSnapshot && !this._javaScriptBlocked())
|
|
158
|
+
await tab.captureSnapshot();
|
|
159
|
+
}
|
|
160
|
+
const result = [];
|
|
161
|
+
result.push(`- Ran Playwright code:
|
|
162
|
+
\`\`\`js
|
|
163
|
+
${code.join('\n')}
|
|
164
|
+
\`\`\`
|
|
165
|
+
`);
|
|
166
|
+
if (this.modalStates().length) {
|
|
167
|
+
result.push(...this.modalStatesMarkdown());
|
|
168
|
+
return {
|
|
169
|
+
content: [{
|
|
170
|
+
type: 'text',
|
|
171
|
+
text: result.join('\n'),
|
|
172
|
+
}],
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
if (this.tabs().length > 1)
|
|
176
|
+
result.push(await this.listTabsMarkdown(), '');
|
|
177
|
+
if (this.tabs().length > 1)
|
|
178
|
+
result.push('### Current tab');
|
|
179
|
+
result.push(`- Page URL: ${tab.page.url()}`, `- Page Title: ${await tab.page.title()}`);
|
|
180
|
+
if (captureSnapshot && tab.hasSnapshot())
|
|
181
|
+
result.push(tab.snapshotOrDie().text());
|
|
182
|
+
const content = actionResult?.content ?? [];
|
|
183
|
+
return {
|
|
184
|
+
content: [
|
|
185
|
+
...content,
|
|
186
|
+
{
|
|
187
|
+
type: 'text',
|
|
188
|
+
text: result.join('\n'),
|
|
189
|
+
}
|
|
190
|
+
],
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
async waitForTimeout(time) {
|
|
194
|
+
if (this._currentTab && !this._javaScriptBlocked())
|
|
195
|
+
await this._currentTab.page.evaluate(() => new Promise(f => setTimeout(f, 1000)));
|
|
196
|
+
else
|
|
197
|
+
await new Promise(f => setTimeout(f, time));
|
|
198
|
+
}
|
|
199
|
+
async _raceAgainstModalDialogs(action) {
|
|
200
|
+
this._pendingAction = {
|
|
201
|
+
dialogShown: new manualPromise_1.ManualPromise(),
|
|
202
|
+
};
|
|
203
|
+
let result;
|
|
204
|
+
try {
|
|
205
|
+
await Promise.race([
|
|
206
|
+
action().then(r => result = r),
|
|
207
|
+
this._pendingAction.dialogShown,
|
|
208
|
+
]);
|
|
209
|
+
}
|
|
210
|
+
finally {
|
|
211
|
+
this._pendingAction = undefined;
|
|
212
|
+
}
|
|
213
|
+
return result;
|
|
214
|
+
}
|
|
215
|
+
_javaScriptBlocked() {
|
|
216
|
+
return this._modalStates.some(state => state.type === 'dialog');
|
|
217
|
+
}
|
|
218
|
+
dialogShown(tab, dialog) {
|
|
219
|
+
this.setModalState({
|
|
220
|
+
type: 'dialog',
|
|
221
|
+
description: `"${dialog.type()}" dialog with message "${dialog.message()}"`,
|
|
222
|
+
dialog,
|
|
223
|
+
}, tab);
|
|
224
|
+
this._pendingAction?.dialogShown.resolve();
|
|
108
225
|
}
|
|
109
226
|
_onPageCreated(page) {
|
|
110
227
|
const tab = new Tab(this, page, tab => this._onPageClosed(tab));
|
|
@@ -113,23 +230,26 @@ class Context {
|
|
|
113
230
|
this._currentTab = tab;
|
|
114
231
|
}
|
|
115
232
|
_onPageClosed(tab) {
|
|
233
|
+
this._modalStates = this._modalStates.filter(state => state.tab !== tab);
|
|
116
234
|
const index = this._tabs.indexOf(tab);
|
|
117
235
|
if (index === -1)
|
|
118
236
|
return;
|
|
119
237
|
this._tabs.splice(index, 1);
|
|
120
238
|
if (this._currentTab === tab)
|
|
121
239
|
this._currentTab = this._tabs[Math.min(index, this._tabs.length - 1)];
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
void this._browserContext.close().then(() => browser?.close()).catch(() => { });
|
|
125
|
-
this._browser = undefined;
|
|
126
|
-
this._browserContext = undefined;
|
|
127
|
-
}
|
|
240
|
+
if (this._browserContext && !this._tabs.length)
|
|
241
|
+
void this.close();
|
|
128
242
|
}
|
|
129
243
|
async close() {
|
|
130
244
|
if (!this._browserContext)
|
|
131
245
|
return;
|
|
132
|
-
|
|
246
|
+
const browserContext = this._browserContext;
|
|
247
|
+
const browser = this._browser;
|
|
248
|
+
this._browserContext = undefined;
|
|
249
|
+
this._browser = undefined;
|
|
250
|
+
await browserContext?.close().then(async () => {
|
|
251
|
+
await browser?.close();
|
|
252
|
+
}).catch(() => { });
|
|
133
253
|
}
|
|
134
254
|
async _ensureBrowserContext() {
|
|
135
255
|
if (!this._browserContext) {
|
|
@@ -178,7 +298,6 @@ class Tab {
|
|
|
178
298
|
context;
|
|
179
299
|
page;
|
|
180
300
|
_console = [];
|
|
181
|
-
_fileChooser;
|
|
182
301
|
_snapshot;
|
|
183
302
|
_onPageClose;
|
|
184
303
|
constructor(context, page, onPageClose) {
|
|
@@ -191,12 +310,18 @@ class Tab {
|
|
|
191
310
|
this._console.length = 0;
|
|
192
311
|
});
|
|
193
312
|
page.on('close', () => this._onClose());
|
|
194
|
-
page.on('filechooser', chooser =>
|
|
313
|
+
page.on('filechooser', chooser => {
|
|
314
|
+
this.context.setModalState({
|
|
315
|
+
type: 'fileChooser',
|
|
316
|
+
description: 'File chooser',
|
|
317
|
+
fileChooser: chooser,
|
|
318
|
+
}, this);
|
|
319
|
+
});
|
|
320
|
+
page.on('dialog', dialog => this.context.dialogShown(this, dialog));
|
|
195
321
|
page.setDefaultNavigationTimeout(60000);
|
|
196
322
|
page.setDefaultTimeout(5000);
|
|
197
323
|
}
|
|
198
324
|
_onClose() {
|
|
199
|
-
this._fileChooser = undefined;
|
|
200
325
|
this._console.length = 0;
|
|
201
326
|
this._onPageClose(this);
|
|
202
327
|
}
|
|
@@ -205,42 +330,10 @@ class Tab {
|
|
|
205
330
|
// Cap load event to 5 seconds, the page is operational at this point.
|
|
206
331
|
await this.page.waitForLoadState('load', { timeout: 5000 }).catch(() => { });
|
|
207
332
|
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
if (!options?.noClearFileChooser)
|
|
211
|
-
this._fileChooser = undefined;
|
|
212
|
-
if (options?.waitForCompletion)
|
|
213
|
-
await (0, utils_1.waitForCompletion)(this.page, () => callback(this));
|
|
214
|
-
else
|
|
215
|
-
await callback(this);
|
|
216
|
-
}
|
|
217
|
-
finally {
|
|
218
|
-
if (options?.captureSnapshot)
|
|
219
|
-
this._snapshot = await PageSnapshot.create(this.page);
|
|
220
|
-
}
|
|
221
|
-
const tabList = this.context.tabs().length > 1 ? await this.context.listTabs() + '\n\nCurrent tab:' + '\n' : '';
|
|
222
|
-
const snapshot = this._snapshot?.text({ status: options?.status, hasFileChooser: !!this._fileChooser }) ?? options?.status ?? '';
|
|
223
|
-
return {
|
|
224
|
-
content: [{
|
|
225
|
-
type: 'text',
|
|
226
|
-
text: tabList + snapshot,
|
|
227
|
-
}],
|
|
228
|
-
};
|
|
229
|
-
}
|
|
230
|
-
async runAndWait(callback, options) {
|
|
231
|
-
return await this.run(callback, {
|
|
232
|
-
waitForCompletion: true,
|
|
233
|
-
...options,
|
|
234
|
-
});
|
|
333
|
+
hasSnapshot() {
|
|
334
|
+
return !!this._snapshot;
|
|
235
335
|
}
|
|
236
|
-
|
|
237
|
-
return await this.run(callback, {
|
|
238
|
-
captureSnapshot: true,
|
|
239
|
-
waitForCompletion: true,
|
|
240
|
-
...options,
|
|
241
|
-
});
|
|
242
|
-
}
|
|
243
|
-
lastSnapshot() {
|
|
336
|
+
snapshotOrDie() {
|
|
244
337
|
if (!this._snapshot)
|
|
245
338
|
throw new Error('No snapshot available');
|
|
246
339
|
return this._snapshot;
|
|
@@ -248,13 +341,11 @@ class Tab {
|
|
|
248
341
|
async console() {
|
|
249
342
|
return this._console;
|
|
250
343
|
}
|
|
251
|
-
async
|
|
252
|
-
|
|
253
|
-
throw new Error('No file chooser visible');
|
|
254
|
-
await this._fileChooser.setFiles(paths);
|
|
255
|
-
this._fileChooser = undefined;
|
|
344
|
+
async captureSnapshot() {
|
|
345
|
+
this._snapshot = await PageSnapshot.create(this.page);
|
|
256
346
|
}
|
|
257
347
|
}
|
|
348
|
+
exports.Tab = Tab;
|
|
258
349
|
class PageSnapshot {
|
|
259
350
|
_frameLocators = [];
|
|
260
351
|
_text;
|
|
@@ -265,25 +356,17 @@ class PageSnapshot {
|
|
|
265
356
|
await snapshot._build(page);
|
|
266
357
|
return snapshot;
|
|
267
358
|
}
|
|
268
|
-
text(
|
|
269
|
-
|
|
270
|
-
if (options?.status) {
|
|
271
|
-
results.push(options.status);
|
|
272
|
-
results.push('');
|
|
273
|
-
}
|
|
274
|
-
if (options?.hasFileChooser) {
|
|
275
|
-
results.push('- There is a file chooser visible that requires browser_file_upload to be called');
|
|
276
|
-
results.push('');
|
|
277
|
-
}
|
|
278
|
-
results.push(this._text);
|
|
279
|
-
return results.join('\n');
|
|
359
|
+
text() {
|
|
360
|
+
return this._text;
|
|
280
361
|
}
|
|
281
362
|
async _build(page) {
|
|
282
363
|
const yamlDocument = await this._snapshotFrame(page);
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
364
|
+
this._text = [
|
|
365
|
+
`- Page Snapshot`,
|
|
366
|
+
'```yaml',
|
|
367
|
+
yamlDocument.toString({ indentSeq: false }).trim(),
|
|
368
|
+
'```',
|
|
369
|
+
].join('\n');
|
|
287
370
|
}
|
|
288
371
|
async _snapshotFrame(frame) {
|
|
289
372
|
const frameIndex = this._frameLocators.push(frame) - 1;
|
|
@@ -336,3 +419,6 @@ class PageSnapshot {
|
|
|
336
419
|
return frame.locator(`aria-ref=${ref}`);
|
|
337
420
|
}
|
|
338
421
|
}
|
|
422
|
+
async function generateLocator(locator) {
|
|
423
|
+
return locator._generateLocatorString();
|
|
424
|
+
}
|
package/lib/index.js
CHANGED
|
@@ -19,8 +19,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
19
19
|
};
|
|
20
20
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
21
|
exports.createServer = createServer;
|
|
22
|
+
const path_1 = __importDefault(require("path"));
|
|
23
|
+
const os_1 = __importDefault(require("os"));
|
|
24
|
+
const fs_1 = __importDefault(require("fs"));
|
|
22
25
|
const server_1 = require("./server");
|
|
23
26
|
const common_1 = __importDefault(require("./tools/common"));
|
|
27
|
+
const console_1 = __importDefault(require("./tools/console"));
|
|
28
|
+
const dialogs_1 = __importDefault(require("./tools/dialogs"));
|
|
24
29
|
const files_1 = __importDefault(require("./tools/files"));
|
|
25
30
|
const install_1 = __importDefault(require("./tools/install"));
|
|
26
31
|
const keyboard_1 = __importDefault(require("./tools/keyboard"));
|
|
@@ -29,9 +34,10 @@ const pdf_1 = __importDefault(require("./tools/pdf"));
|
|
|
29
34
|
const snapshot_1 = __importDefault(require("./tools/snapshot"));
|
|
30
35
|
const tabs_1 = __importDefault(require("./tools/tabs"));
|
|
31
36
|
const screen_1 = __importDefault(require("./tools/screen"));
|
|
32
|
-
const console_1 = require("./resources/console");
|
|
33
37
|
const snapshotTools = [
|
|
34
|
-
...common_1.default,
|
|
38
|
+
...(0, common_1.default)(true),
|
|
39
|
+
...console_1.default,
|
|
40
|
+
...(0, dialogs_1.default)(true),
|
|
35
41
|
...(0, files_1.default)(true),
|
|
36
42
|
...install_1.default,
|
|
37
43
|
...(0, keyboard_1.default)(true),
|
|
@@ -41,7 +47,9 @@ const snapshotTools = [
|
|
|
41
47
|
...(0, tabs_1.default)(true),
|
|
42
48
|
];
|
|
43
49
|
const screenshotTools = [
|
|
44
|
-
...common_1.default,
|
|
50
|
+
...(0, common_1.default)(false),
|
|
51
|
+
...console_1.default,
|
|
52
|
+
...(0, dialogs_1.default)(false),
|
|
45
53
|
...(0, files_1.default)(false),
|
|
46
54
|
...install_1.default,
|
|
47
55
|
...(0, keyboard_1.default)(false),
|
|
@@ -50,21 +58,65 @@ const screenshotTools = [
|
|
|
50
58
|
...screen_1.default,
|
|
51
59
|
...(0, tabs_1.default)(false),
|
|
52
60
|
];
|
|
53
|
-
const resources = [
|
|
54
|
-
console_1.console,
|
|
55
|
-
];
|
|
56
61
|
const packageJSON = require('../package.json');
|
|
57
|
-
function createServer(options) {
|
|
62
|
+
async function createServer(options) {
|
|
63
|
+
let browserName;
|
|
64
|
+
let channel;
|
|
65
|
+
switch (options?.browser) {
|
|
66
|
+
case 'chrome':
|
|
67
|
+
case 'chrome-beta':
|
|
68
|
+
case 'chrome-canary':
|
|
69
|
+
case 'chrome-dev':
|
|
70
|
+
case 'msedge':
|
|
71
|
+
case 'msedge-beta':
|
|
72
|
+
case 'msedge-canary':
|
|
73
|
+
case 'msedge-dev':
|
|
74
|
+
browserName = 'chromium';
|
|
75
|
+
channel = options.browser;
|
|
76
|
+
break;
|
|
77
|
+
case 'chromium':
|
|
78
|
+
browserName = 'chromium';
|
|
79
|
+
break;
|
|
80
|
+
case 'firefox':
|
|
81
|
+
browserName = 'firefox';
|
|
82
|
+
break;
|
|
83
|
+
case 'webkit':
|
|
84
|
+
browserName = 'webkit';
|
|
85
|
+
break;
|
|
86
|
+
default:
|
|
87
|
+
browserName = 'chromium';
|
|
88
|
+
channel = 'chrome';
|
|
89
|
+
}
|
|
90
|
+
const userDataDir = options?.userDataDir ?? await createUserDataDir(browserName);
|
|
91
|
+
const launchOptions = {
|
|
92
|
+
headless: !!(options?.headless ?? (os_1.default.platform() === 'linux' && !process.env.DISPLAY)),
|
|
93
|
+
channel,
|
|
94
|
+
executablePath: options?.executablePath,
|
|
95
|
+
};
|
|
58
96
|
const allTools = options?.vision ? screenshotTools : snapshotTools;
|
|
59
97
|
const tools = allTools.filter(tool => !options?.capabilities || tool.capability === 'core' || options.capabilities.includes(tool.capability));
|
|
60
98
|
return (0, server_1.createServerWithTools)({
|
|
61
99
|
name: 'Playwright',
|
|
62
100
|
version: packageJSON.version,
|
|
63
101
|
tools,
|
|
64
|
-
resources,
|
|
65
|
-
browserName
|
|
66
|
-
userDataDir
|
|
67
|
-
launchOptions
|
|
102
|
+
resources: [],
|
|
103
|
+
browserName,
|
|
104
|
+
userDataDir,
|
|
105
|
+
launchOptions,
|
|
68
106
|
cdpEndpoint: options?.cdpEndpoint,
|
|
69
107
|
});
|
|
70
108
|
}
|
|
109
|
+
async function createUserDataDir(browserName) {
|
|
110
|
+
let cacheDirectory;
|
|
111
|
+
if (process.platform === 'linux')
|
|
112
|
+
cacheDirectory = process.env.XDG_CACHE_HOME || path_1.default.join(os_1.default.homedir(), '.cache');
|
|
113
|
+
else if (process.platform === 'darwin')
|
|
114
|
+
cacheDirectory = path_1.default.join(os_1.default.homedir(), 'Library', 'Caches');
|
|
115
|
+
else if (process.platform === 'win32')
|
|
116
|
+
cacheDirectory = process.env.LOCALAPPDATA || path_1.default.join(os_1.default.homedir(), 'AppData', 'Local');
|
|
117
|
+
else
|
|
118
|
+
throw new Error('Unsupported platform: ' + process.platform);
|
|
119
|
+
const result = path_1.default.join(cacheDirectory, 'ms-playwright', `mcp-${browserName}-profile`);
|
|
120
|
+
await fs_1.default.promises.mkdir(result, { recursive: true });
|
|
121
|
+
return result;
|
|
122
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Copyright (c) Microsoft Corporation.
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.escapeWithQuotes = escapeWithQuotes;
|
|
19
|
+
exports.quote = quote;
|
|
20
|
+
exports.formatObject = formatObject;
|
|
21
|
+
// adapted from:
|
|
22
|
+
// - https://github.com/microsoft/playwright/blob/76ee48dc9d4034536e3ec5b2c7ce8be3b79418a8/packages/playwright-core/src/utils/isomorphic/stringUtils.ts
|
|
23
|
+
// - https://github.com/microsoft/playwright/blob/76ee48dc9d4034536e3ec5b2c7ce8be3b79418a8/packages/playwright-core/src/server/codegen/javascript.ts
|
|
24
|
+
// NOTE: this function should not be used to escape any selectors.
|
|
25
|
+
function escapeWithQuotes(text, char = '\'') {
|
|
26
|
+
const stringified = JSON.stringify(text);
|
|
27
|
+
const escapedText = stringified.substring(1, stringified.length - 1).replace(/\\"/g, '"');
|
|
28
|
+
if (char === '\'')
|
|
29
|
+
return char + escapedText.replace(/[']/g, '\\\'') + char;
|
|
30
|
+
if (char === '"')
|
|
31
|
+
return char + escapedText.replace(/["]/g, '\\"') + char;
|
|
32
|
+
if (char === '`')
|
|
33
|
+
return char + escapedText.replace(/[`]/g, '`') + char;
|
|
34
|
+
throw new Error('Invalid escape char');
|
|
35
|
+
}
|
|
36
|
+
function quote(text) {
|
|
37
|
+
return escapeWithQuotes(text, '\'');
|
|
38
|
+
}
|
|
39
|
+
function formatObject(value, indent = ' ') {
|
|
40
|
+
if (typeof value === 'string')
|
|
41
|
+
return quote(value);
|
|
42
|
+
if (Array.isArray(value))
|
|
43
|
+
return `[${value.map(o => formatObject(o)).join(', ')}]`;
|
|
44
|
+
if (typeof value === 'object') {
|
|
45
|
+
const keys = Object.keys(value).filter(key => value[key] !== undefined).sort();
|
|
46
|
+
if (!keys.length)
|
|
47
|
+
return '{}';
|
|
48
|
+
const tokens = [];
|
|
49
|
+
for (const key of keys)
|
|
50
|
+
tokens.push(`${key}: ${formatObject(value[key])}`);
|
|
51
|
+
return `{\n${indent}${tokens.join(`,\n${indent}`)}\n}`;
|
|
52
|
+
}
|
|
53
|
+
return String(value);
|
|
54
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Copyright (c) Microsoft Corporation.
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.LongStandingScope = exports.ManualPromise = void 0;
|
|
19
|
+
class ManualPromise extends Promise {
|
|
20
|
+
_resolve;
|
|
21
|
+
_reject;
|
|
22
|
+
_isDone;
|
|
23
|
+
constructor() {
|
|
24
|
+
let resolve;
|
|
25
|
+
let reject;
|
|
26
|
+
super((f, r) => {
|
|
27
|
+
resolve = f;
|
|
28
|
+
reject = r;
|
|
29
|
+
});
|
|
30
|
+
this._isDone = false;
|
|
31
|
+
this._resolve = resolve;
|
|
32
|
+
this._reject = reject;
|
|
33
|
+
}
|
|
34
|
+
isDone() {
|
|
35
|
+
return this._isDone;
|
|
36
|
+
}
|
|
37
|
+
resolve(t) {
|
|
38
|
+
this._isDone = true;
|
|
39
|
+
this._resolve(t);
|
|
40
|
+
}
|
|
41
|
+
reject(e) {
|
|
42
|
+
this._isDone = true;
|
|
43
|
+
this._reject(e);
|
|
44
|
+
}
|
|
45
|
+
static get [Symbol.species]() {
|
|
46
|
+
return Promise;
|
|
47
|
+
}
|
|
48
|
+
get [Symbol.toStringTag]() {
|
|
49
|
+
return 'ManualPromise';
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
exports.ManualPromise = ManualPromise;
|
|
53
|
+
class LongStandingScope {
|
|
54
|
+
_terminateError;
|
|
55
|
+
_closeError;
|
|
56
|
+
_terminatePromises = new Map();
|
|
57
|
+
_isClosed = false;
|
|
58
|
+
reject(error) {
|
|
59
|
+
this._isClosed = true;
|
|
60
|
+
this._terminateError = error;
|
|
61
|
+
for (const p of this._terminatePromises.keys())
|
|
62
|
+
p.resolve(error);
|
|
63
|
+
}
|
|
64
|
+
close(error) {
|
|
65
|
+
this._isClosed = true;
|
|
66
|
+
this._closeError = error;
|
|
67
|
+
for (const [p, frames] of this._terminatePromises)
|
|
68
|
+
p.resolve(cloneError(error, frames));
|
|
69
|
+
}
|
|
70
|
+
isClosed() {
|
|
71
|
+
return this._isClosed;
|
|
72
|
+
}
|
|
73
|
+
static async raceMultiple(scopes, promise) {
|
|
74
|
+
return Promise.race(scopes.map(s => s.race(promise)));
|
|
75
|
+
}
|
|
76
|
+
async race(promise) {
|
|
77
|
+
return this._race(Array.isArray(promise) ? promise : [promise], false);
|
|
78
|
+
}
|
|
79
|
+
async safeRace(promise, defaultValue) {
|
|
80
|
+
return this._race([promise], true, defaultValue);
|
|
81
|
+
}
|
|
82
|
+
async _race(promises, safe, defaultValue) {
|
|
83
|
+
const terminatePromise = new ManualPromise();
|
|
84
|
+
const frames = captureRawStack();
|
|
85
|
+
if (this._terminateError)
|
|
86
|
+
terminatePromise.resolve(this._terminateError);
|
|
87
|
+
if (this._closeError)
|
|
88
|
+
terminatePromise.resolve(cloneError(this._closeError, frames));
|
|
89
|
+
this._terminatePromises.set(terminatePromise, frames);
|
|
90
|
+
try {
|
|
91
|
+
return await Promise.race([
|
|
92
|
+
terminatePromise.then(e => safe ? defaultValue : Promise.reject(e)),
|
|
93
|
+
...promises
|
|
94
|
+
]);
|
|
95
|
+
}
|
|
96
|
+
finally {
|
|
97
|
+
this._terminatePromises.delete(terminatePromise);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
exports.LongStandingScope = LongStandingScope;
|
|
102
|
+
function cloneError(error, frames) {
|
|
103
|
+
const clone = new Error();
|
|
104
|
+
clone.name = error.name;
|
|
105
|
+
clone.message = error.message;
|
|
106
|
+
clone.stack = [error.name + ':' + error.message, ...frames].join('\n');
|
|
107
|
+
return clone;
|
|
108
|
+
}
|
|
109
|
+
function captureRawStack() {
|
|
110
|
+
const stackTraceLimit = Error.stackTraceLimit;
|
|
111
|
+
Error.stackTraceLimit = 50;
|
|
112
|
+
const error = new Error();
|
|
113
|
+
const stack = error.stack || '';
|
|
114
|
+
Error.stackTraceLimit = stackTraceLimit;
|
|
115
|
+
return stack.split('\n');
|
|
116
|
+
}
|