playwriter 0.0.103 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bippy.js +1 -1
- package/dist/cdp-relay.d.ts.map +1 -1
- package/dist/cdp-relay.js +5 -0
- package/dist/cdp-relay.js.map +1 -1
- package/dist/cli-help.test.d.ts +2 -0
- package/dist/cli-help.test.d.ts.map +1 -0
- package/dist/cli-help.test.js +31 -0
- package/dist/cli-help.test.js.map +1 -0
- package/dist/cli.js +13 -5
- package/dist/cli.js.map +1 -1
- package/dist/executor.d.ts +2 -1
- package/dist/executor.d.ts.map +1 -1
- package/dist/executor.js +32 -22
- package/dist/executor.js.map +1 -1
- package/dist/extension/background.js +516 -22
- package/dist/extension/manifest.json +4 -2
- package/dist/extension-connection.test.d.ts.map +1 -1
- package/dist/extension-connection.test.js +79 -0
- package/dist/extension-connection.test.js.map +1 -1
- package/dist/ghost-cursor-client.js +170 -83
- package/dist/{recording-ghost-cursor.d.ts → ghost-cursor-controller.d.ts} +15 -10
- package/dist/ghost-cursor-controller.d.ts.map +1 -0
- package/dist/ghost-cursor-controller.js +98 -0
- package/dist/ghost-cursor-controller.js.map +1 -0
- package/dist/ghost-cursor.d.ts.map +1 -1
- package/dist/ghost-cursor.js +42 -26
- package/dist/ghost-cursor.js.map +1 -1
- package/dist/on-mouse-action.test.js +25 -0
- package/dist/on-mouse-action.test.js.map +1 -1
- package/dist/popup-relocation.test.d.ts +7 -0
- package/dist/popup-relocation.test.d.ts.map +1 -0
- package/dist/popup-relocation.test.js +139 -0
- package/dist/popup-relocation.test.js.map +1 -0
- package/dist/prompt.md +13 -12
- package/dist/readability.js +1 -1
- package/dist/relay-core.test.d.ts.map +1 -1
- package/dist/relay-core.test.js +101 -1
- package/dist/relay-core.test.js.map +1 -1
- package/dist/relay-state.d.ts +1 -0
- package/dist/relay-state.d.ts.map +1 -1
- package/dist/relay-state.js.map +1 -1
- package/dist/screen-recording.d.ts +2 -2
- package/dist/screen-recording.d.ts.map +1 -1
- package/dist/screen-recording.js +0 -3
- package/dist/screen-recording.js.map +1 -1
- package/dist/selector-generator.js +1 -1
- package/package.json +6 -6
- package/src/aria-snapshots/github-interactive.txt +5 -3
- package/src/aria-snapshots/github-raw.txt +8 -5
- package/src/aria-snapshots/hackernews-interactive.txt +241 -242
- package/src/aria-snapshots/hackernews-raw.txt +267 -268
- package/src/aria-snapshots/prosemirror-interactive.txt +3 -1
- package/src/aria-snapshots/prosemirror-raw.txt +4 -1
- package/src/assets/aria-labels-hacker-news.png +0 -0
- package/src/assets/aria-labels-old-reddit.png +0 -0
- package/src/cdp-relay.ts +5 -0
- package/src/cli-help.test.ts +41 -0
- package/src/cli.ts +14 -9
- package/src/executor.ts +33 -22
- package/src/extension-connection.test.ts +88 -0
- package/src/ghost-cursor-client.ts +221 -96
- package/src/{recording-ghost-cursor.ts → ghost-cursor-controller.ts} +50 -34
- package/src/ghost-cursor.ts +54 -41
- package/src/on-mouse-action.test.ts +30 -0
- package/src/popup-relocation.test.ts +163 -0
- package/src/relay-core.test.ts +117 -0
- package/src/relay-state.ts +1 -1
- package/src/screen-recording.ts +3 -6
- package/src/skill.md +13 -12
- package/src/snapshots/shadcn-ui-accessibility-full.md +174 -181
- package/src/snapshots/shadcn-ui-accessibility-interactive.md +6 -14
- package/dist/recording-ghost-cursor.d.ts.map +0 -1
- package/dist/recording-ghost-cursor.js +0 -79
- package/dist/recording-ghost-cursor.js.map +0 -1
|
@@ -60,4 +60,6 @@
|
|
|
60
60
|
- role=link[name="Backers"]
|
|
61
61
|
- role=link[name="Code of Conduct"]
|
|
62
62
|
- role=link[name="Discuss"] >> nth=1
|
|
63
|
-
- role=link[name="Report an Issue"]
|
|
63
|
+
- role=link[name="Report an Issue"]
|
|
64
|
+
- role=button[name="Pin element — click any element to copy its Playwriter reference"]
|
|
65
|
+
- role=button[name="Close Playwriter toolbar"]
|
|
@@ -138,4 +138,7 @@
|
|
|
138
138
|
- role=link[name="Backers"]
|
|
139
139
|
- role=link[name="Code of Conduct"]
|
|
140
140
|
- role=link[name="Discuss"] >> nth=1
|
|
141
|
-
- role=link[name="Report an Issue"]
|
|
141
|
+
- role=link[name="Report an Issue"]
|
|
142
|
+
- toolbar "Playwriter tools":
|
|
143
|
+
- role=button[name="Pin element — click any element to copy its Playwriter reference"]
|
|
144
|
+
- role=button[name="Close Playwriter toolbar"]
|
|
Binary file
|
|
Binary file
|
package/src/cdp-relay.ts
CHANGED
|
@@ -163,6 +163,9 @@ export async function startPlayWriterCDPRelayServer({
|
|
|
163
163
|
if (info.email) {
|
|
164
164
|
return `email:${info.email}`
|
|
165
165
|
}
|
|
166
|
+
if (info.installId) {
|
|
167
|
+
return `install:${info.browser || 'unknown'}:${info.installId}`
|
|
168
|
+
}
|
|
166
169
|
if (info.browser) {
|
|
167
170
|
return `browser:${info.browser}`
|
|
168
171
|
}
|
|
@@ -1264,11 +1267,13 @@ export async function startPlayWriterCDPRelayServer({
|
|
|
1264
1267
|
const browser = c.req.query('browser')
|
|
1265
1268
|
const email = c.req.query('email')
|
|
1266
1269
|
const id = c.req.query('id')
|
|
1270
|
+
const installId = c.req.query('installId')
|
|
1267
1271
|
const version = c.req.query('v')
|
|
1268
1272
|
return {
|
|
1269
1273
|
browser: browser || undefined,
|
|
1270
1274
|
email: email || undefined,
|
|
1271
1275
|
id: id || undefined,
|
|
1276
|
+
installId: installId || undefined,
|
|
1272
1277
|
version: version || undefined,
|
|
1273
1278
|
}
|
|
1274
1279
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// Verifies CLI help stays runnable without loading browser-start-only dependencies.
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import { fileURLToPath } from 'node:url'
|
|
4
|
+
import { execFile } from 'node:child_process'
|
|
5
|
+
import { promisify } from 'node:util'
|
|
6
|
+
import { describe, expect, test } from 'vitest'
|
|
7
|
+
|
|
8
|
+
const execFileAsync = promisify(execFile)
|
|
9
|
+
const currentDir = path.dirname(fileURLToPath(import.meta.url))
|
|
10
|
+
const playwriterDir = path.resolve(currentDir, '..')
|
|
11
|
+
const viteNodeBinary = path.join(
|
|
12
|
+
playwriterDir,
|
|
13
|
+
'node_modules',
|
|
14
|
+
'.bin',
|
|
15
|
+
process.platform === 'win32' ? 'vite-node.cmd' : 'vite-node',
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
async function runCli(args: string[]): Promise<{ stdout: string; stderr: string }> {
|
|
19
|
+
return execFileAsync(viteNodeBinary, ['src/cli.ts', ...args], {
|
|
20
|
+
cwd: playwriterDir,
|
|
21
|
+
env: process.env,
|
|
22
|
+
})
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
describe('playwriter cli help', () => {
|
|
26
|
+
test('renders root help without crashing', async () => {
|
|
27
|
+
const { stdout, stderr } = await runCli(['--help'])
|
|
28
|
+
|
|
29
|
+
expect(stdout).toContain('playwriter')
|
|
30
|
+
expect(stdout).toContain('serve')
|
|
31
|
+
expect(stderr).toBe('')
|
|
32
|
+
}, 30000)
|
|
33
|
+
|
|
34
|
+
test('renders serve help without crashing', async () => {
|
|
35
|
+
const { stdout, stderr } = await runCli(['serve', '--help'])
|
|
36
|
+
|
|
37
|
+
expect(stdout).toContain('Start the relay server on this machine')
|
|
38
|
+
expect(stdout).toContain('--replace')
|
|
39
|
+
expect(stderr).toBe('')
|
|
40
|
+
}, 30000)
|
|
41
|
+
})
|
package/src/cli.ts
CHANGED
|
@@ -7,13 +7,6 @@ import { fileURLToPath } from 'node:url'
|
|
|
7
7
|
import { goke } from 'goke'
|
|
8
8
|
import { z } from 'zod'
|
|
9
9
|
import pc from 'picocolors'
|
|
10
|
-
import {
|
|
11
|
-
getBrowserLaunchArgs,
|
|
12
|
-
getDefaultBrowserUserDataDir,
|
|
13
|
-
startBrowserProcess,
|
|
14
|
-
} from './browser-launch.js'
|
|
15
|
-
import { resolveBrowserExecutablePath, shouldUseHeadlessByDefault } from './browser-config.js'
|
|
16
|
-
import { getBundledExtensionPath } from './package-paths.js'
|
|
17
10
|
|
|
18
11
|
// Prevent Buffers from dumping hex bytes in util.inspect output.
|
|
19
12
|
Buffer.prototype[util.inspect.custom] = function () {
|
|
@@ -52,6 +45,14 @@ cli
|
|
|
52
45
|
}
|
|
53
46
|
|
|
54
47
|
try {
|
|
48
|
+
// Avoid loading playwright-core during generic CLI startup/help. This command
|
|
49
|
+
// is the only path that needs browser discovery and bundled extension launch.
|
|
50
|
+
const [{ getBrowserLaunchArgs, getDefaultBrowserUserDataDir, startBrowserProcess }, { resolveBrowserExecutablePath, shouldUseHeadlessByDefault }, { getBundledExtensionPath }] = await Promise.all([
|
|
51
|
+
import('./browser-launch.js'),
|
|
52
|
+
import('./browser-config.js'),
|
|
53
|
+
import('./package-paths.js'),
|
|
54
|
+
])
|
|
55
|
+
|
|
55
56
|
await ensureRelayServer({ logger: console, env: cliRelayEnv })
|
|
56
57
|
|
|
57
58
|
const browserPath = resolveBrowserExecutablePath({ browserPath: binaryPath })
|
|
@@ -324,7 +325,11 @@ cli
|
|
|
324
325
|
.option('--direct [endpoint]', 'Use direct CDP connection without the extension. Enable debugging first at chrome://inspect/#remote-debugging or launch Chrome with --remote-debugging-port=9222. Auto-discovers instances or accepts an explicit ws:// endpoint')
|
|
325
326
|
.action(async (options) => {
|
|
326
327
|
const isLocal = !options.host && !process.env.PLAYWRITER_HOST
|
|
327
|
-
|
|
328
|
+
// goke 6.6: optional-value flags are string | undefined
|
|
329
|
+
// `--direct ws://...` → 'ws://...' (explicit endpoint)
|
|
330
|
+
// `--direct` → '' (bare flag, auto-discover)
|
|
331
|
+
// (omitted) → undefined (don't use direct CDP)
|
|
332
|
+
const directEndpoint = options.direct || null
|
|
328
333
|
|
|
329
334
|
// If --direct with explicit endpoint, resolve it (handles host:port → ws://) then skip discovery
|
|
330
335
|
if (directEndpoint) {
|
|
@@ -344,7 +349,7 @@ cli
|
|
|
344
349
|
}
|
|
345
350
|
|
|
346
351
|
// If --direct with no endpoint, discover Chrome instances
|
|
347
|
-
if (options.direct ===
|
|
352
|
+
if (options.direct === '') {
|
|
348
353
|
if (!isLocal) {
|
|
349
354
|
console.error('Error: --direct auto-discovery only works locally.')
|
|
350
355
|
console.error('For remote relay, pass an explicit endpoint reachable from the relay host:')
|
package/src/executor.ts
CHANGED
|
@@ -37,7 +37,7 @@ import { getPageMarkdown, type GetPageMarkdownOptions } from './page-markdown.js
|
|
|
37
37
|
import { createRecordingApi } from './screen-recording.js'
|
|
38
38
|
import { createDemoVideo } from './ffmpeg.js'
|
|
39
39
|
import { type GhostCursorClientOptions } from './ghost-cursor.js'
|
|
40
|
-
import {
|
|
40
|
+
import { GhostCursorController } from './ghost-cursor-controller.js'
|
|
41
41
|
|
|
42
42
|
const __filename = fileURLToPath(import.meta.url)
|
|
43
43
|
const __dirname = path.dirname(__filename)
|
|
@@ -312,6 +312,8 @@ export class PlaywrightExecutor {
|
|
|
312
312
|
private sessionCwd: string | null
|
|
313
313
|
private hasWarnedExtensionOutdated = false
|
|
314
314
|
|
|
315
|
+
private ghostCursorController: GhostCursorController
|
|
316
|
+
|
|
315
317
|
constructor(options: ExecutorOptions) {
|
|
316
318
|
this.cdpConfig = options.cdpConfig
|
|
317
319
|
this.logger = options.logger || { log: console.log, error: console.error }
|
|
@@ -323,6 +325,13 @@ export class PlaywrightExecutor {
|
|
|
323
325
|
this.sessionCwd || undefined,
|
|
324
326
|
)
|
|
325
327
|
this.sandboxedRequire = this.createSandboxedRequire(require)
|
|
328
|
+
this.ghostCursorController = new GhostCursorController({
|
|
329
|
+
logger: {
|
|
330
|
+
error: (...args: unknown[]) => {
|
|
331
|
+
this.logger.error(...args)
|
|
332
|
+
},
|
|
333
|
+
},
|
|
334
|
+
})
|
|
326
335
|
}
|
|
327
336
|
|
|
328
337
|
private createSandboxedRequire(originalRequire: NodeRequire): NodeRequire {
|
|
@@ -422,6 +431,9 @@ export class PlaywrightExecutor {
|
|
|
422
431
|
const warning = getExtensionOutdatedWarning(playwriterVersion)
|
|
423
432
|
if (warning) {
|
|
424
433
|
this.logger.log(warning)
|
|
434
|
+
// Enqueue so MCP agents see version-skew messages in their next execute
|
|
435
|
+
// response — logger.log alone only reaches stdout, not the LLM.
|
|
436
|
+
this.enqueueWarning(warning)
|
|
425
437
|
this.hasWarnedExtensionOutdated = true
|
|
426
438
|
}
|
|
427
439
|
}
|
|
@@ -433,7 +445,11 @@ export class PlaywrightExecutor {
|
|
|
433
445
|
this.pagesWithListeners.add(page)
|
|
434
446
|
this.setupPageCloseDetection(page)
|
|
435
447
|
this.setupPageConsoleListener(page)
|
|
436
|
-
this.
|
|
448
|
+
this.setupNewPageLogging(page)
|
|
449
|
+
this.ghostCursorController.attachToPage({ page })
|
|
450
|
+
page.on('close', () => {
|
|
451
|
+
this.ghostCursorController.detachFromPage({ page })
|
|
452
|
+
})
|
|
437
453
|
}
|
|
438
454
|
|
|
439
455
|
private setupPageCloseDetection(page: Page) {
|
|
@@ -495,20 +511,21 @@ export class PlaywrightExecutor {
|
|
|
495
511
|
})
|
|
496
512
|
}
|
|
497
513
|
|
|
498
|
-
private
|
|
499
|
-
//
|
|
500
|
-
//
|
|
501
|
-
//
|
|
514
|
+
private setupNewPageLogging(page: Page) {
|
|
515
|
+
// page.on('popup') fires for window.open, target=_blank, and cmd+click
|
|
516
|
+
// (but not context.newPage() or CDP reconnection). The extension
|
|
517
|
+
// auto-relocates popups to tabs, so these pages are controllable via
|
|
518
|
+
// context.pages(). Enqueue synchronously so the warning lands in the
|
|
519
|
+
// enclosing execute() call's scope. initialUrl may be 'about:blank'
|
|
520
|
+
// for blank-then-scripted popups.
|
|
502
521
|
page.on('popup', (popup) => {
|
|
503
|
-
const
|
|
504
|
-
const pages = context.pages()
|
|
522
|
+
const pages = popup.context().pages()
|
|
505
523
|
const rawIndex = pages.indexOf(popup)
|
|
506
524
|
const pageIndex = rawIndex >= 0 ? String(rawIndex) : 'unknown'
|
|
507
|
-
const
|
|
525
|
+
const initialUrl = popup.url() || 'about:blank'
|
|
508
526
|
this.enqueueWarning(
|
|
509
|
-
`
|
|
510
|
-
`
|
|
511
|
-
`Repeat the interaction in a way that does not open a popup, or navigate to the URL directly in a new tab.`,
|
|
527
|
+
`New page opened from current page (index ${pageIndex}, initial url: ${initialUrl}). ` +
|
|
528
|
+
`Access it via context.pages()[${pageIndex}] to interact with it.`,
|
|
512
529
|
)
|
|
513
530
|
})
|
|
514
531
|
}
|
|
@@ -1057,13 +1074,7 @@ export class PlaywrightExecutor {
|
|
|
1057
1074
|
// This permission is granted when the user clicks the Playwriter extension icon on a tab.
|
|
1058
1075
|
const relayPort = this.cdpConfig.port || 19988
|
|
1059
1076
|
const self = this
|
|
1060
|
-
const
|
|
1061
|
-
logger: {
|
|
1062
|
-
error: (...args: unknown[]) => {
|
|
1063
|
-
self.logger.error(...args)
|
|
1064
|
-
},
|
|
1065
|
-
},
|
|
1066
|
-
})
|
|
1077
|
+
const ghostCursorController = this.ghostCursorController
|
|
1067
1078
|
|
|
1068
1079
|
const showGhostCursor = async (options?: ({ page?: Page } & GhostCursorClientOptions)) => {
|
|
1069
1080
|
const targetPage = options?.page || page
|
|
@@ -1076,19 +1087,19 @@ export class PlaywrightExecutor {
|
|
|
1076
1087
|
return rest
|
|
1077
1088
|
})()
|
|
1078
1089
|
|
|
1079
|
-
await
|
|
1090
|
+
await ghostCursorController.show({ page: targetPage, cursorOptions })
|
|
1080
1091
|
}
|
|
1081
1092
|
|
|
1082
1093
|
const hideGhostCursor = async (options?: { page?: Page }) => {
|
|
1083
1094
|
const targetPage = options?.page || page
|
|
1084
|
-
await
|
|
1095
|
+
await ghostCursorController.hide({ page: targetPage })
|
|
1085
1096
|
}
|
|
1086
1097
|
|
|
1087
1098
|
const recordingApi = createRecordingApi({
|
|
1088
1099
|
context,
|
|
1089
1100
|
defaultPage: page,
|
|
1090
1101
|
relayPort,
|
|
1091
|
-
ghostCursorController
|
|
1102
|
+
ghostCursorController,
|
|
1092
1103
|
onStart: () => {
|
|
1093
1104
|
self.recordingStartedAt = Date.now()
|
|
1094
1105
|
self.executionTimestamps = []
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import os from 'node:os'
|
|
3
|
+
import path from 'node:path'
|
|
1
4
|
import { createMCPClient } from './mcp-client.js'
|
|
2
5
|
import { describe, it, expect, beforeAll, afterAll } from 'vitest'
|
|
3
6
|
import { chromium } from '@xmorse/playwright-core'
|
|
4
7
|
import { getCdpUrl } from './utils.js'
|
|
5
8
|
import { setupTestContext, cleanupTestContext, getExtensionServiceWorker, type TestContext, js } from './test-utils.js'
|
|
9
|
+
import { getExtensionsStatus } from './relay-client.js'
|
|
6
10
|
import './test-declarations.js'
|
|
7
11
|
|
|
8
12
|
const TEST_PORT = 19990
|
|
@@ -660,6 +664,90 @@ describe('Extension Connection Tests', () => {
|
|
|
660
664
|
await page.goto('about:blank')
|
|
661
665
|
})
|
|
662
666
|
|
|
667
|
+
it('should keep an active browser connected when another Chromium context starts', async () => {
|
|
668
|
+
const browserContext = getBrowserContext()
|
|
669
|
+
const serviceWorker = await getExtensionServiceWorker(browserContext)
|
|
670
|
+
|
|
671
|
+
await serviceWorker.evaluate(async () => {
|
|
672
|
+
await globalThis.disconnectEverything()
|
|
673
|
+
})
|
|
674
|
+
|
|
675
|
+
const page = await browserContext.newPage()
|
|
676
|
+
const targetUrl = 'https://example.com/multi-context-stability'
|
|
677
|
+
await page.goto(targetUrl)
|
|
678
|
+
await page.waitForLoadState('domcontentloaded')
|
|
679
|
+
await page.bringToFront()
|
|
680
|
+
|
|
681
|
+
const enableResult = await serviceWorker.evaluate(async () => {
|
|
682
|
+
return await globalThis.toggleExtensionForActiveTab()
|
|
683
|
+
})
|
|
684
|
+
expect(enableResult.isConnected).toBe(true)
|
|
685
|
+
|
|
686
|
+
const secondUserDataDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pw-conn-second-'))
|
|
687
|
+
const extensionPath = path.resolve(process.cwd(), '../extension', `dist-${TEST_PORT}`)
|
|
688
|
+
const secondContext = await chromium.launchPersistentContext(secondUserDataDir, {
|
|
689
|
+
channel: 'chromium',
|
|
690
|
+
headless: !process.env.HEADFUL,
|
|
691
|
+
colorScheme: 'dark',
|
|
692
|
+
args: [`--disable-extensions-except=${extensionPath}`, `--load-extension=${extensionPath}`],
|
|
693
|
+
})
|
|
694
|
+
|
|
695
|
+
try {
|
|
696
|
+
await getExtensionServiceWorker(secondContext)
|
|
697
|
+
|
|
698
|
+
const statusSnapshots: Array<{ keys: string[]; activeTargets: number[] }> = []
|
|
699
|
+
for (let i = 0; i < 4; i++) {
|
|
700
|
+
await new Promise((resolve) => {
|
|
701
|
+
setTimeout(resolve, 1000)
|
|
702
|
+
})
|
|
703
|
+
const statuses = await getExtensionsStatus(TEST_PORT)
|
|
704
|
+
statusSnapshots.push({
|
|
705
|
+
keys: statuses.map((status) => {
|
|
706
|
+
return status.stableKey || status.extensionId
|
|
707
|
+
}),
|
|
708
|
+
activeTargets: statuses.map((status) => {
|
|
709
|
+
return status.activeTargets
|
|
710
|
+
}),
|
|
711
|
+
})
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
expect(statusSnapshots.every((snapshot) => {
|
|
715
|
+
return snapshot.keys.length >= 2
|
|
716
|
+
})).toBe(true)
|
|
717
|
+
expect(statusSnapshots.every((snapshot) => {
|
|
718
|
+
return new Set(snapshot.keys).size === snapshot.keys.length
|
|
719
|
+
})).toBe(true)
|
|
720
|
+
expect(statusSnapshots.every((snapshot) => {
|
|
721
|
+
return snapshot.activeTargets.some((count) => count > 0)
|
|
722
|
+
})).toBe(true)
|
|
723
|
+
|
|
724
|
+
const executeResult = await client.callTool({
|
|
725
|
+
name: 'execute',
|
|
726
|
+
arguments: {
|
|
727
|
+
code: js`
|
|
728
|
+
const pages = context.pages();
|
|
729
|
+
const testPage = pages.find((p) => p.url().includes('multi-context-stability'));
|
|
730
|
+
return { pagesCount: pages.length, found: !!testPage, url: testPage?.url() };
|
|
731
|
+
`,
|
|
732
|
+
},
|
|
733
|
+
})
|
|
734
|
+
|
|
735
|
+
const executeOutput = (executeResult as any).content[0].text
|
|
736
|
+
expect(executeOutput).toContain('found: true')
|
|
737
|
+
expect(executeOutput).toContain(targetUrl)
|
|
738
|
+
expect((executeResult as any).isError).not.toBe(true)
|
|
739
|
+
} finally {
|
|
740
|
+
await secondContext.close()
|
|
741
|
+
fs.rmSync(secondUserDataDir, { recursive: true, force: true })
|
|
742
|
+
if (!page.isClosed()) {
|
|
743
|
+
await page.close()
|
|
744
|
+
}
|
|
745
|
+
await serviceWorker.evaluate(async () => {
|
|
746
|
+
await globalThis.disconnectEverything()
|
|
747
|
+
})
|
|
748
|
+
}
|
|
749
|
+
}, 120000)
|
|
750
|
+
|
|
663
751
|
it('should maintain correct page.url() with service worker pages', async () => {
|
|
664
752
|
const browserContext = getBrowserContext()
|
|
665
753
|
const serviceWorker = await getExtensionServiceWorker(browserContext)
|