playwriter 0.0.1 → 0.0.2
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/dist/prompt.md +539 -0
- package/package.json +3 -3
- package/dist/index.d.ts +0 -2
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -2
- package/dist/index.js.map +0 -1
- package/dist/index.test.d.ts +0 -2
- package/dist/index.test.d.ts.map +0 -1
- package/dist/index.test.js +0 -8
- package/dist/index.test.js.map +0 -1
- package/dist/playwriter.d.ts +0 -5
- package/dist/playwriter.d.ts.map +0 -1
- package/dist/playwriter.js +0 -177
- package/dist/playwriter.js.map +0 -1
- package/dist/profiles.d.ts +0 -16
- package/dist/profiles.d.ts.map +0 -1
- package/dist/profiles.js +0 -76
- package/dist/profiles.js.map +0 -1
- package/dist/profiles.test.d.ts +0 -2
- package/dist/profiles.test.d.ts.map +0 -1
- package/dist/profiles.test.js +0 -169
- package/dist/profiles.test.js.map +0 -1
package/dist/prompt.md
ADDED
|
@@ -0,0 +1,539 @@
|
|
|
1
|
+
Executes code in the server to control playwright.
|
|
2
|
+
|
|
3
|
+
You have access to a `page` object where you can call playwright methods on it to accomplish actions on the page.
|
|
4
|
+
|
|
5
|
+
You can also use `console.log` to examine the results of your actions.
|
|
6
|
+
|
|
7
|
+
You only have access to `page`, `context` and node.js globals. Do not try to import anything or setup handlers.
|
|
8
|
+
|
|
9
|
+
Your code should be stateless and do not depend on any state.
|
|
10
|
+
|
|
11
|
+
If you really want to attach listeners you should also detach them using a try finally block, to prevent memory leaks.
|
|
12
|
+
|
|
13
|
+
You can also create a new page via `context.newPage()` if you need to start fresh. You can then find that page by iteration over `context.pages()`:
|
|
14
|
+
|
|
15
|
+
```javascript
|
|
16
|
+
const page = context.pages().find((p) => p.url().includes('/some/path'))
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## important rules
|
|
20
|
+
|
|
21
|
+
- NEVER call `page.waitForTimeout`, instead use `page.waitForSelector` or use a while loop that waits for a condition to be true.
|
|
22
|
+
- when a timeout error happen for example during navigation don't worry too much. try to get the snapshot of the page to see the current state, then continue without retrying if the state is what you expect. If the state is not what you expect, then you can retry the action.
|
|
23
|
+
- only call `page.close()` if the user asks you so or if you are in a test feedback loop and you know the user is not dependently interacting with the page (for example for debugging).
|
|
24
|
+
- always call `new_page` at the start of a conversation. later this page will be passed to the `execute` tool.
|
|
25
|
+
- In some rare cases you can also skip `new_page` tool, if the user asks you to instead use an existing page in the browser. You can set a page as default using `state.page = page`, `execute` calls will be passed this page in the scope later on.
|
|
26
|
+
- if running in localhost and some elements are difficult to target with locators you can update the source code to add `data-testid` attributes to elements you want to target. This will make running tests much easier later on. Also update the source markdown documents your are following if you do so.
|
|
27
|
+
- after every action call the tool `accessibility_snapshot` to get the page structure and understand what elements are available on the page
|
|
28
|
+
- after form submissions use `page.waitForLoadState('networkidle')` to ensure the page is fully loaded before proceeding
|
|
29
|
+
- sometimes when in localhost and using Vite you can encounter issues in the first page load, where a module is not found, because of updated optimization of the node_modules. In these cases you can try reloading the page 2 times and see if the issue resolves itself.
|
|
30
|
+
- for Google and GitHub login always use the Google account you have access to, already signed in
|
|
31
|
+
- if you are following a markdown document describing the steps to follow to test the website, update this document if you encounter unexpected behavior or if you can add information that would make the test faster, for example telling how to wait for actions that trigger loading states or to use a different timeout for specific actions.
|
|
32
|
+
|
|
33
|
+
## getting outputs of code execution
|
|
34
|
+
|
|
35
|
+
You can use `console.log` to print values you want to see in the tool call result
|
|
36
|
+
|
|
37
|
+
## using page.evaluate
|
|
38
|
+
|
|
39
|
+
you can execute client side JavaScript code using `page.evaluate()`
|
|
40
|
+
|
|
41
|
+
When executing code with `page.evaluate()`, return values directly from the evaluate function. Use `console.log()` outside of evaluate to display results:
|
|
42
|
+
|
|
43
|
+
```javascript
|
|
44
|
+
// Get data from the page by returning it
|
|
45
|
+
const title = await page.evaluate(() => document.title)
|
|
46
|
+
console.log('Page title:', title)
|
|
47
|
+
|
|
48
|
+
// Return multiple values as an object
|
|
49
|
+
const pageInfo = await page.evaluate(() => ({
|
|
50
|
+
url: window.location.href,
|
|
51
|
+
buttonCount: document.querySelectorAll('button').length,
|
|
52
|
+
readyState: document.readyState,
|
|
53
|
+
}))
|
|
54
|
+
console.log('Page URL:', pageInfo.url)
|
|
55
|
+
console.log('Number of buttons:', pageInfo.buttonCount)
|
|
56
|
+
console.log('Page ready state:', pageInfo.readyState)
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Finding Elements on the Page
|
|
60
|
+
|
|
61
|
+
you can use the tool accessibility_snapshot to get the page accessibility snapshot tree, which provides a structured view of the page's elements, including their roles and names. This is useful for understanding the page structure and finding elements to interact with.
|
|
62
|
+
|
|
63
|
+
Example accessibility snapshot result:
|
|
64
|
+
|
|
65
|
+
```md
|
|
66
|
+
- generic [active] [ref=e1]:
|
|
67
|
+
- generic [ref=e2]:
|
|
68
|
+
- banner [ref=e3]:
|
|
69
|
+
- generic [ref=e5]:
|
|
70
|
+
- link "shadcn/ui" [ref=e6] [cursor=pointer]:
|
|
71
|
+
- /url: /
|
|
72
|
+
- img
|
|
73
|
+
- generic [ref=e11] [cursor=pointer]: shadcn/ui
|
|
74
|
+
- navigation [ref=e12]:
|
|
75
|
+
- link "Docs" [ref=e13] [cursor=pointer]:
|
|
76
|
+
- /url: /docs/installation
|
|
77
|
+
- link "Components" [ref=e14] [cursor=pointer]:
|
|
78
|
+
- /url: /docs/components
|
|
79
|
+
- link "Blocks" [ref=e15] [cursor=pointer]:
|
|
80
|
+
- /url: /blocks
|
|
81
|
+
- link "Charts" [ref=e16] [cursor=pointer]:
|
|
82
|
+
- /url: /charts/area
|
|
83
|
+
- link "Themes" [ref=e17] [cursor=pointer]:
|
|
84
|
+
- /url: /themes
|
|
85
|
+
- link "Colors" [ref=e18] [cursor=pointer]:
|
|
86
|
+
- /url: /colors
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Then you can use `page.locator(`aria-ref=${ref}`).describe(element);` to get an element with a specific `ref` and interact with it.
|
|
90
|
+
|
|
91
|
+
For example:
|
|
92
|
+
|
|
93
|
+
```javascript
|
|
94
|
+
const componentsLink = page
|
|
95
|
+
// Exact target element reference from the page snapshot
|
|
96
|
+
.locator('aria-ref=e14')
|
|
97
|
+
// Human-readable element description used to obtain permission to interact with the element
|
|
98
|
+
.describe('Components link')
|
|
99
|
+
|
|
100
|
+
componentsLink.click()
|
|
101
|
+
console.log('Clicked on Components link')
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
This approach is the preferred way to find elements on the page, as it allows you to use the structured information from the accessibility snapshot to interact with elements reliably.
|
|
105
|
+
|
|
106
|
+
You can also find `getByRole` to get elements on the page.
|
|
107
|
+
|
|
108
|
+
```javascript
|
|
109
|
+
// Then use the information from the snapshot to click elements
|
|
110
|
+
// For example, if snapshot shows: { "role": "button", "name": "Sign In" }
|
|
111
|
+
await page.getByRole('button', { name: 'Sign In' }).click()
|
|
112
|
+
|
|
113
|
+
// For a link with { "role": "link", "name": "About" }
|
|
114
|
+
await page.getByRole('link', { name: 'About' }).click()
|
|
115
|
+
|
|
116
|
+
// For a textbox with { "role": "textbox", "name": "Email" }
|
|
117
|
+
await page.getByRole('textbox', { name: 'Email' }).fill('user@example.com')
|
|
118
|
+
|
|
119
|
+
// For a heading with { "role": "heading", "name": "Welcome to Example.com" }
|
|
120
|
+
const headingText = await page
|
|
121
|
+
.getByRole('heading', { name: 'Welcome to Example.com' })
|
|
122
|
+
.textContent()
|
|
123
|
+
console.log('Heading text:', headingText)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Complete Example: Find and Click Elements
|
|
127
|
+
|
|
128
|
+
```javascript
|
|
129
|
+
await page.getByRole('button', { name: 'Submit Form' }).click()
|
|
130
|
+
console.log('Clicked submit button')
|
|
131
|
+
|
|
132
|
+
await page.waitForLoadState('networkidle')
|
|
133
|
+
console.log('Form submitted successfully')
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Core Concepts
|
|
137
|
+
|
|
138
|
+
### Page and Context
|
|
139
|
+
|
|
140
|
+
In Playwright, automation happens through a `page` object (representing a browser tab) and `context` (representing a browser session with cookies, storage, etc.).
|
|
141
|
+
|
|
142
|
+
```javascript
|
|
143
|
+
// Assuming you have page and context already available
|
|
144
|
+
const page = await context.newPage()
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Element Selection
|
|
148
|
+
|
|
149
|
+
Playwright uses locators to find elements. The examples below show various selection methods:
|
|
150
|
+
|
|
151
|
+
```javascript
|
|
152
|
+
// By role (recommended)
|
|
153
|
+
await page.getByRole('button', { name: 'Submit' })
|
|
154
|
+
|
|
155
|
+
// By text
|
|
156
|
+
await page.getByText('Welcome')
|
|
157
|
+
|
|
158
|
+
// By placeholder
|
|
159
|
+
await page.getByPlaceholder('Enter email')
|
|
160
|
+
|
|
161
|
+
// By label
|
|
162
|
+
await page.getByLabel('Username')
|
|
163
|
+
|
|
164
|
+
// By test id
|
|
165
|
+
await page.getByTestId('submit-button')
|
|
166
|
+
|
|
167
|
+
// By CSS selector
|
|
168
|
+
await page.locator('.my-class')
|
|
169
|
+
|
|
170
|
+
// By XPath
|
|
171
|
+
await page.locator('//div[@class="content"]')
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Navigation
|
|
175
|
+
|
|
176
|
+
### Navigate to URL
|
|
177
|
+
|
|
178
|
+
```javascript
|
|
179
|
+
await page.goto('https://example.com')
|
|
180
|
+
// Wait for network idle (no requests for 500ms)
|
|
181
|
+
await page.goto('https://example.com', { waitUntil: 'networkidle' })
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Navigate Back/Forward
|
|
185
|
+
|
|
186
|
+
```javascript
|
|
187
|
+
// Go back to previous page
|
|
188
|
+
await page.goBack()
|
|
189
|
+
|
|
190
|
+
// Go forward to next page
|
|
191
|
+
await page.goForward()
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## Screenshots
|
|
195
|
+
|
|
196
|
+
### Take Screenshot
|
|
197
|
+
|
|
198
|
+
```javascript
|
|
199
|
+
// Screenshot of viewport
|
|
200
|
+
await page.screenshot({ path: 'screenshot.png' })
|
|
201
|
+
|
|
202
|
+
// Full page screenshot
|
|
203
|
+
await page.screenshot({ path: 'fullpage.png', fullPage: true })
|
|
204
|
+
|
|
205
|
+
// Screenshot of specific element
|
|
206
|
+
const element = await page.getByRole('button', { name: 'Submit' })
|
|
207
|
+
await element.screenshot({ path: 'button.png' })
|
|
208
|
+
|
|
209
|
+
// Screenshot with custom dimensions
|
|
210
|
+
await page.setViewportSize({ width: 1280, height: 720 })
|
|
211
|
+
await page.screenshot({ path: 'custom-size.png' })
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
## Mouse Interactions
|
|
215
|
+
|
|
216
|
+
### Click Elements
|
|
217
|
+
|
|
218
|
+
```javascript
|
|
219
|
+
// Click by role
|
|
220
|
+
await page.getByRole('button', { name: 'Submit' }).click()
|
|
221
|
+
|
|
222
|
+
// Click at coordinates
|
|
223
|
+
await page.mouse.click(100, 200)
|
|
224
|
+
|
|
225
|
+
// Double click
|
|
226
|
+
await page.getByText('Double click me').dblclick()
|
|
227
|
+
|
|
228
|
+
// Right click
|
|
229
|
+
await page.getByText('Right click me').click({ button: 'right' })
|
|
230
|
+
|
|
231
|
+
// Click with modifiers
|
|
232
|
+
await page.getByText('Ctrl click me').click({ modifiers: ['Control'] })
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### Hover
|
|
236
|
+
|
|
237
|
+
```javascript
|
|
238
|
+
// Hover over element
|
|
239
|
+
await page.getByText('Hover me').hover()
|
|
240
|
+
|
|
241
|
+
// Hover at coordinates
|
|
242
|
+
await page.mouse.move(100, 200)
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
## Keyboard Input
|
|
246
|
+
|
|
247
|
+
### Type Text
|
|
248
|
+
|
|
249
|
+
```javascript
|
|
250
|
+
// Type into input field
|
|
251
|
+
await page.getByLabel('Email').fill('user@example.com')
|
|
252
|
+
|
|
253
|
+
// Type character by character (simulates real typing)
|
|
254
|
+
await page.getByLabel('Email').type('user@example.com', { delay: 100 })
|
|
255
|
+
|
|
256
|
+
// Clear and type
|
|
257
|
+
await page.getByLabel('Email').clear()
|
|
258
|
+
await page.getByLabel('Email').fill('new@example.com')
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### Press Keys
|
|
262
|
+
|
|
263
|
+
```javascript
|
|
264
|
+
// Press single key
|
|
265
|
+
await page.keyboard.press('Enter')
|
|
266
|
+
|
|
267
|
+
// Press key combination
|
|
268
|
+
await page.keyboard.press('Control+A')
|
|
269
|
+
|
|
270
|
+
// Press sequence of keys
|
|
271
|
+
await page.keyboard.press('Tab')
|
|
272
|
+
await page.keyboard.press('Tab')
|
|
273
|
+
await page.keyboard.press('Space')
|
|
274
|
+
|
|
275
|
+
// Common key shortcuts
|
|
276
|
+
await page.keyboard.press('Control+C') // Copy
|
|
277
|
+
await page.keyboard.press('Control+V') // Paste
|
|
278
|
+
await page.keyboard.press('Control+Z') // Undo
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
## Form Interactions
|
|
282
|
+
|
|
283
|
+
### Select Dropdown Options
|
|
284
|
+
|
|
285
|
+
```javascript
|
|
286
|
+
// Select by value
|
|
287
|
+
await page.selectOption('select#country', 'us')
|
|
288
|
+
|
|
289
|
+
// Select by label
|
|
290
|
+
await page.selectOption('select#country', { label: 'United States' })
|
|
291
|
+
|
|
292
|
+
// Select multiple options
|
|
293
|
+
await page.selectOption('select#colors', ['red', 'blue', 'green'])
|
|
294
|
+
|
|
295
|
+
// Get selected option
|
|
296
|
+
const selectedValue = await page.$eval('select#country', (el) => el.value)
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### Checkboxes and Radio Buttons
|
|
300
|
+
|
|
301
|
+
```javascript
|
|
302
|
+
// Check checkbox
|
|
303
|
+
await page.getByLabel('I agree').check()
|
|
304
|
+
|
|
305
|
+
// Uncheck checkbox
|
|
306
|
+
await page.getByLabel('Subscribe').uncheck()
|
|
307
|
+
|
|
308
|
+
// Check if checked
|
|
309
|
+
const isChecked = await page.getByLabel('I agree').isChecked()
|
|
310
|
+
|
|
311
|
+
// Select radio button
|
|
312
|
+
await page.getByLabel('Option A').check()
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
## JavaScript Evaluation
|
|
316
|
+
|
|
317
|
+
### Execute JavaScript in Page Context
|
|
318
|
+
|
|
319
|
+
```javascript
|
|
320
|
+
// Evaluate simple expression
|
|
321
|
+
const result = await page.evaluate(() => 2 + 2)
|
|
322
|
+
|
|
323
|
+
// Access page variables
|
|
324
|
+
const pageTitle = await page.evaluate(() => document.title)
|
|
325
|
+
|
|
326
|
+
// Modify page
|
|
327
|
+
await page.evaluate(() => {
|
|
328
|
+
document.body.style.backgroundColor = 'red'
|
|
329
|
+
})
|
|
330
|
+
|
|
331
|
+
// Pass arguments to page context
|
|
332
|
+
const sum = await page.evaluate(([a, b]) => a + b, [5, 3])
|
|
333
|
+
|
|
334
|
+
// Work with elements
|
|
335
|
+
const elementText = await page.evaluate(
|
|
336
|
+
(el) => el.textContent,
|
|
337
|
+
await page.getByRole('heading'),
|
|
338
|
+
)
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
### Execute JavaScript on Element
|
|
342
|
+
|
|
343
|
+
```javascript
|
|
344
|
+
// Get element property
|
|
345
|
+
const href = await page.getByRole('link').evaluate((el) => el.href)
|
|
346
|
+
|
|
347
|
+
// Modify element
|
|
348
|
+
await page.getByRole('button').evaluate((el) => {
|
|
349
|
+
el.style.backgroundColor = 'green'
|
|
350
|
+
el.disabled = true
|
|
351
|
+
})
|
|
352
|
+
|
|
353
|
+
// Scroll element into view
|
|
354
|
+
await page.getByText('Section').evaluate((el) => el.scrollIntoView())
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
## File Handling
|
|
358
|
+
|
|
359
|
+
### File Upload
|
|
360
|
+
|
|
361
|
+
```javascript
|
|
362
|
+
// Upload single file
|
|
363
|
+
await page.getByLabel('Upload file').setInputFiles('/path/to/file.pdf')
|
|
364
|
+
|
|
365
|
+
// Upload multiple files
|
|
366
|
+
await page
|
|
367
|
+
.getByLabel('Upload files')
|
|
368
|
+
.setInputFiles(['/path/to/file1.pdf', '/path/to/file2.pdf'])
|
|
369
|
+
|
|
370
|
+
// Clear file input
|
|
371
|
+
await page.getByLabel('Upload file').setInputFiles([])
|
|
372
|
+
|
|
373
|
+
// For file inputs, use setInputFiles directly on the input element
|
|
374
|
+
// Find the file input element (often hidden)
|
|
375
|
+
await page.locator('input[type="file"]').setInputFiles('/path/to/file.pdf')
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
## Network Monitoring
|
|
379
|
+
|
|
380
|
+
### Check Network Activity
|
|
381
|
+
|
|
382
|
+
```javascript
|
|
383
|
+
// Wait for a specific request to complete and get its response
|
|
384
|
+
const response = await page.waitForResponse(
|
|
385
|
+
(response) =>
|
|
386
|
+
response.url().includes('/api/user') && response.status() === 200,
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
// Get response data
|
|
390
|
+
const responseBody = await response.json()
|
|
391
|
+
console.log('API response:', responseBody)
|
|
392
|
+
|
|
393
|
+
// Wait for specific request
|
|
394
|
+
const request = await page.waitForRequest('**/api/data')
|
|
395
|
+
console.log('Request URL:', request.url())
|
|
396
|
+
console.log('Request method:', request.method())
|
|
397
|
+
|
|
398
|
+
// Get all resources loaded by the page
|
|
399
|
+
const resources = await page.evaluate(() =>
|
|
400
|
+
performance.getEntriesByType('resource').map((r) => ({
|
|
401
|
+
name: r.name,
|
|
402
|
+
duration: r.duration,
|
|
403
|
+
size: r.transferSize,
|
|
404
|
+
})),
|
|
405
|
+
)
|
|
406
|
+
console.log('Page resources:', resources)
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
## Console Messages
|
|
410
|
+
|
|
411
|
+
### Capture Console Output
|
|
412
|
+
|
|
413
|
+
```javascript
|
|
414
|
+
// Console messages are automatically captured by the MCP implementation
|
|
415
|
+
// Use the console_logs tool to retrieve them
|
|
416
|
+
|
|
417
|
+
// To trigger console messages from the page:
|
|
418
|
+
await page.evaluate(() => {
|
|
419
|
+
console.log('This message will be captured')
|
|
420
|
+
console.error('This error will be captured')
|
|
421
|
+
console.warn('This warning will be captured')
|
|
422
|
+
})
|
|
423
|
+
|
|
424
|
+
// Then use the console_logs MCP tool to retrieve all captured messages
|
|
425
|
+
// The tool provides filtering by type and pagination
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
## Waiting
|
|
429
|
+
|
|
430
|
+
### Wait for Conditions
|
|
431
|
+
|
|
432
|
+
```javascript
|
|
433
|
+
// Wait for element to appear
|
|
434
|
+
await page.waitForSelector('.success-message')
|
|
435
|
+
|
|
436
|
+
// Wait for element to disappear
|
|
437
|
+
await page.waitForSelector('.loading', { state: 'hidden' })
|
|
438
|
+
|
|
439
|
+
await page.waitForURL(/github\.com.*\/pull/)
|
|
440
|
+
await page.waitForURL(/\/new-org/)
|
|
441
|
+
|
|
442
|
+
// Wait for text to appear
|
|
443
|
+
await page.waitForFunction(
|
|
444
|
+
(text) => document.body.textContent.includes(text),
|
|
445
|
+
'Success!',
|
|
446
|
+
)
|
|
447
|
+
|
|
448
|
+
// Wait for navigation
|
|
449
|
+
await page.waitForURL('**/success')
|
|
450
|
+
|
|
451
|
+
// Wait for page load
|
|
452
|
+
await page.waitForLoadState('networkidle')
|
|
453
|
+
|
|
454
|
+
// Wait for specific condition
|
|
455
|
+
await page.waitForFunction(
|
|
456
|
+
(text) => document.querySelector('.status')?.textContent === text,
|
|
457
|
+
'Ready',
|
|
458
|
+
)
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
### Wait for Text to Appear or Disappear
|
|
462
|
+
|
|
463
|
+
```javascript
|
|
464
|
+
// Wait for specific text to appear on the page
|
|
465
|
+
await page.getByText('Loading complete').first().waitFor({ state: 'visible' })
|
|
466
|
+
console.log('Loading complete text is now visible')
|
|
467
|
+
|
|
468
|
+
// Wait for text to disappear from the page
|
|
469
|
+
await page.getByText('Loading...').first().waitFor({ state: 'hidden' })
|
|
470
|
+
console.log('Loading text has disappeared')
|
|
471
|
+
|
|
472
|
+
// Wait for multiple conditions sequentially
|
|
473
|
+
// First wait for loading to disappear, then wait for success message
|
|
474
|
+
await page.getByText('Processing...').first().waitFor({ state: 'hidden' })
|
|
475
|
+
await page.getByText('Success!').first().waitFor({ state: 'visible' })
|
|
476
|
+
console.log('Processing finished and success message appeared')
|
|
477
|
+
|
|
478
|
+
// Example: Wait for error message to disappear before proceeding
|
|
479
|
+
await page
|
|
480
|
+
.getByText('Error: Please try again')
|
|
481
|
+
.first()
|
|
482
|
+
.waitFor({ state: 'hidden' })
|
|
483
|
+
await page.getByRole('button', { name: 'Submit' }).click()
|
|
484
|
+
|
|
485
|
+
// Example: Wait for confirmation text after form submission
|
|
486
|
+
await page.getByRole('button', { name: 'Save' }).click()
|
|
487
|
+
await page
|
|
488
|
+
.getByText('Your changes have been saved')
|
|
489
|
+
.first()
|
|
490
|
+
.waitFor({ state: 'visible' })
|
|
491
|
+
console.log('Save confirmed')
|
|
492
|
+
|
|
493
|
+
// Example: Wait for dynamic content to load
|
|
494
|
+
await page.getByRole('button', { name: 'Load More' }).click()
|
|
495
|
+
await page
|
|
496
|
+
.getByText('Loading more items...')
|
|
497
|
+
.first()
|
|
498
|
+
.waitFor({ state: 'visible' })
|
|
499
|
+
await page
|
|
500
|
+
.getByText('Loading more items...')
|
|
501
|
+
.first()
|
|
502
|
+
.waitFor({ state: 'hidden' })
|
|
503
|
+
console.log('Additional items loaded')
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
### Work with Frames
|
|
507
|
+
|
|
508
|
+
```javascript
|
|
509
|
+
// Get frame by name
|
|
510
|
+
const frame = page.frame('frameName')
|
|
511
|
+
|
|
512
|
+
// Get frame by URL
|
|
513
|
+
const frame = page.frame({ url: /frame\.html/ })
|
|
514
|
+
|
|
515
|
+
// Interact with frame content
|
|
516
|
+
await frame.getByText('In Frame').click()
|
|
517
|
+
|
|
518
|
+
// Get all frames
|
|
519
|
+
const frames = page.frames()
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
## Best Practices
|
|
523
|
+
|
|
524
|
+
### Reliable Selectors
|
|
525
|
+
|
|
526
|
+
```javascript
|
|
527
|
+
// Prefer user-facing attributes
|
|
528
|
+
await page.getByRole('button', { name: 'Submit' })
|
|
529
|
+
await page.getByLabel('Email')
|
|
530
|
+
await page.getByPlaceholder('Search...')
|
|
531
|
+
await page.getByText('Welcome')
|
|
532
|
+
|
|
533
|
+
// Use test IDs for complex cases
|
|
534
|
+
await page.getByTestId('complex-component')
|
|
535
|
+
|
|
536
|
+
// Avoid brittle selectors
|
|
537
|
+
// Bad: await page.locator('.btn-3842');
|
|
538
|
+
// Good: await page.getByRole('button', { name: 'Submit' });
|
|
539
|
+
```
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "playwriter",
|
|
3
3
|
"description": "",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.2",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": "https://github.com/remorses/",
|
|
7
7
|
"scripts": {
|
|
8
|
-
"build": "tsc
|
|
9
|
-
"prepublishOnly": "
|
|
8
|
+
"build": "rm -rf dist *.tsbuildinfo && mkdir dist && cp src/prompt.md dist/ && tsc",
|
|
9
|
+
"prepublishOnly": "pnpm build",
|
|
10
10
|
"watch": "tsc -w",
|
|
11
11
|
"typecheck": "tsc --noEmit",
|
|
12
12
|
"mcp": "vite-node src/mcp.ts",
|
package/dist/index.d.ts
DELETED
package/dist/index.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA"}
|
package/dist/index.js
DELETED
package/dist/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA"}
|
package/dist/index.test.d.ts
DELETED
package/dist/index.test.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.test.d.ts","sourceRoot":"","sources":["../src/index.test.ts"],"names":[],"mappings":""}
|
package/dist/index.test.js
DELETED
package/dist/index.test.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.test.js","sourceRoot":"","sources":["../src/index.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAErC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE;IACf,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AAC3B,CAAC,CAAC,CAAA;AAEF,IAAI,CAAC,KAAK,EAAE,GAAG,EAAE;IACb,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AAC3B,CAAC,CAAC,CAAA"}
|
package/dist/playwriter.d.ts
DELETED
package/dist/playwriter.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"playwriter.d.ts","sourceRoot":"","sources":["../src/playwriter.ts"],"names":[],"mappings":"AAmDA,wBAAsB,eAAe,CAAC,YAAY,CAAC,EAAE,MAAM;;;GAqJ1D"}
|
package/dist/playwriter.js
DELETED
|
@@ -1,177 +0,0 @@
|
|
|
1
|
-
// Removed Playwright import - launching Chrome directly
|
|
2
|
-
import { spawn } from 'child_process';
|
|
3
|
-
import os from 'node:os';
|
|
4
|
-
import fs from 'node:fs';
|
|
5
|
-
import path from 'node:path';
|
|
6
|
-
import { getAllProfiles } from './profiles.js';
|
|
7
|
-
// Find Chrome executable path based on OS
|
|
8
|
-
function findChromeExecutablePath() {
|
|
9
|
-
const osPlatform = os.platform();
|
|
10
|
-
const paths = (() => {
|
|
11
|
-
if (osPlatform === 'darwin') {
|
|
12
|
-
return [
|
|
13
|
-
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
|
|
14
|
-
'/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary',
|
|
15
|
-
'~/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
|
|
16
|
-
];
|
|
17
|
-
}
|
|
18
|
-
if (osPlatform === 'win32') {
|
|
19
|
-
return [
|
|
20
|
-
'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
|
|
21
|
-
'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
|
|
22
|
-
`${process.env.LOCALAPPDATA}\\Google\\Chrome\\Application\\chrome.exe`,
|
|
23
|
-
`${process.env.PROGRAMFILES}\\Google\\Chrome\\Application\\chrome.exe`,
|
|
24
|
-
`${process.env['PROGRAMFILES(X86)']}\\Google\\Chrome\\Application\\chrome.exe`,
|
|
25
|
-
].filter(Boolean);
|
|
26
|
-
}
|
|
27
|
-
// Linux
|
|
28
|
-
return [
|
|
29
|
-
'/usr/bin/google-chrome',
|
|
30
|
-
'/usr/bin/google-chrome-stable',
|
|
31
|
-
'/usr/bin/chromium',
|
|
32
|
-
'/usr/bin/chromium-browser',
|
|
33
|
-
'/snap/bin/chromium',
|
|
34
|
-
];
|
|
35
|
-
})();
|
|
36
|
-
for (const path of paths) {
|
|
37
|
-
const resolvedPath = path.startsWith('~')
|
|
38
|
-
? path.replace('~', process.env.HOME || '')
|
|
39
|
-
: path;
|
|
40
|
-
if (fs.existsSync(resolvedPath)) {
|
|
41
|
-
return resolvedPath;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
throw new Error('Could not find Chrome executable. Please install Google Chrome.');
|
|
45
|
-
}
|
|
46
|
-
export async function startPlaywriter(emailProfile) {
|
|
47
|
-
const cdpPort = 9922;
|
|
48
|
-
try {
|
|
49
|
-
// Find Chrome executable
|
|
50
|
-
const executablePath = findChromeExecutablePath();
|
|
51
|
-
console.log(`Found Chrome at: ${executablePath}`);
|
|
52
|
-
// Get available Chrome profiles
|
|
53
|
-
const profiles = getAllProfiles();
|
|
54
|
-
let selectedProfilePath;
|
|
55
|
-
// If no emailProfile provided, we can't proceed
|
|
56
|
-
if (!emailProfile) {
|
|
57
|
-
throw new Error('Email profile is required to start Chrome');
|
|
58
|
-
}
|
|
59
|
-
// Find the profile matching the email
|
|
60
|
-
const matchingProfile = profiles.find(p => p.email === emailProfile);
|
|
61
|
-
if (!matchingProfile) {
|
|
62
|
-
if (profiles.length === 0) {
|
|
63
|
-
// Create a temporary profile directory for automation
|
|
64
|
-
const tempDir = path.join(os.tmpdir(), 'playwriter-automation-profile');
|
|
65
|
-
if (!fs.existsSync(tempDir)) {
|
|
66
|
-
fs.mkdirSync(tempDir, { recursive: true });
|
|
67
|
-
}
|
|
68
|
-
selectedProfilePath = tempDir;
|
|
69
|
-
console.warn(`No Chrome profiles found. Using temporary profile at: ${tempDir}`);
|
|
70
|
-
}
|
|
71
|
-
else {
|
|
72
|
-
throw new Error(`No Chrome profile found for email: ${emailProfile}. Available emails: ${profiles.map(p => p.email).join(', ')}`);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
else {
|
|
76
|
-
selectedProfilePath = matchingProfile.path;
|
|
77
|
-
console.log(`Using profile for ${emailProfile}: ${selectedProfilePath}`);
|
|
78
|
-
}
|
|
79
|
-
// Start browser with CDP enabled
|
|
80
|
-
console.log(`Starting Chrome with CDP on port ${cdpPort}...`);
|
|
81
|
-
// Get the Chrome user data directory and profile folder
|
|
82
|
-
const chromeUserDataDir = path.dirname(selectedProfilePath);
|
|
83
|
-
const profileFolder = path.basename(selectedProfilePath);
|
|
84
|
-
// Build Chrome arguments
|
|
85
|
-
const chromeArgs = [
|
|
86
|
-
`--remote-debugging-port=${cdpPort}`,
|
|
87
|
-
'--window-position=-32000,-32000', // Position window off-screen
|
|
88
|
-
'--window-size=1280,720',
|
|
89
|
-
'--disable-backgrounding-occluded-windows', // Prevents Chrome from throttling/suspending hidden tabs
|
|
90
|
-
'--disable-gpu', // Disable GPU acceleration for better compatibility
|
|
91
|
-
`--user-data-dir=${chromeUserDataDir}`, // Chrome's main user data directory
|
|
92
|
-
'--no-first-run', // Skip first-run dialogs
|
|
93
|
-
'--disable-default-apps', // Disable default app installation
|
|
94
|
-
'--disable-translate', // Disable translate prompts
|
|
95
|
-
'--disable-features=TranslateUI', // Disable translate UI
|
|
96
|
-
'--no-default-browser-check', // Don't check if Chrome is default browser
|
|
97
|
-
'--disable-session-crashed-bubble', // Disable session restore bubble
|
|
98
|
-
'--disable-infobars', // Disable info bars
|
|
99
|
-
'--automation', // Enable automation mode
|
|
100
|
-
];
|
|
101
|
-
// Add profile-directory for non-default profiles
|
|
102
|
-
if (profileFolder !== 'Default') {
|
|
103
|
-
chromeArgs.push(`--profile-directory=${profileFolder}`);
|
|
104
|
-
}
|
|
105
|
-
// Launch Chrome directly as a subprocess
|
|
106
|
-
console.log('Launching Chrome with args:', chromeArgs);
|
|
107
|
-
const chromeProcess = spawn(executablePath, chromeArgs, {
|
|
108
|
-
detached: false,
|
|
109
|
-
stdio: 'ignore', // Ignore Chrome's output to avoid noise
|
|
110
|
-
});
|
|
111
|
-
chromeProcess.on('error', (error) => {
|
|
112
|
-
console.error('Failed to start Chrome:', error);
|
|
113
|
-
throw error;
|
|
114
|
-
});
|
|
115
|
-
// Give Chrome a moment to start up and open the debugging port
|
|
116
|
-
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
117
|
-
// On macOS, minimize only this Chrome window using its PID
|
|
118
|
-
if (os.platform() === 'darwin' && chromeProcess.pid) {
|
|
119
|
-
try {
|
|
120
|
-
// Minimize the specific Chrome window using its process ID
|
|
121
|
-
// This keeps it running but out of the way
|
|
122
|
-
const minimizeScript = spawn('osascript', [
|
|
123
|
-
'-e', `tell application "System Events"`,
|
|
124
|
-
'-e', `tell (first process whose unix id is ${chromeProcess.pid})`,
|
|
125
|
-
'-e', `try`,
|
|
126
|
-
'-e', `set value of attribute "AXMinimized" of window 1 to true`,
|
|
127
|
-
'-e', `end try`,
|
|
128
|
-
'-e', `end tell`,
|
|
129
|
-
'-e', `end tell`
|
|
130
|
-
]);
|
|
131
|
-
minimizeScript.on('error', () => {
|
|
132
|
-
// Silently ignore - window might already be hidden
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
catch (e) {
|
|
136
|
-
// Ignore errors, this is best-effort
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
console.log(`Chrome started with CDP on port ${cdpPort} (window is hidden off-screen)`);
|
|
140
|
-
return { cdpPort, chromeProcess };
|
|
141
|
-
// // Resolve @playwright/mcp package.json path
|
|
142
|
-
// const require = createRequire(import.meta.url)
|
|
143
|
-
// const mcpPackageJsonPath = require.resolve('@playwright/mcp/package.json')
|
|
144
|
-
// const mcpCliPath = path.resolve(mcpPackageJsonPath, '..', 'cli.js')
|
|
145
|
-
// console.log(`Found MCP CLI at: ${mcpCliPath}`)
|
|
146
|
-
// // Start MCP CLI process
|
|
147
|
-
// console.log('Starting MCP CLI...')
|
|
148
|
-
// const mcpProcess = spawn('node', [
|
|
149
|
-
// mcpCliPath,
|
|
150
|
-
// '--cdp-endpoint',
|
|
151
|
-
// `http://localhost:${cdpPort}`
|
|
152
|
-
// ], {
|
|
153
|
-
// stdio: 'inherit', // Forward all logs
|
|
154
|
-
// })
|
|
155
|
-
// mcpProcess.on('error', (error) => {
|
|
156
|
-
// console.error('Failed to start MCP CLI:', error)
|
|
157
|
-
// })
|
|
158
|
-
// mcpProcess.on('exit', (code, signal) => {
|
|
159
|
-
// console.log(`MCP CLI exited with code ${code} and signal ${signal}`)
|
|
160
|
-
// })
|
|
161
|
-
// // Handle cleanup
|
|
162
|
-
// const cleanup = async () => {
|
|
163
|
-
// console.log('Shutting down...')
|
|
164
|
-
// mcpProcess.kill()
|
|
165
|
-
// chromeProcess.kill()
|
|
166
|
-
// process.exit(0)
|
|
167
|
-
// }
|
|
168
|
-
// process.on('SIGINT', cleanup)
|
|
169
|
-
// process.on('SIGTERM', cleanup)
|
|
170
|
-
// return { chromeProcess, mcpProcess }
|
|
171
|
-
}
|
|
172
|
-
catch (error) {
|
|
173
|
-
console.error('Failed to start Playwriter:', error);
|
|
174
|
-
throw error;
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
//# sourceMappingURL=playwriter.js.map
|
package/dist/playwriter.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"playwriter.js","sourceRoot":"","sources":["../src/playwriter.ts"],"names":[],"mappings":"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"}
|
package/dist/profiles.d.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
/** Resolve Chrome's "User Data" root on the current OS. */
|
|
2
|
-
export declare function getChromeUserDataDir(): string;
|
|
3
|
-
/** Read and parse the top-level "Local State" JSON once. */
|
|
4
|
-
export declare function readLocalState(): any;
|
|
5
|
-
/** Map <profile-folder> → <signed-in email> (empty string if none). */
|
|
6
|
-
export declare function getProfileEmailMap(): Map<string, string>;
|
|
7
|
-
/** Return the full path to a profile directory for the given email. */
|
|
8
|
-
export declare function getProfilePathByEmail(email: string): string | null;
|
|
9
|
-
/** Get all available Chrome profiles with their paths and emails */
|
|
10
|
-
export declare function getAllProfiles(): Array<{
|
|
11
|
-
folder: string;
|
|
12
|
-
email: string;
|
|
13
|
-
path: string;
|
|
14
|
-
displayName: string;
|
|
15
|
-
}>;
|
|
16
|
-
//# sourceMappingURL=profiles.d.ts.map
|
package/dist/profiles.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"profiles.d.ts","sourceRoot":"","sources":["../src/profiles.ts"],"names":[],"mappings":"AAIA,2DAA2D;AAC3D,wBAAgB,oBAAoB,IAAI,MAAM,CAqB7C;AAED,4DAA4D;AAC5D,wBAAgB,cAAc,IAAI,GAAG,CAgBpC;AAED,uEAAuE;AACvE,wBAAgB,kBAAkB,IAAI,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CASxD;AAED,uEAAuE;AACvE,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAQlE;AAED,oEAAoE;AACpE,wBAAgB,cAAc,IAAI,KAAK,CAAC;IACpC,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,CAAA;CACtB,CAAC,CA8BD"}
|
package/dist/profiles.js
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import os from 'node:os';
|
|
4
|
-
/** Resolve Chrome's "User Data" root on the current OS. */
|
|
5
|
-
export function getChromeUserDataDir() {
|
|
6
|
-
const home = os.homedir();
|
|
7
|
-
switch (os.platform()) {
|
|
8
|
-
case 'win32':
|
|
9
|
-
return path.join(process.env.LOCALAPPDATA, 'Google', 'Chrome', 'User Data');
|
|
10
|
-
case 'darwin':
|
|
11
|
-
return path.join(home, 'Library', 'Application Support', 'Google', 'Chrome');
|
|
12
|
-
default: // linux, freebsd, …
|
|
13
|
-
return path.join(home, '.config', 'google-chrome');
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
/** Read and parse the top-level "Local State" JSON once. */
|
|
17
|
-
export function readLocalState() {
|
|
18
|
-
try {
|
|
19
|
-
const dir = getChromeUserDataDir();
|
|
20
|
-
const localStatePath = path.join(dir, 'Local State');
|
|
21
|
-
if (!fs.existsSync(localStatePath)) {
|
|
22
|
-
console.warn(`Chrome Local State file not found at: ${localStatePath}`);
|
|
23
|
-
return {};
|
|
24
|
-
}
|
|
25
|
-
const raw = fs.readFileSync(localStatePath, 'utf8');
|
|
26
|
-
return JSON.parse(raw);
|
|
27
|
-
}
|
|
28
|
-
catch (error) {
|
|
29
|
-
console.error('Failed to read Chrome Local State:', error);
|
|
30
|
-
return {};
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
/** Map <profile-folder> → <signed-in email> (empty string if none). */
|
|
34
|
-
export function getProfileEmailMap() {
|
|
35
|
-
const localState = readLocalState();
|
|
36
|
-
const infoCache = localState.profile?.info_cache ?? {};
|
|
37
|
-
return new Map(Object.entries(infoCache).map(([folder, obj]) => [
|
|
38
|
-
folder,
|
|
39
|
-
obj.user_name ?? '',
|
|
40
|
-
]));
|
|
41
|
-
}
|
|
42
|
-
/** Return the full path to a profile directory for the given email. */
|
|
43
|
-
export function getProfilePathByEmail(email) {
|
|
44
|
-
const root = getChromeUserDataDir();
|
|
45
|
-
for (const [folder, userEmail] of getProfileEmailMap()) {
|
|
46
|
-
if (userEmail.toLowerCase() === email.toLowerCase()) {
|
|
47
|
-
return path.join(root, folder); // e.g. "…/User Data/Profile 2"
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
return null; // not found
|
|
51
|
-
}
|
|
52
|
-
/** Get all available Chrome profiles with their paths and emails */
|
|
53
|
-
export function getAllProfiles() {
|
|
54
|
-
const root = getChromeUserDataDir();
|
|
55
|
-
const profiles = [];
|
|
56
|
-
for (const [folder, email] of getProfileEmailMap()) {
|
|
57
|
-
profiles.push({
|
|
58
|
-
folder,
|
|
59
|
-
email,
|
|
60
|
-
path: path.join(root, folder),
|
|
61
|
-
displayName: email || `${folder} (no email)`,
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
// Also include Default profile if it exists
|
|
65
|
-
const defaultPath = path.join(root, 'Default');
|
|
66
|
-
if (fs.existsSync(defaultPath) && !profiles.some(p => p.folder === 'Default')) {
|
|
67
|
-
profiles.unshift({
|
|
68
|
-
folder: 'Default',
|
|
69
|
-
email: '',
|
|
70
|
-
path: defaultPath,
|
|
71
|
-
displayName: 'Default (no email)',
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
return profiles;
|
|
75
|
-
}
|
|
76
|
-
//# sourceMappingURL=profiles.js.map
|
package/dist/profiles.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"profiles.js","sourceRoot":"","sources":["../src/profiles.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAA;AACxB,OAAO,IAAI,MAAM,WAAW,CAAA;AAC5B,OAAO,EAAE,MAAM,SAAS,CAAA;AAExB,2DAA2D;AAC3D,MAAM,UAAU,oBAAoB;IAChC,MAAM,IAAI,GAAG,EAAE,CAAC,OAAO,EAAE,CAAA;IACzB,QAAQ,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC;QACpB,KAAK,OAAO;YACR,OAAO,IAAI,CAAC,IAAI,CACZ,OAAO,CAAC,GAAG,CAAC,YAAa,EACzB,QAAQ,EACR,QAAQ,EACR,WAAW,CACd,CAAA;QACL,KAAK,QAAQ;YACT,OAAO,IAAI,CAAC,IAAI,CACZ,IAAI,EACJ,SAAS,EACT,qBAAqB,EACrB,QAAQ,EACR,QAAQ,CACX,CAAA;QACL,SAAS,oBAAoB;YACzB,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,eAAe,CAAC,CAAA;IAC1D,CAAC;AACL,CAAC;AAED,4DAA4D;AAC5D,MAAM,UAAU,cAAc;IAC1B,IAAI,CAAC;QACD,MAAM,GAAG,GAAG,oBAAoB,EAAE,CAAA;QAClC,MAAM,cAAc,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,aAAa,CAAC,CAAA;QAEpD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,cAAc,CAAC,EAAE,CAAC;YACjC,OAAO,CAAC,IAAI,CAAC,yCAAyC,cAAc,EAAE,CAAC,CAAA;YACvE,OAAO,EAAE,CAAA;QACb,CAAC;QAED,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,cAAc,EAAE,MAAM,CAAC,CAAA;QACnD,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IAC1B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,oCAAoC,EAAE,KAAK,CAAC,CAAA;QAC1D,OAAO,EAAE,CAAA;IACb,CAAC;AACL,CAAC;AAED,uEAAuE;AACvE,MAAM,UAAU,kBAAkB;IAC9B,MAAM,UAAU,GAAG,cAAc,EAAE,CAAA;IACnC,MAAM,SAAS,GAAG,UAAU,CAAC,OAAO,EAAE,UAAU,IAAI,EAAE,CAAA;IACtD,OAAO,IAAI,GAAG,CACV,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,GAAG,CAAgB,EAAE,EAAE,CAAC;QAC5D,MAAM;QACN,GAAG,CAAC,SAAS,IAAI,EAAE;KACtB,CAAC,CACL,CAAA;AACL,CAAC;AAED,uEAAuE;AACvE,MAAM,UAAU,qBAAqB,CAAC,KAAa;IAC/C,MAAM,IAAI,GAAG,oBAAoB,EAAE,CAAA;IACnC,KAAK,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,IAAI,kBAAkB,EAAE,EAAE,CAAC;QACrD,IAAI,SAAS,CAAC,WAAW,EAAE,KAAK,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;YAClD,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA,CAAC,+BAA+B;QAClE,CAAC;IACL,CAAC;IACD,OAAO,IAAI,CAAA,CAAC,YAAY;AAC5B,CAAC;AAED,oEAAoE;AACpE,MAAM,UAAU,cAAc;IAM1B,MAAM,IAAI,GAAG,oBAAoB,EAAE,CAAA;IACnC,MAAM,QAAQ,GAKT,EAAE,CAAA;IAEP,KAAK,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,kBAAkB,EAAE,EAAE,CAAC;QACjD,QAAQ,CAAC,IAAI,CAAC;YACV,MAAM;YACN,KAAK;YACL,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC;YAC7B,WAAW,EAAE,KAAK,IAAI,GAAG,MAAM,aAAa;SAC/C,CAAC,CAAA;IACN,CAAC;IAED,4CAA4C;IAC5C,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,CAAC,CAAA;IAC9C,IAAI,EAAE,CAAC,UAAU,CAAC,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,SAAS,CAAC,EAAE,CAAC;QAC5E,QAAQ,CAAC,OAAO,CAAC;YACb,MAAM,EAAE,SAAS;YACjB,KAAK,EAAE,EAAE;YACT,IAAI,EAAE,WAAW;YACjB,WAAW,EAAE,oBAAoB;SACpC,CAAC,CAAA;IACN,CAAC;IAED,OAAO,QAAQ,CAAA;AACnB,CAAC"}
|
package/dist/profiles.test.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"profiles.test.d.ts","sourceRoot":"","sources":["../src/profiles.test.ts"],"names":[],"mappings":""}
|
package/dist/profiles.test.js
DELETED
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect } from 'vitest';
|
|
2
|
-
import os from 'node:os';
|
|
3
|
-
import fs from 'node:fs';
|
|
4
|
-
import { getChromeUserDataDir, readLocalState, getProfileEmailMap, getProfilePathByEmail, getAllProfiles, } from './profiles.js';
|
|
5
|
-
describe('Chrome Profile Detection (Real System)', () => {
|
|
6
|
-
test('getChromeUserDataDir returns correct path for current OS', () => {
|
|
7
|
-
const chromeDir = getChromeUserDataDir();
|
|
8
|
-
expect(chromeDir).toMatchInlineSnapshot(`"/Users/morse/Library/Application Support/Google/Chrome"`);
|
|
9
|
-
});
|
|
10
|
-
test('readLocalState reads actual Chrome local state', () => {
|
|
11
|
-
const localState = readLocalState();
|
|
12
|
-
// Check if we have a valid object (might be empty if Chrome not installed)
|
|
13
|
-
expect(typeof localState).toMatchInlineSnapshot(`"object"`);
|
|
14
|
-
// If Chrome is installed, we should have some properties
|
|
15
|
-
if (Object.keys(localState).length > 0) {
|
|
16
|
-
expect(Object.keys(localState).sort()).toMatchInlineSnapshot(`
|
|
17
|
-
[
|
|
18
|
-
"accessibility",
|
|
19
|
-
"app_shims",
|
|
20
|
-
"autofill",
|
|
21
|
-
"background_tracing",
|
|
22
|
-
"breadcrumbs",
|
|
23
|
-
"browser",
|
|
24
|
-
"hardware_acceleration_mode_previous",
|
|
25
|
-
"invalidation",
|
|
26
|
-
"legacy",
|
|
27
|
-
"local",
|
|
28
|
-
"management",
|
|
29
|
-
"network_time",
|
|
30
|
-
"optimization_guide",
|
|
31
|
-
"password_manager",
|
|
32
|
-
"performance_intervention",
|
|
33
|
-
"performance_tuning",
|
|
34
|
-
"policy",
|
|
35
|
-
"privacy_budget",
|
|
36
|
-
"profile",
|
|
37
|
-
"profile_network_context_service",
|
|
38
|
-
"profiles",
|
|
39
|
-
"segmentation_platform",
|
|
40
|
-
"session_id_generator_last_value",
|
|
41
|
-
"signin",
|
|
42
|
-
"subresource_filter",
|
|
43
|
-
"tab_stats",
|
|
44
|
-
"tpcd",
|
|
45
|
-
"ukm",
|
|
46
|
-
"uninstall_metrics",
|
|
47
|
-
"updateclientdata",
|
|
48
|
-
"updateclientlastupdatecheckerror",
|
|
49
|
-
"updateclientlastupdatecheckerrorcategory",
|
|
50
|
-
"updateclientlastupdatecheckerrorextracode1",
|
|
51
|
-
"user_experience_metrics",
|
|
52
|
-
"variations_compressed_seed",
|
|
53
|
-
"variations_country",
|
|
54
|
-
"variations_crash_streak",
|
|
55
|
-
"variations_failed_to_fetch_seed_streak",
|
|
56
|
-
"variations_google_groups",
|
|
57
|
-
"variations_last_fetch_time",
|
|
58
|
-
"variations_limited_entropy_synthetic_trial_seed",
|
|
59
|
-
"variations_limited_entropy_synthetic_trial_seed_v2",
|
|
60
|
-
"variations_permanent_consistency_country",
|
|
61
|
-
"variations_safe_compressed_seed",
|
|
62
|
-
"variations_safe_seed_date",
|
|
63
|
-
"variations_safe_seed_fetch_time",
|
|
64
|
-
"variations_safe_seed_locale",
|
|
65
|
-
"variations_safe_seed_milestone",
|
|
66
|
-
"variations_safe_seed_permanent_consistency_country",
|
|
67
|
-
"variations_safe_seed_session_consistency_country",
|
|
68
|
-
"variations_safe_seed_signature",
|
|
69
|
-
"variations_seed_date",
|
|
70
|
-
"variations_seed_milestone",
|
|
71
|
-
"variations_seed_signature",
|
|
72
|
-
"was",
|
|
73
|
-
]
|
|
74
|
-
`);
|
|
75
|
-
}
|
|
76
|
-
});
|
|
77
|
-
test('getProfileEmailMap returns actual Chrome profiles', () => {
|
|
78
|
-
const emailMap = getProfileEmailMap();
|
|
79
|
-
// Convert to array for snapshot
|
|
80
|
-
const profiles = Array.from(emailMap.entries()).map(([folder, email]) => ({
|
|
81
|
-
folder,
|
|
82
|
-
hasEmail: email.length > 0,
|
|
83
|
-
}));
|
|
84
|
-
expect(profiles).toMatchInlineSnapshot(`
|
|
85
|
-
[
|
|
86
|
-
{
|
|
87
|
-
"folder": "Default",
|
|
88
|
-
"hasEmail": true,
|
|
89
|
-
},
|
|
90
|
-
{
|
|
91
|
-
"folder": "Profile 0",
|
|
92
|
-
"hasEmail": false,
|
|
93
|
-
},
|
|
94
|
-
{
|
|
95
|
-
"folder": "Profile 2",
|
|
96
|
-
"hasEmail": true,
|
|
97
|
-
},
|
|
98
|
-
{
|
|
99
|
-
"folder": "Profile 3",
|
|
100
|
-
"hasEmail": true,
|
|
101
|
-
},
|
|
102
|
-
]
|
|
103
|
-
`);
|
|
104
|
-
});
|
|
105
|
-
test('getProfilePathByEmail works with real profiles', () => {
|
|
106
|
-
const emailMap = getProfileEmailMap();
|
|
107
|
-
// Test with first email found (if any)
|
|
108
|
-
const firstEmail = Array.from(emailMap.values()).find(email => email.length > 0);
|
|
109
|
-
if (firstEmail) {
|
|
110
|
-
const profilePath = getProfilePathByEmail(firstEmail);
|
|
111
|
-
expect(profilePath).toBeTruthy();
|
|
112
|
-
expect(fs.existsSync(profilePath)).toMatchInlineSnapshot(`true`);
|
|
113
|
-
}
|
|
114
|
-
else {
|
|
115
|
-
// No emails found
|
|
116
|
-
expect(getProfilePathByEmail('test@example.com')).toMatchInlineSnapshot();
|
|
117
|
-
}
|
|
118
|
-
});
|
|
119
|
-
test('getAllProfiles returns all actual Chrome profiles', () => {
|
|
120
|
-
const profiles = getAllProfiles();
|
|
121
|
-
// Sanitize paths for snapshot (replace home directory)
|
|
122
|
-
const home = os.homedir();
|
|
123
|
-
const sanitizedProfiles = profiles.map(profile => ({
|
|
124
|
-
folder: profile.folder,
|
|
125
|
-
hasEmail: profile.email.length > 0,
|
|
126
|
-
displayName: profile.displayName,
|
|
127
|
-
pathExists: fs.existsSync(profile.path),
|
|
128
|
-
relativePath: profile.path.replace(home, '~'),
|
|
129
|
-
}));
|
|
130
|
-
expect(sanitizedProfiles).toMatchInlineSnapshot(`
|
|
131
|
-
[
|
|
132
|
-
{
|
|
133
|
-
"displayName": "t.de.rossi.01@gmail.com",
|
|
134
|
-
"folder": "Default",
|
|
135
|
-
"hasEmail": true,
|
|
136
|
-
"pathExists": true,
|
|
137
|
-
"relativePath": "~/Library/Application Support/Google/Chrome/Default",
|
|
138
|
-
},
|
|
139
|
-
{
|
|
140
|
-
"displayName": "Profile 0 (no email)",
|
|
141
|
-
"folder": "Profile 0",
|
|
142
|
-
"hasEmail": false,
|
|
143
|
-
"pathExists": true,
|
|
144
|
-
"relativePath": "~/Library/Application Support/Google/Chrome/Profile 0",
|
|
145
|
-
},
|
|
146
|
-
{
|
|
147
|
-
"displayName": "daer.tommy@gmail.com",
|
|
148
|
-
"folder": "Profile 2",
|
|
149
|
-
"hasEmail": true,
|
|
150
|
-
"pathExists": true,
|
|
151
|
-
"relativePath": "~/Library/Application Support/Google/Chrome/Profile 2",
|
|
152
|
-
},
|
|
153
|
-
{
|
|
154
|
-
"displayName": "tommy@holocron.so",
|
|
155
|
-
"folder": "Profile 3",
|
|
156
|
-
"hasEmail": true,
|
|
157
|
-
"pathExists": true,
|
|
158
|
-
"relativePath": "~/Library/Application Support/Google/Chrome/Profile 3",
|
|
159
|
-
},
|
|
160
|
-
]
|
|
161
|
-
`);
|
|
162
|
-
});
|
|
163
|
-
test('Chrome user data directory exists on system', () => {
|
|
164
|
-
const chromeDir = getChromeUserDataDir();
|
|
165
|
-
const exists = fs.existsSync(chromeDir);
|
|
166
|
-
expect(exists).toMatchInlineSnapshot(`true`);
|
|
167
|
-
});
|
|
168
|
-
});
|
|
169
|
-
//# sourceMappingURL=profiles.test.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"profiles.test.js","sourceRoot":"","sources":["../src/profiles.test.ts"],"names":[],"mappings":"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"}
|