rebrowser-playwright-core 1.48.2 → 1.49.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/ThirdPartyNotices.txt +70 -384
- package/bin/reinstall_chrome_beta_linux.sh +10 -8
- package/bin/reinstall_chrome_stable_linux.sh +10 -8
- package/bin/reinstall_msedge_beta_linux.sh +11 -9
- package/bin/reinstall_msedge_dev_linux.sh +11 -9
- package/bin/reinstall_msedge_stable_linux.sh +11 -9
- package/browsers.json +19 -11
- package/lib/cli/program.js +23 -5
- package/lib/client/channelOwner.js +2 -2
- package/lib/client/locator.js +7 -0
- package/lib/client/network.js +5 -0
- package/lib/client/page.js +4 -2
- package/lib/client/tracing.js +13 -0
- package/lib/client/waiter.js +15 -11
- package/lib/generated/consoleApiSource.js +1 -1
- package/lib/generated/injectedScriptSource.js +1 -1
- package/lib/generated/pollingRecorderSource.js +1 -1
- package/lib/generated/webSocketMockSource.js +1 -1
- package/lib/protocol/validator.js +23 -3
- package/lib/server/ariaSnapshot.js +33 -0
- package/lib/server/bidi/bidiChromium.js +1 -1
- package/lib/server/bidi/bidiConnection.js +0 -2
- package/lib/server/bidi/bidiExecutionContext.js +0 -3
- package/lib/server/browserType.js +4 -1
- package/lib/server/chromium/chromium.js +6 -2
- package/lib/server/chromium/crConnection.js +153 -0
- package/lib/server/chromium/crExecutionContext.js +0 -13
- package/lib/server/chromium/crPage.js +8 -10
- package/lib/server/codegen/csharp.js +11 -16
- package/lib/server/codegen/java.js +4 -7
- package/lib/server/codegen/javascript.js +28 -6
- package/lib/server/codegen/python.js +12 -16
- package/lib/server/debugController.js +8 -5
- package/lib/server/deviceDescriptorsSource.json +127 -127
- package/lib/server/dispatchers/androidDispatcher.js +13 -2
- package/lib/server/dispatchers/debugControllerDispatcher.js +1 -1
- package/lib/server/dispatchers/dispatcher.js +1 -1
- package/lib/server/dispatchers/frameDispatcher.js +8 -1
- package/lib/server/dispatchers/tracingDispatcher.js +10 -0
- package/lib/server/dom.js +79 -36
- package/lib/server/fetch.js +30 -17
- package/lib/server/firefox/ffExecutionContext.js +0 -12
- package/lib/server/firefox/ffNetworkManager.js +1 -1
- package/lib/server/frames.js +66 -196
- package/lib/server/index.js +0 -6
- package/lib/server/javascript.js +0 -6
- package/lib/server/launchApp.js +5 -5
- package/lib/server/page.js +52 -8
- package/lib/server/progress.js +0 -3
- package/lib/server/recorder/recorderApp.js +6 -15
- package/lib/server/recorder/recorderCollection.js +1 -1
- package/lib/server/recorder/recorderInTraceViewer.js +3 -3
- package/lib/server/recorder/recorderUtils.js +2 -1
- package/lib/server/recorder.js +66 -31
- package/lib/server/registry/index.js +70 -23
- package/lib/server/registry/nativeDeps.js +6 -3
- package/lib/server/socksClientCertificatesInterceptor.js +1 -1
- package/lib/server/trace/recorder/snapshotter.js +1 -12
- package/lib/server/trace/recorder/snapshotterInjected.js +19 -1
- package/lib/server/trace/recorder/tracing.js +69 -15
- package/lib/server/trace/test/inMemorySnapshotter.js +3 -3
- package/lib/server/trace/viewer/traceViewer.js +2 -1
- package/lib/server/webkit/wkBrowser.js +2 -2
- package/lib/server/webkit/wkExecutionContext.js +0 -13
- package/lib/server/webkit/wkPage.js +6 -0
- package/lib/utils/comparators.js +12 -30
- package/lib/utils/crypto.js +1 -4
- package/lib/utils/hostPlatform.js +6 -0
- package/lib/utils/isomorphic/ariaSnapshot.js +267 -0
- package/lib/utils/isomorphic/locatorGenerators.js +23 -5
- package/lib/utils/isomorphic/recorderUtils.js +36 -4
- package/lib/utils/isomorphic/stringUtils.js +30 -0
- package/lib/utils/isomorphic/urlMatch.js +5 -1
- package/lib/utils/network.js +1 -1
- package/lib/utils/sequence.js +64 -0
- package/lib/utils/stackTrace.js +16 -3
- package/lib/utils/zones.js +32 -23
- package/lib/utilsBundle.js +4 -5
- package/lib/utilsBundleImpl/index.js +190 -33
- package/lib/vite/htmlReport/index.html +19 -16
- package/lib/vite/recorder/assets/codeMirrorModule-C5j27SYZ.js +24 -0
- package/lib/vite/recorder/assets/{index-BW-aOBcL.css → index-iA1aAGZg.css} +1 -1
- package/lib/vite/recorder/assets/index-m0XdVJHe.js +184 -0
- package/lib/vite/recorder/index.html +2 -2
- package/lib/vite/traceViewer/assets/codeMirrorModule-BBCX0spr.js +24 -0
- package/lib/vite/traceViewer/assets/inspectorTab-B4Iw34Ow.js +68 -0
- package/lib/vite/traceViewer/assets/workbench-DFlGYqou.js +9 -0
- package/lib/vite/traceViewer/{embedded.BlHoW5LY.js → embedded.CmarehFk.js} +1 -1
- package/lib/vite/traceViewer/embedded.html +5 -5
- package/lib/vite/traceViewer/{index.DaWVfou1.js → index.FBucu_gE.js} +1 -1
- package/lib/vite/traceViewer/index.html +24 -7
- package/lib/vite/traceViewer/{inspectorTab.DLjBDrQR.css → inspectorTab.DEOUW62d.css} +1 -1
- package/lib/vite/traceViewer/recorder.CW_rzcEp.js +2 -0
- package/lib/vite/traceViewer/recorder.html +3 -3
- package/lib/vite/traceViewer/sw.bundle.js +3 -3
- package/lib/vite/traceViewer/uiMode.CDUVjusr.js +5 -0
- package/lib/vite/traceViewer/uiMode.html +6 -6
- package/lib/vite/traceViewer/{uiMode.CAYqod-m.css → uiMode.voC1ZiOQ.css} +1 -1
- package/lib/vite/traceViewer/workbench.C-zR9ysA.css +1 -0
- package/package.json +1 -1
- package/types/protocol.d.ts +58 -10
- package/types/types.d.ts +134 -29
- package/lib/third_party/diff_match_patch.js +0 -2222
- package/lib/vite/recorder/assets/codeMirrorModule-baozm8ur.js +0 -24
- package/lib/vite/recorder/assets/index-2ElAIWFB.js +0 -42
- package/lib/vite/traceViewer/assets/codeMirrorModule-Bh1rfd2w.js +0 -24
- package/lib/vite/traceViewer/assets/inspectorTab-7GHnKvSD.js +0 -64
- package/lib/vite/traceViewer/assets/workbench-DPQnTHYP.js +0 -9
- package/lib/vite/traceViewer/recorder.C4zxcvd2.js +0 -2
- package/lib/vite/traceViewer/uiMode.mTXWniJb.js +0 -5
- package/lib/vite/traceViewer/workbench.D3JVcA9K.css +0 -1
|
@@ -183,6 +183,159 @@ class CRSession extends _events.EventEmitter {
|
|
|
183
183
|
}
|
|
184
184
|
this._callbacks.clear();
|
|
185
185
|
}
|
|
186
|
+
async __re__emitExecutionContext({
|
|
187
|
+
world,
|
|
188
|
+
targetId,
|
|
189
|
+
frame = null
|
|
190
|
+
}) {
|
|
191
|
+
const fixMode = process.env['REBROWSER_PATCHES_RUNTIME_FIX_MODE'] || 'addBinding';
|
|
192
|
+
const utilityWorldName = process.env['REBROWSER_PATCHES_UTILITY_WORLD_NAME'] !== '0' ? process.env['REBROWSER_PATCHES_UTILITY_WORLD_NAME'] || 'util' : '__playwright_utility_world__';
|
|
193
|
+
process.env['REBROWSER_PATCHES_DEBUG'] && console.log(`[rebrowser-patches][crSession] targetId = ${targetId}, world = ${world}, frame = ${frame ? 'Y' : 'N'}, fixMode = ${fixMode}`);
|
|
194
|
+
let getWorldPromise;
|
|
195
|
+
if (fixMode === 'addBinding') {
|
|
196
|
+
if (world === 'utility') {
|
|
197
|
+
getWorldPromise = this.__re__getIsolatedWorld({
|
|
198
|
+
client: this,
|
|
199
|
+
frameId: targetId,
|
|
200
|
+
worldName: utilityWorldName
|
|
201
|
+
}).then(contextId => {
|
|
202
|
+
return {
|
|
203
|
+
id: contextId,
|
|
204
|
+
// use UTILITY_WORLD_NAME value from crPage.ts otherwise _onExecutionContextCreated will ignore it
|
|
205
|
+
name: '__playwright_utility_world__',
|
|
206
|
+
auxData: {
|
|
207
|
+
frameId: targetId,
|
|
208
|
+
isDefault: false
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
});
|
|
212
|
+
} else if (world === 'main') {
|
|
213
|
+
getWorldPromise = this.__re__getMainWorld({
|
|
214
|
+
client: this,
|
|
215
|
+
frameId: targetId,
|
|
216
|
+
isWorker: frame === null
|
|
217
|
+
}).then(contextId => {
|
|
218
|
+
return {
|
|
219
|
+
id: contextId,
|
|
220
|
+
name: '',
|
|
221
|
+
auxData: {
|
|
222
|
+
frameId: targetId,
|
|
223
|
+
isDefault: true
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
} else if (fixMode === 'alwaysIsolated') {
|
|
229
|
+
// use only utility context
|
|
230
|
+
getWorldPromise = this.__re__getIsolatedWorld({
|
|
231
|
+
client: this,
|
|
232
|
+
frameId: targetId,
|
|
233
|
+
worldName: utilityWorldName
|
|
234
|
+
}).then(contextId => {
|
|
235
|
+
// make it look as main world
|
|
236
|
+
return {
|
|
237
|
+
id: contextId,
|
|
238
|
+
name: '',
|
|
239
|
+
auxData: {
|
|
240
|
+
frameId: targetId,
|
|
241
|
+
isDefault: true
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
const contextPayload = await getWorldPromise;
|
|
247
|
+
this.emit('Runtime.executionContextCreated', {
|
|
248
|
+
context: contextPayload
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
async __re__getMainWorld({
|
|
252
|
+
client,
|
|
253
|
+
frameId,
|
|
254
|
+
isWorker = false
|
|
255
|
+
}) {
|
|
256
|
+
let contextId;
|
|
257
|
+
|
|
258
|
+
// random name to make it harder to detect for any 3rd party script by watching window object and events
|
|
259
|
+
const randomName = [...Array(Math.floor(Math.random() * (10 + 1)) + 10)].map(() => Math.random().toString(36)[2]).join('');
|
|
260
|
+
process.env['REBROWSER_PATCHES_DEBUG'] && console.log(`[rebrowser-patches][getMainWorld] binding name = ${randomName}`);
|
|
261
|
+
|
|
262
|
+
// add the binding
|
|
263
|
+
await client.send('Runtime.addBinding', {
|
|
264
|
+
name: randomName
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
// listen for 'Runtime.bindingCalled' event
|
|
268
|
+
const bindingCalledHandler = ({
|
|
269
|
+
name,
|
|
270
|
+
payload,
|
|
271
|
+
executionContextId
|
|
272
|
+
}) => {
|
|
273
|
+
process.env['REBROWSER_PATCHES_DEBUG'] && console.log('[rebrowser-patches][bindingCalledHandler]', {
|
|
274
|
+
name,
|
|
275
|
+
payload,
|
|
276
|
+
executionContextId
|
|
277
|
+
});
|
|
278
|
+
if (contextId > 0) {
|
|
279
|
+
// already acquired the id
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
if (name !== randomName) {
|
|
283
|
+
// ignore irrelevant bindings
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
if (payload !== frameId) {
|
|
287
|
+
// ignore irrelevant frames
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
contextId = executionContextId;
|
|
291
|
+
// remove this listener
|
|
292
|
+
client.off('Runtime.bindingCalled', bindingCalledHandler);
|
|
293
|
+
};
|
|
294
|
+
client.on('Runtime.bindingCalled', bindingCalledHandler);
|
|
295
|
+
if (isWorker) {
|
|
296
|
+
// workers don't support `Page.addScriptToEvaluateOnNewDocument` and `Page.createIsolatedWorld`, but there are no iframes inside of them, so it's safe to just use Runtime.evaluate
|
|
297
|
+
await client.send('Runtime.evaluate', {
|
|
298
|
+
expression: `this['${randomName}']('${frameId}')`
|
|
299
|
+
});
|
|
300
|
+
} else {
|
|
301
|
+
// we could call the binding right from `addScriptToEvaluateOnNewDocument`, but this way it will be called in all existing frames and it's hard to distinguish children from the parent
|
|
302
|
+
await client.send('Page.addScriptToEvaluateOnNewDocument', {
|
|
303
|
+
source: `document.addEventListener('${randomName}', (e) => self['${randomName}'](e.detail.frameId))`,
|
|
304
|
+
runImmediately: true
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
// create new isolated world for this frame
|
|
308
|
+
const createIsolatedWorldResult = await client.send('Page.createIsolatedWorld', {
|
|
309
|
+
frameId,
|
|
310
|
+
// use randomName for worldName to distinguish from normal utility world
|
|
311
|
+
worldName: randomName,
|
|
312
|
+
grantUniveralAccess: true
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
// emit event in the specific frame from the isolated world
|
|
316
|
+
await client.send('Runtime.evaluate', {
|
|
317
|
+
expression: `document.dispatchEvent(new CustomEvent('${randomName}', { detail: { frameId: '${frameId}' } }))`,
|
|
318
|
+
contextId: createIsolatedWorldResult.executionContextId
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
process.env['REBROWSER_PATCHES_DEBUG'] && console.log(`[rebrowser-patches][getMainWorld] result:`, {
|
|
322
|
+
contextId
|
|
323
|
+
});
|
|
324
|
+
return contextId;
|
|
325
|
+
}
|
|
326
|
+
async __re__getIsolatedWorld({
|
|
327
|
+
client,
|
|
328
|
+
frameId,
|
|
329
|
+
worldName
|
|
330
|
+
}) {
|
|
331
|
+
const createIsolatedWorldResult = await client.send('Page.createIsolatedWorld', {
|
|
332
|
+
frameId,
|
|
333
|
+
worldName,
|
|
334
|
+
grantUniveralAccess: true
|
|
335
|
+
});
|
|
336
|
+
process.env['REBROWSER_PATCHES_DEBUG'] && console.log(`[rebrowser-patches][getIsolatedWorld] result:`, createIsolatedWorldResult);
|
|
337
|
+
return createIsolatedWorldResult.executionContextId;
|
|
338
|
+
}
|
|
186
339
|
}
|
|
187
340
|
exports.CRSession = CRSession;
|
|
188
341
|
class CDPSession extends _events.EventEmitter {
|
|
@@ -58,19 +58,6 @@ class CRExecutionContext {
|
|
|
58
58
|
if (exceptionDetails) throw new js.JavaScriptErrorInEvaluate((0, _crProtocolHelper.getExceptionMessage)(exceptionDetails));
|
|
59
59
|
return remoteObject.objectId;
|
|
60
60
|
}
|
|
61
|
-
rawCallFunctionNoReply(func, ...args) {
|
|
62
|
-
this._client.send('Runtime.callFunctionOn', {
|
|
63
|
-
functionDeclaration: func.toString(),
|
|
64
|
-
arguments: args.map(a => a instanceof js.JSHandle ? {
|
|
65
|
-
objectId: a._objectId
|
|
66
|
-
} : {
|
|
67
|
-
value: a
|
|
68
|
-
}),
|
|
69
|
-
returnByValue: true,
|
|
70
|
-
executionContextId: this._contextId,
|
|
71
|
-
userGesture: true
|
|
72
|
-
}).catch(() => {});
|
|
73
|
-
}
|
|
74
61
|
async evaluateWithArguments(expression, returnByValue, utilityScript, values, objectIds) {
|
|
75
62
|
const {
|
|
76
63
|
exceptionDetails,
|
|
@@ -318,7 +318,10 @@ class CRPage {
|
|
|
318
318
|
async inputActionEpilogue() {
|
|
319
319
|
await this._mainFrameSession._client.send('Page.enable').catch(e => {});
|
|
320
320
|
}
|
|
321
|
-
async resetForReuse() {
|
|
321
|
+
async resetForReuse() {
|
|
322
|
+
// See https://github.com/microsoft/playwright/issues/22432.
|
|
323
|
+
await this.rawMouse.move(-1, -1, 'none', new Set(), new Set(), true);
|
|
324
|
+
}
|
|
322
325
|
async pdf(options) {
|
|
323
326
|
return this._pdf.generate(options);
|
|
324
327
|
}
|
|
@@ -625,7 +628,7 @@ class FrameSession {
|
|
|
625
628
|
return;
|
|
626
629
|
}
|
|
627
630
|
const url = event.targetInfo.url;
|
|
628
|
-
const worker = new _page.Worker(this._page, url);
|
|
631
|
+
const worker = new _page.Worker(this._page, url, event.targetInfo.targetId, session);
|
|
629
632
|
this._page._addWorker(event.sessionId, worker);
|
|
630
633
|
this._workerSessions.set(event.sessionId, session);
|
|
631
634
|
session.once('Runtime.executionContextCreated', async event => {
|
|
@@ -717,6 +720,8 @@ class FrameSession {
|
|
|
717
720
|
_onDialog(event) {
|
|
718
721
|
if (!this._page._frameManager.frame(this._targetId)) return; // Our frame/subtree may be gone already.
|
|
719
722
|
this._page.emitOnContext(_browserContext.BrowserContext.Events.Dialog, new dialog.Dialog(this._page, event.type, event.message, async (accept, promptText) => {
|
|
723
|
+
// TODO: this should actually be a CDP event that notifies about a cancelled navigation attempt.
|
|
724
|
+
if (this._isMainFrame() && event.type === 'beforeunload' && !accept) this._page._frameManager.frameAbortedNavigation(this._page.mainFrame()._id, 'navigation cancelled by beforeunload dialog');
|
|
720
725
|
await this._client.send('Page.handleJavaScriptDialog', {
|
|
721
726
|
accept,
|
|
722
727
|
promptText
|
|
@@ -1061,16 +1066,9 @@ class FrameSession {
|
|
|
1061
1066
|
return this._adoptBackendNodeId(nodeInfo.node.backendNodeId, to);
|
|
1062
1067
|
}
|
|
1063
1068
|
async _adoptBackendNodeId(backendNodeId, to) {
|
|
1064
|
-
let executionContextId;
|
|
1065
|
-
if (process.env['REBROWSER_PATCHES_RUNTIME_FIX_MODE'] === '0') {
|
|
1066
|
-
executionContextId = to[contextDelegateSymbol]._contextId;
|
|
1067
|
-
} else {
|
|
1068
|
-
// contextDelegateSymbol isn't used if fix is enabled
|
|
1069
|
-
executionContextId = to._delegate._contextId;
|
|
1070
|
-
}
|
|
1071
1069
|
const result = await this._client._sendMayFail('DOM.resolveNode', {
|
|
1072
1070
|
backendNodeId,
|
|
1073
|
-
executionContextId
|
|
1071
|
+
executionContextId: to[contextDelegateSymbol]._contextId
|
|
1074
1072
|
});
|
|
1075
1073
|
if (!result || result.object.subtype === 'null') throw new Error(dom.kUnableToAdoptErrorMessage);
|
|
1076
1074
|
return to.createHandle(result.object).asElement();
|
|
@@ -130,6 +130,8 @@ class CSharpLanguageGenerator {
|
|
|
130
130
|
const assertion = action.value ? `ToHaveValueAsync(${quote(action.value)})` : `ToBeEmptyAsync()`;
|
|
131
131
|
return `await Expect(${subject}.${this._asLocator(action.selector)}).${assertion};`;
|
|
132
132
|
}
|
|
133
|
+
case 'assertSnapshot':
|
|
134
|
+
return `await Expect(${subject}.${this._asLocator(action.selector)}).ToMatchAriaSnapshotAsync(${quote(action.snapshot)});`;
|
|
133
135
|
}
|
|
134
136
|
}
|
|
135
137
|
_asLocator(selector) {
|
|
@@ -149,6 +151,7 @@ class CSharpLanguageGenerator {
|
|
|
149
151
|
using var playwright = await Playwright.CreateAsync();
|
|
150
152
|
await using var browser = await playwright.${toPascal(options.browserName)}.LaunchAsync(${formatObject(options.launchOptions, ' ', 'BrowserTypeLaunchOptions')});
|
|
151
153
|
var context = await browser.NewContextAsync(${formatContextOptions(options.contextOptions, options.deviceName)});`);
|
|
154
|
+
if (options.contextOptions.recordHar) formatter.add(` await context.RouteFromHARAsync(${quote(options.contextOptions.recordHar.path)});`);
|
|
152
155
|
formatter.newLine();
|
|
153
156
|
return formatter.format();
|
|
154
157
|
}
|
|
@@ -173,6 +176,7 @@ class CSharpLanguageGenerator {
|
|
|
173
176
|
formatter.add(` [${this._mode === 'nunit' ? 'Test' : 'TestMethod'}]
|
|
174
177
|
public async Task MyTest()
|
|
175
178
|
{`);
|
|
179
|
+
if (options.contextOptions.recordHar) formatter.add(` await context.RouteFromHARAsync(${quote(options.contextOptions.recordHar.path)});`);
|
|
176
180
|
return formatter.format();
|
|
177
181
|
}
|
|
178
182
|
generateFooter(saveStorage) {
|
|
@@ -238,29 +242,20 @@ function getPropertyName(key) {
|
|
|
238
242
|
function toPascal(value) {
|
|
239
243
|
return value[0].toUpperCase() + value.slice(1);
|
|
240
244
|
}
|
|
241
|
-
function
|
|
242
|
-
|
|
243
|
-
...
|
|
245
|
+
function formatContextOptions(contextOptions, deviceName) {
|
|
246
|
+
let options = {
|
|
247
|
+
...contextOptions
|
|
244
248
|
};
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
result['recordHarContent'] = options.recordHar.content;
|
|
248
|
-
result['recordHarMode'] = options.recordHar.mode;
|
|
249
|
-
result['recordHarOmitContent'] = options.recordHar.omitContent;
|
|
250
|
-
result['recordHarUrlFilter'] = options.recordHar.urlFilter;
|
|
251
|
-
delete result.recordHar;
|
|
252
|
-
}
|
|
253
|
-
return result;
|
|
254
|
-
}
|
|
255
|
-
function formatContextOptions(options, deviceName) {
|
|
249
|
+
// recordHAR is replaced with routeFromHAR in the generated code.
|
|
250
|
+
delete options.recordHar;
|
|
256
251
|
const device = deviceName && _deviceDescriptors.deviceDescriptors[deviceName];
|
|
257
252
|
if (!device) {
|
|
258
253
|
if (!Object.entries(options).length) return '';
|
|
259
|
-
return formatObject(
|
|
254
|
+
return formatObject(options, ' ', 'BrowserNewContextOptions');
|
|
260
255
|
}
|
|
261
256
|
options = (0, _language.sanitizeDeviceOptions)(device, options);
|
|
262
257
|
if (!Object.entries(options).length) return `playwright.Devices[${quote(deviceName)}]`;
|
|
263
|
-
return formatObject(
|
|
258
|
+
return formatObject(options, ' ', `BrowserNewContextOptions(playwright.Devices[${quote(deviceName)}])`);
|
|
264
259
|
}
|
|
265
260
|
class CSharpFormatter {
|
|
266
261
|
constructor(offset = 0) {
|
|
@@ -108,7 +108,7 @@ class JavaLanguageGenerator {
|
|
|
108
108
|
case 'navigate':
|
|
109
109
|
return `${subject}.navigate(${quote(action.url)});`;
|
|
110
110
|
case 'select':
|
|
111
|
-
return `${subject}.${this._asLocator(action.selector, inFrameLocator)}.selectOption(${formatSelectOption(action.options.length
|
|
111
|
+
return `${subject}.${this._asLocator(action.selector, inFrameLocator)}.selectOption(${formatSelectOption(action.options.length === 1 ? action.options[0] : action.options)});`;
|
|
112
112
|
case 'assertText':
|
|
113
113
|
return `assertThat(${subject}.${this._asLocator(action.selector, inFrameLocator)}).${action.substring ? 'containsText' : 'hasText'}(${quote(action.text)});`;
|
|
114
114
|
case 'assertChecked':
|
|
@@ -120,6 +120,8 @@ class JavaLanguageGenerator {
|
|
|
120
120
|
const assertion = action.value ? `hasValue(${quote(action.value)})` : `isEmpty()`;
|
|
121
121
|
return `assertThat(${subject}.${this._asLocator(action.selector, inFrameLocator)}).${assertion};`;
|
|
122
122
|
}
|
|
123
|
+
case 'assertSnapshot':
|
|
124
|
+
return `assertThat(${subject}.${this._asLocator(action.selector, inFrameLocator)}).matchesAriaSnapshot(${quote(action.snapshot)});`;
|
|
123
125
|
}
|
|
124
126
|
}
|
|
125
127
|
_asLocator(selector, inFrameLocator) {
|
|
@@ -153,6 +155,7 @@ class JavaLanguageGenerator {
|
|
|
153
155
|
try (Playwright playwright = Playwright.create()) {
|
|
154
156
|
Browser browser = playwright.${options.browserName}().launch(${formatLaunchOptions(options.launchOptions)});
|
|
155
157
|
BrowserContext context = browser.newContext(${formatContextOptions(options.contextOptions, options.deviceName)});`);
|
|
158
|
+
if (options.contextOptions.recordHar) formatter.add(` context.routeFromHAR(${quote(options.contextOptions.recordHar.path)});`);
|
|
156
159
|
return formatter.format();
|
|
157
160
|
}
|
|
158
161
|
generateFooter(saveStorage) {
|
|
@@ -190,7 +193,6 @@ function formatLaunchOptions(options) {
|
|
|
190
193
|
return lines.join('\n');
|
|
191
194
|
}
|
|
192
195
|
function formatContextOptions(contextOptions, deviceName) {
|
|
193
|
-
var _options$recordHar, _options$recordHar2, _options$recordHar3, _options$recordHar4, _options$recordHar5, _options$recordHar6, _options$recordHar7;
|
|
194
196
|
const lines = [];
|
|
195
197
|
if (!Object.keys(contextOptions).length && !deviceName) return '';
|
|
196
198
|
const device = deviceName ? _deviceDescriptors.deviceDescriptors[deviceName] : {};
|
|
@@ -208,11 +210,6 @@ function formatContextOptions(contextOptions, deviceName) {
|
|
|
208
210
|
if (options.isMobile) lines.push(` .setIsMobile(${options.isMobile})`);
|
|
209
211
|
if (options.locale) lines.push(` .setLocale(${quote(options.locale)})`);
|
|
210
212
|
if (options.proxy) lines.push(` .setProxy(new Proxy(${quote(options.proxy.server)}))`);
|
|
211
|
-
if ((_options$recordHar = options.recordHar) !== null && _options$recordHar !== void 0 && _options$recordHar.content) lines.push(` .setRecordHarContent(HarContentPolicy.${(_options$recordHar2 = options.recordHar) === null || _options$recordHar2 === void 0 ? void 0 : _options$recordHar2.content.toUpperCase()})`);
|
|
212
|
-
if ((_options$recordHar3 = options.recordHar) !== null && _options$recordHar3 !== void 0 && _options$recordHar3.mode) lines.push(` .setRecordHarMode(HarMode.${(_options$recordHar4 = options.recordHar) === null || _options$recordHar4 === void 0 ? void 0 : _options$recordHar4.mode.toUpperCase()})`);
|
|
213
|
-
if ((_options$recordHar5 = options.recordHar) !== null && _options$recordHar5 !== void 0 && _options$recordHar5.omitContent) lines.push(` .setRecordHarOmitContent(true)`);
|
|
214
|
-
if ((_options$recordHar6 = options.recordHar) !== null && _options$recordHar6 !== void 0 && _options$recordHar6.path) lines.push(` .setRecordHarPath(Paths.get(${quote(options.recordHar.path)}))`);
|
|
215
|
-
if ((_options$recordHar7 = options.recordHar) !== null && _options$recordHar7 !== void 0 && _options$recordHar7.urlFilter) lines.push(` .setRecordHarUrlFilter(${quote(options.recordHar.urlFilter)})`);
|
|
216
213
|
if (options.serviceWorkers) lines.push(` .setServiceWorkers(ServiceWorkerPolicy.${options.serviceWorkers.toUpperCase()})`);
|
|
217
214
|
if (options.storageState) lines.push(` .setStorageStatePath(Paths.get(${quote(options.storageState)}))`);
|
|
218
215
|
if (options.timezoneId) lines.push(` .setTimezoneId(${quote(options.timezoneId)})`);
|
|
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.JavaScriptLanguageGenerator = exports.JavaScriptFormatter = void 0;
|
|
7
|
+
exports.quoteMultiline = quoteMultiline;
|
|
7
8
|
var _language = require("./language");
|
|
8
9
|
var _deviceDescriptors = require("../deviceDescriptors");
|
|
9
10
|
var _utils = require("../../utils");
|
|
@@ -92,7 +93,7 @@ class JavaScriptLanguageGenerator {
|
|
|
92
93
|
case 'navigate':
|
|
93
94
|
return `await ${subject}.goto(${quote(action.url)});`;
|
|
94
95
|
case 'select':
|
|
95
|
-
return `await ${subject}.${this._asLocator(action.selector)}.selectOption(${formatObject(action.options.length
|
|
96
|
+
return `await ${subject}.${this._asLocator(action.selector)}.selectOption(${formatObject(action.options.length === 1 ? action.options[0] : action.options)});`;
|
|
96
97
|
case 'assertText':
|
|
97
98
|
return `${this._isTest ? '' : '// '}await expect(${subject}.${this._asLocator(action.selector)}).${action.substring ? 'toContainText' : 'toHaveText'}(${quote(action.text)});`;
|
|
98
99
|
case 'assertChecked':
|
|
@@ -104,6 +105,8 @@ class JavaScriptLanguageGenerator {
|
|
|
104
105
|
const assertion = action.value ? `toHaveValue(${quote(action.value)})` : `toBeEmpty()`;
|
|
105
106
|
return `${this._isTest ? '' : '// '}await expect(${subject}.${this._asLocator(action.selector)}).${assertion};`;
|
|
106
107
|
}
|
|
108
|
+
case 'assertSnapshot':
|
|
109
|
+
return `${this._isTest ? '' : '// '}await expect(${subject}.${this._asLocator(action.selector)}).toMatchAriaSnapshot(${quoteMultiline(action.snapshot)});`;
|
|
107
110
|
}
|
|
108
111
|
}
|
|
109
112
|
_asLocator(selector) {
|
|
@@ -119,11 +122,12 @@ class JavaScriptLanguageGenerator {
|
|
|
119
122
|
}
|
|
120
123
|
generateTestHeader(options) {
|
|
121
124
|
const formatter = new JavaScriptFormatter();
|
|
122
|
-
const useText = formatContextOptions(options.contextOptions, options.deviceName);
|
|
125
|
+
const useText = formatContextOptions(options.contextOptions, options.deviceName, this._isTest);
|
|
123
126
|
formatter.add(`
|
|
124
127
|
import { test, expect${options.deviceName ? ', devices' : ''} } from '@playwright/test';
|
|
125
128
|
${useText ? '\ntest.use(' + useText + ');\n' : ''}
|
|
126
129
|
test('test', async ({ page }) => {`);
|
|
130
|
+
if (options.contextOptions.recordHar) formatter.add(` await page.routeFromHAR(${quote(options.contextOptions.recordHar.path)});`);
|
|
127
131
|
return formatter.format();
|
|
128
132
|
}
|
|
129
133
|
generateTestFooter(saveStorage) {
|
|
@@ -136,7 +140,8 @@ ${useText ? '\ntest.use(' + useText + ');\n' : ''}
|
|
|
136
140
|
|
|
137
141
|
(async () => {
|
|
138
142
|
const browser = await ${options.browserName}.launch(${formatObjectOrVoid(options.launchOptions)});
|
|
139
|
-
const context = await browser.newContext(${formatContextOptions(options.contextOptions, options.deviceName)});`);
|
|
143
|
+
const context = await browser.newContext(${formatContextOptions(options.contextOptions, options.deviceName, false)});`);
|
|
144
|
+
if (options.contextOptions.recordHar) formatter.add(` await context.routeFromHAR(${quote(options.contextOptions.recordHar.path)});`);
|
|
140
145
|
return formatter.format();
|
|
141
146
|
}
|
|
142
147
|
generateStandaloneFooter(saveStorage) {
|
|
@@ -169,8 +174,13 @@ function formatObjectOrVoid(value, indent = ' ') {
|
|
|
169
174
|
const result = formatObject(value, indent);
|
|
170
175
|
return result === '{}' ? '' : result;
|
|
171
176
|
}
|
|
172
|
-
function formatContextOptions(options, deviceName) {
|
|
177
|
+
function formatContextOptions(options, deviceName, isTest) {
|
|
173
178
|
const device = deviceName && _deviceDescriptors.deviceDescriptors[deviceName];
|
|
179
|
+
// recordHAR is replaced with routeFromHAR in the generated code.
|
|
180
|
+
options = {
|
|
181
|
+
...options,
|
|
182
|
+
recordHar: undefined
|
|
183
|
+
};
|
|
174
184
|
if (!device) return formatObjectOrVoid(options);
|
|
175
185
|
// Filter out all the properties from the device descriptor.
|
|
176
186
|
let serializedObject = formatObjectOrVoid((0, _language.sanitizeDeviceOptions)(device, options));
|
|
@@ -189,10 +199,12 @@ class JavaScriptFormatter {
|
|
|
189
199
|
this._baseOffset = ' '.repeat(offset);
|
|
190
200
|
}
|
|
191
201
|
prepend(text) {
|
|
192
|
-
|
|
202
|
+
const trim = isMultilineString(text) ? line => line : line => line.trim();
|
|
203
|
+
this._lines = text.trim().split('\n').map(trim).concat(this._lines);
|
|
193
204
|
}
|
|
194
205
|
add(text) {
|
|
195
|
-
|
|
206
|
+
const trim = isMultilineString(text) ? line => line : line => line.trim();
|
|
207
|
+
this._lines.push(...text.trim().split('\n').map(trim));
|
|
196
208
|
}
|
|
197
209
|
newLine() {
|
|
198
210
|
this._lines.push('');
|
|
@@ -220,4 +232,14 @@ function wrapWithStep(description, body) {
|
|
|
220
232
|
return description ? `await test.step(\`${description}\`, async () => {
|
|
221
233
|
${body}
|
|
222
234
|
});` : body;
|
|
235
|
+
}
|
|
236
|
+
function quoteMultiline(text, indent = ' ') {
|
|
237
|
+
const escape = text => text.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\$\{/g, '\\${');
|
|
238
|
+
const lines = text.split('\n');
|
|
239
|
+
if (lines.length === 1) return '`' + escape(text) + '`';
|
|
240
|
+
return '`\n' + lines.map(line => indent + escape(line).replace(/\${/g, '\\${')).join('\n') + `\n${indent}\``;
|
|
241
|
+
}
|
|
242
|
+
function isMultilineString(text) {
|
|
243
|
+
var _text$match;
|
|
244
|
+
return (_text$match = text.match(/`[\S\s]*`/)) === null || _text$match === void 0 ? void 0 : _text$match[0].includes('\n');
|
|
223
245
|
}
|
|
@@ -114,6 +114,8 @@ class PythonLanguageGenerator {
|
|
|
114
114
|
const assertion = action.value ? `to_have_value(${quote(action.value)})` : `to_be_empty()`;
|
|
115
115
|
return `expect(${subject}.${this._asLocator(action.selector)}).${assertion};`;
|
|
116
116
|
}
|
|
117
|
+
case 'assertSnapshot':
|
|
118
|
+
return `expect(${subject}.${this._asLocator(action.selector)}).to_match_aria_snapshot(${quote(action.snapshot)})`;
|
|
117
119
|
}
|
|
118
120
|
}
|
|
119
121
|
_asLocator(selector) {
|
|
@@ -135,6 +137,7 @@ from playwright.sync_api import Page, expect
|
|
|
135
137
|
${fixture}
|
|
136
138
|
|
|
137
139
|
def test_example(page: Page) -> None {`);
|
|
140
|
+
if (options.contextOptions.recordHar) formatter.add(` page.route_from_har(${quote(options.contextOptions.recordHar.path)})`);
|
|
138
141
|
} else if (this._isAsync) {
|
|
139
142
|
formatter.add(`
|
|
140
143
|
import asyncio
|
|
@@ -145,6 +148,7 @@ from playwright.async_api import Playwright, async_playwright, expect
|
|
|
145
148
|
async def run(playwright: Playwright) -> None {
|
|
146
149
|
browser = await playwright.${options.browserName}.launch(${formatOptions(options.launchOptions, false)})
|
|
147
150
|
context = await browser.new_context(${formatContextOptions(options.contextOptions, options.deviceName)})`);
|
|
151
|
+
if (options.contextOptions.recordHar) formatter.add(` await page.route_from_har(${quote(options.contextOptions.recordHar.path)})`);
|
|
148
152
|
} else {
|
|
149
153
|
formatter.add(`
|
|
150
154
|
import re
|
|
@@ -154,6 +158,7 @@ from playwright.sync_api import Playwright, sync_playwright, expect
|
|
|
154
158
|
def run(playwright: Playwright) -> None {
|
|
155
159
|
browser = playwright.${options.browserName}.launch(${formatOptions(options.launchOptions, false)})
|
|
156
160
|
context = browser.new_context(${formatContextOptions(options.contextOptions, options.deviceName)})`);
|
|
161
|
+
if (options.contextOptions.recordHar) formatter.add(` context.route_from_har(${quote(options.contextOptions.recordHar.path)})`);
|
|
157
162
|
}
|
|
158
163
|
return formatter.format();
|
|
159
164
|
}
|
|
@@ -205,24 +210,15 @@ function formatOptions(value, hasArguments, asDict) {
|
|
|
205
210
|
return `${(0, _utils.toSnakeCase)(key)}=${formatValue(value[key])}`;
|
|
206
211
|
}).join(', ');
|
|
207
212
|
}
|
|
208
|
-
function convertContextOptions(options) {
|
|
209
|
-
const result = {
|
|
210
|
-
...options
|
|
211
|
-
};
|
|
212
|
-
if (options.recordHar) {
|
|
213
|
-
result['record_har_path'] = options.recordHar.path;
|
|
214
|
-
result['record_har_content'] = options.recordHar.content;
|
|
215
|
-
result['record_har_mode'] = options.recordHar.mode;
|
|
216
|
-
result['record_har_omit_content'] = options.recordHar.omitContent;
|
|
217
|
-
result['record_har_url_filter'] = options.recordHar.urlFilter;
|
|
218
|
-
delete result.recordHar;
|
|
219
|
-
}
|
|
220
|
-
return result;
|
|
221
|
-
}
|
|
222
213
|
function formatContextOptions(options, deviceName, asDict) {
|
|
214
|
+
// recordHAR is replaced with routeFromHAR in the generated code.
|
|
215
|
+
options = {
|
|
216
|
+
...options,
|
|
217
|
+
recordHar: undefined
|
|
218
|
+
};
|
|
223
219
|
const device = deviceName && _deviceDescriptors.deviceDescriptors[deviceName];
|
|
224
|
-
if (!device) return formatOptions(
|
|
225
|
-
return `**playwright.devices[${quote(deviceName)}]` + formatOptions(
|
|
220
|
+
if (!device) return formatOptions(options, false, asDict);
|
|
221
|
+
return `**playwright.devices[${quote(deviceName)}]` + formatOptions((0, _language.sanitizeDeviceOptions)(device, options), true, asDict);
|
|
226
222
|
}
|
|
227
223
|
class PythonFormatter {
|
|
228
224
|
constructor(offset = 0) {
|
|
@@ -9,6 +9,7 @@ var _instrumentation = require("./instrumentation");
|
|
|
9
9
|
var _recorder = require("./recorder");
|
|
10
10
|
var _recorderApp = require("./recorder/recorderApp");
|
|
11
11
|
var _utils = require("../utils");
|
|
12
|
+
var _ariaSnapshot = require("./ariaSnapshot");
|
|
12
13
|
/**
|
|
13
14
|
* Copyright (c) Microsoft Corporation.
|
|
14
15
|
*
|
|
@@ -118,8 +119,10 @@ class DebugController extends _instrumentation.SdkObject {
|
|
|
118
119
|
};
|
|
119
120
|
this._autoCloseTimer = setTimeout(heartBeat, 30000);
|
|
120
121
|
}
|
|
121
|
-
async highlight(
|
|
122
|
-
for (const recorder of await this._allRecorders())
|
|
122
|
+
async highlight(params) {
|
|
123
|
+
for (const recorder of await this._allRecorders()) {
|
|
124
|
+
if (params.ariaTemplate) recorder.setHighlightedAriaTemplate((0, _ariaSnapshot.parseYamlForAriaSnapshot)(params.ariaTemplate));else if (params.selector) recorder.setHighlightedSelector(this._sdkLanguage, params.selector);
|
|
125
|
+
}
|
|
123
126
|
}
|
|
124
127
|
async hideHighlight() {
|
|
125
128
|
// Hide all active recorder highlights.
|
|
@@ -197,10 +200,10 @@ class InspectingRecorderApp extends _recorderApp.EmptyRecorderApp {
|
|
|
197
200
|
this._debugController = void 0;
|
|
198
201
|
this._debugController = debugController;
|
|
199
202
|
}
|
|
200
|
-
async
|
|
201
|
-
const locator = (0, _utils.asLocator)(this._debugController._sdkLanguage, selector);
|
|
203
|
+
async elementPicked(elementInfo) {
|
|
204
|
+
const locator = (0, _utils.asLocator)(this._debugController._sdkLanguage, elementInfo.selector);
|
|
202
205
|
this._debugController.emit(DebugController.Events.InspectRequested, {
|
|
203
|
-
selector,
|
|
206
|
+
selector: elementInfo.selector,
|
|
204
207
|
locator
|
|
205
208
|
});
|
|
206
209
|
}
|