@andrew-chen-wang/camoufox-mcp 0.0.1

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,344 @@
1
+ /**
2
+ * Copyright (c) Microsoft Corporation.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ import fs from 'node:fs/promises';
17
+ import { z } from 'zod';
18
+ import * as javascript from '../javascript.js';
19
+ import { outputFile } from '../config.js';
20
+ import { defineTool } from './tool.js';
21
+ const storageState = defineTool({
22
+ capability: 'core',
23
+ schema: {
24
+ name: 'browser_storage_state',
25
+ title: 'Save storage state',
26
+ description: 'Save the current browser storage state to a file',
27
+ inputSchema: z.object({
28
+ filename: z.string().optional(),
29
+ }),
30
+ type: 'readOnly',
31
+ },
32
+ handle: async (context, params) => {
33
+ const browserContext = await context.browserContext();
34
+ const fileName = await outputFile(context.config, params.filename ?? `storage-state-${new Date().toISOString()}.json`);
35
+ return {
36
+ code: [`await context.storageState({ path: ${javascript.quote(fileName)} });`],
37
+ action: async () => {
38
+ await browserContext.storageState({ path: fileName });
39
+ return { content: [{ type: 'text', text: fileName }] };
40
+ },
41
+ captureSnapshot: false,
42
+ waitForNetwork: false,
43
+ };
44
+ },
45
+ });
46
+ const setStorageState = defineTool({
47
+ capability: 'core',
48
+ schema: {
49
+ name: 'browser_set_storage_state',
50
+ title: 'Load storage state',
51
+ description: 'Load browser storage state from a file',
52
+ inputSchema: z.object({
53
+ filename: z.string(),
54
+ }),
55
+ type: 'destructive',
56
+ },
57
+ handle: async (context, params) => {
58
+ return {
59
+ code: [`// Load storage state from ${params.filename}`],
60
+ action: async () => {
61
+ const browserContext = await context.browserContext();
62
+ const page = context.currentTabOrDie().page;
63
+ const state = JSON.parse(await fs.readFile(params.filename, 'utf8'));
64
+ await browserContext.clearCookies();
65
+ if (state.cookies?.length)
66
+ await browserContext.addCookies(state.cookies);
67
+ for (const originState of state.origins ?? []) {
68
+ await page.goto(originState.origin, { waitUntil: 'domcontentloaded' });
69
+ await page.evaluate(entries => {
70
+ localStorage.clear();
71
+ for (const entry of entries)
72
+ localStorage.setItem(entry.name, entry.value);
73
+ }, originState.localStorage);
74
+ }
75
+ },
76
+ captureSnapshot: true,
77
+ waitForNetwork: true,
78
+ };
79
+ },
80
+ });
81
+ const cookieList = defineTool({
82
+ capability: 'core',
83
+ schema: {
84
+ name: 'browser_cookie_list',
85
+ title: 'List cookies',
86
+ description: 'List cookies for the current browser context',
87
+ inputSchema: z.object({
88
+ domain: z.string().optional(),
89
+ path: z.string().optional(),
90
+ }),
91
+ type: 'readOnly',
92
+ },
93
+ handle: async (context, params) => ({
94
+ code: ['// <internal code to list cookies>'],
95
+ action: async () => {
96
+ const browserContext = await context.browserContext();
97
+ const cookies = (await browserContext.cookies()).filter(cookie => {
98
+ if (params.domain && cookie.domain !== params.domain)
99
+ return false;
100
+ if (params.path && cookie.path !== params.path)
101
+ return false;
102
+ return true;
103
+ });
104
+ return { content: [{ type: 'text', text: JSON.stringify(cookies, null, 2) }] };
105
+ },
106
+ captureSnapshot: false,
107
+ waitForNetwork: false,
108
+ }),
109
+ });
110
+ const cookieGet = defineTool({
111
+ capability: 'core',
112
+ schema: {
113
+ name: 'browser_cookie_get',
114
+ title: 'Get cookie',
115
+ description: 'Get a cookie by name',
116
+ inputSchema: z.object({
117
+ name: z.string(),
118
+ }),
119
+ type: 'readOnly',
120
+ },
121
+ handle: async (context, params) => ({
122
+ code: ['// <internal code to get cookie>'],
123
+ action: async () => {
124
+ const browserContext = await context.browserContext();
125
+ const cookie = (await browserContext.cookies()).find(entry => entry.name === params.name);
126
+ return { content: [{ type: 'text', text: JSON.stringify(cookie ?? null, null, 2) }] };
127
+ },
128
+ captureSnapshot: false,
129
+ waitForNetwork: false,
130
+ }),
131
+ });
132
+ const cookieSet = defineTool({
133
+ capability: 'core',
134
+ schema: {
135
+ name: 'browser_cookie_set',
136
+ title: 'Set cookie',
137
+ description: 'Set a cookie in the current browser context',
138
+ inputSchema: z.object({
139
+ name: z.string(),
140
+ value: z.string(),
141
+ domain: z.string().optional(),
142
+ path: z.string().optional(),
143
+ expires: z.number().optional(),
144
+ httpOnly: z.boolean().optional(),
145
+ secure: z.boolean().optional(),
146
+ sameSite: z.enum(['Strict', 'Lax', 'None']).optional(),
147
+ }),
148
+ type: 'destructive',
149
+ },
150
+ handle: async (context, params) => ({
151
+ code: ['// <internal code to set cookie>'],
152
+ action: async () => {
153
+ const browserContext = await context.browserContext();
154
+ const pageUrl = context.currentTabOrDie().page.url();
155
+ const cookie = {
156
+ ...params,
157
+ };
158
+ if (!cookie.domain) {
159
+ if (!/^https?:/.test(pageUrl))
160
+ throw new Error('cookie domain is required when the current page does not have an http(s) URL.');
161
+ cookie.url = pageUrl;
162
+ }
163
+ await browserContext.addCookies([cookie]);
164
+ },
165
+ captureSnapshot: false,
166
+ waitForNetwork: false,
167
+ }),
168
+ });
169
+ const cookieDelete = defineTool({
170
+ capability: 'core',
171
+ schema: {
172
+ name: 'browser_cookie_delete',
173
+ title: 'Delete cookie',
174
+ description: 'Delete a cookie by name',
175
+ inputSchema: z.object({
176
+ name: z.string(),
177
+ }),
178
+ type: 'destructive',
179
+ },
180
+ handle: async (context, params) => ({
181
+ code: ['// <internal code to delete cookie>'],
182
+ action: async () => {
183
+ const browserContext = await context.browserContext();
184
+ const cookies = await browserContext.cookies();
185
+ const remaining = cookies.filter(cookie => cookie.name !== params.name);
186
+ await browserContext.clearCookies();
187
+ if (remaining.length)
188
+ await browserContext.addCookies(remaining.map(toSetCookie));
189
+ },
190
+ captureSnapshot: false,
191
+ waitForNetwork: false,
192
+ }),
193
+ });
194
+ const cookieClear = defineTool({
195
+ capability: 'core',
196
+ schema: {
197
+ name: 'browser_cookie_clear',
198
+ title: 'Clear cookies',
199
+ description: 'Clear all cookies',
200
+ inputSchema: z.object({}),
201
+ type: 'destructive',
202
+ },
203
+ handle: async (context) => ({
204
+ code: ['// <internal code to clear cookies>'],
205
+ action: async () => {
206
+ const browserContext = await context.browserContext();
207
+ await browserContext.clearCookies();
208
+ },
209
+ captureSnapshot: false,
210
+ waitForNetwork: false,
211
+ }),
212
+ });
213
+ function createWebStorageTools(kind) {
214
+ const prefix = kind === 'localStorage' ? 'browser_localstorage_' : 'browser_sessionstorage_';
215
+ const titlePrefix = kind === 'localStorage' ? 'localStorage' : 'sessionStorage';
216
+ const list = defineTool({
217
+ capability: 'core',
218
+ schema: {
219
+ name: `${prefix}list`,
220
+ title: `List ${titlePrefix}`,
221
+ description: `List all ${titlePrefix} key-value pairs`,
222
+ inputSchema: z.object({}),
223
+ type: 'readOnly',
224
+ },
225
+ handle: async (context) => ({
226
+ code: [`// <internal code to list ${titlePrefix}>`],
227
+ action: async () => {
228
+ const page = context.currentTabOrDie().page;
229
+ const result = await page.evaluate(storageKind => Object.fromEntries(Array.from(window[storageKind].entries())), kind);
230
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
231
+ },
232
+ captureSnapshot: false,
233
+ waitForNetwork: false,
234
+ }),
235
+ });
236
+ const get = defineTool({
237
+ capability: 'core',
238
+ schema: {
239
+ name: `${prefix}get`,
240
+ title: `Get ${titlePrefix} item`,
241
+ description: `Get a ${titlePrefix} item by key`,
242
+ inputSchema: z.object({
243
+ key: z.string(),
244
+ }),
245
+ type: 'readOnly',
246
+ },
247
+ handle: async (context, params) => ({
248
+ code: [`// <internal code to get ${titlePrefix} item>`],
249
+ action: async () => {
250
+ const page = context.currentTabOrDie().page;
251
+ const value = await page.evaluate(({ storageKind, key }) => window[storageKind].getItem(key), { storageKind: kind, key: params.key });
252
+ return { content: [{ type: 'text', text: value ?? '' }] };
253
+ },
254
+ captureSnapshot: false,
255
+ waitForNetwork: false,
256
+ }),
257
+ });
258
+ const set = defineTool({
259
+ capability: 'core',
260
+ schema: {
261
+ name: `${prefix}set`,
262
+ title: `Set ${titlePrefix} item`,
263
+ description: `Set a ${titlePrefix} item`,
264
+ inputSchema: z.object({
265
+ key: z.string(),
266
+ value: z.string(),
267
+ }),
268
+ type: 'destructive',
269
+ },
270
+ handle: async (context, params) => ({
271
+ code: [`// <internal code to set ${titlePrefix} item>`],
272
+ action: async () => {
273
+ const page = context.currentTabOrDie().page;
274
+ await page.evaluate(({ storageKind, key, value }) => window[storageKind].setItem(key, value), { storageKind: kind, key: params.key, value: params.value });
275
+ },
276
+ captureSnapshot: false,
277
+ waitForNetwork: false,
278
+ }),
279
+ });
280
+ const del = defineTool({
281
+ capability: 'core',
282
+ schema: {
283
+ name: `${prefix}delete`,
284
+ title: `Delete ${titlePrefix} item`,
285
+ description: `Delete a ${titlePrefix} item`,
286
+ inputSchema: z.object({
287
+ key: z.string(),
288
+ }),
289
+ type: 'destructive',
290
+ },
291
+ handle: async (context, params) => ({
292
+ code: [`// <internal code to delete ${titlePrefix} item>`],
293
+ action: async () => {
294
+ const page = context.currentTabOrDie().page;
295
+ await page.evaluate(({ storageKind, key }) => window[storageKind].removeItem(key), { storageKind: kind, key: params.key });
296
+ },
297
+ captureSnapshot: false,
298
+ waitForNetwork: false,
299
+ }),
300
+ });
301
+ const clear = defineTool({
302
+ capability: 'core',
303
+ schema: {
304
+ name: `${prefix}clear`,
305
+ title: `Clear ${titlePrefix}`,
306
+ description: `Clear all ${titlePrefix}`,
307
+ inputSchema: z.object({}),
308
+ type: 'destructive',
309
+ },
310
+ handle: async (context) => ({
311
+ code: [`// <internal code to clear ${titlePrefix}>`],
312
+ action: async () => {
313
+ const page = context.currentTabOrDie().page;
314
+ await page.evaluate(storageKind => window[storageKind].clear(), kind);
315
+ },
316
+ captureSnapshot: false,
317
+ waitForNetwork: false,
318
+ }),
319
+ });
320
+ return [list, get, set, del, clear];
321
+ }
322
+ function toSetCookie(cookie) {
323
+ return {
324
+ name: cookie.name,
325
+ value: cookie.value,
326
+ domain: cookie.domain,
327
+ path: cookie.path,
328
+ expires: cookie.expires,
329
+ httpOnly: cookie.httpOnly,
330
+ secure: cookie.secure,
331
+ sameSite: cookie.sameSite,
332
+ };
333
+ }
334
+ export default [
335
+ storageState,
336
+ setStorageState,
337
+ cookieList,
338
+ cookieGet,
339
+ cookieSet,
340
+ cookieDelete,
341
+ cookieClear,
342
+ ...createWebStorageTools('localStorage'),
343
+ ...createWebStorageTools('sessionStorage'),
344
+ ];
@@ -0,0 +1,172 @@
1
+ /**
2
+ * Copyright (c) Microsoft Corporation.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ import { z } from 'zod';
17
+ import { defineTool } from './tool.js';
18
+ const listTabs = defineTool({
19
+ capability: 'tabs',
20
+ schema: {
21
+ name: 'browser_tab_list',
22
+ title: 'List tabs',
23
+ description: 'List browser tabs',
24
+ inputSchema: z.object({}),
25
+ type: 'readOnly',
26
+ },
27
+ handle: async (context) => {
28
+ await context.ensureTab();
29
+ return {
30
+ code: [`// <internal code to list tabs>`],
31
+ captureSnapshot: false,
32
+ waitForNetwork: false,
33
+ resultOverride: {
34
+ content: [{
35
+ type: 'text',
36
+ text: await context.listTabsMarkdown(),
37
+ }],
38
+ },
39
+ };
40
+ },
41
+ });
42
+ const selectTab = captureSnapshot => defineTool({
43
+ capability: 'tabs',
44
+ schema: {
45
+ name: 'browser_tab_select',
46
+ title: 'Select a tab',
47
+ description: 'Select a tab by index',
48
+ inputSchema: z.object({
49
+ index: z.number().describe('The index of the tab to select'),
50
+ }),
51
+ type: 'readOnly',
52
+ },
53
+ handle: async (context, params) => {
54
+ await context.selectTab(params.index);
55
+ const code = [
56
+ `// <internal code to select tab ${params.index}>`,
57
+ ];
58
+ return {
59
+ code,
60
+ captureSnapshot,
61
+ waitForNetwork: false
62
+ };
63
+ },
64
+ });
65
+ const newTab = captureSnapshot => defineTool({
66
+ capability: 'tabs',
67
+ schema: {
68
+ name: 'browser_tab_new',
69
+ title: 'Open a new tab',
70
+ description: 'Open a new tab',
71
+ inputSchema: z.object({
72
+ url: z.string().optional().describe('The URL to navigate to in the new tab. If not provided, the new tab will be blank.'),
73
+ }),
74
+ type: 'readOnly',
75
+ },
76
+ handle: async (context, params) => {
77
+ await context.newTab();
78
+ if (params.url)
79
+ await context.currentTabOrDie().navigate(params.url);
80
+ const code = [
81
+ `// <internal code to open a new tab>`,
82
+ ];
83
+ return {
84
+ code,
85
+ captureSnapshot,
86
+ waitForNetwork: false
87
+ };
88
+ },
89
+ });
90
+ const closeTab = captureSnapshot => defineTool({
91
+ capability: 'tabs',
92
+ schema: {
93
+ name: 'browser_tab_close',
94
+ title: 'Close a tab',
95
+ description: 'Close a tab',
96
+ inputSchema: z.object({
97
+ index: z.number().optional().describe('The index of the tab to close. Closes current tab if not provided.'),
98
+ }),
99
+ type: 'destructive',
100
+ },
101
+ handle: async (context, params) => {
102
+ await context.closeTab(params.index);
103
+ const code = [
104
+ `// <internal code to close tab ${params.index}>`,
105
+ ];
106
+ return {
107
+ code,
108
+ captureSnapshot,
109
+ waitForNetwork: false
110
+ };
111
+ },
112
+ });
113
+ const tabs = captureSnapshot => defineTool({
114
+ capability: 'tabs',
115
+ schema: {
116
+ name: 'browser_tabs',
117
+ title: 'Manage tabs',
118
+ description: 'List, create, select, or close browser tabs',
119
+ inputSchema: z.object({
120
+ action: z.enum(['list', 'new', 'select', 'close']),
121
+ index: z.number().optional().describe('Tab index for select/close actions'),
122
+ url: z.string().optional().describe('Optional URL for new tab'),
123
+ }),
124
+ type: 'readOnly',
125
+ },
126
+ handle: async (context, params) => {
127
+ if (params.action === 'list') {
128
+ await context.ensureTab();
129
+ return {
130
+ code: ['// <internal code to list tabs>'],
131
+ captureSnapshot: false,
132
+ waitForNetwork: false,
133
+ resultOverride: {
134
+ content: [{ type: 'text', text: await context.listTabsMarkdown() }],
135
+ },
136
+ };
137
+ }
138
+ if (params.action === 'new') {
139
+ await context.newTab();
140
+ if (params.url)
141
+ await context.currentTabOrDie().navigate(params.url);
142
+ return {
143
+ code: ['// <internal code to open a new tab>'],
144
+ captureSnapshot,
145
+ waitForNetwork: false,
146
+ };
147
+ }
148
+ if (params.action === 'select') {
149
+ if (params.index === undefined)
150
+ throw new Error('index is required when action is "select".');
151
+ await context.selectTab(params.index);
152
+ return {
153
+ code: [`// <internal code to select tab ${params.index}>`],
154
+ captureSnapshot,
155
+ waitForNetwork: false,
156
+ };
157
+ }
158
+ await context.closeTab(params.index);
159
+ return {
160
+ code: [`// <internal code to close tab ${params.index}>`],
161
+ captureSnapshot,
162
+ waitForNetwork: false,
163
+ };
164
+ },
165
+ });
166
+ export default (captureSnapshot) => [
167
+ tabs(captureSnapshot),
168
+ listTabs,
169
+ newTab(captureSnapshot),
170
+ selectTab(captureSnapshot),
171
+ closeTab(captureSnapshot),
172
+ ];
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Copyright (c) Microsoft Corporation.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ import { z } from 'zod';
17
+ import { defineTool } from './tool.js';
18
+ const generateTestSchema = z.object({
19
+ name: z.string().describe('The name of the test'),
20
+ description: z.string().describe('The description of the test'),
21
+ steps: z.array(z.string()).describe('The steps of the test'),
22
+ });
23
+ const generateTest = defineTool({
24
+ capability: 'testing',
25
+ schema: {
26
+ name: 'browser_generate_playwright_test',
27
+ title: 'Generate a Playwright test',
28
+ description: 'Generate a Playwright test for given scenario',
29
+ inputSchema: generateTestSchema,
30
+ type: 'readOnly',
31
+ },
32
+ handle: async (context, params) => {
33
+ return {
34
+ resultOverride: {
35
+ content: [{
36
+ type: 'text',
37
+ text: instructions(params),
38
+ }],
39
+ },
40
+ code: [],
41
+ captureSnapshot: false,
42
+ waitForNetwork: false,
43
+ };
44
+ },
45
+ });
46
+ const instructions = (params) => [
47
+ `## Instructions`,
48
+ `- You are a playwright test generator.`,
49
+ `- You are given a scenario and you need to generate a playwright test for it.`,
50
+ '- DO NOT generate test code based on the scenario alone. DO run steps one by one using the tools provided instead.',
51
+ '- Only after all steps are completed, emit a Playwright TypeScript test that uses @playwright/test based on message history',
52
+ '- Save generated test file in the tests directory',
53
+ `Test name: ${params.name}`,
54
+ `Description: ${params.description}`,
55
+ `Steps:`,
56
+ ...params.steps.map((step, index) => `- ${index + 1}. ${step}`),
57
+ ].join('\n');
58
+ export default [
59
+ generateTest,
60
+ ];
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Copyright (c) Microsoft Corporation.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ export function defineTool(tool) {
17
+ return tool;
18
+ }
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Copyright (c) Microsoft Corporation.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ export async function waitForCompletion(context, tab, callback) {
17
+ const requests = new Set();
18
+ let frameNavigated = false;
19
+ let waitCallback = () => { };
20
+ const waitBarrier = new Promise(f => { waitCallback = f; });
21
+ const requestListener = (request) => requests.add(request);
22
+ const requestFinishedListener = (request) => {
23
+ requests.delete(request);
24
+ if (!requests.size)
25
+ waitCallback();
26
+ };
27
+ const frameNavigateListener = (frame) => {
28
+ if (frame.parentFrame())
29
+ return;
30
+ frameNavigated = true;
31
+ dispose();
32
+ clearTimeout(timeout);
33
+ void tab.waitForLoadState('load').then(waitCallback);
34
+ };
35
+ const onTimeout = () => {
36
+ dispose();
37
+ waitCallback();
38
+ };
39
+ tab.page.on('request', requestListener);
40
+ tab.page.on('requestfinished', requestFinishedListener);
41
+ tab.page.on('framenavigated', frameNavigateListener);
42
+ const timeout = setTimeout(onTimeout, 10000);
43
+ const dispose = () => {
44
+ tab.page.off('request', requestListener);
45
+ tab.page.off('requestfinished', requestFinishedListener);
46
+ tab.page.off('framenavigated', frameNavigateListener);
47
+ clearTimeout(timeout);
48
+ };
49
+ try {
50
+ const result = await callback();
51
+ if (!requests.size && !frameNavigated)
52
+ waitCallback();
53
+ await waitBarrier;
54
+ await context.waitForTimeout(1000);
55
+ return result;
56
+ }
57
+ finally {
58
+ dispose();
59
+ }
60
+ }
61
+ export function sanitizeForFilePath(s) {
62
+ const sanitize = (s) => s.replace(/[\x00-\x2C\x2E-\x2F\x3A-\x40\x5B-\x60\x7B-\x7F]+/g, '-');
63
+ const separator = s.lastIndexOf('.');
64
+ if (separator === -1)
65
+ return sanitize(s);
66
+ return sanitize(s.substring(0, separator)) + '.' + sanitize(s.substring(separator + 1));
67
+ }
68
+ export async function generateLocator(locator) {
69
+ try {
70
+ return await locator._generateLocatorString();
71
+ }
72
+ catch (e) {
73
+ if (e instanceof Error && /locator._generateLocatorString: Timeout .* exceeded/.test(e.message))
74
+ throw new Error('Ref not found, likely because element was removed. Use browser_snapshot to see what elements are currently on the page.');
75
+ throw e;
76
+ }
77
+ }
78
+ export async function callOnPageNoTrace(page, callback) {
79
+ return await page._wrapApiCall(() => callback(page), { internal: true });
80
+ }
81
+ export function resolveLocator(tab, params) {
82
+ if (params.selector)
83
+ return params.element ? tab.page.locator(params.selector).describe(params.element) : tab.page.locator(params.selector);
84
+ if (params.ref)
85
+ return tab.snapshotOrDie().refLocator({ element: params.element ?? params.ref, ref: params.ref });
86
+ throw new Error('Either ref or selector must be provided.');
87
+ }