playwriter 0.0.63 → 0.0.89
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/a11y-client.js +18 -8
- package/dist/aria-snapshot.d.ts +41 -3
- package/dist/aria-snapshot.d.ts.map +1 -1
- package/dist/aria-snapshot.js +134 -55
- package/dist/aria-snapshot.js.map +1 -1
- package/dist/aria-snapshot.test.js +5 -2
- package/dist/aria-snapshot.test.js.map +1 -1
- package/dist/aria-snapshot.unit.test.js +83 -41
- package/dist/aria-snapshot.unit.test.js.map +1 -1
- package/dist/assets/cursors/screen-studio/pointer-macos-tahoe-data-url.d.ts +5 -0
- package/dist/assets/cursors/screen-studio/pointer-macos-tahoe-data-url.d.ts.map +1 -0
- package/dist/assets/cursors/screen-studio/pointer-macos-tahoe-data-url.js +5 -0
- package/dist/assets/cursors/screen-studio/pointer-macos-tahoe-data-url.js.map +1 -0
- package/dist/bippy.js +1 -1
- package/dist/cdp-log.d.ts +1 -1
- package/dist/cdp-log.d.ts.map +1 -1
- package/dist/cdp-log.js +1 -1
- package/dist/cdp-log.js.map +1 -1
- package/dist/cdp-relay.d.ts.map +1 -1
- package/dist/cdp-relay.js +492 -298
- package/dist/cdp-relay.js.map +1 -1
- package/dist/cdp-session.d.ts.map +1 -1
- package/dist/cdp-session.js.map +1 -1
- package/dist/cdp-types.d.ts.map +1 -1
- package/dist/cdp-types.js +7 -7
- package/dist/cdp-types.js.map +1 -1
- package/dist/clean-html.d.ts.map +1 -1
- package/dist/clean-html.js +4 -5
- package/dist/clean-html.js.map +1 -1
- package/dist/cli.js +45 -27
- package/dist/cli.js.map +1 -1
- package/dist/create-logger.d.ts.map +1 -1
- package/dist/create-logger.js +3 -1
- package/dist/create-logger.js.map +1 -1
- package/dist/debugger-examples-types.d.ts.map +1 -1
- package/dist/debugger.d.ts.map +1 -1
- package/dist/debugger.js +1 -3
- package/dist/debugger.js.map +1 -1
- package/dist/diff-utils.d.ts.map +1 -1
- package/dist/diff-utils.js +1 -4
- package/dist/diff-utils.js.map +1 -1
- package/dist/editor-api.md +12 -2
- package/dist/editor-examples.d.ts +1 -1
- package/dist/editor-examples.d.ts.map +1 -1
- package/dist/editor-examples.js +1 -1
- package/dist/editor-examples.js.map +1 -1
- package/dist/editor.d.ts +1 -1
- package/dist/editor.d.ts.map +1 -1
- package/dist/editor.js +1 -1
- package/dist/editor.js.map +1 -1
- package/dist/executor.d.ts +26 -3
- package/dist/executor.d.ts.map +1 -1
- package/dist/executor.js +297 -64
- package/dist/executor.js.map +1 -1
- package/dist/executor.unit.test.js +38 -1
- package/dist/executor.unit.test.js.map +1 -1
- package/dist/extension-connection.test.js +139 -36
- package/dist/extension-connection.test.js.map +1 -1
- package/dist/ffmpeg.d.ts +148 -0
- package/dist/ffmpeg.d.ts.map +1 -0
- package/dist/ffmpeg.js +523 -0
- package/dist/ffmpeg.js.map +1 -0
- package/dist/ghost-browser.d.ts.map +1 -1
- package/dist/ghost-browser.js.map +1 -1
- package/dist/ghost-cursor-client.js +287 -0
- package/dist/ghost-cursor.d.ts +27 -0
- package/dist/ghost-cursor.d.ts.map +1 -0
- package/dist/ghost-cursor.js +63 -0
- package/dist/ghost-cursor.js.map +1 -0
- package/dist/htmlrewrite.d.ts.map +1 -1
- package/dist/htmlrewrite.js +17 -55
- package/dist/htmlrewrite.js.map +1 -1
- package/dist/htmlrewrite.test.js.map +1 -1
- package/dist/kill-port.d.ts.map +1 -1
- package/dist/kill-port.js +1 -3
- package/dist/kill-port.js.map +1 -1
- package/dist/locator-selector.test.d.ts +2 -0
- package/dist/locator-selector.test.d.ts.map +1 -0
- package/dist/locator-selector.test.js +96 -0
- package/dist/locator-selector.test.js.map +1 -0
- package/dist/mcp-client.js.map +1 -1
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +8 -3
- package/dist/mcp.js.map +1 -1
- package/dist/on-mouse-action.test.d.ts +2 -0
- package/dist/on-mouse-action.test.d.ts.map +1 -0
- package/dist/on-mouse-action.test.js +155 -0
- package/dist/on-mouse-action.test.js.map +1 -0
- package/dist/page-markdown.js +4 -4
- package/dist/page-markdown.js.map +1 -1
- package/dist/prompt.md +450 -377
- package/dist/protocol.d.ts +4 -0
- package/dist/protocol.d.ts.map +1 -1
- package/dist/readability.js +16 -2
- package/dist/recording-ghost-cursor.d.ts +41 -0
- package/dist/recording-ghost-cursor.d.ts.map +1 -0
- package/dist/recording-ghost-cursor.js +79 -0
- package/dist/recording-ghost-cursor.js.map +1 -0
- package/dist/recording-relay.d.ts.map +1 -1
- package/dist/recording-relay.js +8 -8
- package/dist/recording-relay.js.map +1 -1
- package/dist/relay-client.d.ts +17 -4
- package/dist/relay-client.d.ts.map +1 -1
- package/dist/relay-client.js +45 -11
- package/dist/relay-client.js.map +1 -1
- package/dist/relay-core.test.d.ts.map +1 -1
- package/dist/relay-core.test.js +515 -26
- package/dist/relay-core.test.js.map +1 -1
- package/dist/relay-navigation.test.d.ts.map +1 -1
- package/dist/relay-navigation.test.js +169 -31
- package/dist/relay-navigation.test.js.map +1 -1
- package/dist/relay-session.test.d.ts.map +1 -1
- package/dist/relay-session.test.js +113 -65
- package/dist/relay-session.test.js.map +1 -1
- package/dist/relay-state.d.ts +158 -0
- package/dist/relay-state.d.ts.map +1 -0
- package/dist/relay-state.js +306 -0
- package/dist/relay-state.js.map +1 -0
- package/dist/relay-state.test.d.ts +2 -0
- package/dist/relay-state.test.d.ts.map +1 -0
- package/dist/relay-state.test.js +472 -0
- package/dist/relay-state.test.js.map +1 -0
- package/dist/scoped-fs.d.ts.map +1 -1
- package/dist/scoped-fs.js.map +1 -1
- package/dist/screen-recording.d.ts +66 -4
- package/dist/screen-recording.d.ts.map +1 -1
- package/dist/screen-recording.js +150 -13
- package/dist/screen-recording.js.map +1 -1
- package/dist/screen-recording.test.d.ts +2 -0
- package/dist/screen-recording.test.d.ts.map +1 -0
- package/dist/screen-recording.test.js +102 -0
- package/dist/screen-recording.test.js.map +1 -0
- package/dist/selector-generator.js +1 -1
- package/dist/snapshot-tools.test.js +71 -28
- package/dist/snapshot-tools.test.js.map +1 -1
- package/dist/start-relay-server.d.ts +1 -1
- package/dist/start-relay-server.d.ts.map +1 -1
- package/dist/start-relay-server.js +1 -1
- package/dist/start-relay-server.js.map +1 -1
- package/dist/styles-api.md +8 -1
- package/dist/styles-examples.d.ts +1 -1
- package/dist/styles-examples.d.ts.map +1 -1
- package/dist/styles-examples.js +1 -1
- package/dist/styles-examples.js.map +1 -1
- package/dist/styles.d.ts.map +1 -1
- package/dist/styles.js +1 -3
- package/dist/styles.js.map +1 -1
- package/dist/test-declarations.d.ts.map +1 -1
- package/dist/test-utils.d.ts +1 -1
- package/dist/test-utils.d.ts.map +1 -1
- package/dist/test-utils.js +7 -5
- package/dist/test-utils.js.map +1 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js.map +1 -1
- package/dist/wait-for-page-load.d.ts.map +1 -1
- package/dist/wait-for-page-load.js +1 -1
- package/dist/wait-for-page-load.js.map +1 -1
- package/package.json +4 -3
- package/src/a11y-client.ts +5 -4
- package/src/aria-snapshot.test.ts +5 -2
- package/src/aria-snapshot.ts +306 -117
- package/src/aria-snapshot.unit.test.ts +199 -141
- package/src/aria-snapshots/github-interactive.txt +2 -0
- package/src/aria-snapshots/github-raw.txt +5 -1
- package/src/aria-snapshots/hackernews-interactive.txt +238 -241
- package/src/aria-snapshots/hackernews-raw.txt +265 -269
- package/src/assets/aria-labels-example.png +0 -0
- package/src/assets/aria-labels-github.png +0 -0
- package/src/assets/aria-labels-hacker-news.png +0 -0
- package/src/assets/aria-labels-old-reddit.png +0 -0
- package/src/assets/cursors/screen-studio/pointer-macos-tahoe-data-url.ts +5 -0
- package/src/assets/cursors/screen-studio/pointer-macos-tahoe.svg +18 -0
- package/src/cdp-log.ts +4 -1
- package/src/cdp-relay.ts +1059 -737
- package/src/cdp-session.ts +12 -3
- package/src/cdp-types.ts +51 -51
- package/src/clean-html.ts +4 -5
- package/src/cli.ts +82 -55
- package/src/create-logger.ts +5 -3
- package/src/debugger-examples-types.ts +4 -1
- package/src/debugger.ts +1 -5
- package/src/diff-utils.ts +2 -5
- package/src/editor-examples.ts +11 -1
- package/src/editor.ts +10 -2
- package/src/executor.ts +374 -73
- package/src/executor.unit.test.ts +48 -1
- package/src/extension-connection.test.ts +612 -488
- package/src/ffmpeg.ts +769 -0
- package/src/ghost-browser.ts +4 -6
- package/src/ghost-cursor-client.ts +369 -0
- package/src/ghost-cursor.ts +110 -0
- package/src/htmlrewrite.test.ts +6 -2
- package/src/htmlrewrite.ts +348 -386
- package/src/kill-port.ts +1 -3
- package/src/locator-selector.test.ts +115 -0
- package/src/mcp-client.ts +1 -1
- package/src/mcp.ts +21 -15
- package/src/on-mouse-action.test.ts +196 -0
- package/src/page-markdown.ts +7 -7
- package/src/protocol.ts +73 -57
- package/src/recording-ghost-cursor.ts +113 -0
- package/src/recording-relay.ts +20 -12
- package/src/relay-client.ts +85 -18
- package/src/relay-core.test.ts +1117 -578
- package/src/relay-navigation.test.ts +648 -483
- package/src/relay-session.test.ts +984 -929
- package/src/relay-state.test.ts +570 -0
- package/src/relay-state.ts +497 -0
- package/src/resource.md +21 -49
- package/src/scoped-fs.ts +9 -3
- package/src/screen-recording.test.ts +111 -0
- package/src/screen-recording.ts +256 -31
- package/src/skill.md +476 -396
- package/src/snapshot-tools.test.ts +580 -528
- package/src/snapshots/shadcn-ui-accessibility-full.md +8 -8
- package/src/snapshots/shadcn-ui-accessibility-interactive.md +8 -8
- package/src/start-relay-server.ts +14 -11
- package/src/styles-examples.ts +8 -1
- package/src/styles.ts +20 -21
- package/src/test-declarations.ts +6 -6
- package/src/test-utils.ts +104 -91
- package/src/utils.ts +2 -1
- package/src/wait-for-page-load.ts +6 -1
package/dist/relay-core.test.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { createMCPClient } from './mcp-client.js';
|
|
2
2
|
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
3
|
+
import { chromium } from '@xmorse/playwright-core';
|
|
3
4
|
import { getCDPSessionForPage } from './cdp-session.js';
|
|
4
|
-
import {
|
|
5
|
+
import { getCdpUrl, LOG_CDP_FILE_PATH } from './utils.js';
|
|
6
|
+
import fs from 'node:fs';
|
|
7
|
+
import { setupTestContext, cleanupTestContext, getExtensionServiceWorker, withTimeout, js, tryJsonParse, createSimpleServer, } from './test-utils.js';
|
|
5
8
|
import './test-declarations.js';
|
|
6
9
|
const TEST_PORT = 19987;
|
|
7
10
|
describe('Relay Core Tests', () => {
|
|
@@ -24,6 +27,26 @@ describe('Relay Core Tests', () => {
|
|
|
24
27
|
throw new Error('Browser not initialized');
|
|
25
28
|
return testCtx.browserContext;
|
|
26
29
|
};
|
|
30
|
+
const ensureConnectedTabForExecute = async () => {
|
|
31
|
+
const browserContext = getBrowserContext();
|
|
32
|
+
const serviceWorker = await getExtensionServiceWorker(browserContext);
|
|
33
|
+
const connectedTabCount = await serviceWorker.evaluate(async () => {
|
|
34
|
+
const state = globalThis.getExtensionState();
|
|
35
|
+
return state.tabs.size;
|
|
36
|
+
});
|
|
37
|
+
if (connectedTabCount > 0) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const page = await browserContext.newPage();
|
|
41
|
+
await page.goto('about:blank');
|
|
42
|
+
await page.bringToFront();
|
|
43
|
+
await serviceWorker.evaluate(async () => {
|
|
44
|
+
await globalThis.toggleExtensionForActiveTab();
|
|
45
|
+
});
|
|
46
|
+
await new Promise((r) => {
|
|
47
|
+
setTimeout(r, 100);
|
|
48
|
+
});
|
|
49
|
+
};
|
|
27
50
|
it('should inject script via addScriptTag through CDP relay', async () => {
|
|
28
51
|
const browserContext = getBrowserContext();
|
|
29
52
|
const serviceWorker = await withTimeout({
|
|
@@ -43,7 +66,9 @@ describe('Relay Core Tests', () => {
|
|
|
43
66
|
timeoutMs: 10000,
|
|
44
67
|
errorMessage: 'Timed out toggling extension for active tab',
|
|
45
68
|
});
|
|
46
|
-
await new Promise((r) => {
|
|
69
|
+
await new Promise((r) => {
|
|
70
|
+
setTimeout(r, 100);
|
|
71
|
+
});
|
|
47
72
|
const cdpSession = await withTimeout({
|
|
48
73
|
promise: getCDPSessionForPage({ page }),
|
|
49
74
|
timeoutMs: 10000,
|
|
@@ -71,6 +96,138 @@ describe('Relay Core Tests', () => {
|
|
|
71
96
|
await cdpSession.detach();
|
|
72
97
|
await page.close();
|
|
73
98
|
}, 60000);
|
|
99
|
+
it('should emit download events for both Browser and Page domains in extension mode', async () => {
|
|
100
|
+
const browserContext = getBrowserContext();
|
|
101
|
+
const serviceWorker = await getExtensionServiceWorker(browserContext);
|
|
102
|
+
const logFilePath = LOG_CDP_FILE_PATH;
|
|
103
|
+
const logLineCountBefore = fs.existsSync(logFilePath)
|
|
104
|
+
? fs
|
|
105
|
+
.readFileSync(logFilePath, 'utf-8')
|
|
106
|
+
.split('\n')
|
|
107
|
+
.filter((line) => {
|
|
108
|
+
return line.trim().length > 0;
|
|
109
|
+
}).length
|
|
110
|
+
: 0;
|
|
111
|
+
const server = await createSimpleServer({
|
|
112
|
+
routes: {
|
|
113
|
+
'/': `<!doctype html>
|
|
114
|
+
<html>
|
|
115
|
+
<body>
|
|
116
|
+
<button id="download-button">Download</button>
|
|
117
|
+
<script>
|
|
118
|
+
const button = document.getElementById('download-button');
|
|
119
|
+
button.addEventListener('click', () => {
|
|
120
|
+
const blob = new Blob(['playwriter-download-test'], { type: 'text/plain' });
|
|
121
|
+
const url = URL.createObjectURL(blob);
|
|
122
|
+
const anchor = document.createElement('a');
|
|
123
|
+
anchor.href = url;
|
|
124
|
+
anchor.download = 'playwriter-download-test.txt';
|
|
125
|
+
document.body.appendChild(anchor);
|
|
126
|
+
anchor.click();
|
|
127
|
+
anchor.remove();
|
|
128
|
+
setTimeout(() => {
|
|
129
|
+
URL.revokeObjectURL(url);
|
|
130
|
+
}, 1000);
|
|
131
|
+
});
|
|
132
|
+
</script>
|
|
133
|
+
</body>
|
|
134
|
+
</html>`,
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
const page = await browserContext.newPage();
|
|
138
|
+
await page.goto(server.baseUrl, { waitUntil: 'domcontentloaded' });
|
|
139
|
+
await page.bringToFront();
|
|
140
|
+
await serviceWorker.evaluate(async () => {
|
|
141
|
+
await globalThis.toggleExtensionForActiveTab();
|
|
142
|
+
});
|
|
143
|
+
const directBrowser = await withTimeout({
|
|
144
|
+
promise: chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT })),
|
|
145
|
+
timeoutMs: 10000,
|
|
146
|
+
errorMessage: 'Timed out connecting over CDP for download reproduction test',
|
|
147
|
+
});
|
|
148
|
+
const connectedPage = directBrowser
|
|
149
|
+
.contexts()[0]
|
|
150
|
+
.pages()
|
|
151
|
+
.find((candidatePage) => {
|
|
152
|
+
return candidatePage.url() === server.baseUrl + '/';
|
|
153
|
+
});
|
|
154
|
+
if (!connectedPage) {
|
|
155
|
+
throw new Error('Connected page not found for download reproduction test');
|
|
156
|
+
}
|
|
157
|
+
const downloadResult = await Promise.all([
|
|
158
|
+
connectedPage.waitForEvent('download', { timeout: 3000 }).then((download) => {
|
|
159
|
+
return { timedOut: false, suggestedFilename: download.suggestedFilename() };
|
|
160
|
+
}, (error) => {
|
|
161
|
+
return { timedOut: true, errorMessage: error.message };
|
|
162
|
+
}),
|
|
163
|
+
connectedPage.click('#download-button'),
|
|
164
|
+
]);
|
|
165
|
+
expect(downloadResult[0]).toMatchInlineSnapshot(`
|
|
166
|
+
{
|
|
167
|
+
"suggestedFilename": "playwriter-download-test.txt",
|
|
168
|
+
"timedOut": false,
|
|
169
|
+
}
|
|
170
|
+
`);
|
|
171
|
+
await directBrowser.close();
|
|
172
|
+
await page.close();
|
|
173
|
+
await server.close();
|
|
174
|
+
const logLinesAfter = fs
|
|
175
|
+
.readFileSync(logFilePath, 'utf-8')
|
|
176
|
+
.split('\n')
|
|
177
|
+
.filter((line) => {
|
|
178
|
+
return line.trim().length > 0;
|
|
179
|
+
})
|
|
180
|
+
.slice(logLineCountBefore);
|
|
181
|
+
const newEntries = logLinesAfter
|
|
182
|
+
.map((line) => {
|
|
183
|
+
return tryJsonParse(line);
|
|
184
|
+
})
|
|
185
|
+
.filter((entry) => {
|
|
186
|
+
return Boolean(entry && typeof entry === 'object' && 'direction' in entry && 'message' in entry);
|
|
187
|
+
});
|
|
188
|
+
const methods = newEntries
|
|
189
|
+
.map((entry) => {
|
|
190
|
+
return {
|
|
191
|
+
direction: entry.direction,
|
|
192
|
+
method: typeof entry.message?.method === 'string' ? entry.message.method : 'response',
|
|
193
|
+
};
|
|
194
|
+
})
|
|
195
|
+
.filter((entry) => {
|
|
196
|
+
return (entry.method.includes('download') ||
|
|
197
|
+
entry.method === 'Browser.setDownloadBehavior' ||
|
|
198
|
+
entry.method === 'Page.setDownloadBehavior');
|
|
199
|
+
});
|
|
200
|
+
const summary = {
|
|
201
|
+
hasBrowserSetDownloadBehavior: methods.some((entry) => {
|
|
202
|
+
return entry.direction === 'from-playwright' && entry.method === 'Browser.setDownloadBehavior';
|
|
203
|
+
}),
|
|
204
|
+
hasPageSetDownloadBehavior: methods.some((entry) => {
|
|
205
|
+
return entry.direction === 'to-extension' && entry.method === 'Page.setDownloadBehavior';
|
|
206
|
+
}),
|
|
207
|
+
hasPageDownloadWillBegin: methods.some((entry) => {
|
|
208
|
+
return entry.method === 'Page.downloadWillBegin';
|
|
209
|
+
}),
|
|
210
|
+
hasPageDownloadProgress: methods.some((entry) => {
|
|
211
|
+
return entry.method === 'Page.downloadProgress';
|
|
212
|
+
}),
|
|
213
|
+
hasBrowserDownloadWillBegin: methods.some((entry) => {
|
|
214
|
+
return entry.method === 'Browser.downloadWillBegin';
|
|
215
|
+
}),
|
|
216
|
+
hasBrowserDownloadProgress: methods.some((entry) => {
|
|
217
|
+
return entry.method === 'Browser.downloadProgress';
|
|
218
|
+
}),
|
|
219
|
+
};
|
|
220
|
+
expect(summary).toMatchInlineSnapshot(`
|
|
221
|
+
{
|
|
222
|
+
"hasBrowserDownloadProgress": false,
|
|
223
|
+
"hasBrowserDownloadWillBegin": false,
|
|
224
|
+
"hasBrowserSetDownloadBehavior": true,
|
|
225
|
+
"hasPageDownloadProgress": false,
|
|
226
|
+
"hasPageDownloadWillBegin": false,
|
|
227
|
+
"hasPageSetDownloadBehavior": true,
|
|
228
|
+
}
|
|
229
|
+
`);
|
|
230
|
+
}, 120000);
|
|
74
231
|
it('should execute code and capture console output', async () => {
|
|
75
232
|
await client.callTool({
|
|
76
233
|
name: 'execute',
|
|
@@ -107,6 +264,108 @@ describe('Relay Core Tests', () => {
|
|
|
107
264
|
`);
|
|
108
265
|
expect(result.content).toBeDefined();
|
|
109
266
|
}, 30000);
|
|
267
|
+
// Repro test for https://github.com/remorses/playwriter/issues/66.
|
|
268
|
+
// Current limitation: extension-mode routing does not support root-session
|
|
269
|
+
// Storage.getCookies in playwriter. MUST use Network.getCookies via page CDP
|
|
270
|
+
// session instead (see test below), so this repro stays skipped.
|
|
271
|
+
it.skip('should reproduce page.route failure in MCP execute path (issue #66)', async () => {
|
|
272
|
+
const server = await createSimpleServer({
|
|
273
|
+
routes: {
|
|
274
|
+
'/': '<!doctype html><html><body>route issue repro</body></html>',
|
|
275
|
+
'/api/data': '{"ok":true}',
|
|
276
|
+
},
|
|
277
|
+
});
|
|
278
|
+
try {
|
|
279
|
+
const result = await client.callTool({
|
|
280
|
+
name: 'execute',
|
|
281
|
+
arguments: {
|
|
282
|
+
code: js `
|
|
283
|
+
const newPage = await context.newPage();
|
|
284
|
+
state.issue66Page = newPage;
|
|
285
|
+
await newPage.goto('${server.baseUrl}', { waitUntil: 'domcontentloaded' });
|
|
286
|
+
|
|
287
|
+
let routeFetchError = null;
|
|
288
|
+
await newPage.route('**/api/**', async (route) => {
|
|
289
|
+
try {
|
|
290
|
+
const response = await route.fetch();
|
|
291
|
+
await route.fulfill({ response });
|
|
292
|
+
} catch (error) {
|
|
293
|
+
routeFetchError = error instanceof Error ? error.message : String(error);
|
|
294
|
+
await route.abort();
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
await newPage.evaluate(async () => {
|
|
299
|
+
await fetch('/api/data').catch(() => null);
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
return { routeFetchError };
|
|
303
|
+
`,
|
|
304
|
+
},
|
|
305
|
+
});
|
|
306
|
+
const resultWithContent = result;
|
|
307
|
+
const content = Array.isArray(resultWithContent.content) ? resultWithContent.content : [];
|
|
308
|
+
const firstContent = content[0];
|
|
309
|
+
const output = typeof firstContent === 'object' && firstContent !== null && 'text' in firstContent
|
|
310
|
+
? String(firstContent.text ?? '')
|
|
311
|
+
: '';
|
|
312
|
+
expect(output).toContain('routeFetchError');
|
|
313
|
+
expect(output).toContain('Storage.getCookies');
|
|
314
|
+
expect(output).toContain('No tab found for method Storage.getCookies');
|
|
315
|
+
}
|
|
316
|
+
finally {
|
|
317
|
+
try {
|
|
318
|
+
await client.callTool({
|
|
319
|
+
name: 'execute',
|
|
320
|
+
arguments: {
|
|
321
|
+
code: js `
|
|
322
|
+
if (state.issue66Page && !state.issue66Page.isClosed()) {
|
|
323
|
+
await state.issue66Page.close();
|
|
324
|
+
}
|
|
325
|
+
delete state.issue66Page;
|
|
326
|
+
`,
|
|
327
|
+
},
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
catch {
|
|
331
|
+
// Ignore cleanup failure if MCP disconnected due to the repro.
|
|
332
|
+
}
|
|
333
|
+
await server.close();
|
|
334
|
+
}
|
|
335
|
+
}, 30000);
|
|
336
|
+
it('should read cookies via Network.getCookies through page CDP session', async () => {
|
|
337
|
+
const browserContext = getBrowserContext();
|
|
338
|
+
const serviceWorker = await getExtensionServiceWorker(browserContext);
|
|
339
|
+
const server = await createSimpleServer({
|
|
340
|
+
routes: {
|
|
341
|
+
'/': '<!doctype html><html><body>cookies test</body></html>',
|
|
342
|
+
},
|
|
343
|
+
});
|
|
344
|
+
const page = await browserContext.newPage();
|
|
345
|
+
try {
|
|
346
|
+
await page.goto(server.baseUrl, { waitUntil: 'domcontentloaded' });
|
|
347
|
+
await page.bringToFront();
|
|
348
|
+
await serviceWorker.evaluate(async () => {
|
|
349
|
+
await globalThis.toggleExtensionForActiveTab();
|
|
350
|
+
});
|
|
351
|
+
await new Promise((r) => {
|
|
352
|
+
setTimeout(r, 200);
|
|
353
|
+
});
|
|
354
|
+
await page.evaluate(() => {
|
|
355
|
+
document.cookie = 'issue66=ok; path=/';
|
|
356
|
+
});
|
|
357
|
+
const cdpSession = await getCDPSessionForPage({ page });
|
|
358
|
+
const cookiesResult = await cdpSession.send('Network.getCookies', { urls: [page.url()] });
|
|
359
|
+
const cookie = cookiesResult.cookies.find((value) => {
|
|
360
|
+
return value.name === 'issue66';
|
|
361
|
+
});
|
|
362
|
+
expect(cookie?.value).toBe('ok');
|
|
363
|
+
}
|
|
364
|
+
finally {
|
|
365
|
+
await page.close();
|
|
366
|
+
await server.close();
|
|
367
|
+
}
|
|
368
|
+
}, 30000);
|
|
110
369
|
it('should show extension as connected for pages created via newPage()', async () => {
|
|
111
370
|
const browserContext = getBrowserContext();
|
|
112
371
|
const serviceWorker = await getExtensionServiceWorker(browserContext);
|
|
@@ -131,7 +390,7 @@ describe('Relay Core Tests', () => {
|
|
|
131
390
|
connected: !!testTab && !!testTab.id && state.tabs.has(testTab.id),
|
|
132
391
|
tabId: testTab?.id,
|
|
133
392
|
tabInfo: testTab?.id ? state.tabs.get(testTab.id) : null,
|
|
134
|
-
connectionState: state.connectionState
|
|
393
|
+
connectionState: state.connectionState,
|
|
135
394
|
};
|
|
136
395
|
});
|
|
137
396
|
expect(extensionState.connected).toBe(true);
|
|
@@ -150,7 +409,7 @@ describe('Relay Core Tests', () => {
|
|
|
150
409
|
},
|
|
151
410
|
});
|
|
152
411
|
}, 30000);
|
|
153
|
-
const
|
|
412
|
+
const snapshotTestCases = [
|
|
154
413
|
{
|
|
155
414
|
name: 'hacker-news',
|
|
156
415
|
url: 'https://news.ycombinator.com/item?id=1',
|
|
@@ -162,7 +421,7 @@ describe('Relay Core Tests', () => {
|
|
|
162
421
|
expectedContent: ['shadcn'],
|
|
163
422
|
},
|
|
164
423
|
];
|
|
165
|
-
for (const testCase of
|
|
424
|
+
for (const testCase of snapshotTestCases) {
|
|
166
425
|
it(`should get accessibility snapshot of ${testCase.name}`, async () => {
|
|
167
426
|
await client.callTool({
|
|
168
427
|
name: 'execute',
|
|
@@ -181,8 +440,8 @@ describe('Relay Core Tests', () => {
|
|
|
181
440
|
arguments: {
|
|
182
441
|
code: js `
|
|
183
442
|
await state.page.goto('${testCase.url}', { waitUntil: 'domcontentloaded' });
|
|
184
|
-
const
|
|
185
|
-
return
|
|
443
|
+
const snap = await snapshot({ page: state.page, showDiffSinceLastCall: false, interactiveOnly: true });
|
|
444
|
+
return snap;
|
|
186
445
|
`,
|
|
187
446
|
},
|
|
188
447
|
});
|
|
@@ -199,8 +458,8 @@ describe('Relay Core Tests', () => {
|
|
|
199
458
|
name: 'execute',
|
|
200
459
|
arguments: {
|
|
201
460
|
code: js `
|
|
202
|
-
const
|
|
203
|
-
return
|
|
461
|
+
const snap = await snapshot({ page: state.page, showDiffSinceLastCall: false, interactiveOnly: false });
|
|
462
|
+
return snap;
|
|
204
463
|
`,
|
|
205
464
|
},
|
|
206
465
|
});
|
|
@@ -541,21 +800,92 @@ describe('Relay Core Tests', () => {
|
|
|
541
800
|
},
|
|
542
801
|
});
|
|
543
802
|
}, 30000);
|
|
544
|
-
|
|
545
|
-
|
|
803
|
+
it('should capture console logs from cross-origin iframes', async () => {
|
|
804
|
+
// Two servers on different ports = different origins
|
|
805
|
+
const iframeServer = await createSimpleServer({
|
|
806
|
+
routes: {
|
|
807
|
+
'/iframe.html': `<!doctype html><html><body>
|
|
808
|
+
<script>
|
|
809
|
+
console.log('iframe-log-ALPHA');
|
|
810
|
+
console.error('iframe-error-BETA');
|
|
811
|
+
console.warn('iframe-warn-GAMMA');
|
|
812
|
+
</script>
|
|
813
|
+
<p>cross-origin iframe</p>
|
|
814
|
+
</body></html>`,
|
|
815
|
+
},
|
|
816
|
+
});
|
|
817
|
+
const parentServer = await createSimpleServer({
|
|
818
|
+
routes: {
|
|
819
|
+
'/': `<!doctype html><html><body>
|
|
820
|
+
<script>console.log('parent-log-DELTA');</script>
|
|
821
|
+
<iframe src="${iframeServer.baseUrl}/iframe.html"></iframe>
|
|
822
|
+
</body></html>`,
|
|
823
|
+
},
|
|
824
|
+
});
|
|
825
|
+
try {
|
|
826
|
+
// Clear logs and navigate to the parent page with cross-origin iframe
|
|
827
|
+
await client.callTool({
|
|
828
|
+
name: 'execute',
|
|
829
|
+
arguments: {
|
|
830
|
+
code: js `
|
|
831
|
+
clearAllLogs();
|
|
832
|
+
state.iframePage = await context.newPage();
|
|
833
|
+
await state.iframePage.goto('${parentServer.baseUrl}', { waitUntil: 'networkidle' });
|
|
834
|
+
// Wait for iframe to load and logs to be captured
|
|
835
|
+
await state.iframePage.frameLocator('iframe').locator('p').waitFor({ timeout: 5000 });
|
|
836
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
837
|
+
`,
|
|
838
|
+
},
|
|
839
|
+
});
|
|
840
|
+
// Retrieve logs and verify both parent and iframe logs are captured
|
|
841
|
+
const logsResult = await client.callTool({
|
|
842
|
+
name: 'execute',
|
|
843
|
+
arguments: {
|
|
844
|
+
code: js `
|
|
845
|
+
const logs = await getLatestLogs({ page: state.iframePage });
|
|
846
|
+
console.log('Cross-origin iframe logs count:', logs.length);
|
|
847
|
+
logs.forEach(log => console.log(log));
|
|
848
|
+
`,
|
|
849
|
+
},
|
|
850
|
+
});
|
|
851
|
+
const output = logsResult.content[0].text;
|
|
852
|
+
// Parent page log
|
|
853
|
+
expect(output).toContain('parent-log-DELTA');
|
|
854
|
+
// Cross-origin iframe logs
|
|
855
|
+
expect(output).toContain('iframe-log-ALPHA');
|
|
856
|
+
expect(output).toContain('iframe-error-BETA');
|
|
857
|
+
expect(output).toContain('iframe-warn-GAMMA');
|
|
858
|
+
// Clean up
|
|
859
|
+
await client.callTool({
|
|
860
|
+
name: 'execute',
|
|
861
|
+
arguments: {
|
|
862
|
+
code: js `
|
|
863
|
+
await state.iframePage.close();
|
|
864
|
+
delete state.iframePage;
|
|
865
|
+
`,
|
|
866
|
+
},
|
|
867
|
+
});
|
|
868
|
+
}
|
|
869
|
+
finally {
|
|
870
|
+
await Promise.all([parentServer.close(), iframeServer.close()]);
|
|
871
|
+
}
|
|
872
|
+
}, 60000);
|
|
873
|
+
it('should preserve system color scheme instead of forcing light mode', async () => {
|
|
546
874
|
const browserContext = getBrowserContext();
|
|
547
875
|
const serviceWorker = await getExtensionServiceWorker(browserContext);
|
|
548
876
|
const page = await browserContext.newPage();
|
|
549
877
|
await page.goto('https://example.com');
|
|
550
878
|
await page.bringToFront();
|
|
879
|
+
// test-utils launches with colorScheme: 'dark', so before MCP connection
|
|
880
|
+
// the browser should report dark mode
|
|
551
881
|
const colorSchemeBefore = await page.evaluate(() => {
|
|
552
882
|
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
|
553
883
|
});
|
|
554
|
-
|
|
884
|
+
expect(colorSchemeBefore).toBe('dark');
|
|
555
885
|
await serviceWorker.evaluate(async () => {
|
|
556
886
|
await globalThis.toggleExtensionForActiveTab();
|
|
557
887
|
});
|
|
558
|
-
await new Promise(r => setTimeout(r,
|
|
888
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
559
889
|
const result = await client.callTool({
|
|
560
890
|
name: 'execute',
|
|
561
891
|
arguments: {
|
|
@@ -573,14 +903,17 @@ describe('Relay Core Tests', () => {
|
|
|
573
903
|
},
|
|
574
904
|
});
|
|
575
905
|
console.log('Color scheme after MCP connection:', result.content);
|
|
906
|
+
// After MCP connection, color scheme should NOT be forced to light.
|
|
907
|
+
// The page.ts default is now 'no-override', so the browser's actual
|
|
908
|
+
// color scheme (dark, from test-utils launch config) should be preserved.
|
|
576
909
|
expect(result.content).toMatchInlineSnapshot(`
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
910
|
+
[
|
|
911
|
+
{
|
|
912
|
+
"text": "[return value] { matchesDark: true, matchesLight: false }",
|
|
913
|
+
"type": "text",
|
|
914
|
+
},
|
|
915
|
+
]
|
|
916
|
+
`);
|
|
584
917
|
await page.close();
|
|
585
918
|
}, 60000);
|
|
586
919
|
it('should get clean HTML with getCleanHTML', async () => {
|
|
@@ -607,7 +940,7 @@ describe('Relay Core Tests', () => {
|
|
|
607
940
|
await serviceWorker.evaluate(async () => {
|
|
608
941
|
await globalThis.toggleExtensionForActiveTab();
|
|
609
942
|
});
|
|
610
|
-
await new Promise(r => setTimeout(r, 400));
|
|
943
|
+
await new Promise((r) => setTimeout(r, 400));
|
|
611
944
|
// Test basic getCleanHTML
|
|
612
945
|
const result = await client.callTool({
|
|
613
946
|
name: 'execute',
|
|
@@ -698,7 +1031,7 @@ describe('Relay Core Tests', () => {
|
|
|
698
1031
|
await serviceWorker.evaluate(async () => {
|
|
699
1032
|
await globalThis.toggleExtensionForActiveTab();
|
|
700
1033
|
});
|
|
701
|
-
await new Promise(r => setTimeout(r, 400));
|
|
1034
|
+
await new Promise((r) => setTimeout(r, 400));
|
|
702
1035
|
// Test basic getPageMarkdown
|
|
703
1036
|
const result = await client.callTool({
|
|
704
1037
|
name: 'execute',
|
|
@@ -759,7 +1092,7 @@ describe('Relay Core Tests', () => {
|
|
|
759
1092
|
await serviceWorker.evaluate(async () => {
|
|
760
1093
|
await globalThis.disconnectEverything();
|
|
761
1094
|
});
|
|
762
|
-
await new Promise(r => setTimeout(r, 100));
|
|
1095
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
763
1096
|
// 2. Create first page and enable extension
|
|
764
1097
|
const page1 = await browserContext.newPage();
|
|
765
1098
|
await page1.goto('https://example.com/first-page');
|
|
@@ -767,7 +1100,7 @@ describe('Relay Core Tests', () => {
|
|
|
767
1100
|
await serviceWorker.evaluate(async () => {
|
|
768
1101
|
await globalThis.toggleExtensionForActiveTab();
|
|
769
1102
|
});
|
|
770
|
-
await new Promise(r => setTimeout(r, 100));
|
|
1103
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
771
1104
|
// 3. Reset MCP to ensure page1 becomes the default page (only page available)
|
|
772
1105
|
const resetResult = await client.callTool({
|
|
773
1106
|
name: 'reset',
|
|
@@ -793,10 +1126,10 @@ describe('Relay Core Tests', () => {
|
|
|
793
1126
|
await serviceWorker.evaluate(async () => {
|
|
794
1127
|
await globalThis.toggleExtensionForActiveTab();
|
|
795
1128
|
});
|
|
796
|
-
await new Promise(r => setTimeout(r, 100));
|
|
1129
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
797
1130
|
// 6. Close the first page (which is the default `page` in MCP scope)
|
|
798
1131
|
await page1.close();
|
|
799
|
-
await new Promise(r => setTimeout(r, 100));
|
|
1132
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
800
1133
|
// 7. Execute code via MCP - should NOT fail with "page closed" error
|
|
801
1134
|
// Instead, it should automatically switch to the second page
|
|
802
1135
|
const afterCloseResult = await client.callTool({
|
|
@@ -819,5 +1152,161 @@ describe('Relay Core Tests', () => {
|
|
|
819
1152
|
// Cleanup
|
|
820
1153
|
await page2.close();
|
|
821
1154
|
}, 60000);
|
|
1155
|
+
it('should show descriptive error when clicking a hidden element', async () => {
|
|
1156
|
+
await ensureConnectedTabForExecute();
|
|
1157
|
+
// Create a fresh page and set content with a collapsed details element
|
|
1158
|
+
await client.callTool({
|
|
1159
|
+
name: 'execute',
|
|
1160
|
+
arguments: {
|
|
1161
|
+
code: js `
|
|
1162
|
+
state.errorTestPage = await context.newPage();
|
|
1163
|
+
await state.errorTestPage.setContent(\`
|
|
1164
|
+
<details>
|
|
1165
|
+
<summary>Toggle</summary>
|
|
1166
|
+
<button id="hidden-btn">Hidden Button</button>
|
|
1167
|
+
</details>
|
|
1168
|
+
\`);
|
|
1169
|
+
`,
|
|
1170
|
+
},
|
|
1171
|
+
});
|
|
1172
|
+
const result = await client.callTool({
|
|
1173
|
+
name: 'execute',
|
|
1174
|
+
arguments: {
|
|
1175
|
+
code: js `
|
|
1176
|
+
await state.errorTestPage.click('#hidden-btn', { timeout: 100 });
|
|
1177
|
+
`,
|
|
1178
|
+
},
|
|
1179
|
+
});
|
|
1180
|
+
expect(result).toMatchInlineSnapshot(`
|
|
1181
|
+
{
|
|
1182
|
+
"content": [
|
|
1183
|
+
{
|
|
1184
|
+
"text": "
|
|
1185
|
+
Error executing code: page.click: Timeout 100ms exceeded. Element is not visible — it may be hidden by CSS, inside a collapsed <details>, inactive tab, or closed accordion. Try: interact with the page to reveal it first, or use { force: true } to skip visibility checks
|
|
1186
|
+
Call log:
|
|
1187
|
+
[2m - waiting for locator('#hidden-btn')[22m
|
|
1188
|
+
[2m - locator resolved to <button id="hidden-btn">Hidden Button</button>[22m
|
|
1189
|
+
[2m - attempting click action[22m
|
|
1190
|
+
[2m 2 × waiting for element to be visible, enabled and stable[22m
|
|
1191
|
+
[2m - element is not visible[22m
|
|
1192
|
+
[2m - retrying click action[22m
|
|
1193
|
+
[2m - waiting 20ms[22m
|
|
1194
|
+
[2m - waiting for element to be visible, enabled and stable[22m
|
|
1195
|
+
[2m - element is not visible[22m
|
|
1196
|
+
[2m - retrying click action[22m
|
|
1197
|
+
[2m - waiting 100ms[22m
|
|
1198
|
+
",
|
|
1199
|
+
"type": "text",
|
|
1200
|
+
},
|
|
1201
|
+
],
|
|
1202
|
+
"isError": true,
|
|
1203
|
+
}
|
|
1204
|
+
`);
|
|
1205
|
+
// Cleanup
|
|
1206
|
+
await client.callTool({ name: 'execute', arguments: { code: js `await state.errorTestPage.close(); delete state.errorTestPage;` } });
|
|
1207
|
+
}, 30000);
|
|
1208
|
+
it('should show descriptive error when clicking an element covered by another', async () => {
|
|
1209
|
+
await ensureConnectedTabForExecute();
|
|
1210
|
+
await client.callTool({
|
|
1211
|
+
name: 'execute',
|
|
1212
|
+
arguments: {
|
|
1213
|
+
code: js `
|
|
1214
|
+
state.errorTestPage = await context.newPage();
|
|
1215
|
+
await state.errorTestPage.setContent(\`
|
|
1216
|
+
<div style="position:relative">
|
|
1217
|
+
<button id="covered-btn" style="position:absolute;top:0;left:0">Covered</button>
|
|
1218
|
+
<div id="overlay" style="position:absolute;top:0;left:0;width:200px;height:200px;background:red;z-index:10">Overlay</div>
|
|
1219
|
+
</div>
|
|
1220
|
+
\`);
|
|
1221
|
+
`,
|
|
1222
|
+
},
|
|
1223
|
+
});
|
|
1224
|
+
const result = await client.callTool({
|
|
1225
|
+
name: 'execute',
|
|
1226
|
+
arguments: {
|
|
1227
|
+
code: js `
|
|
1228
|
+
await state.errorTestPage.click('#covered-btn', { timeout: 100 });
|
|
1229
|
+
`,
|
|
1230
|
+
},
|
|
1231
|
+
});
|
|
1232
|
+
expect(result).toMatchInlineSnapshot(`
|
|
1233
|
+
{
|
|
1234
|
+
"content": [
|
|
1235
|
+
{
|
|
1236
|
+
"text": "
|
|
1237
|
+
Error executing code: page.click: Timeout 100ms exceeded. <div id="overlay">Overlay</div> intercepts pointer events
|
|
1238
|
+
Call log:
|
|
1239
|
+
[2m - waiting for locator('#covered-btn')[22m
|
|
1240
|
+
[2m - locator resolved to <button id="covered-btn">Covered</button>[22m
|
|
1241
|
+
[2m - attempting click action[22m
|
|
1242
|
+
[2m 2 × waiting for element to be visible, enabled and stable[22m
|
|
1243
|
+
[2m - element is visible, enabled and stable[22m
|
|
1244
|
+
[2m - scrolling into view if needed[22m
|
|
1245
|
+
[2m - done scrolling[22m
|
|
1246
|
+
[2m - <div id="overlay">Overlay</div> intercepts pointer events[22m
|
|
1247
|
+
[2m - retrying click action[22m
|
|
1248
|
+
[2m - waiting 20ms[22m
|
|
1249
|
+
[2m - waiting for element to be visible, enabled and stable[22m
|
|
1250
|
+
[2m - element is visible, enabled and stable[22m
|
|
1251
|
+
[2m - scrolling into view if needed[22m
|
|
1252
|
+
[2m - done scrolling[22m
|
|
1253
|
+
[2m - <div id="overlay">Overlay</div> intercepts pointer events[22m
|
|
1254
|
+
[2m - retrying click action[22m
|
|
1255
|
+
[2m - waiting 100ms[22m
|
|
1256
|
+
",
|
|
1257
|
+
"type": "text",
|
|
1258
|
+
},
|
|
1259
|
+
],
|
|
1260
|
+
"isError": true,
|
|
1261
|
+
}
|
|
1262
|
+
`);
|
|
1263
|
+
await client.callTool({ name: 'execute', arguments: { code: js `await state.errorTestPage.close(); delete state.errorTestPage;` } });
|
|
1264
|
+
}, 30000);
|
|
1265
|
+
it('should show descriptive error when clicking a display:none element', async () => {
|
|
1266
|
+
await ensureConnectedTabForExecute();
|
|
1267
|
+
await client.callTool({
|
|
1268
|
+
name: 'execute',
|
|
1269
|
+
arguments: {
|
|
1270
|
+
code: js `
|
|
1271
|
+
state.errorTestPage = await context.newPage();
|
|
1272
|
+
await state.errorTestPage.setContent('<button id="invisible" style="display:none">Invisible</button>');
|
|
1273
|
+
`,
|
|
1274
|
+
},
|
|
1275
|
+
});
|
|
1276
|
+
const result = await client.callTool({
|
|
1277
|
+
name: 'execute',
|
|
1278
|
+
arguments: {
|
|
1279
|
+
code: js `
|
|
1280
|
+
await state.errorTestPage.click('#invisible', { timeout: 100 });
|
|
1281
|
+
`,
|
|
1282
|
+
},
|
|
1283
|
+
});
|
|
1284
|
+
expect(result).toMatchInlineSnapshot(`
|
|
1285
|
+
{
|
|
1286
|
+
"content": [
|
|
1287
|
+
{
|
|
1288
|
+
"text": "
|
|
1289
|
+
Error executing code: page.click: Timeout 100ms exceeded. Element is not visible — it may be hidden by CSS, inside a collapsed <details>, inactive tab, or closed accordion. Try: interact with the page to reveal it first, or use { force: true } to skip visibility checks
|
|
1290
|
+
Call log:
|
|
1291
|
+
[2m - waiting for locator('#invisible')[22m
|
|
1292
|
+
[2m - locator resolved to <button id="invisible">Invisible</button>[22m
|
|
1293
|
+
[2m - attempting click action[22m
|
|
1294
|
+
[2m 2 × waiting for element to be visible, enabled and stable[22m
|
|
1295
|
+
[2m - element is not visible[22m
|
|
1296
|
+
[2m - retrying click action[22m
|
|
1297
|
+
[2m - waiting 20ms[22m
|
|
1298
|
+
[2m - waiting for element to be visible, enabled and stable[22m
|
|
1299
|
+
[2m - element is not visible[22m
|
|
1300
|
+
[2m - retrying click action[22m
|
|
1301
|
+
[2m - waiting 100ms[22m
|
|
1302
|
+
",
|
|
1303
|
+
"type": "text",
|
|
1304
|
+
},
|
|
1305
|
+
],
|
|
1306
|
+
"isError": true,
|
|
1307
|
+
}
|
|
1308
|
+
`);
|
|
1309
|
+
await client.callTool({ name: 'execute', arguments: { code: js `await state.errorTestPage.close(); delete state.errorTestPage;` } });
|
|
1310
|
+
}, 30000);
|
|
822
1311
|
});
|
|
823
1312
|
//# sourceMappingURL=relay-core.test.js.map
|