@rstest/browser 0.8.5 → 0.9.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/LICENSE-APACHE-2.0 +202 -0
- package/NOTICE +11 -0
- package/dist/361.js +8 -0
- package/dist/augmentExpect.d.ts +73 -0
- package/dist/browser-container/container-static/css/index.5c72297783.css +1 -0
- package/dist/browser-container/container-static/js/{565.226c9ef5.js → 101.36a8ccdf84.js} +4024 -3856
- package/dist/browser-container/container-static/js/101.36a8ccdf84.js.LICENSE.txt +1 -0
- package/dist/browser-container/container-static/js/{index.c1d17467.js → index.0687a8142a.js} +742 -692
- package/dist/browser-container/container-static/js/{lib-react.97ee79b0.js → lib-react.dcf2a5e57a.js} +10 -10
- package/dist/browser-container/container-static/js/lib-react.dcf2a5e57a.js.LICENSE.txt +1 -0
- package/dist/browser-container/index.html +1 -1
- package/dist/browser.d.ts +2 -0
- package/dist/browser.js +583 -0
- package/dist/browserRpcRegistry.d.ts +18 -0
- package/dist/client/api.d.ts +3 -0
- package/dist/client/browserRpc.d.ts +2 -0
- package/dist/client/dispatchTransport.d.ts +11 -0
- package/dist/client/entry.d.ts +1 -5
- package/dist/client/locator.d.ts +125 -0
- package/dist/client/snapshot.d.ts +0 -6
- package/dist/concurrency.d.ts +12 -0
- package/dist/dispatchCapabilities.d.ts +34 -0
- package/dist/dispatchRouter.d.ts +20 -0
- package/dist/headlessLatestRerunScheduler.d.ts +19 -0
- package/dist/headlessTransport.d.ts +12 -0
- package/dist/index.js +1580 -258
- package/dist/protocol.d.ts +44 -33
- package/dist/providers/index.d.ts +79 -0
- package/dist/providers/playwright/compileLocator.d.ts +3 -0
- package/dist/providers/playwright/dispatchBrowserRpc.d.ts +13 -0
- package/dist/providers/playwright/expectUtils.d.ts +24 -0
- package/dist/providers/playwright/implementation.d.ts +2 -0
- package/dist/providers/playwright/index.d.ts +1 -0
- package/dist/providers/playwright/runtime.d.ts +5 -0
- package/dist/providers/playwright/textMatcher.d.ts +8 -0
- package/dist/rpcProtocol.d.ts +145 -0
- package/dist/runSession.d.ts +33 -0
- package/dist/sessionRegistry.d.ts +34 -0
- package/dist/sourceMap/sourceMapLoader.d.ts +14 -0
- package/dist/watchRerunPlanner.d.ts +21 -0
- package/package.json +15 -10
- package/src/AGENTS.md +128 -0
- package/src/augmentExpect.ts +62 -0
- package/src/browser.ts +3 -0
- package/src/browserRpcRegistry.ts +57 -0
- package/src/client/AGENTS.md +82 -0
- package/src/client/api.ts +213 -0
- package/src/client/browserRpc.ts +86 -0
- package/src/client/dispatchTransport.ts +178 -0
- package/src/client/entry.ts +96 -33
- package/src/client/locator.ts +452 -0
- package/src/client/snapshot.ts +32 -97
- package/src/client/sourceMapSupport.ts +26 -37
- package/src/concurrency.ts +62 -0
- package/src/dispatchCapabilities.ts +162 -0
- package/src/dispatchRouter.ts +82 -0
- package/src/env.d.ts +8 -1
- package/src/headlessLatestRerunScheduler.ts +76 -0
- package/src/headlessTransport.ts +28 -0
- package/src/hostController.ts +1292 -367
- package/src/protocol.ts +66 -31
- package/src/providers/index.ts +103 -0
- package/src/providers/playwright/compileLocator.ts +130 -0
- package/src/providers/playwright/dispatchBrowserRpc.ts +372 -0
- package/src/providers/playwright/expectUtils.ts +57 -0
- package/src/providers/playwright/implementation.ts +33 -0
- package/src/providers/playwright/index.ts +1 -0
- package/src/providers/playwright/runtime.ts +32 -0
- package/src/providers/playwright/textMatcher.ts +10 -0
- package/src/rpcProtocol.ts +220 -0
- package/src/runSession.ts +110 -0
- package/src/sessionRegistry.ts +89 -0
- package/src/sourceMap/sourceMapLoader.ts +96 -0
- package/src/watchRerunPlanner.ts +77 -0
- package/dist/browser-container/container-static/css/index.5a71c757.css +0 -1
- package/dist/browser-container/container-static/js/565.226c9ef5.js.LICENSE.txt +0 -1
- package/dist/browser-container/container-static/js/lib-react.97ee79b0.js.LICENSE.txt +0 -1
- package/dist/browser-container/container-static/js/scheduler.5accca0c.js +0 -407
- package/dist/browser-container/scheduler.html +0 -19
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contains adapted logic from Playwright matchers:
|
|
3
|
+
* https://github.com/microsoft/playwright/blob/main/packages/playwright/src/matchers/matchers.ts
|
|
4
|
+
* Copyright (c) Microsoft Corporation, Apache-2.0.
|
|
5
|
+
*/
|
|
6
|
+
import type { FrameLocator, Locator, Page } from 'playwright';
|
|
7
|
+
import {
|
|
8
|
+
supportedExpectElementMatchers,
|
|
9
|
+
supportedLocatorActions,
|
|
10
|
+
} from '../../browserRpcRegistry';
|
|
11
|
+
import type {
|
|
12
|
+
BrowserLocatorIR,
|
|
13
|
+
BrowserLocatorText,
|
|
14
|
+
BrowserRpcRequest,
|
|
15
|
+
} from '../../rpcProtocol';
|
|
16
|
+
import { compilePlaywrightLocator } from './compileLocator';
|
|
17
|
+
import { formatExpectError, serializeExpectedText } from './expectUtils';
|
|
18
|
+
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
// Iframe lookup
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
|
|
23
|
+
const escapeCssAttrValue = (value: string): string => {
|
|
24
|
+
// Minimal escaping for use in CSS attribute selectors with single quotes.
|
|
25
|
+
// https://www.w3.org/TR/selectors-4/#attribute-representation
|
|
26
|
+
return value.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const getRunnerFrame = async (
|
|
30
|
+
containerPage: Page,
|
|
31
|
+
testPath: string,
|
|
32
|
+
timeoutMs: number,
|
|
33
|
+
): Promise<FrameLocator> => {
|
|
34
|
+
const selector = `iframe[data-test-file='${escapeCssAttrValue(testPath)}']`;
|
|
35
|
+
const iframe = containerPage.locator(selector);
|
|
36
|
+
|
|
37
|
+
const count = await iframe.count();
|
|
38
|
+
if (count === 0) {
|
|
39
|
+
const known = await containerPage
|
|
40
|
+
.locator('iframe[data-test-file]')
|
|
41
|
+
.evaluateAll((nodes) =>
|
|
42
|
+
nodes.map((n) => (n as HTMLIFrameElement).dataset.testFile),
|
|
43
|
+
);
|
|
44
|
+
throw new Error(
|
|
45
|
+
`Runner iframe not found for testPath: ${JSON.stringify(testPath)}. ` +
|
|
46
|
+
`Known iframes: ${JSON.stringify(known)}. ` +
|
|
47
|
+
`Timeout: ${timeoutMs}ms`,
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return containerPage.frameLocator(selector);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
// Table-driven expect matcher dispatch
|
|
56
|
+
// ---------------------------------------------------------------------------
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Calls Playwright's internal `_expect()` and throws on mismatch.
|
|
60
|
+
*
|
|
61
|
+
* NOTE: `_expect()` is a Playwright semi-internal API used by its own test
|
|
62
|
+
* runner to implement all web-first assertions. It is not part of the public
|
|
63
|
+
* docs but is stable across minor versions. All Playwright-specific coupling
|
|
64
|
+
* is intentionally confined to this provider module.
|
|
65
|
+
* See: https://github.com/nicolo-ribaudo/playwright/blob/HEAD/packages/playwright-core/src/client/locator.ts
|
|
66
|
+
*/
|
|
67
|
+
const callExpect = async (
|
|
68
|
+
locator: Locator,
|
|
69
|
+
expectMethod: string,
|
|
70
|
+
options: Record<string, unknown>,
|
|
71
|
+
fallbackMessage: string,
|
|
72
|
+
): Promise<null> => {
|
|
73
|
+
const result = await (locator as any)._expect(expectMethod, options);
|
|
74
|
+
if (result.matches !== !options.isNot) {
|
|
75
|
+
throw new Error(formatExpectError(result) || fallbackMessage);
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const assertSerializedText = (
|
|
81
|
+
value: unknown,
|
|
82
|
+
matcherName: string,
|
|
83
|
+
): BrowserLocatorText => {
|
|
84
|
+
const t = value as any;
|
|
85
|
+
if (!t || (t.type !== 'string' && t.type !== 'regexp')) {
|
|
86
|
+
throw new Error(`${matcherName} expects a serialized text matcher`);
|
|
87
|
+
}
|
|
88
|
+
return t as BrowserLocatorText;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const assertStringArg = (
|
|
92
|
+
value: unknown,
|
|
93
|
+
matcherName: string,
|
|
94
|
+
label: string,
|
|
95
|
+
): string => {
|
|
96
|
+
if (typeof value !== 'string' || !value) {
|
|
97
|
+
throw new Error(`${matcherName} expects ${label}`);
|
|
98
|
+
}
|
|
99
|
+
return value;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
/** Simple boolean state matchers — no extra args. */
|
|
103
|
+
const simpleMatchers: Record<string, string> = {
|
|
104
|
+
toBeVisible: 'to.be.visible',
|
|
105
|
+
toBeHidden: 'to.be.hidden',
|
|
106
|
+
toBeEnabled: 'to.be.enabled',
|
|
107
|
+
toBeDisabled: 'to.be.disabled',
|
|
108
|
+
toBeAttached: 'to.be.attached',
|
|
109
|
+
toBeDetached: 'to.be.detached',
|
|
110
|
+
toBeEditable: 'to.be.editable',
|
|
111
|
+
toBeFocused: 'to.be.focused',
|
|
112
|
+
toBeEmpty: 'to.be.empty',
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
/** Text matchers that take a single serialized text arg. */
|
|
116
|
+
const textMatchers: Record<
|
|
117
|
+
string,
|
|
118
|
+
{
|
|
119
|
+
expectMethod: string;
|
|
120
|
+
textOptions?: { matchSubstring?: boolean; normalizeWhiteSpace?: boolean };
|
|
121
|
+
}
|
|
122
|
+
> = {
|
|
123
|
+
toHaveId: { expectMethod: 'to.have.id' },
|
|
124
|
+
toHaveText: {
|
|
125
|
+
expectMethod: 'to.have.text',
|
|
126
|
+
textOptions: { normalizeWhiteSpace: true },
|
|
127
|
+
},
|
|
128
|
+
toContainText: {
|
|
129
|
+
expectMethod: 'to.have.text',
|
|
130
|
+
textOptions: { matchSubstring: true, normalizeWhiteSpace: true },
|
|
131
|
+
},
|
|
132
|
+
toHaveValue: { expectMethod: 'to.have.value' },
|
|
133
|
+
toHaveClass: { expectMethod: 'to.have.class' },
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Dispatches an expect matcher call on the given Playwright locator.
|
|
138
|
+
* Returns `null` on success, throws on mismatch or invalid args.
|
|
139
|
+
*/
|
|
140
|
+
const dispatchExpectMatcher = (
|
|
141
|
+
locator: Locator,
|
|
142
|
+
request: BrowserRpcRequest,
|
|
143
|
+
isNot: boolean,
|
|
144
|
+
timeout: number,
|
|
145
|
+
): Promise<null> => {
|
|
146
|
+
const { method, args } = request;
|
|
147
|
+
|
|
148
|
+
// --- Simple boolean state matchers ---
|
|
149
|
+
const simpleExpect = simpleMatchers[method];
|
|
150
|
+
if (simpleExpect) {
|
|
151
|
+
return callExpect(
|
|
152
|
+
locator,
|
|
153
|
+
simpleExpect,
|
|
154
|
+
{ isNot, timeout },
|
|
155
|
+
`Expected element ${method
|
|
156
|
+
.replace('toBe', 'to be ')
|
|
157
|
+
.replace(/([A-Z])/g, ' $1')
|
|
158
|
+
.trim()
|
|
159
|
+
.toLowerCase()}`,
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// --- Text matchers (single serialized text arg) ---
|
|
164
|
+
const textDef = textMatchers[method];
|
|
165
|
+
if (textDef) {
|
|
166
|
+
const expected = assertSerializedText(args[0], method);
|
|
167
|
+
return callExpect(
|
|
168
|
+
locator,
|
|
169
|
+
textDef.expectMethod,
|
|
170
|
+
{
|
|
171
|
+
isNot,
|
|
172
|
+
timeout,
|
|
173
|
+
expectedText: serializeExpectedText(expected, textDef.textOptions),
|
|
174
|
+
},
|
|
175
|
+
`Expected element ${method}`,
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// --- Matchers with custom arg handling ---
|
|
180
|
+
switch (method) {
|
|
181
|
+
case 'toBeInViewport': {
|
|
182
|
+
const ratio = args[0];
|
|
183
|
+
if (ratio !== undefined && typeof ratio !== 'number') {
|
|
184
|
+
throw new Error(
|
|
185
|
+
`toBeInViewport expects ratio to be a number, got ${typeof ratio}`,
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
return callExpect(
|
|
189
|
+
locator,
|
|
190
|
+
'to.be.in.viewport',
|
|
191
|
+
{ isNot, timeout, expectedNumber: ratio },
|
|
192
|
+
'Expected element to be in viewport',
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
case 'toBeChecked':
|
|
196
|
+
return callExpect(
|
|
197
|
+
locator,
|
|
198
|
+
'to.be.checked',
|
|
199
|
+
{ isNot, timeout, expectedValue: { checked: true } },
|
|
200
|
+
'Expected element to be checked',
|
|
201
|
+
);
|
|
202
|
+
case 'toBeUnchecked':
|
|
203
|
+
return callExpect(
|
|
204
|
+
locator,
|
|
205
|
+
'to.be.checked',
|
|
206
|
+
{ isNot, timeout, expectedValue: { checked: false } },
|
|
207
|
+
'Expected element to be unchecked',
|
|
208
|
+
);
|
|
209
|
+
case 'toHaveCount': {
|
|
210
|
+
const expected = args[0];
|
|
211
|
+
if (typeof expected !== 'number') {
|
|
212
|
+
throw new Error(`toHaveCount expects a number, got ${typeof expected}`);
|
|
213
|
+
}
|
|
214
|
+
return callExpect(
|
|
215
|
+
locator,
|
|
216
|
+
'to.have.count',
|
|
217
|
+
{ isNot, timeout, expectedNumber: expected },
|
|
218
|
+
`Expected count ${expected}`,
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
case 'toHaveAttribute': {
|
|
222
|
+
const name = assertStringArg(
|
|
223
|
+
args[0],
|
|
224
|
+
'toHaveAttribute',
|
|
225
|
+
'an attribute name',
|
|
226
|
+
);
|
|
227
|
+
if (args.length < 2) {
|
|
228
|
+
return callExpect(
|
|
229
|
+
locator,
|
|
230
|
+
'to.have.attribute',
|
|
231
|
+
{ isNot, timeout, expressionArg: name },
|
|
232
|
+
`Expected attribute ${name} to be present`,
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
const expected = assertSerializedText(args[1], 'toHaveAttribute');
|
|
236
|
+
return callExpect(
|
|
237
|
+
locator,
|
|
238
|
+
'to.have.attribute.value',
|
|
239
|
+
{
|
|
240
|
+
isNot,
|
|
241
|
+
timeout,
|
|
242
|
+
expressionArg: name,
|
|
243
|
+
expectedText: serializeExpectedText(expected),
|
|
244
|
+
},
|
|
245
|
+
`Expected attribute ${name} to match`,
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
case 'toHaveCSS': {
|
|
249
|
+
const name = assertStringArg(args[0], 'toHaveCSS', 'a CSS property name');
|
|
250
|
+
const expected = assertSerializedText(args[1], 'toHaveCSS');
|
|
251
|
+
return callExpect(
|
|
252
|
+
locator,
|
|
253
|
+
'to.have.css',
|
|
254
|
+
{
|
|
255
|
+
isNot,
|
|
256
|
+
timeout,
|
|
257
|
+
expressionArg: name,
|
|
258
|
+
expectedText: serializeExpectedText(expected),
|
|
259
|
+
},
|
|
260
|
+
`Expected CSS ${name} to match`,
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
case 'toHaveJSProperty': {
|
|
264
|
+
const name = assertStringArg(
|
|
265
|
+
args[0],
|
|
266
|
+
'toHaveJSProperty',
|
|
267
|
+
'a property name',
|
|
268
|
+
);
|
|
269
|
+
const expectedValue = args[1];
|
|
270
|
+
try {
|
|
271
|
+
JSON.stringify(expectedValue);
|
|
272
|
+
} catch {
|
|
273
|
+
throw new Error(
|
|
274
|
+
'toHaveJSProperty expects a JSON-serializable expected value',
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
return callExpect(
|
|
278
|
+
locator,
|
|
279
|
+
'to.have.property',
|
|
280
|
+
{ isNot, timeout, expressionArg: name, expectedValue },
|
|
281
|
+
`Expected JS property ${name} to match`,
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
throw new Error(`Unhandled expect matcher: ${method}`);
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
// ---------------------------------------------------------------------------
|
|
290
|
+
// Config dispatch
|
|
291
|
+
// ---------------------------------------------------------------------------
|
|
292
|
+
|
|
293
|
+
const dispatchConfigMethod = async (
|
|
294
|
+
request: BrowserRpcRequest,
|
|
295
|
+
): Promise<null> => {
|
|
296
|
+
switch (request.method) {
|
|
297
|
+
case 'setTestIdAttribute': {
|
|
298
|
+
const attr = request.args[0];
|
|
299
|
+
if (typeof attr !== 'string' || !attr) {
|
|
300
|
+
throw new Error(
|
|
301
|
+
'setTestIdAttribute expects a non-empty string argument',
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
const playwright = await import('playwright');
|
|
305
|
+
playwright.selectors.setTestIdAttribute(attr);
|
|
306
|
+
return null;
|
|
307
|
+
}
|
|
308
|
+
default:
|
|
309
|
+
throw new Error(`Unknown config method: ${request.method}`);
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
// ---------------------------------------------------------------------------
|
|
314
|
+
// Public entry
|
|
315
|
+
// ---------------------------------------------------------------------------
|
|
316
|
+
|
|
317
|
+
export async function dispatchPlaywrightBrowserRpc({
|
|
318
|
+
containerPage,
|
|
319
|
+
runnerPage,
|
|
320
|
+
request,
|
|
321
|
+
timeoutFallbackMs,
|
|
322
|
+
}: {
|
|
323
|
+
containerPage?: Page;
|
|
324
|
+
runnerPage?: Page;
|
|
325
|
+
request: BrowserRpcRequest;
|
|
326
|
+
timeoutFallbackMs: number;
|
|
327
|
+
}): Promise<unknown> {
|
|
328
|
+
// Config operations don't need a locator or runner frame.
|
|
329
|
+
if (request.kind === 'config') {
|
|
330
|
+
return dispatchConfigMethod(request);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const testPath = request.testPath;
|
|
334
|
+
if (!testPath) {
|
|
335
|
+
throw new Error('Browser RPC request is missing testPath');
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const timeout =
|
|
339
|
+
typeof request.timeout === 'number' ? request.timeout : timeoutFallbackMs;
|
|
340
|
+
|
|
341
|
+
const locatorRoot = runnerPage
|
|
342
|
+
? runnerPage
|
|
343
|
+
: await getRunnerFrame(
|
|
344
|
+
containerPage ??
|
|
345
|
+
(() => {
|
|
346
|
+
throw new Error('Browser container page is not initialized');
|
|
347
|
+
})(),
|
|
348
|
+
testPath,
|
|
349
|
+
timeout,
|
|
350
|
+
);
|
|
351
|
+
const locator = compilePlaywrightLocator(
|
|
352
|
+
locatorRoot,
|
|
353
|
+
request.locator as BrowserLocatorIR,
|
|
354
|
+
);
|
|
355
|
+
|
|
356
|
+
if (request.kind === 'locator') {
|
|
357
|
+
if (!supportedLocatorActions.has(request.method)) {
|
|
358
|
+
throw new Error(`Locator method not supported: ${request.method}`);
|
|
359
|
+
}
|
|
360
|
+
const target: any = locator as any;
|
|
361
|
+
return await target[request.method](...request.args);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
if (request.kind === 'expect') {
|
|
365
|
+
if (!supportedExpectElementMatchers.has(request.method)) {
|
|
366
|
+
throw new Error(`Expect matcher not supported: ${request.method}`);
|
|
367
|
+
}
|
|
368
|
+
return dispatchExpectMatcher(locator, request, !!request.isNot, timeout);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
throw new Error(`Unknown browser rpc kind: ${request.kind}`);
|
|
372
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contains adapted logic from Playwright matchers:
|
|
3
|
+
* https://github.com/microsoft/playwright/blob/main/packages/playwright/src/matchers/matchers.ts
|
|
4
|
+
* Copyright (c) Microsoft Corporation, Apache-2.0.
|
|
5
|
+
*/
|
|
6
|
+
import type { BrowserLocatorText } from '../../rpcProtocol';
|
|
7
|
+
|
|
8
|
+
type ExpectedTextValue = {
|
|
9
|
+
string?: string;
|
|
10
|
+
regexSource?: string;
|
|
11
|
+
regexFlags?: string;
|
|
12
|
+
matchSubstring?: boolean;
|
|
13
|
+
ignoreCase?: boolean;
|
|
14
|
+
normalizeWhiteSpace?: boolean;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const serializeExpectedText = (
|
|
18
|
+
text: BrowserLocatorText,
|
|
19
|
+
options?: {
|
|
20
|
+
matchSubstring?: boolean;
|
|
21
|
+
normalizeWhiteSpace?: boolean;
|
|
22
|
+
ignoreCase?: boolean;
|
|
23
|
+
},
|
|
24
|
+
): ExpectedTextValue[] => {
|
|
25
|
+
const base: ExpectedTextValue = {
|
|
26
|
+
matchSubstring: options?.matchSubstring,
|
|
27
|
+
ignoreCase: options?.ignoreCase,
|
|
28
|
+
normalizeWhiteSpace: options?.normalizeWhiteSpace,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
if (text.type === 'string') {
|
|
32
|
+
return [{ ...base, string: text.value }];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return [
|
|
36
|
+
{
|
|
37
|
+
...base,
|
|
38
|
+
regexSource: text.source,
|
|
39
|
+
regexFlags: text.flags,
|
|
40
|
+
},
|
|
41
|
+
];
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export const formatExpectError = (result: {
|
|
45
|
+
errorMessage?: string;
|
|
46
|
+
log?: string[];
|
|
47
|
+
}): string => {
|
|
48
|
+
const parts: string[] = [];
|
|
49
|
+
if (result.errorMessage) {
|
|
50
|
+
parts.push(result.errorMessage);
|
|
51
|
+
}
|
|
52
|
+
if (result.log?.length) {
|
|
53
|
+
parts.push('Call log:');
|
|
54
|
+
parts.push(...result.log.map((l) => `- ${l}`));
|
|
55
|
+
}
|
|
56
|
+
return parts.join('\n');
|
|
57
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Page } from 'playwright';
|
|
2
|
+
import type {
|
|
3
|
+
BrowserProviderImplementation,
|
|
4
|
+
BrowserProviderRuntime,
|
|
5
|
+
} from '../index';
|
|
6
|
+
import { dispatchPlaywrightBrowserRpc } from './dispatchBrowserRpc';
|
|
7
|
+
import { launchPlaywrightBrowser } from './runtime';
|
|
8
|
+
|
|
9
|
+
export const playwrightProviderImplementation: BrowserProviderImplementation = {
|
|
10
|
+
name: 'playwright',
|
|
11
|
+
async launchRuntime({
|
|
12
|
+
browserName,
|
|
13
|
+
headless,
|
|
14
|
+
}): Promise<BrowserProviderRuntime> {
|
|
15
|
+
return launchPlaywrightBrowser({
|
|
16
|
+
browserName,
|
|
17
|
+
headless,
|
|
18
|
+
});
|
|
19
|
+
},
|
|
20
|
+
async dispatchRpc({
|
|
21
|
+
containerPage,
|
|
22
|
+
runnerPage,
|
|
23
|
+
request,
|
|
24
|
+
timeoutFallbackMs,
|
|
25
|
+
}): Promise<unknown> {
|
|
26
|
+
return dispatchPlaywrightBrowserRpc({
|
|
27
|
+
containerPage: containerPage as Page | undefined,
|
|
28
|
+
runnerPage: runnerPage as Page | undefined,
|
|
29
|
+
request,
|
|
30
|
+
timeoutFallbackMs,
|
|
31
|
+
});
|
|
32
|
+
},
|
|
33
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { playwrightProviderImplementation } from './implementation';
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { BrowserProviderRuntime } from '../index';
|
|
2
|
+
|
|
3
|
+
type PlaywrightModule = typeof import('playwright');
|
|
4
|
+
type PlaywrightBrowserType = PlaywrightModule['chromium'];
|
|
5
|
+
|
|
6
|
+
export async function launchPlaywrightBrowser({
|
|
7
|
+
browserName,
|
|
8
|
+
headless,
|
|
9
|
+
}: {
|
|
10
|
+
browserName: 'chromium' | 'firefox' | 'webkit';
|
|
11
|
+
headless: boolean | undefined;
|
|
12
|
+
}): Promise<BrowserProviderRuntime> {
|
|
13
|
+
const playwright = await import('playwright');
|
|
14
|
+
const browserType = playwright[browserName] as PlaywrightBrowserType;
|
|
15
|
+
|
|
16
|
+
const browser = await browserType.launch({
|
|
17
|
+
headless,
|
|
18
|
+
// Chromium-specific args (ignored by other browsers)
|
|
19
|
+
args:
|
|
20
|
+
browserName === 'chromium'
|
|
21
|
+
? [
|
|
22
|
+
'--disable-popup-blocking',
|
|
23
|
+
'--no-first-run',
|
|
24
|
+
'--no-default-browser-check',
|
|
25
|
+
]
|
|
26
|
+
: undefined,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
browser: browser as unknown as BrowserProviderRuntime['browser'],
|
|
31
|
+
};
|
|
32
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export const reviveBrowserLocatorText = (
|
|
2
|
+
text:
|
|
3
|
+
| { type: 'string'; value: string }
|
|
4
|
+
| { type: 'regexp'; source: string; flags?: string },
|
|
5
|
+
): string | RegExp => {
|
|
6
|
+
if (text.type === 'string') {
|
|
7
|
+
return text.value;
|
|
8
|
+
}
|
|
9
|
+
return new RegExp(text.source, text.flags);
|
|
10
|
+
};
|