build(deps): bump playwright from 1.49.1 to 1.50.1

This commit is contained in:
2025-02-21 17:22:03 -07:00
parent 79c9869e65
commit dc6d9c68a9
174 changed files with 3064 additions and 1955 deletions

View File

@@ -1,33 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.parseAriaSnapshot = parseAriaSnapshot;
exports.parseYamlForAriaSnapshot = parseYamlForAriaSnapshot;
var _ariaSnapshot = require("../utils/isomorphic/ariaSnapshot");
var _utilsBundle = require("../utilsBundle");
/**
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
function parseAriaSnapshot(text) {
return (0, _ariaSnapshot.parseYamlTemplate)(parseYamlForAriaSnapshot(text));
}
function parseYamlForAriaSnapshot(text) {
const parsed = _utilsBundle.yaml.parse(text);
if (!Array.isArray(parsed)) throw new Error('Expected object key starting with "- ":\n\n' + text + '\n');
return parsed;
}

View File

@@ -80,8 +80,8 @@ class BidiBrowser extends _browser.Browser {
browser._defaultContext = new BidiBrowserContext(browser, undefined, options.persistent);
await browser._defaultContext._initialize();
// Create default page as we cannot get access to the existing one.
const pageDelegate = await browser._defaultContext.newPageDelegate();
await pageDelegate.pageOrError();
const page = await browser._defaultContext.doCreateNewPage();
await page.waitForInitializedOrError();
}
return browser;
}
@@ -129,6 +129,8 @@ class BidiBrowser extends _browser.Browser {
if (!parentFrame) continue;
page._session.addFrameBrowsingContext(event.context);
page._page._frameManager.frameAttached(event.context, parentFrameId);
const frame = page._page._frameManager.frame(event.context);
if (frame) frame._url = event.url;
return;
}
return;
@@ -139,6 +141,7 @@ class BidiBrowser extends _browser.Browser {
const session = this._connection.createMainFrameBrowsingContextSession(event.context);
const opener = event.originalOpener && this._bidiPages.get(event.originalOpener);
const page = new _bidiPage.BidiPage(context, session, opener || null);
page._page.mainFrame()._url = event.url;
this._bidiPages.set(event.context, page);
}
_onBrowsingContextDestroyed(event) {
@@ -173,10 +176,10 @@ class BidiBrowserContext extends _browserContext.BrowserContext {
_bidiPages() {
return [...this._browser._bidiPages.values()].filter(bidiPage => bidiPage._browserContext === this);
}
pages() {
return this._bidiPages().map(bidiPage => bidiPage._initializedPage).filter(Boolean);
possiblyUninitializedPages() {
return this._bidiPages().map(bidiPage => bidiPage._page);
}
async newPageDelegate() {
async doCreateNewPage() {
(0, _browserContext.assertBrowserContextIsNotOwned)(this);
const {
context
@@ -184,7 +187,7 @@ class BidiBrowserContext extends _browserContext.BrowserContext {
type: bidi.BrowsingContext.CreateType.Window,
userContext: this._browserContextId
});
return this._browser._bidiPages.get(context);
return this._browser._bidiPages.get(context)._page;
}
async doGetCookies(urls) {
const {

View File

@@ -109,7 +109,26 @@ class BidiExecutionContext {
throw new js.JavaScriptErrorInEvaluate('Unexpected response type: ' + JSON.stringify(response));
}
async getProperties(context, objectId) {
throw new Error('Method not implemented.');
const handle = this.createHandle(context, {
objectId
});
try {
const names = await handle.evaluate(object => {
const names = [];
const descriptors = Object.getOwnPropertyDescriptors(object);
for (const name in descriptors) {
var _descriptors$name;
if ((_descriptors$name = descriptors[name]) !== null && _descriptors$name !== void 0 && _descriptors$name.enumerable) names.push(name);
}
return names;
});
const values = await Promise.all(names.map(name => handle.evaluateHandle((object, name) => object[name], name)));
const map = new Map();
for (let i = 0; i < names.length; i++) map.set(names[i], values[i]);
return map;
} finally {
handle.dispose();
}
}
createHandle(context, jsRemoteObject) {
const remoteObject = jsRemoteObject;

View File

@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", {
exports.RawTouchscreenImpl = exports.RawMouseImpl = exports.RawKeyboardImpl = void 0;
var bidi = _interopRequireWildcard(require("./third_party/bidiProtocol"));
var _bidiKeyboard = require("./third_party/bidiKeyboard");
var _input = require("../input");
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
/**
@@ -32,20 +33,21 @@ class RawKeyboardImpl {
setSession(session) {
this._session = session;
}
async keydown(modifiers, code, keyCode, keyCodeWithoutLocation, key, location, autoRepeat, text) {
async keydown(modifiers, keyName, description, autoRepeat) {
keyName = (0, _input.resolveSmartModifierString)(keyName);
const actions = [];
actions.push({
type: 'keyDown',
value: (0, _bidiKeyboard.getBidiKeyValue)(key)
value: (0, _bidiKeyboard.getBidiKeyValue)(keyName)
});
// TODO: add modifiers?
await this._performActions(actions);
}
async keyup(modifiers, code, keyCode, keyCodeWithoutLocation, key, location) {
async keyup(modifiers, keyName, description) {
keyName = (0, _input.resolveSmartModifierString)(keyName);
const actions = [];
actions.push({
type: 'keyUp',
value: (0, _bidiKeyboard.getBidiKeyValue)(key)
value: (0, _bidiKeyboard.getBidiKeyValue)(keyName)
});
await this._performActions(actions);
}

View File

@@ -57,10 +57,12 @@ class BidiNetworkManager {
if (param.intercepts) {
// We do not support intercepting redirects.
if (redirectedFrom) {
var _redirectedFrom$_orig;
var _redirectedFrom$_orig, _redirectedFrom$_orig2;
let params = {};
if ((_redirectedFrom$_orig = redirectedFrom._originalRequestRoute) !== null && _redirectedFrom$_orig !== void 0 && _redirectedFrom$_orig._alreadyContinuedHeaders) params = toBidiRequestHeaders((_redirectedFrom$_orig2 = redirectedFrom._originalRequestRoute._alreadyContinuedHeaders) !== null && _redirectedFrom$_orig2 !== void 0 ? _redirectedFrom$_orig2 : []);
this._session.sendMayFail('network.continueRequest', {
request: param.request.request,
...(((_redirectedFrom$_orig = redirectedFrom._originalRequestRoute) === null || _redirectedFrom$_orig === void 0 ? void 0 : _redirectedFrom$_orig._alreadyContinuedHeaders) || {})
...params
});
} else {
route = new BidiRouteImpl(this._session, param.request.request);
@@ -80,10 +82,10 @@ class BidiNetworkManager {
const startTime = timings.requestTime;
function relativeToStart(time) {
if (!time) return -1;
return (time - startTime) / 1000;
return time - startTime;
}
const timing = {
startTime: startTime / 1000,
startTime: startTime,
requestStart: relativeToStart(timings.requestStart),
responseStart: relativeToStart(timings.responseStart),
domainLookupStart: relativeToStart(timings.dnsStart),
@@ -111,7 +113,7 @@ class BidiNetworkManager {
// Keep redirected requests in the map for future reference as redirectedFrom.
const isRedirected = response.status() >= 300 && response.status() <= 399;
const responseEndTime = params.request.timings.responseEnd / 1000 - response.timing().startTime;
const responseEndTime = params.request.timings.responseEnd - response.timing().startTime;
if (isRedirected) {
response._requestFinished(responseEndTime);
} else {
@@ -287,11 +289,8 @@ function fromBidiHeaders(bidiHeaders) {
}
function toBidiRequestHeaders(allHeaders) {
const bidiHeaders = toBidiHeaders(allHeaders);
const cookies = bidiHeaders.filter(h => h.name.toLowerCase() === 'cookie');
const headers = bidiHeaders.filter(h => h.name.toLowerCase() !== 'cookie');
return {
cookies,
headers
headers: bidiHeaders
};
}
function toBidiResponseHeaders(headers) {

View File

@@ -41,7 +41,6 @@ class BidiPage {
this.rawKeyboard = void 0;
this.rawTouchscreen = void 0;
this._page = void 0;
this._pagePromise = void 0;
this._session = void 0;
this._opener = void 0;
this._realmToContext = void 0;
@@ -49,7 +48,6 @@ class BidiPage {
this._browserContext = void 0;
this._networkManager = void 0;
this._pdf = void 0;
this._initializedPage = null;
this._initScriptIds = [];
this._session = bidiSession;
this._opener = opener;
@@ -65,15 +63,13 @@ class BidiPage {
this._sessionListeners = [_eventsHelper.eventsHelper.addEventListener(bidiSession, 'script.realmCreated', this._onRealmCreated.bind(this)), _eventsHelper.eventsHelper.addEventListener(bidiSession, 'script.message', this._onScriptMessage.bind(this)), _eventsHelper.eventsHelper.addEventListener(bidiSession, 'browsingContext.contextDestroyed', this._onBrowsingContextDestroyed.bind(this)), _eventsHelper.eventsHelper.addEventListener(bidiSession, 'browsingContext.navigationStarted', this._onNavigationStarted.bind(this)), _eventsHelper.eventsHelper.addEventListener(bidiSession, 'browsingContext.navigationAborted', this._onNavigationAborted.bind(this)), _eventsHelper.eventsHelper.addEventListener(bidiSession, 'browsingContext.navigationFailed', this._onNavigationFailed.bind(this)), _eventsHelper.eventsHelper.addEventListener(bidiSession, 'browsingContext.fragmentNavigated', this._onFragmentNavigated.bind(this)), _eventsHelper.eventsHelper.addEventListener(bidiSession, 'browsingContext.domContentLoaded', this._onDomContentLoaded.bind(this)), _eventsHelper.eventsHelper.addEventListener(bidiSession, 'browsingContext.load', this._onLoad.bind(this)), _eventsHelper.eventsHelper.addEventListener(bidiSession, 'browsingContext.userPromptOpened', this._onUserPromptOpened.bind(this)), _eventsHelper.eventsHelper.addEventListener(bidiSession, 'log.entryAdded', this._onLogEntryAdded.bind(this))];
// Initialize main frame.
this._pagePromise = this._initialize().finally(async () => {
await this._page.initOpener(this._opener);
}).then(() => {
this._initializedPage = this._page;
this._page.reportAsNew();
return this._page;
}).catch(e => {
this._page.reportAsNew(e);
return e;
// TODO: Wait for first execution context to be created and maybe about:blank navigated.
this._initialize().then(() => {
var _this$_opener;
return this._page.reportAsNew((_this$_opener = this._opener) === null || _this$_opener === void 0 ? void 0 : _this$_opener._page);
}, error => {
var _this$_opener2;
return this._page.reportAsNew((_this$_opener2 = this._opener) === null || _this$_opener2 === void 0 ? void 0 : _this$_opener2._page, error);
});
}
async _initialize() {
@@ -84,18 +80,11 @@ class BidiPage {
async _addAllInitScripts() {
return Promise.all(this._page.allInitScripts().map(initScript => this.addInitScript(initScript)));
}
potentiallyUninitializedPage() {
return this._page;
}
didClose() {
this._session.dispose();
_eventsHelper.eventsHelper.removeEventListeners(this._sessionListeners);
this._page._didClose();
}
async pageOrError() {
// TODO: Wait for first execution context to be created and maybe about:blank navigated.
return this._pagePromise;
}
_onFrameAttached(frameId, parentFrameId) {
return this._page._frameManager.frameAttached(frameId, parentFrameId);
}
@@ -311,7 +300,7 @@ class BidiPage {
}
async _onScriptMessage(event) {
if (event.channel !== kPlaywrightBindingChannel) return;
const pageOrError = await this.pageOrError();
const pageOrError = await this._page.waitForInitializedOrError();
if (pageOrError instanceof Error) return;
const context = this._realmToContext.get(event.source.realm);
if (!context) return;
@@ -351,7 +340,7 @@ class BidiPage {
context: this._session.sessionId,
format: {
type: `image/${format === 'png' ? 'png' : 'jpeg'}`,
quality: quality || 80
quality: quality ? quality / 100 : 0.8
},
origin: documentRect ? 'document' : 'viewport',
clip: {

View File

@@ -13,18 +13,18 @@ exports.getBidiKeyValue = void 0;
/* eslint-disable curly */
const getBidiKeyValue = key => {
switch (key) {
const getBidiKeyValue = keyName => {
switch (keyName) {
case '\r':
case '\n':
key = 'Enter';
keyName = 'Enter';
break;
}
// Measures the number of code points rather than UTF-16 code units.
if ([...key].length === 1) {
return key;
if ([...keyName].length === 1) {
return keyName;
}
switch (key) {
switch (keyName) {
case 'Cancel':
return '\uE001';
case 'Help':
@@ -137,6 +137,8 @@ const getBidiKeyValue = key => {
return '\uE052';
case 'MetaRight':
return '\uE053';
case 'Space':
return ' ';
case 'Digit0':
return '0';
case 'Digit1':
@@ -232,7 +234,7 @@ const getBidiKeyValue = key => {
case 'Quote':
return '"';
default:
throw new Error(`Unknown key: "${key}"`);
throw new Error(`Unknown key: "${keyName}"`);
}
};
exports.getBidiKeyValue = getBidiKeyValue;

View File

@@ -102,7 +102,7 @@ class BrowserContext extends _instrumentation.SdkObject {
this._debugger = new _debugger.Debugger(this);
// When PWDEBUG=1, show inspector for each context.
if ((0, _utils.debugMode)() === 'inspector') await _recorder.Recorder.show('actions', this, _recorderApp.RecorderApp.factory(this), {
if ((0, _utils.debugMode)() === 'inspector') await _recorder.Recorder.show(this, _recorderApp.RecorderApp.factory(this), {
pauseOnNextStatement: true
});
@@ -201,6 +201,9 @@ class BrowserContext extends _instrumentation.SdkObject {
this._closePromiseFulfill(new Error('Context closed'));
this.emit(BrowserContext.Events.Close);
}
pages() {
return this.possiblyUninitializedPages().filter(page => page.initializedOrUndefined());
}
// BrowserContext methods.
@@ -227,6 +230,9 @@ class BrowserContext extends _instrumentation.SdkObject {
setHTTPCredentials(httpCredentials) {
return this.doSetHTTPCredentials(httpCredentials);
}
hasBinding(name) {
return this._pageBindings.has(name);
}
async exposeBinding(name, needsHandle, playwrightBinding) {
if (this._pageBindings.has(name)) throw new Error(`Function "${name}" has been already registered`);
for (const page of this.pages()) {
@@ -266,27 +272,29 @@ class BrowserContext extends _instrumentation.SdkObject {
this._timeoutSettings.setDefaultTimeout(timeout);
}
async _loadDefaultContextAsIs(progress) {
if (!this.pages().length) {
if (!this.possiblyUninitializedPages().length) {
const waitForEvent = _helper.helper.waitForEvent(progress, this, BrowserContext.Events.Page);
progress.cleanupWhenAborted(() => waitForEvent.dispose);
const page = await waitForEvent.promise;
if (page._pageIsError) throw page._pageIsError;
// Race against BrowserContext.close
await Promise.race([waitForEvent.promise, this._closePromise]);
}
const pages = this.pages();
if (pages[0]._pageIsError) throw pages[0]._pageIsError;
await pages[0].mainFrame()._waitForLoadState(progress, 'load');
return pages;
const page = this.possiblyUninitializedPages()[0];
if (!page) return;
const pageOrError = await page.waitForInitializedOrError();
if (pageOrError instanceof Error) throw pageOrError;
await page.mainFrame()._waitForLoadState(progress, 'load');
return page;
}
async _loadDefaultContext(progress) {
const pages = await this._loadDefaultContextAsIs(progress);
const defaultPage = await this._loadDefaultContextAsIs(progress);
if (!defaultPage) return;
const browserName = this._browser.options.name;
if (this._options.isMobile && browserName === 'chromium' || this._options.locale && browserName === 'webkit') {
// Workaround for:
// - chromium fails to change isMobile for existing page;
// - webkit fails to change locale for existing page.
const oldPage = pages[0];
await this.newPage(progress.metadata);
await oldPage.close(progress.metadata);
await defaultPage.close(progress.metadata);
}
}
_authenticateProxyViaHeader() {
@@ -319,8 +327,8 @@ class BrowserContext extends _instrumentation.SdkObject {
password: password || ''
};
}
async addInitScript(source) {
const initScript = new _page6.InitScript(source);
async addInitScript(source, name) {
const initScript = new _page6.InitScript(source, false /* internal */, name);
this.initScripts.push(initScript);
await this.doAddInitScript(initScript);
}
@@ -380,9 +388,9 @@ class BrowserContext extends _instrumentation.SdkObject {
await this._closePromise;
}
async newPage(metadata) {
const pageDelegate = await this.newPageDelegate();
if (metadata.isServerSide) pageDelegate.potentiallyUninitializedPage().markAsServerSideOnly();
const pageOrError = await pageDelegate.pageOrError();
const page = await this.doCreateNewPage();
if (metadata.isServerSide) page.markAsServerSideOnly();
const pageOrError = await page.waitForInitializedOrError();
if (pageOrError instanceof _page6.Page) {
if (pageOrError.isClosed()) throw new Error('Page has been closed.');
return pageOrError;

View File

@@ -134,7 +134,7 @@ class CRBrowser extends _browser.Browser {
return this.options.name === 'clank';
}
async _waitForAllPagesToBeInitialized() {
await Promise.all([...this._crPages.values()].map(page => page.pageOrError()));
await Promise.all([...this._crPages.values()].map(crPage => crPage._page.waitForInitializedOrError()));
}
_onAttachedToTarget({
targetInfo,
@@ -239,9 +239,9 @@ class CRBrowser extends _browser.Browser {
return;
}
page.willBeginDownload();
let originPage = page._initializedPage;
let originPage = page._page.initializedOrUndefined();
// If it's a new window download, report it on the opener page.
if (!originPage && page._opener) originPage = page._opener._initializedPage;
if (!originPage && page._opener) originPage = page._opener._page.initializedOrUndefined();
if (!originPage) return;
this._downloadCreated(originPage, payload.guid, payload.url, payload.suggestedFilename);
}
@@ -312,10 +312,10 @@ class CRBrowserContext extends _browserContext.BrowserContext {
_crPages() {
return [...this._browser._crPages.values()].filter(crPage => crPage._browserContext === this);
}
pages() {
return this._crPages().map(crPage => crPage._initializedPage).filter(Boolean);
possiblyUninitializedPages() {
return this._crPages().map(crPage => crPage._page);
}
async newPageDelegate() {
async doCreateNewPage() {
(0, _browserContext.assertBrowserContextIsNotOwned)(this);
const oldKeys = this._browser.isClank() ? new Set(this._browser._crPages.keys()) : undefined;
let {
@@ -338,7 +338,7 @@ class CRBrowserContext extends _browserContext.BrowserContext {
(0, _utils.assert)(newKeys.size === 1);
[targetId] = [...newKeys];
}
return this._browser._crPages.get(targetId);
return this._browser._crPages.get(targetId)._page;
}
async doGetCookies(urls) {
const {
@@ -372,7 +372,7 @@ class CRBrowserContext extends _browserContext.BrowserContext {
});
}
async doGrantPermissions(origin, permissions) {
const webPermissionToProtocol = new Map([['geolocation', 'geolocation'], ['midi', 'midi'], ['notifications', 'notifications'], ['camera', 'videoCapture'], ['microphone', 'audioCapture'], ['background-sync', 'backgroundSync'], ['ambient-light-sensor', 'sensors'], ['accelerometer', 'sensors'], ['gyroscope', 'sensors'], ['magnetometer', 'sensors'], ['accessibility-events', 'accessibilityEvents'], ['clipboard-read', 'clipboardReadWrite'], ['clipboard-write', 'clipboardSanitizedWrite'], ['payment-handler', 'paymentHandler'],
const webPermissionToProtocol = new Map([['geolocation', 'geolocation'], ['midi', 'midi'], ['notifications', 'notifications'], ['camera', 'videoCapture'], ['microphone', 'audioCapture'], ['background-sync', 'backgroundSync'], ['ambient-light-sensor', 'sensors'], ['accelerometer', 'sensors'], ['gyroscope', 'sensors'], ['magnetometer', 'sensors'], ['clipboard-read', 'clipboardReadWrite'], ['clipboard-write', 'clipboardSanitizedWrite'], ['payment-handler', 'paymentHandler'],
// chrome-specific permissions we have.
['midi-sysex', 'midiSysex'], ['storage-access', 'storageAccess']]);
const filtered = permissions.map(permission => {
@@ -466,7 +466,7 @@ class CRBrowserContext extends _browserContext.BrowserContext {
// When persistent context is closed, we do not necessary get Target.detachedFromTarget
// for all the background pages.
for (const [targetId, backgroundPage] of this._browser._backgroundPages.entries()) {
if (backgroundPage._browserContext === this && backgroundPage._initializedPage) {
if (backgroundPage._browserContext === this && backgroundPage._page.initializedOrUndefined()) {
backgroundPage.didClose();
this._browser._backgroundPages.delete(targetId);
}
@@ -487,7 +487,7 @@ class CRBrowserContext extends _browserContext.BrowserContext {
backgroundPages() {
const result = [];
for (const backgroundPage of this._browser._backgroundPages.values()) {
if (backgroundPage._browserContext === this && backgroundPage._initializedPage) result.push(backgroundPage._initializedPage);
if (backgroundPage._browserContext === this && backgroundPage._page.initializedOrUndefined()) result.push(backgroundPage._page);
}
return result;
}

View File

@@ -100,11 +100,7 @@ class CRExecutionContext {
}
exports.CRExecutionContext = CRExecutionContext;
function rewriteError(error) {
if (error.message.includes('Object reference chain is too long')) return {
result: {
type: 'undefined'
}
};
if (error.message.includes('Object reference chain is too long')) throw new Error('Cannot serialize result: object reference chain is too long.');
if (error.message.includes('Object couldn\'t be returned by value')) return {
result: {
type: 'undefined'

View File

@@ -48,13 +48,19 @@ class RawKeyboardImpl {
// remove the trailing : to match the Chromium command names.
return commands.map(c => c.substring(0, c.length - 1));
}
async keydown(modifiers, code, keyCode, keyCodeWithoutLocation, key, location, autoRepeat, text) {
async keydown(modifiers, keyName, description, autoRepeat) {
const {
code,
key,
location,
text
} = description;
if (code === 'Escape' && (await this._dragManger.cancelDrag())) return;
const commands = this._commandsForCode(code, modifiers);
await this._client.send('Input.dispatchKeyEvent', {
type: text ? 'keyDown' : 'rawKeyDown',
modifiers: (0, _crProtocolHelper.toModifiersMask)(modifiers),
windowsVirtualKeyCode: keyCodeWithoutLocation,
windowsVirtualKeyCode: description.keyCodeWithoutLocation,
code,
commands,
key,
@@ -65,12 +71,17 @@ class RawKeyboardImpl {
isKeypad: location === input.keypadLocation
});
}
async keyup(modifiers, code, keyCode, keyCodeWithoutLocation, key, location) {
async keyup(modifiers, keyName, description) {
const {
code,
key,
location
} = description;
await this._client.send('Input.dispatchKeyEvent', {
type: 'keyUp',
modifiers: (0, _crProtocolHelper.toModifiersMask)(modifiers),
key,
windowsVirtualKeyCode: keyCodeWithoutLocation,
windowsVirtualKeyCode: description.keyCodeWithoutLocation,
code,
location
});

View File

@@ -68,8 +68,6 @@ class CRPage {
this._pdf = void 0;
this._coverage = void 0;
this._browserContext = void 0;
this._pagePromise = void 0;
this._initializedPage = null;
this._isBackgroundPage = void 0;
// Holds window features for the next popup being opened via window.open,
// until the popup target arrives. This could be racy if two oopifs
@@ -105,29 +103,15 @@ class CRPage {
screen: viewportSize
};
}
// Note: it is important to call |reportAsNew| before resolving pageOrError promise,
// so that anyone who awaits pageOrError got a ready and reported page.
this._pagePromise = this._mainFrameSession._initialize(bits.hasUIWindow).then(async r => {
await this._page.initOpener(this._opener);
return r;
}).catch(async e => {
await this._page.initOpener(this._opener);
throw e;
}).then(() => {
this._initializedPage = this._page;
this._reportAsNew();
return this._page;
}).catch(e => {
this._reportAsNew(e);
return e;
const createdEvent = this._isBackgroundPage ? _crBrowser.CRBrowserContext.CREvents.BackgroundPage : _browserContext.BrowserContext.Events.Page;
this._mainFrameSession._initialize(bits.hasUIWindow).then(() => {
var _this$_opener;
return this._page.reportAsNew((_this$_opener = this._opener) === null || _this$_opener === void 0 ? void 0 : _this$_opener._page, undefined, createdEvent);
}, error => {
var _this$_opener2;
return this._page.reportAsNew((_this$_opener2 = this._opener) === null || _this$_opener2 === void 0 ? void 0 : _this$_opener2._page, error, createdEvent);
});
}
potentiallyUninitializedPage() {
return this._page;
}
_reportAsNew(error) {
this._page.reportAsNew(error, this._isBackgroundPage ? _crBrowser.CRBrowserContext.CREvents.BackgroundPage : _browserContext.BrowserContext.Events.Page);
}
async _forAllFrameSessions(cb) {
const frameSessions = Array.from(this._sessions.values());
await Promise.all(frameSessions.map(frameSession => {
@@ -155,9 +139,6 @@ class CRPage {
willBeginDownload() {
this._mainFrameSession._willBeginDownload();
}
async pageOrError() {
return this._pagePromise;
}
didClose() {
for (const session of this._sessions.values()) session.dispose();
this._page._didClose();
@@ -382,6 +363,9 @@ class FrameSession {
this._firstNonInitialNavigationCommittedFulfill = f;
this._firstNonInitialNavigationCommittedReject = r;
});
// The Promise is not always awaited (e.g. FrameSession._initialize can throw)
// so we catch errors here to prevent unhandled promise rejection.
this._firstNonInitialNavigationCommittedPromise.catch(() => {});
}
_isMainFrame() {
return this._targetId === this._crPage._targetId;
@@ -413,7 +397,7 @@ class FrameSession {
// Note: it is important to start video recorder before sending Page.startScreencast,
// and it is equally important to send Page.startScreencast before sending Runtime.runIfWaitingForDebugger.
await this._createVideoRecorder(screencastId, screencastOptions);
this._crPage.pageOrError().then(p => {
this._crPage._page.waitForInitializedOrError().then(p => {
if (p instanceof Error) this._stopVideoRecording().catch(() => {});
});
}
@@ -614,6 +598,9 @@ class FrameSession {
const frame = this._page._frameManager.frame(targetId);
if (!frame) return; // Subtree may be already gone due to renderer/browser race.
this._page._frameManager.removeChildFramesRecursively(frame);
for (const [contextId, context] of this._contextIdToContext) {
if (context.frame === frame) this._onExecutionContextDestroyed(contextId);
}
const frameSession = new FrameSession(this._crPage, session, targetId, this);
this._crPage._sessions.set(targetId, frameSession);
frameSession._initialize(false).catch(e => e);
@@ -704,7 +691,7 @@ class FrameSession {
this._page._addConsoleMessage(event.type, values, (0, _crProtocolHelper.toConsoleMessageLocation)(event.stackTrace));
}
async _onBindingCalled(event) {
const pageOrError = await this._crPage.pageOrError();
const pageOrError = await this._crPage._page.waitForInitializedOrError();
if (!(pageOrError instanceof Error)) {
const context = this._contextIdToContext.get(event.executionContextId);
if (context) await this._page._onBindingCalled(event.payload, context);
@@ -762,8 +749,7 @@ class FrameSession {
await this._page._onFileChooserOpened(handle);
}
_willBeginDownload() {
const originPage = this._crPage._initializedPage;
if (!originPage) {
if (!this._crPage._page.initializedOrUndefined()) {
// Resume the page creation with an error. The page will automatically close right
// after the download begins.
this._firstNonInitialNavigationCommittedReject(new Error('Starting new page download'));
@@ -802,7 +788,7 @@ class FrameSession {
});
// Wait for the first frame before reporting video to the client.
gotFirstFrame.then(() => {
this._crPage._browserContext._browser._videoStarted(this._crPage._browserContext, screencastId, options.outputFile, this._crPage.pageOrError());
this._crPage._browserContext._browser._videoStarted(this._crPage._browserContext, screencastId, options.outputFile, this._crPage._page.waitForInitializedOrError());
});
}
async _stopVideoRecording() {

View File

@@ -151,7 +151,12 @@ class CSharpLanguageGenerator {
using var playwright = await Playwright.CreateAsync();
await using var browser = await playwright.${toPascal(options.browserName)}.LaunchAsync(${formatObject(options.launchOptions, ' ', 'BrowserTypeLaunchOptions')});
var context = await browser.NewContextAsync(${formatContextOptions(options.contextOptions, options.deviceName)});`);
if (options.contextOptions.recordHar) formatter.add(` await context.RouteFromHARAsync(${quote(options.contextOptions.recordHar.path)});`);
if (options.contextOptions.recordHar) {
const url = options.contextOptions.recordHar.urlFilter;
formatter.add(` await context.RouteFromHARAsync(${quote(options.contextOptions.recordHar.path)}${url ? `, ${formatObject({
url
}, ' ', 'BrowserContextRouteFromHAROptions')}` : ''});`);
}
formatter.newLine();
return formatter.format();
}
@@ -176,7 +181,12 @@ class CSharpLanguageGenerator {
formatter.add(` [${this._mode === 'nunit' ? 'Test' : 'TestMethod'}]
public async Task MyTest()
{`);
if (options.contextOptions.recordHar) formatter.add(` await context.RouteFromHARAsync(${quote(options.contextOptions.recordHar.path)});`);
if (options.contextOptions.recordHar) {
const url = options.contextOptions.recordHar.urlFilter;
formatter.add(` await Context.RouteFromHARAsync(${quote(options.contextOptions.recordHar.path)}${url ? `, ${formatObject({
url
}, ' ', 'BrowserContextRouteFromHAROptions')}` : ''});`);
}
return formatter.format();
}
generateFooter(saveStorage) {

View File

@@ -135,27 +135,38 @@ class JavaLanguageGenerator {
import com.microsoft.playwright.Page;
import com.microsoft.playwright.options.*;
import org.junit.jupiter.api.*;
${options.contextOptions.recordHar ? `import java.nio.file.Paths;\n` : ''}import org.junit.jupiter.api.*;
import static com.microsoft.playwright.assertions.PlaywrightAssertions.*;
@UsePlaywright
public class TestExample {
@Test
void test(Page page) {`);
if (options.contextOptions.recordHar) {
const url = options.contextOptions.recordHar.urlFilter;
const recordHarOptions = typeof url === 'string' ? `, new Page.RouteFromHAROptions()
.setUrl(${quote(url)})` : '';
formatter.add(` page.routeFromHAR(Paths.get(${quote(options.contextOptions.recordHar.path)})${recordHarOptions});`);
}
return formatter.format();
}
formatter.add(`
import com.microsoft.playwright.*;
import com.microsoft.playwright.options.*;
import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
import java.util.*;
${options.contextOptions.recordHar ? `import java.nio.file.Paths;\n` : ''}import java.util.*;
public class Example {
public static void main(String[] args) {
try (Playwright playwright = Playwright.create()) {
Browser browser = playwright.${options.browserName}().launch(${formatLaunchOptions(options.launchOptions)});
BrowserContext context = browser.newContext(${formatContextOptions(options.contextOptions, options.deviceName)});`);
if (options.contextOptions.recordHar) formatter.add(` context.routeFromHAR(${quote(options.contextOptions.recordHar.path)});`);
if (options.contextOptions.recordHar) {
const url = options.contextOptions.recordHar.urlFilter;
const recordHarOptions = typeof url === 'string' ? `, new BrowserContext.RouteFromHAROptions()
.setUrl(${quote(url)})` : '';
formatter.add(` context.routeFromHAR(Paths.get(${quote(options.contextOptions.recordHar.path)})${recordHarOptions});`);
}
return formatter.format();
}
generateFooter(saveStorage) {

View File

@@ -106,7 +106,10 @@ class JavaScriptLanguageGenerator {
return `${this._isTest ? '' : '// '}await expect(${subject}.${this._asLocator(action.selector)}).${assertion};`;
}
case 'assertSnapshot':
return `${this._isTest ? '' : '// '}await expect(${subject}.${this._asLocator(action.selector)}).toMatchAriaSnapshot(${quoteMultiline(action.snapshot)});`;
{
const commentIfNeeded = this._isTest ? '' : '// ';
return `${commentIfNeeded}await expect(${subject}.${this._asLocator(action.selector)}).toMatchAriaSnapshot(${quoteMultiline(action.snapshot, `${commentIfNeeded} `)});`;
}
}
}
_asLocator(selector) {
@@ -127,7 +130,12 @@ class JavaScriptLanguageGenerator {
import { test, expect${options.deviceName ? ', devices' : ''} } from '@playwright/test';
${useText ? '\ntest.use(' + useText + ');\n' : ''}
test('test', async ({ page }) => {`);
if (options.contextOptions.recordHar) formatter.add(` await page.routeFromHAR(${quote(options.contextOptions.recordHar.path)});`);
if (options.contextOptions.recordHar) {
const url = options.contextOptions.recordHar.urlFilter;
formatter.add(` await page.routeFromHAR(${quote(options.contextOptions.recordHar.path)}${url ? `, ${formatOptions({
url
}, false)}` : ''});`);
}
return formatter.format();
}
generateTestFooter(saveStorage) {

View File

@@ -32,7 +32,7 @@ class JsonlLanguageGenerator {
const locator = actionInContext.action.selector ? JSON.parse((0, _utils.asLocator)('jsonl', actionInContext.action.selector)) : undefined;
const entry = {
...actionInContext.action,
pageAlias: actionInContext.frame.pageAlias,
...actionInContext.frame,
locator
};
return JSON.stringify(entry);

View File

@@ -123,6 +123,7 @@ class PythonLanguageGenerator {
}
generateHeader(options) {
const formatter = new PythonFormatter();
const recordHar = options.contextOptions.recordHar;
if (this._isPyTest) {
const contextOptions = formatContextOptions(options.contextOptions, options.deviceName, true /* asDict */);
const fixture = contextOptions ? `
@@ -132,12 +133,12 @@ def browser_context_args(browser_context_args, playwright) {
return {${contextOptions}}
}
` : '';
formatter.add(`${options.deviceName ? 'import pytest\n' : ''}import re
formatter.add(`${options.deviceName || contextOptions ? 'import pytest\n' : ''}import re
from playwright.sync_api import Page, expect
${fixture}
def test_example(page: Page) -> None {`);
if (options.contextOptions.recordHar) formatter.add(` page.route_from_har(${quote(options.contextOptions.recordHar.path)})`);
if (recordHar) formatter.add(` page.route_from_har(${quote(recordHar.path)}${typeof recordHar.urlFilter === 'string' ? `, url=${quote(recordHar.urlFilter)}` : ''})`);
} else if (this._isAsync) {
formatter.add(`
import asyncio
@@ -148,7 +149,7 @@ from playwright.async_api import Playwright, async_playwright, expect
async def run(playwright: Playwright) -> None {
browser = await playwright.${options.browserName}.launch(${formatOptions(options.launchOptions, false)})
context = await browser.new_context(${formatContextOptions(options.contextOptions, options.deviceName)})`);
if (options.contextOptions.recordHar) formatter.add(` await page.route_from_har(${quote(options.contextOptions.recordHar.path)})`);
if (recordHar) formatter.add(` await context.route_from_har(${quote(recordHar.path)}${typeof recordHar.urlFilter === 'string' ? `, url=${quote(recordHar.urlFilter)}` : ''})`);
} else {
formatter.add(`
import re
@@ -158,7 +159,7 @@ from playwright.sync_api import Playwright, sync_playwright, expect
def run(playwright: Playwright) -> None {
browser = playwright.${options.browserName}.launch(${formatOptions(options.launchOptions, false)})
context = browser.new_context(${formatContextOptions(options.contextOptions, options.deviceName)})`);
if (options.contextOptions.recordHar) formatter.add(` context.route_from_har(${quote(options.contextOptions.recordHar.path)})`);
if (recordHar) formatter.add(` context.route_from_har(${quote(recordHar.path)}${typeof recordHar.urlFilter === 'string' ? `, url=${quote(recordHar.urlFilter)}` : ''})`);
}
return formatter.format();
}

View File

@@ -9,7 +9,9 @@ var _instrumentation = require("./instrumentation");
var _recorder = require("./recorder");
var _recorderApp = require("./recorder/recorderApp");
var _utils = require("../utils");
var _ariaSnapshot = require("./ariaSnapshot");
var _utilsBundle = require("../utilsBundle");
var _locatorParser = require("../utils/isomorphic/locatorParser");
var _ariaSnapshot = require("../utils/isomorphic/ariaSnapshot");
/**
* Copyright (c) Microsoft Corporation.
*
@@ -35,9 +37,6 @@ class DebugController extends _instrumentation.SdkObject {
},
instrumentation: (0, _instrumentation.createInstrumentation)()
}, undefined, 'DebugController');
this._autoCloseTimer = void 0;
// TODO: remove in 1.27
this._autoCloseAllowed = false;
this._trackHierarchyListener = void 0;
this._playwright = void 0;
this._sdkLanguage = 'javascript';
@@ -48,20 +47,17 @@ class DebugController extends _instrumentation.SdkObject {
this._codegenId = codegenId;
this._sdkLanguage = sdkLanguage;
}
setAutoCloseAllowed(allowed) {
this._autoCloseAllowed = allowed;
}
dispose() {
this.setReportStateChanged(false);
this.setAutoCloseAllowed(false);
}
setReportStateChanged(enabled) {
if (enabled && !this._trackHierarchyListener) {
this._trackHierarchyListener = {
onPageOpen: () => this._emitSnapshot(),
onPageClose: () => this._emitSnapshot()
onPageOpen: () => this._emitSnapshot(false),
onPageClose: () => this._emitSnapshot(false)
};
this._playwright.instrumentation.addListener(this._trackHierarchyListener, null);
this._emitSnapshot(true);
} else if (!enabled && this._trackHierarchyListener) {
this._playwright.instrumentation.removeListener(this._trackHierarchyListener);
this._trackHierarchyListener = undefined;
@@ -83,7 +79,6 @@ class DebugController extends _instrumentation.SdkObject {
recorder.hideHighlightedSelector();
recorder.setMode('none');
}
this.setAutoCloseEnabled(true);
return;
}
if (!this._playwright.allBrowsers().length) await this._playwright.chromium.launch(internalMetadata, {
@@ -108,20 +103,13 @@ class DebugController extends _instrumentation.SdkObject {
if (params.mode !== 'inspecting') recorder.setOutput(this._codegenId, params.file);
recorder.setMode(params.mode);
}
this.setAutoCloseEnabled(true);
}
async setAutoCloseEnabled(enabled) {
if (!this._autoCloseAllowed) return;
if (this._autoCloseTimer) clearTimeout(this._autoCloseTimer);
if (!enabled) return;
const heartBeat = () => {
if (!this._playwright.allPages().length) (0, _processLauncher.gracefullyProcessExitDoNotHang)(0);else this._autoCloseTimer = setTimeout(heartBeat, 5000);
};
this._autoCloseTimer = setTimeout(heartBeat, 30000);
}
async highlight(params) {
// Assert parameters validity.
if (params.selector) (0, _locatorParser.unsafeLocatorOrSelectorAsSelector)(this._sdkLanguage, params.selector, 'data-testid');
const ariaTemplate = params.ariaTemplate ? (0, _ariaSnapshot.parseAriaSnapshotUnsafe)(_utilsBundle.yaml, params.ariaTemplate) : undefined;
for (const recorder of await this._allRecorders()) {
if (params.ariaTemplate) recorder.setHighlightedAriaTemplate((0, _ariaSnapshot.parseYamlForAriaSnapshot)(params.ariaTemplate));else if (params.selector) recorder.setHighlightedSelector(this._sdkLanguage, params.selector);
if (ariaTemplate) recorder.setHighlightedAriaTemplate(ariaTemplate);else if (params.selector) recorder.setHighlightedSelector(this._sdkLanguage, params.selector);
}
}
async hideHighlight() {
@@ -144,23 +132,9 @@ class DebugController extends _instrumentation.SdkObject {
reason: 'Close all browsers requested'
})));
}
_emitSnapshot() {
const browsers = [];
let pageCount = 0;
for (const browser of this._playwright.allBrowsers()) {
const b = {
contexts: []
};
browsers.push(b);
for (const context of browser.contexts()) {
const c = {
pages: []
};
b.contexts.push(c);
for (const page of context.pages()) c.pages.push(page.mainFrame().url());
pageCount += context.pages().length;
}
}
_emitSnapshot(initial) {
const pageCount = this._playwright.allPages().length;
if (initial && !pageCount) return;
this.emit(DebugController.Events.StateChanged, {
pageCount
});
@@ -204,7 +178,8 @@ class InspectingRecorderApp extends _recorderApp.EmptyRecorderApp {
const locator = (0, _utils.asLocator)(this._debugController._sdkLanguage, elementInfo.selector);
this._debugController.emit(DebugController.Events.InspectRequested, {
selector: elementInfo.selector,
locator
locator,
ariaSnapshot: elementInfo.ariaSnapshot
});
}
async setSources(sources) {

View File

@@ -121,7 +121,7 @@ function shouldPauseBeforeStep(metadata) {
if (!metadata.apiName) return false;
// Always stop on 'close'
if (metadata.method === 'close') return true;
if (metadata.method === 'waitForSelector' || metadata.method === 'waitForEventInfo') return false; // Never stop on those, primarily for the test harness.
if (metadata.method === 'waitForSelector' || metadata.method === 'waitForEventInfo' || metadata.method === 'querySelector' || metadata.method === 'querySelectorAll') return false; // Never stop on those, primarily for the test harness.
const step = metadata.type + '.' + metadata.method;
// Stop before everything that generates snapshot. But don't stop before those marked as pausesBeforeInputActions
// since we stop in them on a separate instrumentation signal.

View File

@@ -110,7 +110,7 @@
"defaultBrowserType": "webkit"
},
"Galaxy S5": {
"userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.16 Mobile Safari/537.36",
"viewport": {
"width": 360,
"height": 640
@@ -121,7 +121,7 @@
"defaultBrowserType": "chromium"
},
"Galaxy S5 landscape": {
"userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.16 Mobile Safari/537.36",
"viewport": {
"width": 640,
"height": 360
@@ -132,7 +132,7 @@
"defaultBrowserType": "chromium"
},
"Galaxy S8": {
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.16 Mobile Safari/537.36",
"viewport": {
"width": 360,
"height": 740
@@ -143,7 +143,7 @@
"defaultBrowserType": "chromium"
},
"Galaxy S8 landscape": {
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.16 Mobile Safari/537.36",
"viewport": {
"width": 740,
"height": 360
@@ -154,7 +154,7 @@
"defaultBrowserType": "chromium"
},
"Galaxy S9+": {
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.16 Mobile Safari/537.36",
"viewport": {
"width": 320,
"height": 658
@@ -165,7 +165,7 @@
"defaultBrowserType": "chromium"
},
"Galaxy S9+ landscape": {
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.16 Mobile Safari/537.36",
"viewport": {
"width": 658,
"height": 320
@@ -176,7 +176,7 @@
"defaultBrowserType": "chromium"
},
"Galaxy Tab S4": {
"userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.16 Safari/537.36",
"viewport": {
"width": 712,
"height": 1138
@@ -187,7 +187,7 @@
"defaultBrowserType": "chromium"
},
"Galaxy Tab S4 landscape": {
"userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.16 Safari/537.36",
"viewport": {
"width": 1138,
"height": 712
@@ -1098,7 +1098,7 @@
"defaultBrowserType": "webkit"
},
"LG Optimus L70": {
"userAgent": "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/131.0.6778.33 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/133.0.6943.16 Mobile Safari/537.36",
"viewport": {
"width": 384,
"height": 640
@@ -1109,7 +1109,7 @@
"defaultBrowserType": "chromium"
},
"LG Optimus L70 landscape": {
"userAgent": "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/131.0.6778.33 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/133.0.6943.16 Mobile Safari/537.36",
"viewport": {
"width": 640,
"height": 384
@@ -1120,7 +1120,7 @@
"defaultBrowserType": "chromium"
},
"Microsoft Lumia 550": {
"userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Mobile Safari/537.36 Edge/14.14263",
"userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.16 Mobile Safari/537.36 Edge/14.14263",
"viewport": {
"width": 640,
"height": 360
@@ -1131,7 +1131,7 @@
"defaultBrowserType": "chromium"
},
"Microsoft Lumia 550 landscape": {
"userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Mobile Safari/537.36 Edge/14.14263",
"userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.16 Mobile Safari/537.36 Edge/14.14263",
"viewport": {
"width": 360,
"height": 640
@@ -1142,7 +1142,7 @@
"defaultBrowserType": "chromium"
},
"Microsoft Lumia 950": {
"userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Mobile Safari/537.36 Edge/14.14263",
"userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.16 Mobile Safari/537.36 Edge/14.14263",
"viewport": {
"width": 360,
"height": 640
@@ -1153,7 +1153,7 @@
"defaultBrowserType": "chromium"
},
"Microsoft Lumia 950 landscape": {
"userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Mobile Safari/537.36 Edge/14.14263",
"userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.16 Mobile Safari/537.36 Edge/14.14263",
"viewport": {
"width": 640,
"height": 360
@@ -1164,7 +1164,7 @@
"defaultBrowserType": "chromium"
},
"Nexus 10": {
"userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.16 Safari/537.36",
"viewport": {
"width": 800,
"height": 1280
@@ -1175,7 +1175,7 @@
"defaultBrowserType": "chromium"
},
"Nexus 10 landscape": {
"userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.16 Safari/537.36",
"viewport": {
"width": 1280,
"height": 800
@@ -1186,7 +1186,7 @@
"defaultBrowserType": "chromium"
},
"Nexus 4": {
"userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.16 Mobile Safari/537.36",
"viewport": {
"width": 384,
"height": 640
@@ -1197,7 +1197,7 @@
"defaultBrowserType": "chromium"
},
"Nexus 4 landscape": {
"userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.16 Mobile Safari/537.36",
"viewport": {
"width": 640,
"height": 384
@@ -1208,7 +1208,7 @@
"defaultBrowserType": "chromium"
},
"Nexus 5": {
"userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.16 Mobile Safari/537.36",
"viewport": {
"width": 360,
"height": 640
@@ -1219,7 +1219,7 @@
"defaultBrowserType": "chromium"
},
"Nexus 5 landscape": {
"userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.16 Mobile Safari/537.36",
"viewport": {
"width": 640,
"height": 360
@@ -1230,7 +1230,7 @@
"defaultBrowserType": "chromium"
},
"Nexus 5X": {
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.16 Mobile Safari/537.36",
"viewport": {
"width": 412,
"height": 732
@@ -1241,7 +1241,7 @@
"defaultBrowserType": "chromium"
},
"Nexus 5X landscape": {
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.16 Mobile Safari/537.36",
"viewport": {
"width": 732,
"height": 412
@@ -1252,7 +1252,7 @@
"defaultBrowserType": "chromium"
},
"Nexus 6": {
"userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.16 Mobile Safari/537.36",
"viewport": {
"width": 412,
"height": 732
@@ -1263,7 +1263,7 @@
"defaultBrowserType": "chromium"
},
"Nexus 6 landscape": {
"userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.16 Mobile Safari/537.36",
"viewport": {
"width": 732,
"height": 412
@@ -1274,7 +1274,7 @@
"defaultBrowserType": "chromium"
},
"Nexus 6P": {
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.16 Mobile Safari/537.36",
"viewport": {
"width": 412,
"height": 732
@@ -1285,7 +1285,7 @@
"defaultBrowserType": "chromium"
},
"Nexus 6P landscape": {
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.16 Mobile Safari/537.36",
"viewport": {
"width": 732,
"height": 412
@@ -1296,7 +1296,7 @@
"defaultBrowserType": "chromium"
},
"Nexus 7": {
"userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.16 Safari/537.36",
"viewport": {
"width": 600,
"height": 960
@@ -1307,7 +1307,7 @@
"defaultBrowserType": "chromium"
},
"Nexus 7 landscape": {
"userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.16 Safari/537.36",
"viewport": {
"width": 960,
"height": 600
@@ -1362,7 +1362,7 @@
"defaultBrowserType": "webkit"
},
"Pixel 2": {
"userAgent": "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.16 Mobile Safari/537.36",
"viewport": {
"width": 411,
"height": 731
@@ -1373,7 +1373,7 @@
"defaultBrowserType": "chromium"
},
"Pixel 2 landscape": {
"userAgent": "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.16 Mobile Safari/537.36",
"viewport": {
"width": 731,
"height": 411
@@ -1384,7 +1384,7 @@
"defaultBrowserType": "chromium"
},
"Pixel 2 XL": {
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.16 Mobile Safari/537.36",
"viewport": {
"width": 411,
"height": 823
@@ -1395,7 +1395,7 @@
"defaultBrowserType": "chromium"
},
"Pixel 2 XL landscape": {
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.16 Mobile Safari/537.36",
"viewport": {
"width": 823,
"height": 411
@@ -1406,7 +1406,7 @@
"defaultBrowserType": "chromium"
},
"Pixel 3": {
"userAgent": "Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.16 Mobile Safari/537.36",
"viewport": {
"width": 393,
"height": 786
@@ -1417,7 +1417,7 @@
"defaultBrowserType": "chromium"
},
"Pixel 3 landscape": {
"userAgent": "Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.16 Mobile Safari/537.36",
"viewport": {
"width": 786,
"height": 393
@@ -1428,7 +1428,7 @@
"defaultBrowserType": "chromium"
},
"Pixel 4": {
"userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.16 Mobile Safari/537.36",
"viewport": {
"width": 353,
"height": 745
@@ -1439,7 +1439,7 @@
"defaultBrowserType": "chromium"
},
"Pixel 4 landscape": {
"userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.16 Mobile Safari/537.36",
"viewport": {
"width": 745,
"height": 353
@@ -1450,7 +1450,7 @@
"defaultBrowserType": "chromium"
},
"Pixel 4a (5G)": {
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.16 Mobile Safari/537.36",
"screen": {
"width": 412,
"height": 892
@@ -1465,7 +1465,7 @@
"defaultBrowserType": "chromium"
},
"Pixel 4a (5G) landscape": {
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.16 Mobile Safari/537.36",
"screen": {
"height": 892,
"width": 412
@@ -1480,7 +1480,7 @@
"defaultBrowserType": "chromium"
},
"Pixel 5": {
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.16 Mobile Safari/537.36",
"screen": {
"width": 393,
"height": 851
@@ -1495,7 +1495,7 @@
"defaultBrowserType": "chromium"
},
"Pixel 5 landscape": {
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.16 Mobile Safari/537.36",
"screen": {
"width": 851,
"height": 393
@@ -1510,7 +1510,7 @@
"defaultBrowserType": "chromium"
},
"Pixel 7": {
"userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.16 Mobile Safari/537.36",
"screen": {
"width": 412,
"height": 915
@@ -1525,7 +1525,7 @@
"defaultBrowserType": "chromium"
},
"Pixel 7 landscape": {
"userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.16 Mobile Safari/537.36",
"screen": {
"width": 915,
"height": 412
@@ -1540,7 +1540,7 @@
"defaultBrowserType": "chromium"
},
"Moto G4": {
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.16 Mobile Safari/537.36",
"viewport": {
"width": 360,
"height": 640
@@ -1551,7 +1551,7 @@
"defaultBrowserType": "chromium"
},
"Moto G4 landscape": {
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.16 Mobile Safari/537.36",
"viewport": {
"width": 640,
"height": 360
@@ -1562,7 +1562,7 @@
"defaultBrowserType": "chromium"
},
"Desktop Chrome HiDPI": {
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Safari/537.36",
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.16 Safari/537.36",
"screen": {
"width": 1792,
"height": 1120
@@ -1577,7 +1577,7 @@
"defaultBrowserType": "chromium"
},
"Desktop Edge HiDPI": {
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Safari/537.36 Edg/131.0.6778.33",
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.16 Safari/537.36 Edg/133.0.6943.16",
"screen": {
"width": 1792,
"height": 1120
@@ -1592,7 +1592,7 @@
"defaultBrowserType": "chromium"
},
"Desktop Firefox HiDPI": {
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:132.0) Gecko/20100101 Firefox/132.0",
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:134.0) Gecko/20100101 Firefox/134.0",
"screen": {
"width": 1792,
"height": 1120
@@ -1622,7 +1622,7 @@
"defaultBrowserType": "webkit"
},
"Desktop Chrome": {
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Safari/537.36",
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.16 Safari/537.36",
"screen": {
"width": 1920,
"height": 1080
@@ -1637,7 +1637,7 @@
"defaultBrowserType": "chromium"
},
"Desktop Edge": {
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Safari/537.36 Edg/131.0.6778.33",
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.16 Safari/537.36 Edg/133.0.6943.16",
"screen": {
"width": 1920,
"height": 1080
@@ -1652,7 +1652,7 @@
"defaultBrowserType": "chromium"
},
"Desktop Firefox": {
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:132.0) Gecko/20100101 Firefox/132.0",
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:134.0) Gecko/20100101 Firefox/134.0",
"screen": {
"width": 1920,
"height": 1080

View File

@@ -20,7 +20,6 @@ var _writableStreamDispatcher = require("./writableStreamDispatcher");
var _dialogDispatcher = require("./dialogDispatcher");
var _errors = require("../errors");
var _elementHandlerDispatcher = require("./elementHandlerDispatcher");
var _recorderInTraceViewer = require("../recorder/recorderInTraceViewer");
var _recorderApp = require("../recorder/recorderApp");
var _webSocketRouteDispatcher = require("./webSocketRouteDispatcher");
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
@@ -284,7 +283,7 @@ class BrowserContextDispatcher extends _dispatcher.Dispatcher {
}
async setWebSocketInterceptionPatterns(params, metadata) {
this._webSocketInterceptionPatterns = params.patterns;
if (params.patterns.length) await _webSocketRouteDispatcher.WebSocketRouteDispatcher.installIfNeeded(this, this._context);
if (params.patterns.length) await _webSocketRouteDispatcher.WebSocketRouteDispatcher.installIfNeeded(this._context);
}
async storageState(params, metadata) {
return await this._context.storageState();
@@ -294,17 +293,7 @@ class BrowserContextDispatcher extends _dispatcher.Dispatcher {
await this._context.close(params);
}
async enableRecorder(params) {
if (params.codegenMode === 'trace-events') {
await this._context.tracing.start({
name: 'trace',
snapshots: true,
screenshots: true,
live: true
});
await _recorder.Recorder.show('trace-events', this._context, _recorderInTraceViewer.RecorderInTraceViewer.factory(this._context), params);
} else {
await _recorder.Recorder.show('actions', this._context, _recorderApp.RecorderApp.factory(this._context), params);
}
await _recorder.Recorder.show(this._context, _recorderApp.RecorderApp.factory(this._context), params);
}
async pause(params, metadata) {
// Debugger will take care of this.

View File

@@ -33,11 +33,13 @@ class DebugControllerDispatcher extends _dispatcher.Dispatcher {
this._dispatchEvent('stateChanged', params);
}), _utils.eventsHelper.addEventListener(this._object, _debugController.DebugController.Events.InspectRequested, ({
selector,
locator
locator,
ariaSnapshot
}) => {
this._dispatchEvent('inspectRequested', {
selector,
locator
locator,
ariaSnapshot
});
}), _utils.eventsHelper.addEventListener(this._object, _debugController.DebugController.Events.SourceChanged, ({
text,

View File

@@ -10,7 +10,8 @@ var _elementHandlerDispatcher = require("./elementHandlerDispatcher");
var _jsHandleDispatcher = require("./jsHandleDispatcher");
var _networkDispatchers = require("./networkDispatchers");
var _utils = require("../../utils");
var _ariaSnapshot = require("../ariaSnapshot");
var _ariaSnapshot = require("../../utils/isomorphic/ariaSnapshot");
var _utilsBundle = require("../../utilsBundle");
/**
* Copyright (c) Microsoft Corporation.
*
@@ -276,7 +277,7 @@ class FrameDispatcher extends _dispatcher.Dispatcher {
async expect(params, metadata) {
metadata.potentiallyClosesScope = true;
let expectedValue = params.expectedValue ? (0, _jsHandleDispatcher.parseArgument)(params.expectedValue) : undefined;
if (params.expression === 'to.match.aria' && expectedValue) expectedValue = (0, _ariaSnapshot.parseAriaSnapshot)(expectedValue);
if (params.expression === 'to.match.aria' && expectedValue) expectedValue = (0, _ariaSnapshot.parseAriaSnapshotUnsafe)(_utilsBundle.yaml, expectedValue);
const result = await this._frame.expect(metadata, params.selector, {
...params,
expectedValue

View File

@@ -181,7 +181,7 @@ class PageDispatcher extends _dispatcher.Dispatcher {
}
async setWebSocketInterceptionPatterns(params, metadata) {
this._webSocketInterceptionPatterns = params.patterns;
if (params.patterns.length) await _webSocketRouteDispatcher.WebSocketRouteDispatcher.installIfNeeded(this.parentScope(), this._page);
if (params.patterns.length) await _webSocketRouteDispatcher.WebSocketRouteDispatcher.installIfNeeded(this._page);
}
async expectScreenshot(params, metadata) {
const mask = (params.mask || []).map(({

View File

@@ -28,8 +28,6 @@ var _class;
*/
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
const kBindingInstalledSymbol = Symbol('webSocketRouteBindingInstalled');
const kInitScriptInstalledSymbol = Symbol('webSocketRouteInitScriptInstalled');
class WebSocketRouteDispatcher extends _dispatcher.Dispatcher {
constructor(scope, id, url, frame) {
super(scope, {
@@ -55,15 +53,16 @@ class WebSocketRouteDispatcher extends _dispatcher.Dispatcher {
webSocketRoute: this
});
}
static async installIfNeeded(contextDispatcher, target) {
static async installIfNeeded(target) {
const kBindingName = '__pwWebSocketBinding';
const context = target instanceof _page.Page ? target.context() : target;
if (!context[kBindingInstalledSymbol]) {
context[kBindingInstalledSymbol] = true;
await context.exposeBinding('__pwWebSocketBinding', false, (source, payload) => {
if (!context.hasBinding(kBindingName)) {
await context.exposeBinding(kBindingName, false, (source, payload) => {
if (payload.type === 'onCreate') {
const pageDispatcher = _pageDispatcher.PageDispatcher.fromNullable(contextDispatcher, source.page);
const contextDispatcher = (0, _dispatcher.existingDispatcher)(context);
const pageDispatcher = contextDispatcher ? _pageDispatcher.PageDispatcher.fromNullable(contextDispatcher, source.page) : undefined;
let scope;
if (pageDispatcher && matchesPattern(pageDispatcher, context._options.baseURL, payload.url)) scope = pageDispatcher;else if (matchesPattern(contextDispatcher, context._options.baseURL, payload.url)) scope = contextDispatcher;
if (pageDispatcher && matchesPattern(pageDispatcher, context._options.baseURL, payload.url)) scope = pageDispatcher;else if (contextDispatcher && matchesPattern(contextDispatcher, context._options.baseURL, payload.url)) scope = contextDispatcher;
if (scope) {
new WebSocketRouteDispatcher(scope, payload.id, payload.url, source.frame);
} else {
@@ -96,15 +95,15 @@ class WebSocketRouteDispatcher extends _dispatcher.Dispatcher {
});
});
}
if (!target[kInitScriptInstalledSymbol]) {
target[kInitScriptInstalledSymbol] = true;
const kInitScriptName = 'webSocketMockSource';
if (!target.initScripts.find(s => s.name === kInitScriptName)) {
await target.addInitScript(`
(() => {
const module = {};
${webSocketMockSource.source}
(module.exports.inject())(globalThis);
})();
`);
`, kInitScriptName);
}
}
async connect(params) {

View File

@@ -7,6 +7,7 @@ exports.NonRecoverableDOMError = exports.FrameExecutionContext = exports.Element
exports.assertDone = assertDone;
exports.isNonRecoverableDOMError = isNonRecoverableDOMError;
exports.kUnableToAdoptErrorMessage = void 0;
exports.throwElementIsNotAttached = throwElementIsNotAttached;
exports.throwRetargetableDOMError = throwRetargetableDOMError;
var _fs = _interopRequireDefault(require("fs"));
var injectedScriptSource = _interopRequireWildcard(require("../generated/injectedScriptSource"));
@@ -711,7 +712,8 @@ class ElementHandle extends js.JSHandle {
async _setChecked(progress, state, options) {
const isChecked = async () => {
const result = await this.evaluateInUtility(([injected, node]) => injected.elementState(node, 'checked'), {});
return throwRetargetableDOMError(result);
if (result === 'error:notconnected' || result.received === 'error:notconnected') throwElementIsNotAttached();
return result.matches;
};
await this._markAsTargetElement(progress.metadata);
if ((await isChecked()) === state) return 'done';
@@ -837,9 +839,12 @@ class ElementHandle extends js.JSHandle {
}
exports.ElementHandle = ElementHandle;
function throwRetargetableDOMError(result) {
if (result === 'error:notconnected') throw new Error('Element is not attached to the DOM');
if (result === 'error:notconnected') throwElementIsNotAttached();
return result;
}
function throwElementIsNotAttached() {
throw new Error('Element is not attached to the DOM');
}
function assertDone(result) {
// This function converts 'done' to void and ensures typescript catches unhandled errors.
}

View File

@@ -8,6 +8,7 @@ exports.createProxyAgent = createProxyAgent;
var _http = _interopRequireDefault(require("http"));
var _https = _interopRequireDefault(require("https"));
var _stream = require("stream");
var _url = _interopRequireDefault(require("url"));
var _zlib = _interopRequireDefault(require("zlib"));
var _timeoutSettings = require("../common/timeoutSettings");
var _userAgent = require("../utils/userAgent");
@@ -388,7 +389,6 @@ class APIRequestContext extends _instrumentation.SdkObject {
}));
request.on('close', () => _utils.eventsHelper.removeEventListeners(listeners));
request.on('socket', socket => {
var _tcpConnectionAt, _tcpConnectionAt3;
if (request.reusedSocket) {
reusedSocketAt = (0, _utils.monotonicTime)();
return;
@@ -397,14 +397,13 @@ class APIRequestContext extends _instrumentation.SdkObject {
// happy eyeballs don't emit lookup and connect events, so we use our custom ones
const happyEyeBallsTimings = (0, _happyEyeballs.timingForSocket)(socket);
dnsLookupAt = happyEyeBallsTimings.dnsLookupAt;
(_tcpConnectionAt = tcpConnectionAt) !== null && _tcpConnectionAt !== void 0 ? _tcpConnectionAt : tcpConnectionAt = happyEyeBallsTimings.tcpConnectionAt;
tcpConnectionAt = happyEyeBallsTimings.tcpConnectionAt;
// non-happy-eyeballs sockets
listeners.push(_utils.eventsHelper.addEventListener(socket, 'lookup', () => {
dnsLookupAt = (0, _utils.monotonicTime)();
}), _utils.eventsHelper.addEventListener(socket, 'connect', () => {
var _tcpConnectionAt2;
(_tcpConnectionAt2 = tcpConnectionAt) !== null && _tcpConnectionAt2 !== void 0 ? _tcpConnectionAt2 : tcpConnectionAt = (0, _utils.monotonicTime)();
tcpConnectionAt = (0, _utils.monotonicTime)();
}), _utils.eventsHelper.addEventListener(socket, 'secureConnect', () => {
tlsHandshakeAt = (0, _utils.monotonicTime)();
if (socket instanceof _tls.TLSSocket) {
@@ -419,21 +418,12 @@ class APIRequestContext extends _instrumentation.SdkObject {
};
}
}));
// when using socks proxy, having the socket means the connection got established
if (agent instanceof _utilsBundle.SocksProxyAgent) (_tcpConnectionAt3 = tcpConnectionAt) !== null && _tcpConnectionAt3 !== void 0 ? _tcpConnectionAt3 : tcpConnectionAt = (0, _utils.monotonicTime)();
serverIPAddress = socket.remoteAddress;
serverPort = socket.remotePort;
});
request.on('finish', () => {
requestFinishAt = (0, _utils.monotonicTime)();
});
// http proxy
request.on('proxyConnect', () => {
var _tcpConnectionAt4;
(_tcpConnectionAt4 = tcpConnectionAt) !== null && _tcpConnectionAt4 !== void 0 ? _tcpConnectionAt4 : tcpConnectionAt = (0, _utils.monotonicTime)();
});
progress.log(`${options.method} ${url.toString()}`);
if (options.headers) {
for (const [name, value] of Object.entries(options.headers)) progress.log(` ${name}: ${value}`);
@@ -579,13 +569,17 @@ class GlobalAPIRequestContext extends APIRequestContext {
}
exports.GlobalAPIRequestContext = GlobalAPIRequestContext;
function createProxyAgent(proxy) {
var _proxyURL$protocol;
const proxyURL = new URL(proxy.server);
if ((_proxyURL$protocol = proxyURL.protocol) !== null && _proxyURL$protocol !== void 0 && _proxyURL$protocol.startsWith('socks')) return new _utilsBundle.SocksProxyAgent(proxyURL);
if (proxy.username) proxyURL.username = proxy.username;
if (proxy.password) proxyURL.password = proxy.password;
// TODO: We should use HttpProxyAgent conditional on proxyURL.protocol instead of always using CONNECT method.
return new _utilsBundle.HttpsProxyAgent(proxyURL);
var _proxyOpts$protocol;
const proxyOpts = _url.default.parse(proxy.server);
if ((_proxyOpts$protocol = proxyOpts.protocol) !== null && _proxyOpts$protocol !== void 0 && _proxyOpts$protocol.startsWith('socks')) {
return new _utilsBundle.SocksProxyAgent({
host: proxyOpts.hostname,
port: proxyOpts.port || undefined
});
}
if (proxy.username) proxyOpts.auth = `${proxy.username}:${proxy.password || ''}`;
// TODO: We should use HttpProxyAgent conditional on proxyOpts.protocol instead of always using CONNECT method.
return new _utilsBundle.HttpsProxyAgent(proxyOpts);
}
function toHeadersArray(rawHeaders) {
const result = [];

View File

@@ -132,13 +132,13 @@ class FFBrowser extends _browser.Browser {
// Abort the navigation that turned into download.
ffPage._page._frameManager.frameAbortedNavigation(payload.frameId, 'Download is starting');
let originPage = ffPage._initializedPage;
let originPage = ffPage._page.initializedOrUndefined();
// If it's a new window download, report it on the opener page.
if (!originPage) {
// Resume the page creation with an error. The page will automatically close right
// after the download begins.
ffPage._markAsError(new Error('Starting new page download'));
if (ffPage._opener) originPage = ffPage._opener._initializedPage;
if (ffPage._opener) originPage = ffPage._opener._page.initializedOrUndefined();
}
if (!originPage) return;
this._downloadCreated(originPage, payload.uuid, payload.url, payload.suggestedFileName);
@@ -268,10 +268,10 @@ class FFBrowserContext extends _browserContext.BrowserContext {
_ffPages() {
return Array.from(this._browser._ffPages.values()).filter(ffPage => ffPage._browserContext === this);
}
pages() {
return this._ffPages().map(ffPage => ffPage._initializedPage).filter(pageOrNull => !!pageOrNull);
possiblyUninitializedPages() {
return this._ffPages().map(ffPage => ffPage._page);
}
async newPageDelegate() {
async doCreateNewPage() {
(0, _browserContext.assertBrowserContextIsNotOwned)(this);
const {
targetId
@@ -281,7 +281,7 @@ class FFBrowserContext extends _browserContext.BrowserContext {
if (e.message.includes('Failed to override timezone')) throw new Error(`Invalid timezone ID: ${this._options.timezoneId}`);
throw e;
});
return this._browser._ffPages.get(targetId);
return this._browser._ffPages.get(targetId)._page;
}
async doGetCookies(urls) {
const {
@@ -457,4 +457,6 @@ function toJugglerProxyOptions(proxy) {
// Prefs for quick fixes that didn't make it to the build.
// Should all be moved to `playwright.cfg`.
const kBandaidFirefoxUserPrefs = {};
const kBandaidFirefoxUserPrefs = {
'dom.fetchKeepalive.enabled': false
};

View File

@@ -47,12 +47,18 @@ class RawKeyboardImpl {
this._client = void 0;
this._client = client;
}
async keydown(modifiers, code, keyCode, keyCodeWithoutLocation, key, location, autoRepeat, text) {
async keydown(modifiers, keyName, description, autoRepeat) {
let text = description.text;
// Firefox will figure out Enter by itself
if (text === '\r') text = '';
const {
code,
key,
location
} = description;
await this._client.send('Page.dispatchKeyEvent', {
type: 'keydown',
keyCode: keyCodeWithoutLocation,
keyCode: description.keyCodeWithoutLocation,
code,
key,
repeat: autoRepeat,
@@ -60,11 +66,16 @@ class RawKeyboardImpl {
text
});
}
async keyup(modifiers, code, keyCode, keyCodeWithoutLocation, key, location) {
async keyup(modifiers, keyName, description) {
const {
code,
key,
location
} = description;
await this._client.send('Page.dispatchKeyEvent', {
type: 'keyup',
key,
keyCode: keyCodeWithoutLocation,
keyCode: description.keyCodeWithoutLocation,
code,
location,
repeat: false

View File

@@ -15,7 +15,6 @@ var _ffInput = require("./ffInput");
var _ffNetworkManager = require("./ffNetworkManager");
var _stackTrace = require("../../utils/stackTrace");
var _debugLogger = require("../../utils/debugLogger");
var _manualPromise = require("../../utils/manualPromise");
var _browserContext = require("../browserContext");
var _errors = require("../errors");
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
@@ -48,9 +47,7 @@ class FFPage {
this._page = void 0;
this._networkManager = void 0;
this._browserContext = void 0;
this._pagePromise = new _manualPromise.ManualPromise();
this._initializedPage = null;
this._initializationFailed = false;
this._reportedAsNew = false;
this._opener = void 0;
this._contextIdToContext = void 0;
this._eventListeners = void 0;
@@ -70,34 +67,22 @@ class FFPage {
this._page.on(_page.Page.Events.FrameDetached, frame => this._removeContextsForFrame(frame));
// TODO: remove Page.willOpenNewWindowAsynchronously from the protocol.
this._eventListeners = [_eventsHelper.eventsHelper.addEventListener(this._session, 'Page.eventFired', this._onEventFired.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.frameAttached', this._onFrameAttached.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.frameDetached', this._onFrameDetached.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.navigationAborted', this._onNavigationAborted.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.navigationCommitted', this._onNavigationCommitted.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.navigationStarted', this._onNavigationStarted.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.sameDocumentNavigation', this._onSameDocumentNavigation.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Runtime.executionContextCreated', this._onExecutionContextCreated.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Runtime.executionContextDestroyed', this._onExecutionContextDestroyed.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Runtime.executionContextsCleared', this._onExecutionContextsCleared.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.linkClicked', event => this._onLinkClicked(event.phase)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.uncaughtError', this._onUncaughtError.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Runtime.console', this._onConsole.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.dialogOpened', this._onDialogOpened.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.bindingCalled', this._onBindingCalled.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.fileChooserOpened', this._onFileChooserOpened.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.workerCreated', this._onWorkerCreated.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.workerDestroyed', this._onWorkerDestroyed.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.dispatchMessageFromWorker', this._onDispatchMessageFromWorker.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.crashed', this._onCrashed.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.videoRecordingStarted', this._onVideoRecordingStarted.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.webSocketCreated', this._onWebSocketCreated.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.webSocketClosed', this._onWebSocketClosed.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.webSocketFrameReceived', this._onWebSocketFrameReceived.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.webSocketFrameSent', this._onWebSocketFrameSent.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.screencastFrame', this._onScreencastFrame.bind(this))];
this._session.once('Page.ready', async () => {
await this._page.initOpener(this._opener);
if (this._initializationFailed) return;
// Note: it is important to call |reportAsNew| before resolving pageOrError promise,
// so that anyone who awaits pageOrError got a ready and reported page.
this._initializedPage = this._page;
this._page.reportAsNew();
this._pagePromise.resolve(this._page);
this._session.once('Page.ready', () => {
var _this$_opener;
if (this._reportedAsNew) return;
this._reportedAsNew = true;
this._page.reportAsNew((_this$_opener = this._opener) === null || _this$_opener === void 0 ? void 0 : _this$_opener._page);
});
// Ideally, we somehow ensure that utility world is created before Page.ready arrives, but currently it is racy.
// Therefore, we can end up with an initialized page without utility world, although very unlikely.
this.addInitScript(new _page.InitScript('', true), UTILITY_WORLD_NAME).catch(e => this._markAsError(e));
}
potentiallyUninitializedPage() {
return this._page;
}
async _markAsError(error) {
// Same error may be report twice: channer disconnected and session.send fails.
if (this._initializationFailed) return;
this._initializationFailed = true;
if (!this._initializedPage) {
await this._page.initOpener(this._opener);
this._page.reportAsNew(error);
this._pagePromise.resolve(error);
}
}
async pageOrError() {
return this._pagePromise;
var _this$_opener2;
// Same error may be reported twice: channel disconnected and session.send fails.
if (this._reportedAsNew) return;
this._reportedAsNew = true;
this._page.reportAsNew((_this$_opener2 = this._opener) === null || _this$_opener2 === void 0 ? void 0 : _this$_opener2._page, error);
}
_onWebSocketCreated(event) {
this._page._frameManager.onWebSocketCreated(webSocketId(event.frameId, event.wsid), event.requestURL);
@@ -213,7 +198,7 @@ class FFPage {
}, params.defaultValue));
}
async _onBindingCalled(event) {
const pageOrError = await this.pageOrError();
const pageOrError = await this._page.waitForInitializedOrError();
if (!(pageOrError instanceof Error)) {
const context = this._contextIdToContext.get(event.executionContextId);
if (context) await this._page._onBindingCalled(event.payload, context);
@@ -286,7 +271,7 @@ class FFPage {
this._page._didCrash();
}
_onVideoRecordingStarted(event) {
this._browserContext._browser._videoStarted(this._browserContext, event.screencastId, event.file, this.pageOrError());
this._browserContext._browser._videoStarted(this._browserContext, event.screencastId, event.file, this._page.waitForInitializedOrError());
}
didClose() {
this._markAsError(new _errors.TargetClosedError());

View File

@@ -242,9 +242,8 @@ class FrameManager {
request
});
if (request._isFavicon) {
route === null || route === void 0 || route.continue({
isFallback: true
}).catch(() => {});
// Abort favicon requests to avoid network access in case of interception.
route === null || route === void 0 || route.abort('aborted').catch(() => {});
return;
}
this._page.emitOnContext(_browserContext.BrowserContext.Events.Request, request);
@@ -806,9 +805,9 @@ class Frame extends _instrumentation.SdkObject {
return this._url;
}
origin() {
var _network$parsedURL;
var _network$parseURL;
if (!this._url.startsWith('http')) return;
return (_network$parsedURL = network.parsedURL(this._url)) === null || _network$parsedURL === void 0 ? void 0 : _network$parsedURL.origin;
return (_network$parseURL = network.parseURL(this._url)) === null || _network$parseURL === void 0 ? void 0 : _network$parseURL.origin;
}
parentFrame() {
return this._parentFrame;
@@ -1135,7 +1134,8 @@ class Frame extends _instrumentation.SdkObject {
}, {
state
}, options, scope);
return dom.throwRetargetableDOMError(result);
if (result.received === 'error:notconnected') dom.throwElementIsNotAttached();
return result.matches;
}
async isVisible(metadata, selector, options = {}, scope) {
const controller = new _progress.ProgressController(metadata, this);
@@ -1153,8 +1153,11 @@ class Frame extends _instrumentation.SdkObject {
root
}) => {
const element = injected.querySelector(info.parsed, root || document, info.strict);
const state = element ? injected.elementState(element, 'visible') : false;
return state === 'error:notconnected' ? false : state;
const state = element ? injected.elementState(element, 'visible') : {
matches: false,
received: 'error:notconnected'
};
return state.matches;
}, {
info: resolved.info,
root: resolved.frame === this ? scope : undefined
@@ -1655,16 +1658,6 @@ function verifyLifecycle(name, waitUntil) {
return waitUntil;
}
function renderUnexpectedValue(expression, received) {
if (expression === 'to.be.checked') return received ? 'checked' : 'unchecked';
if (expression === 'to.be.unchecked') return received ? 'unchecked' : 'checked';
if (expression === 'to.be.visible') return received ? 'visible' : 'hidden';
if (expression === 'to.be.hidden') return received ? 'hidden' : 'visible';
if (expression === 'to.be.enabled') return received ? 'enabled' : 'disabled';
if (expression === 'to.be.disabled') return received ? 'disabled' : 'enabled';
if (expression === 'to.be.editable') return received ? 'editable' : 'readonly';
if (expression === 'to.be.readonly') return received ? 'readonly' : 'editable';
if (expression === 'to.be.empty') return received ? 'empty' : 'not empty';
if (expression === 'to.be.focused') return received ? 'focused' : 'not focused';
if (expression === 'to.match.aria') return received ? received.raw : received;
return received;
}

View File

@@ -196,7 +196,7 @@ class HarTracer {
if (!this._shouldIncludeEntryWithUrl(request.url())) return;
const page = (_request$frame = request.frame()) === null || _request$frame === void 0 ? void 0 : _request$frame._page;
if (this._page && page !== this._page) return;
const url = network.parsedURL(request.url());
const url = network.parseURL(request.url());
if (!url) return;
const pageEntry = this._createPageEntryIfNeeded(page);
const harEntry = createHarEntry(request.method(), url, (_request$frame2 = request.frame()) === null || _request$frame2 === void 0 ? void 0 : _request$frame2.guid, this._options);

View File

@@ -40,8 +40,7 @@ class Keyboard {
const autoRepeat = this._pressedKeys.has(description.code);
this._pressedKeys.add(description.code);
if (kModifiers.includes(description.key)) this._pressedModifiers.add(description.key);
const text = description.text;
await this._raw.keydown(this._pressedModifiers, description.code, description.keyCode, description.keyCodeWithoutLocation, description.key, description.location, autoRepeat, text);
await this._raw.keydown(this._pressedModifiers, key, description, autoRepeat);
}
_keyDescriptionForString(str) {
const keyString = resolveSmartModifierString(str);
@@ -61,7 +60,7 @@ class Keyboard {
const description = this._keyDescriptionForString(key);
if (kModifiers.includes(description.key)) this._pressedModifiers.delete(description.key);
this._pressedKeys.delete(description.code);
await this._raw.keyup(this._pressedModifiers, description.code, description.keyCode, description.keyCodeWithoutLocation, description.key, description.location);
await this._raw.keyup(this._pressedModifiers, key, description);
}
async insertText(text) {
await this._raw.sendText(text);

View File

@@ -7,7 +7,7 @@ exports.WebSocket = exports.Route = exports.Response = exports.Request = void 0;
exports.filterCookies = filterCookies;
exports.kMaxCookieExpiresDateInSeconds = void 0;
exports.mergeHeaders = mergeHeaders;
exports.parsedURL = parsedURL;
exports.parseURL = parseURL;
exports.rewriteCookies = rewriteCookies;
exports.singleHeader = singleHeader;
exports.statusText = statusText;
@@ -75,7 +75,7 @@ function rewriteCookies(cookies) {
return copy;
});
}
function parsedURL(url) {
function parseURL(url) {
try {
return new URL(url);
} catch (e) {

View File

@@ -47,7 +47,8 @@ class Page extends _instrumentation.SdkObject {
super(browserContext, 'page');
this._closedState = 'open';
this._closedPromise = new _manualPromise.ManualPromise();
this._initialized = false;
this._initialized = void 0;
this._initializedPromise = new _manualPromise.ManualPromise();
this._eventsToEmitAfterInitialized = [];
this._crashed = false;
this.openScope = new _utils.LongStandingScope();
@@ -72,7 +73,6 @@ class Page extends _instrumentation.SdkObject {
this._clientRequestInterceptor = void 0;
this._serverRequestInterceptor = void 0;
this._ownedContext = void 0;
this._pageIsError = void 0;
this._video = null;
this._opener = void 0;
this._isServerSideOnly = false;
@@ -96,19 +96,21 @@ class Page extends _instrumentation.SdkObject {
if (delegate.pdf) this.pdf = delegate.pdf.bind(delegate);
this.coverage = delegate.coverage ? delegate.coverage() : null;
}
async initOpener(opener) {
if (!opener) return;
const openerPage = await opener.pageOrError();
if (openerPage instanceof Page && !openerPage.isClosed()) this._opener = openerPage;
async reportAsNew(opener, error = undefined, contextEvent = _browserContext.BrowserContext.Events.Page) {
if (opener) {
const openerPageOrError = await opener.waitForInitializedOrError();
if (openerPageOrError instanceof Page && !openerPageOrError.isClosed()) this._opener = openerPageOrError;
}
this._markInitialized(error, contextEvent);
}
reportAsNew(error = undefined, contextEvent = _browserContext.BrowserContext.Events.Page) {
_markInitialized(error = undefined, contextEvent = _browserContext.BrowserContext.Events.Page) {
if (error) {
// Initialization error could have happened because of
// context/browser closure. Just ignore the page.
if (this._browserContext.isClosingOrClosed()) return;
this._setIsError(error);
this._frameManager.createDummyMainFrameIfNeeded();
}
this._initialized = true;
this._initialized = error || this;
this.emitOnContext(contextEvent, this);
for (const {
event,
@@ -120,10 +122,17 @@ class Page extends _instrumentation.SdkObject {
// in that case we fire another Close event to ensure that each reported Page will have
// corresponding Close event after it is reported on the context.
if (this.isClosed()) this.emit(Page.Events.Close);else this.instrumentation.onPageOpen(this);
// Note: it is important to resolve _initializedPromise at the end,
// so that anyone who awaits waitForInitializedOrError got a ready and reported page.
this._initializedPromise.resolve(this._initialized);
}
initializedOrUndefined() {
return this._initialized ? this : undefined;
}
waitForInitializedOrError() {
return this._initializedPromise;
}
emitOnContext(event, ...args) {
if (this._isServerSideOnly) return;
this._browserContext.emit(event, ...args);
@@ -407,8 +416,8 @@ class Page extends _instrumentation.SdkObject {
async bringToFront() {
await this._delegate.bringToFront();
}
async addInitScript(source) {
const initScript = new InitScript(source);
async addInitScript(source, name) {
const initScript = new InitScript(source, false /* internal */, name);
this.initScripts.push(initScript);
await this._delegate.addInitScript(initScript);
}
@@ -532,10 +541,6 @@ class Page extends _instrumentation.SdkObject {
if (!runBeforeUnload) await this._closedPromise;
if (this._ownedContext) await this._ownedContext.close(options);
}
_setIsError(error) {
this._pageIsError = error;
this._frameManager.createDummyMainFrameIfNeeded();
}
isClosed() {
return this._closedState === 'closed';
}
@@ -781,9 +786,10 @@ function addPageBinding(playwrightBinding, bindingName, needsHandle, utilityScri
globalThis[bindingName].__installed = true;
}
class InitScript {
constructor(source, internal) {
constructor(source, internal, name) {
this.source = void 0;
this.internal = void 0;
this.name = void 0;
const guid = (0, _utils.createGuid)();
this.source = `(() => {
globalThis.__pwInitScripts = globalThis.__pwInitScripts || {};
@@ -794,6 +800,7 @@ class InitScript {
${source}
})();`;
this.internal = !!internal;
this.name = name;
}
}
exports.InitScript = InitScript;

View File

@@ -12,7 +12,6 @@ var _browserContext = require("./browserContext");
var _debugger = require("./debugger");
var _contextRecorder = require("./recorder/contextRecorder");
var _recorderUtils = require("./recorder/recorderUtils");
var _recorderUtils2 = require("../utils/isomorphic/recorderUtils");
var _selectorParser = require("../utils/isomorphic/selectorParser");
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
@@ -36,26 +35,26 @@ const recorderSymbol = Symbol('recorderSymbol');
class Recorder {
static async showInspector(context, params, recorderAppFactory) {
if ((0, _utils.isUnderTest)()) params.language = process.env.TEST_INSPECTOR_LANGUAGE;
return await Recorder.show('actions', context, recorderAppFactory, params);
return await Recorder.show(context, recorderAppFactory, params);
}
static showInspectorNoReply(context, recorderAppFactory) {
Recorder.showInspector(context, {}, recorderAppFactory).catch(() => {});
}
static show(codegenMode, context, recorderAppFactory, params) {
static show(context, recorderAppFactory, params) {
let recorderPromise = context[recorderSymbol];
if (!recorderPromise) {
recorderPromise = Recorder._create(codegenMode, context, recorderAppFactory, params);
recorderPromise = Recorder._create(context, recorderAppFactory, params);
context[recorderSymbol] = recorderPromise;
}
return recorderPromise;
}
static async _create(codegenMode, context, recorderAppFactory, params = {}) {
const recorder = new Recorder(codegenMode, context, params);
static async _create(context, recorderAppFactory, params = {}) {
const recorder = new Recorder(context, params);
const recorderApp = await recorderAppFactory(recorder);
await recorder._install(recorderApp);
return recorder;
}
constructor(codegenMode, context, params) {
constructor(context, params) {
this.handleSIGINT = void 0;
this._context = void 0;
this._mode = void 0;
@@ -73,7 +72,7 @@ class Recorder {
this._currentLanguage = void 0;
this._mode = params.mode || 'none';
this.handleSIGINT = params.handleSIGINT;
this._contextRecorder = new _contextRecorder.ContextRecorder(codegenMode, context, params, {});
this._contextRecorder = new _contextRecorder.ContextRecorder(context, params, {});
this._context = context;
this._omitCallTracking = !!params.omitCallTracking;
this._debugger = context.debugger();
@@ -121,6 +120,10 @@ class Recorder {
this._contextRecorder.clearScript();
return;
}
if (data.event === 'runTask') {
this._contextRecorder.runTask(data.params.task);
return;
}
});
await Promise.all([recorderApp.setMode(this._mode), recorderApp.setPaused(this._debugger.isPaused()), this._pushAllSources()]);
this._context.once(_browserContext.BrowserContext.Events.Close, () => {
@@ -165,7 +168,7 @@ class Recorder {
var _this$_recorderApp2;
const selectorChain = await (0, _contextRecorder.generateFrameSelector)(frame);
await ((_this$_recorderApp2 = this._recorderApp) === null || _this$_recorderApp2 === void 0 ? void 0 : _this$_recorderApp2.elementPicked({
selector: (0, _recorderUtils2.buildFullSelector)(selectorChain, elementInfo.selector),
selector: (0, _recorderUtils.buildFullSelector)(selectorChain, elementInfo.selector),
ariaSnapshot: elementInfo.ariaSnapshot
}, true));
});

View File

@@ -0,0 +1,177 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.Chat = void 0;
exports.asString = asString;
var _transport = require("../transport");
/**
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
class Chat {
constructor(wsEndpoint) {
this._history = [];
this._connectionPromise = void 0;
this._chatSinks = new Map();
this._wsEndpoint = void 0;
this._wsEndpoint = wsEndpoint;
}
clearHistory() {
this._history = [];
}
async post(prompt) {
await this._append('user', prompt);
let text = await asString(await this._post());
if (text.startsWith('```json') && text.endsWith('```')) text = text.substring('```json'.length, text.length - '```'.length);
for (let i = 0; i < 3; ++i) {
try {
return JSON.parse(text);
} catch (e) {
await this._append('user', String(e));
}
}
throw new Error('Failed to parse response: ' + text);
}
async _append(user, content) {
this._history.push({
user,
content
});
}
async _connection() {
if (!this._connectionPromise) {
this._connectionPromise = _transport.WebSocketTransport.connect(undefined, this._wsEndpoint).then(transport => {
return new Connection(transport, (method, params) => this._dispatchEvent(method, params), () => {});
});
}
return this._connectionPromise;
}
_dispatchEvent(method, params) {
if (method === 'chatChunk') {
const {
chatId,
chunk
} = params;
const chunkSink = this._chatSinks.get(chatId);
chunkSink(chunk);
if (!chunk) this._chatSinks.delete(chatId);
}
}
async _post() {
const connection = await this._connection();
const result = await connection.send('chat', {
history: this._history
});
const {
chatId
} = result;
const {
iterable,
addChunk
} = iterablePump();
this._chatSinks.set(chatId, addChunk);
return iterable;
}
}
exports.Chat = Chat;
async function asString(stream) {
let result = '';
for await (const chunk of stream) result += chunk;
return result;
}
function iterablePump() {
let controller;
const stream = new ReadableStream({
start: c => controller = c
});
const iterable = async function* () {
const reader = stream.getReader();
while (true) {
const {
done,
value
} = await reader.read();
if (done) break;
yield value;
}
}();
return {
iterable,
addChunk: chunk => {
if (chunk) controller.enqueue(chunk);else controller.close();
}
};
}
class Connection {
constructor(transport, onEvent, onClose) {
this._transport = void 0;
this._lastId = 0;
this._closed = false;
this._pending = new Map();
this._onEvent = void 0;
this._onClose = void 0;
this._transport = transport;
this._onEvent = onEvent;
this._onClose = onClose;
this._transport.onmessage = this._dispatchMessage.bind(this);
this._transport.onclose = this._close.bind(this);
}
send(method, params) {
const id = this._lastId++;
const message = {
id,
method,
params
};
this._transport.send(message);
return new Promise((resolve, reject) => {
this._pending.set(id, {
resolve,
reject
});
});
}
_dispatchMessage(message) {
if (message.id === undefined) {
this._onEvent(message.method, message.params);
return;
}
const callback = this._pending.get(message.id);
this._pending.delete(message.id);
if (!callback) return;
if (message.error) {
callback.reject(new Error(message.error.message));
return;
}
callback.resolve(message.result);
}
_close() {
this._closed = true;
this._transport.onmessage = undefined;
this._transport.onclose = undefined;
for (const {
reject
} of this._pending.values()) reject(new Error('Connection closed'));
this._onClose();
}
isClosed() {
return this._closed;
}
close() {
if (!this._closed) this._transport.close();
}
}

View File

@@ -35,7 +35,7 @@ function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e;
*/
class ContextRecorder extends _events.EventEmitter {
constructor(codegenMode, context, params, delegate) {
constructor(context, params, delegate) {
super();
this._collection = void 0;
this._pageAliases = new Map();
@@ -49,8 +49,6 @@ class ContextRecorder extends _events.EventEmitter {
this._throttledOutputFile = null;
this._orderedLanguages = [];
this._listeners = [];
this._codegenMode = void 0;
this._codegenMode = codegenMode;
this._context = context;
this._params = params;
this._delegate = delegate;
@@ -112,7 +110,7 @@ class ContextRecorder extends _events.EventEmitter {
var _this$_throttledOutpu3;
(_this$_throttledOutpu3 = this._throttledOutputFile) === null || _this$_throttledOutpu3 === void 0 || _this$_throttledOutpu3.flush();
}));
this.setEnabled(true);
this.setEnabled(params.mode === 'recording');
}
setOutput(codegenId, outputFile) {
var _this$_collection;
@@ -145,14 +143,6 @@ class ContextRecorder extends _events.EventEmitter {
}
setEnabled(enabled) {
this._collection.setEnabled(enabled);
if (this._codegenMode === 'trace-events') {
if (enabled) this._context.tracing.startChunk({
name: 'trace',
title: 'trace'
}).catch(() => {});else this._context.tracing.stopChunk({
mode: 'discard'
}).catch(() => {});
}
}
dispose() {
_utils.eventsHelper.removeEventListeners(this._listeners);
@@ -198,6 +188,9 @@ class ContextRecorder extends _events.EventEmitter {
for (const page of this._context.pages()) this._onFrameNavigated(page.mainFrame(), page);
}
}
runTask(task) {
// TODO: implement
}
_describeMainFrame(page) {
return {
pageAlias: this._pageAliases.get(page),
@@ -289,9 +282,7 @@ async function generateFrameSelectorInParent(parent, frame) {
return injected.generateSelectorSimple(element);
}, frameElement);
return selector;
} catch (e) {
return e.toString();
}
} catch (e) {}
}, (0, _utils.monotonicTime)() + 2000);
if (!result.timedOut && result.result) return result.result;
if (frame.name()) return `iframe[name=${(0, _utils.quoteCSSAttributeValue)(frame.name())}]`;

View File

@@ -87,7 +87,7 @@ class RecorderApp extends _events.EventEmitter {
}).catch(() => {});
});
const mainFrame = this._page.mainFrame();
await mainFrame.goto((0, _instrumentation.serverSideCallMetadata)(), 'https://playwright/index.html');
await mainFrame.goto((0, _instrumentation.serverSideCallMetadata)(), process.env.PW_HMR ? 'http://localhost:44225' : 'https://playwright/index.html');
}
static factory(context) {
return async recorder => {

View File

@@ -7,7 +7,6 @@ exports.RecorderCollection = void 0;
var _events = require("events");
var _time = require("../../utils/time");
var _recorderUtils = require("./recorderUtils");
var _errors = require("../errors");
var _recorderRunner = require("./recorderRunner");
var _debug = require("../../utils/debug");
/**
@@ -42,8 +41,8 @@ class RecorderCollection extends _events.EventEmitter {
this._enabled = enabled;
}
async performAction(actionInContext) {
await this._addAction(actionInContext, async callMetadata => {
await (0, _recorderRunner.performAction)(callMetadata, this._pageAliases, actionInContext);
await this._addAction(actionInContext, async () => {
await (0, _recorderRunner.performAction)(this._pageAliases, actionInContext);
});
}
addRecordedAction(actionInContext) {
@@ -61,21 +60,10 @@ class RecorderCollection extends _events.EventEmitter {
this._fireChange();
return;
}
const {
callMetadata,
mainFrame
} = (0, _recorderUtils.callMetadataForAction)(this._pageAliases, actionInContext);
await mainFrame.instrumentation.onBeforeCall(mainFrame, callMetadata);
this._actions.push(actionInContext);
this._fireChange();
const error = await (callback === null || callback === void 0 ? void 0 : callback(callMetadata).catch(e => e));
callMetadata.endTime = (0, _time.monotonicTime)();
actionInContext.endTime = callMetadata.endTime;
callMetadata.error = error ? (0, _errors.serializeError)(error) : undefined;
// Do not wait for onAfterCall so that performAction returned immediately after the action.
mainFrame.instrumentation.onAfterCall(mainFrame, callMetadata).then(() => {
this._fireChange();
}).catch(() => {});
await (callback === null || callback === void 0 ? void 0 : callback().catch());
actionInContext.endTime = (0, _time.monotonicTime)();
}
signal(pageAlias, frame, signal) {
if (!this._enabled) return;
@@ -84,7 +72,7 @@ class RecorderCollection extends _events.EventEmitter {
const lastAction = this._actions[this._actions.length - 1];
const signalThreshold = (0, _debug.isUnderTest)() ? 500 : 5000;
let generateGoto = false;
if (!lastAction) generateGoto = true;else if (lastAction.action.name !== 'click' && lastAction.action.name !== 'press') generateGoto = true;else if (timestamp - lastAction.startTime > signalThreshold) generateGoto = true;
if (!lastAction) generateGoto = true;else if (lastAction.action.name !== 'click' && lastAction.action.name !== 'press' && lastAction.action.name !== 'fill') generateGoto = true;else if (timestamp - lastAction.startTime > signalThreshold) generateGoto = true;
if (generateGoto) {
this.addRecordedAction({
frame: {

View File

@@ -1,144 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.RecorderInTraceViewer = void 0;
var _path = _interopRequireDefault(require("path"));
var _events = require("events");
var _traceViewer = require("../trace/viewer/traceViewer");
var _manualPromise = require("../../utils/manualPromise");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
class RecorderInTraceViewer extends _events.EventEmitter {
static factory(context) {
return async recorder => {
const transport = new RecorderTransport();
const trace = _path.default.join(context._browser.options.tracesDir, 'trace');
const {
wsEndpointForTest,
tracePage,
traceServer
} = await openApp(trace, {
transport,
headless: !context._browser.options.headful
});
return new RecorderInTraceViewer(transport, tracePage, traceServer, wsEndpointForTest);
};
}
constructor(transport, tracePage, traceServer, wsEndpointForTest) {
super();
this.wsEndpointForTest = void 0;
this._transport = void 0;
this._tracePage = void 0;
this._traceServer = void 0;
this._transport = transport;
this._transport.eventSink.resolve(this);
this._tracePage = tracePage;
this._traceServer = traceServer;
this.wsEndpointForTest = wsEndpointForTest;
this._tracePage.once('close', () => {
this.close();
});
}
async close() {
await this._tracePage.context().close({
reason: 'Recorder window closed'
});
await this._traceServer.stop();
}
async setPaused(paused) {
this._transport.deliverEvent('setPaused', {
paused
});
}
async setMode(mode) {
this._transport.deliverEvent('setMode', {
mode
});
}
async setRunningFile(file) {
this._transport.deliverEvent('setRunningFile', {
file
});
}
async elementPicked(elementInfo, userGesture) {
this._transport.deliverEvent('elementPicked', {
elementInfo,
userGesture
});
}
async updateCallLogs(callLogs) {
this._transport.deliverEvent('updateCallLogs', {
callLogs
});
}
async setSources(sources) {
this._transport.deliverEvent('setSources', {
sources
});
if (process.env.PWTEST_CLI_IS_UNDER_TEST && sources.length) {
if (process._didSetSourcesForTest(sources[0].text)) this.close();
}
}
async setActions(actions, sources) {
this._transport.deliverEvent('setActions', {
actions,
sources
});
}
}
exports.RecorderInTraceViewer = RecorderInTraceViewer;
async function openApp(trace, options) {
const traceServer = await (0, _traceViewer.startTraceViewerServer)(options);
await (0, _traceViewer.installRootRedirect)(traceServer, [trace], {
...options,
webApp: 'recorder.html'
});
const page = await (0, _traceViewer.openTraceViewerApp)(traceServer.urlPrefix('precise'), 'chromium', options);
return {
wsEndpointForTest: page.context()._browser.options.wsEndpoint,
tracePage: page,
traceServer
};
}
class RecorderTransport {
constructor() {
this._connected = new _manualPromise.ManualPromise();
this.eventSink = new _manualPromise.ManualPromise();
this.sendEvent = void 0;
this.close = void 0;
}
onconnect() {
this._connected.resolve();
}
async dispatch(method, params) {
const eventSink = await this.eventSink;
eventSink.emit('event', {
event: method,
params
});
}
onclose() {}
deliverEvent(method, params) {
this._connected.then(() => {
var _this$sendEvent;
return (_this$sendEvent = this.sendEvent) === null || _this$sendEvent === void 0 ? void 0 : _this$sendEvent.call(this, method, params);
});
}
}

View File

@@ -7,8 +7,8 @@ exports.performAction = performAction;
exports.toClickOptions = toClickOptions;
var _utils = require("../../utils");
var _language = require("../codegen/language");
var _instrumentation = require("../instrumentation");
var _recorderUtils = require("./recorderUtils");
var _recorderUtils2 = require("../../utils/isomorphic/recorderUtils");
/**
* Copyright (c) Microsoft Corporation.
*
@@ -25,7 +25,8 @@ var _recorderUtils2 = require("../../utils/isomorphic/recorderUtils");
* limitations under the License.
*/
async function performAction(callMetadata, pageAliases, actionInContext) {
async function performAction(pageAliases, actionInContext) {
const callMetadata = (0, _instrumentation.serverSideCallMetadata)();
const mainFrame = (0, _recorderUtils.mainFrameForAction)(pageAliases, actionInContext);
const {
action
@@ -42,7 +43,7 @@ async function performAction(callMetadata, pageAliases, actionInContext) {
await mainFrame._page.close(callMetadata);
return;
}
const selector = (0, _recorderUtils2.buildFullSelector)(actionInContext.frame.framePath, action.selector);
const selector = (0, _recorderUtils.buildFullSelector)(actionInContext.frame.framePath, action.selector);
if (action.name === 'click') {
const options = toClickOptions(action);
await mainFrame.click(callMetadata, selector, {
@@ -105,6 +106,9 @@ async function performAction(callMetadata, pageAliases, actionInContext) {
await mainFrame.expect(callMetadata, selector, {
selector,
expression: 'to.be.checked',
expectedValue: {
checked: action.checked
},
isNot: !action.checked,
timeout: kActionTimeout
});

View File

@@ -3,13 +3,11 @@
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.callMetadataForAction = callMetadataForAction;
exports.buildFullSelector = buildFullSelector;
exports.collapseActions = collapseActions;
exports.frameForAction = frameForAction;
exports.mainFrameForAction = mainFrameForAction;
exports.metadataToCallLog = metadataToCallLog;
var _utils = require("../../utils");
var _recorderUtils = require("../../utils/isomorphic/recorderUtils");
/**
* Copyright (c) Microsoft Corporation.
*
@@ -26,6 +24,9 @@ var _recorderUtils = require("../../utils/isomorphic/recorderUtils");
* limitations under the License.
*/
function buildFullSelector(framePath, selector) {
return [...framePath, selector].join(' >> internal:control=enter-frame >> ');
}
function metadataToCallLog(metadata, status) {
var _metadata$params, _metadata$params2, _metadata$error;
let title = metadata.apiName || metadata.method;
@@ -64,36 +65,11 @@ async function frameForAction(pageAliases, actionInContext, action) {
const pageAlias = actionInContext.frame.pageAlias;
const page = (_find2 = [...pageAliases.entries()].find(([, alias]) => pageAlias === alias)) === null || _find2 === void 0 ? void 0 : _find2[0];
if (!page) throw new Error('Internal error: page not found');
const fullSelector = (0, _recorderUtils.buildFullSelector)(actionInContext.frame.framePath, action.selector);
const fullSelector = buildFullSelector(actionInContext.frame.framePath, action.selector);
const result = await page.mainFrame().selectors.resolveFrameForSelector(fullSelector);
if (!result) throw new Error('Internal error: frame not found');
return result.frame;
}
function callMetadataForAction(pageAliases, actionInContext) {
const mainFrame = mainFrameForAction(pageAliases, actionInContext);
const {
method,
apiName,
params
} = (0, _recorderUtils.traceParamsForAction)(actionInContext);
const callMetadata = {
id: `call@${(0, _utils.createGuid)()}`,
apiName,
objectId: mainFrame.guid,
pageId: mainFrame._page.guid,
frameId: mainFrame.guid,
startTime: actionInContext.startTime,
endTime: 0,
type: 'Frame',
method,
params,
log: []
};
return {
callMetadata,
mainFrame
};
}
function collapseActions(actions) {
const result = [];
for (const action of actions) {

View File

@@ -41,7 +41,7 @@ async function downloadBrowserWithProgressBar(title, browserDirectory, executabl
}
const zipPath = _path.default.join(_os.default.tmpdir(), downloadFileName);
try {
const retryCount = 3;
const retryCount = 5;
for (let attempt = 1; attempt <= retryCount; ++attempt) {
_debugLogger.debugLogger.log('install', `downloading ${title} - attempt #${attempt}`);
const url = downloadURLs[(attempt - 1) % downloadURLs.length];

View File

@@ -104,7 +104,7 @@ async function installDependenciesLinux(targets, dryRun) {
for (const target of targets) {
const info = _nativeDeps.deps[platform];
if (!info) {
console.warn(`Cannot install dependencies for ${platform}!`); // eslint-disable-line no-console
console.warn(`Cannot install dependencies for ${platform} with Playwright ${(0, _userAgent.getPlaywrightVersion)()}!`); // eslint-disable-line no-console
return;
}
libraries.push(...info[target]);
@@ -132,11 +132,11 @@ async function installDependenciesLinux(targets, dryRun) {
child.on('error', reject);
});
}
async function validateDependenciesWindows(windowsExeAndDllDirectories) {
async function validateDependenciesWindows(sdkLanguage, windowsExeAndDllDirectories) {
const directoryPaths = windowsExeAndDllDirectories;
const lddPaths = [];
for (const directoryPath of directoryPaths) lddPaths.push(...(await executablesOrSharedLibraries(directoryPath)));
const allMissingDeps = await Promise.all(lddPaths.map(lddPath => missingFileDependenciesWindows(lddPath)));
const allMissingDeps = await Promise.all(lddPaths.map(lddPath => missingFileDependenciesWindows(sdkLanguage, lddPath)));
const missingDeps = new Set();
for (const deps of allMissingDeps) {
for (const dep of deps) missingDeps.add(dep);
@@ -238,8 +238,8 @@ async function executablesOrSharedLibraries(directoryPath) {
}))).filter(Boolean);
return executablersOrLibraries;
}
async function missingFileDependenciesWindows(filePath) {
const executable = _path.default.join(__dirname, '..', '..', '..', 'bin', 'PrintDeps.exe');
async function missingFileDependenciesWindows(sdkLanguage, filePath) {
const executable = _.registry.findExecutable('winldd').executablePathOrDie(sdkLanguage);
const dirname = _path.default.dirname(filePath);
const {
stdout,

View File

@@ -51,11 +51,20 @@ function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e;
const PACKAGE_PATH = _path.default.join(__dirname, '..', '..', '..');
const BIN_PATH = _path.default.join(__dirname, '..', '..', '..', 'bin');
const PLAYWRIGHT_CDN_MIRRORS = ['https://playwright.azureedge.net', 'https://playwright-akamai.azureedge.net', 'https://playwright-verizon.azureedge.net'];
const PLAYWRIGHT_CDN_MIRRORS = ['https://cdn.playwright.dev/dbazure/download/playwright',
// ESRP CDN
'https://playwright.download.prss.microsoft.com/dbazure/download/playwright',
// Directly hit ESRP CDN
'https://cdn.playwright.dev' // Hit the Storage Bucket directly
];
if (process.env.PW_TEST_CDN_THAT_SHOULD_WORK) {
for (let i = 0; i < PLAYWRIGHT_CDN_MIRRORS.length; i++) {
const cdn = PLAYWRIGHT_CDN_MIRRORS[i];
if (cdn !== process.env.PW_TEST_CDN_THAT_SHOULD_WORK) PLAYWRIGHT_CDN_MIRRORS[i] = cdn + '.does-not-resolve.playwright.dev';
if (cdn !== process.env.PW_TEST_CDN_THAT_SHOULD_WORK) {
const parsedCDN = new URL(cdn);
parsedCDN.hostname = parsedCDN.hostname + '.does-not-resolve.playwright.dev';
PLAYWRIGHT_CDN_MIRRORS[i] = parsedCDN.toString();
}
}
}
const EXECUTABLE_PATHS = {
@@ -83,6 +92,11 @@ const EXECUTABLE_PATHS = {
'linux': ['ffmpeg-linux'],
'mac': ['ffmpeg-mac'],
'win': ['ffmpeg-win64.exe']
},
'winldd': {
'linux': undefined,
'mac': undefined,
'win': ['PrintDeps.exe']
}
};
const DOWNLOAD_PATHS = {
@@ -173,6 +187,35 @@ const DOWNLOAD_PATHS = {
'mac15-arm64': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-mac-arm64.zip',
'win64': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-win64.zip'
},
'chromium-tip-of-tree-headless-shell': {
'<unknown>': undefined,
'ubuntu18.04-x64': undefined,
'ubuntu20.04-x64': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-linux.zip',
'ubuntu22.04-x64': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-linux.zip',
'ubuntu24.04-x64': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-linux.zip',
'ubuntu18.04-arm64': undefined,
'ubuntu20.04-arm64': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-linux-arm64.zip',
'ubuntu22.04-arm64': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-linux-arm64.zip',
'ubuntu24.04-arm64': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-linux-arm64.zip',
'debian11-x64': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-linux.zip',
'debian11-arm64': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-linux-arm64.zip',
'debian12-x64': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-linux.zip',
'debian12-arm64': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-linux-arm64.zip',
'mac10.13': undefined,
'mac10.14': undefined,
'mac10.15': undefined,
'mac11': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-mac.zip',
'mac11-arm64': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-mac-arm64.zip',
'mac12': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-mac.zip',
'mac12-arm64': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-mac-arm64.zip',
'mac13': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-mac.zip',
'mac13-arm64': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-mac-arm64.zip',
'mac14': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-mac.zip',
'mac14-arm64': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-mac-arm64.zip',
'mac15': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-mac.zip',
'mac15-arm64': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-mac-arm64.zip',
'win64': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-win64.zip'
},
'firefox': {
'<unknown>': undefined,
'ubuntu18.04-x64': undefined,
@@ -289,6 +332,35 @@ const DOWNLOAD_PATHS = {
'mac15-arm64': 'builds/ffmpeg/%s/ffmpeg-mac-arm64.zip',
'win64': 'builds/ffmpeg/%s/ffmpeg-win64.zip'
},
'winldd': {
'<unknown>': undefined,
'ubuntu18.04-x64': undefined,
'ubuntu20.04-x64': undefined,
'ubuntu22.04-x64': undefined,
'ubuntu24.04-x64': undefined,
'ubuntu18.04-arm64': undefined,
'ubuntu20.04-arm64': undefined,
'ubuntu22.04-arm64': undefined,
'ubuntu24.04-arm64': undefined,
'debian11-x64': undefined,
'debian11-arm64': undefined,
'debian12-x64': undefined,
'debian12-arm64': undefined,
'mac10.13': undefined,
'mac10.14': undefined,
'mac10.15': undefined,
'mac11': undefined,
'mac11-arm64': undefined,
'mac12': undefined,
'mac12-arm64': undefined,
'mac13': undefined,
'mac13-arm64': undefined,
'mac14': undefined,
'mac14-arm64': undefined,
'mac15': undefined,
'mac15-arm64': undefined,
'win64': 'builds/winldd/%s/winldd-win64.zip'
},
'android': {
'<unknown>': 'builds/android/%s/android.zip',
'ubuntu18.04-x64': undefined,
@@ -351,7 +423,14 @@ function isBrowserDirectory(browserDirectory) {
return false;
}
function readDescriptors(browsersJSON) {
return browsersJSON['browsers'].map(obj => {
const headlessShells = [];
for (const browserName of ['chromium', 'chromium-tip-of-tree']) {
headlessShells.push({
...browsersJSON.browsers.find(browser => browser.name === browserName),
name: `${browserName}-headless-shell`
});
}
return [...browsersJSON.browsers, ...headlessShells].map(obj => {
const name = obj.name;
const revisionOverride = (obj.revisionOverrides || {})[_hostPlatform.hostPlatform];
const revision = revisionOverride || obj.revision;
@@ -373,7 +452,7 @@ function readDescriptors(browsersJSON) {
return descriptor;
});
}
const allDownloadable = ['android', 'chromium', 'firefox', 'webkit', 'ffmpeg', 'firefox-beta', 'chromium-tip-of-tree', 'chromium-headless-shell'];
const allDownloadable = ['android', 'chromium', 'firefox', 'webkit', 'ffmpeg', 'firefox-beta', 'chromium-tip-of-tree', 'chromium-headless-shell', 'chromium-tip-of-tree-headless-shell'];
class Registry {
constructor(browsersJSON) {
this._executables = void 0;
@@ -430,6 +509,23 @@ class Registry {
_dependencyGroup: 'chromium',
_isHermeticInstallation: true
});
const chromiumTipOfTreeHeadlessShell = descriptors.find(d => d.name === 'chromium-tip-of-tree-headless-shell');
const chromiumTipOfTreeHeadlessShellExecutable = findExecutablePath(chromiumTipOfTreeHeadlessShell.dir, 'chromium-headless-shell');
this._executables.push({
type: 'channel',
name: 'chromium-tip-of-tree-headless-shell',
browserName: 'chromium',
directory: chromiumTipOfTreeHeadlessShell.dir,
executablePath: () => chromiumTipOfTreeHeadlessShellExecutable,
executablePathOrDie: sdkLanguage => executablePathOrDie('chromium', chromiumTipOfTreeHeadlessShellExecutable, chromiumTipOfTreeHeadlessShell.installByDefault, sdkLanguage),
installType: chromiumTipOfTreeHeadlessShell.installByDefault ? 'download-by-default' : 'download-on-demand',
_validateHostRequirements: sdkLanguage => this._validateHostRequirements(sdkLanguage, chromiumTipOfTreeHeadlessShell.dir, ['chrome-linux'], [], ['chrome-win']),
downloadURLs: this._downloadURLs(chromiumTipOfTreeHeadlessShell),
browserVersion: chromium.browserVersion,
_install: () => this._downloadExecutable(chromiumTipOfTreeHeadlessShell, chromiumTipOfTreeHeadlessShellExecutable),
_dependencyGroup: 'chromium',
_isHermeticInstallation: true
});
const chromiumTipOfTree = descriptors.find(d => d.name === 'chromium-tip-of-tree');
const chromiumTipOfTreeExecutable = findExecutablePath(chromiumTipOfTree.dir, 'chromium');
this._executables.push({
@@ -615,6 +711,22 @@ class Registry {
_dependencyGroup: 'tools',
_isHermeticInstallation: true
});
const winldd = descriptors.find(d => d.name === 'winldd');
const winlddExecutable = findExecutablePath(winldd.dir, 'winldd');
this._executables.push({
type: 'tool',
name: 'winldd',
browserName: undefined,
directory: winldd.dir,
executablePath: () => winlddExecutable,
executablePathOrDie: sdkLanguage => executablePathOrDie('winldd', winlddExecutable, winldd.installByDefault, sdkLanguage),
installType: process.platform === 'win32' ? 'download-by-default' : 'none',
_validateHostRequirements: () => Promise.resolve(),
downloadURLs: this._downloadURLs(winldd),
_install: () => this._downloadExecutable(winldd, winlddExecutable),
_dependencyGroup: 'tools',
_isHermeticInstallation: true
});
const android = descriptors.find(d => d.name === 'android');
this._executables.push({
type: 'tool',
@@ -751,7 +863,7 @@ class Registry {
}
async _validateHostRequirements(sdkLanguage, browserDirectory, linuxLddDirectories, dlOpenLibraries, windowsExeAndDllDirectories) {
if (os.platform() === 'linux') return await (0, _dependencies.validateDependenciesLinux)(sdkLanguage, linuxLddDirectories.map(d => _path.default.join(browserDirectory, d)), dlOpenLibraries);
if (os.platform() === 'win32' && os.arch() === 'x64') return await (0, _dependencies.validateDependenciesWindows)(windowsExeAndDllDirectories.map(d => _path.default.join(browserDirectory, d)));
if (os.platform() === 'win32' && os.arch() === 'x64') return await (0, _dependencies.validateDependenciesWindows)(sdkLanguage, windowsExeAndDllDirectories.map(d => _path.default.join(browserDirectory, d)));
}
async installDeps(executablesToInstallDeps, dryRun) {
const executables = this._dedupe(executablesToInstallDeps);
@@ -1017,6 +1129,7 @@ async function installBrowsersForNpmInstall(browsers) {
return false;
}
const executables = [];
if (process.platform === 'win32') executables.push(registry.findExecutable('winldd'));
for (const browserName of browsers) {
const executable = registry.findExecutable(browserName);
if (!executable || executable.installType === 'none') throw new Error(`Cannot install ${browserName}`);

View File

@@ -129,7 +129,7 @@ const deps = exports.deps = {
tools: ['xvfb', 'fonts-noto-color-emoji', 'fonts-unifont', 'libfontconfig1', 'libfreetype6', 'xfonts-cyrillic', 'xfonts-scalable', 'fonts-liberation', 'fonts-ipafont-gothic', 'fonts-wqy-zenhei', 'fonts-tlwg-loma-otf', 'fonts-freefont-ttf'],
chromium: ['libasound2', 'libatk-bridge2.0-0', 'libatk1.0-0', 'libatspi2.0-0', 'libcairo2', 'libcups2', 'libdbus-1-3', 'libdrm2', 'libgbm1', 'libglib2.0-0', 'libnspr4', 'libnss3', 'libpango-1.0-0', 'libwayland-client0', 'libx11-6', 'libxcb1', 'libxcomposite1', 'libxdamage1', 'libxext6', 'libxfixes3', 'libxkbcommon0', 'libxrandr2'],
firefox: ['ffmpeg', 'libasound2', 'libatk1.0-0', 'libcairo-gobject2', 'libcairo2', 'libdbus-1-3', 'libdbus-glib-1-2', 'libfontconfig1', 'libfreetype6', 'libgdk-pixbuf-2.0-0', 'libglib2.0-0', 'libgtk-3-0', 'libpango-1.0-0', 'libpangocairo-1.0-0', 'libx11-6', 'libx11-xcb1', 'libxcb-shm0', 'libxcb1', 'libxcomposite1', 'libxcursor1', 'libxdamage1', 'libxext6', 'libxfixes3', 'libxi6', 'libxrandr2', 'libxrender1', 'libxtst6'],
webkit: ['libsoup-3.0-0', 'libenchant-2-2', 'gstreamer1.0-libav', 'gstreamer1.0-plugins-bad', 'gstreamer1.0-plugins-base', 'gstreamer1.0-plugins-good', 'libicu70', 'libatk-bridge2.0-0', 'libatk1.0-0', 'libcairo2', 'libdbus-1-3', 'libdrm2', 'libegl1', 'libepoxy0', 'libevdev2', 'libffi7', 'libfontconfig1', 'libfreetype6', 'libgbm1', 'libgdk-pixbuf-2.0-0', 'libgles2', 'libglib2.0-0', 'libglx0', 'libgstreamer-gl1.0-0', 'libgstreamer-plugins-base1.0-0', 'libgstreamer1.0-0', 'libgtk-3-0', 'libgudev-1.0-0', 'libharfbuzz-icu0', 'libharfbuzz0b', 'libhyphen0', 'libjpeg-turbo8', 'liblcms2-2', 'libmanette-0.2-0', 'libnotify4', 'libopengl0', 'libopenjp2-7', 'libopus0', 'libpango-1.0-0', 'libpng16-16', 'libproxy1v5', 'libsecret-1-0', 'libwayland-client0', 'libwayland-egl1', 'libwayland-server0', 'libwebpdemux2', 'libwoff1', 'libx11-6', 'libxcomposite1', 'libxdamage1', 'libxkbcommon0', 'libxml2', 'libxslt1.1', 'libx264-163', 'libatomic1', 'libevent-2.1-7', 'libavif13'],
webkit: ['libsoup-3.0-0', 'libenchant-2-2', 'gstreamer1.0-libav', 'gstreamer1.0-plugins-bad', 'gstreamer1.0-plugins-base', 'gstreamer1.0-plugins-good', 'libicu70', 'libatk-bridge2.0-0', 'libatk1.0-0', 'libcairo2', 'libdbus-1-3', 'libdrm2', 'libegl1', 'libepoxy0', 'libevdev2', 'libffi7', 'libfontconfig1', 'libfreetype6', 'libgbm1', 'libgdk-pixbuf-2.0-0', 'libgles2', 'libglib2.0-0', 'libglx0', 'libgstreamer-gl1.0-0', 'libgstreamer-plugins-base1.0-0', 'libgstreamer1.0-0', 'libgtk-4-1', 'libgudev-1.0-0', 'libharfbuzz-icu0', 'libharfbuzz0b', 'libhyphen0', 'libjpeg-turbo8', 'liblcms2-2', 'libmanette-0.2-0', 'libnotify4', 'libopengl0', 'libopenjp2-7', 'libopus0', 'libpango-1.0-0', 'libpng16-16', 'libproxy1v5', 'libsecret-1-0', 'libwayland-client0', 'libwayland-egl1', 'libwayland-server0', 'libwebpdemux2', 'libwoff1', 'libx11-6', 'libxcomposite1', 'libxdamage1', 'libxkbcommon0', 'libxml2', 'libxslt1.1', 'libx264-163', 'libatomic1', 'libevent-2.1-7', 'libavif13'],
lib2package: {
'libavif.so.13': 'libavif13',
'libsoup-3.0.so.0': 'libsoup-3.0-0',
@@ -169,6 +169,7 @@ const deps = exports.deps = {
'libgsttag-1.0.so.0': 'libgstreamer-plugins-base1.0-0',
'libgstvideo-1.0.so.0': 'libgstreamer-plugins-base1.0-0',
'libgtk-3.so.0': 'libgtk-3-0',
'libgtk-4.so.1': 'libgtk-4-1',
'libgudev-1.0.so.0': 'libgudev-1.0-0',
'libharfbuzz-icu.so.0': 'libharfbuzz-icu0',
'libharfbuzz.so.0': 'libharfbuzz0b',
@@ -225,7 +226,7 @@ const deps = exports.deps = {
tools: ['xvfb', 'fonts-noto-color-emoji', 'fonts-unifont', 'libfontconfig1', 'libfreetype6', 'xfonts-cyrillic', 'xfonts-scalable', 'fonts-liberation', 'fonts-ipafont-gothic', 'fonts-wqy-zenhei', 'fonts-tlwg-loma-otf', 'fonts-freefont-ttf'],
chromium: ['libasound2t64', 'libatk-bridge2.0-0t64', 'libatk1.0-0t64', 'libatspi2.0-0t64', 'libcairo2', 'libcups2t64', 'libdbus-1-3', 'libdrm2', 'libgbm1', 'libglib2.0-0t64', 'libnspr4', 'libnss3', 'libpango-1.0-0', 'libx11-6', 'libxcb1', 'libxcomposite1', 'libxdamage1', 'libxext6', 'libxfixes3', 'libxkbcommon0', 'libxrandr2'],
firefox: ['libasound2t64', 'libatk1.0-0t64', 'libcairo-gobject2', 'libcairo2', 'libdbus-1-3', 'libfontconfig1', 'libfreetype6', 'libgdk-pixbuf-2.0-0', 'libglib2.0-0t64', 'libgtk-3-0t64', 'libpango-1.0-0', 'libpangocairo-1.0-0', 'libx11-6', 'libx11-xcb1', 'libxcb-shm0', 'libxcb1', 'libxcomposite1', 'libxcursor1', 'libxdamage1', 'libxext6', 'libxfixes3', 'libxi6', 'libxrandr2', 'libxrender1'],
webkit: ['gstreamer1.0-libav', 'gstreamer1.0-plugins-bad', 'gstreamer1.0-plugins-base', 'gstreamer1.0-plugins-good', 'libicu74', 'libatomic1', 'libatk-bridge2.0-0t64', 'libatk1.0-0t64', 'libcairo-gobject2', 'libcairo2', 'libdbus-1-3', 'libdrm2', 'libenchant-2-2', 'libepoxy0', 'libevent-2.1-7t64', 'libflite1', 'libfontconfig1', 'libfreetype6', 'libgbm1', 'libgdk-pixbuf-2.0-0', 'libgles2', 'libglib2.0-0t64', 'libgstreamer-gl1.0-0', 'libgstreamer-plugins-bad1.0-0', 'libgstreamer-plugins-base1.0-0', 'libgstreamer1.0-0', 'libgtk-3-0t64', 'libharfbuzz-icu0', 'libharfbuzz0b', 'libhyphen0', 'libicu74', 'libjpeg-turbo8', 'liblcms2-2', 'libmanette-0.2-0', 'libopus0', 'libpango-1.0-0', 'libpangocairo-1.0-0', 'libpng16-16t64', 'libsecret-1-0', 'libvpx9', 'libwayland-client0', 'libwayland-egl1', 'libwayland-server0', 'libwebp7', 'libwebpdemux2', 'libwoff1', 'libx11-6', 'libxkbcommon0', 'libxml2', 'libxslt1.1', 'libx264-164', 'libavif16'],
webkit: ['gstreamer1.0-libav', 'gstreamer1.0-plugins-bad', 'gstreamer1.0-plugins-base', 'gstreamer1.0-plugins-good', 'libicu74', 'libatomic1', 'libatk-bridge2.0-0t64', 'libatk1.0-0t64', 'libcairo-gobject2', 'libcairo2', 'libdbus-1-3', 'libdrm2', 'libenchant-2-2', 'libepoxy0', 'libevent-2.1-7t64', 'libflite1', 'libfontconfig1', 'libfreetype6', 'libgbm1', 'libgdk-pixbuf-2.0-0', 'libgles2', 'libglib2.0-0t64', 'libgstreamer-gl1.0-0', 'libgstreamer-plugins-bad1.0-0', 'libgstreamer-plugins-base1.0-0', 'libgstreamer1.0-0', 'libgtk-4-1', 'libharfbuzz-icu0', 'libharfbuzz0b', 'libhyphen0', 'libicu74', 'libjpeg-turbo8', 'liblcms2-2', 'libmanette-0.2-0', 'libopus0', 'libpango-1.0-0', 'libpangocairo-1.0-0', 'libpng16-16t64', 'libsecret-1-0', 'libvpx9', 'libwayland-client0', 'libwayland-egl1', 'libwayland-server0', 'libwebp7', 'libwebpdemux2', 'libwoff1', 'libx11-6', 'libxkbcommon0', 'libxml2', 'libxslt1.1', 'libx264-164', 'libavif16'],
lib2package: {
'libavif.so.16': 'libavif16',
'libasound.so.2': 'libasound2t64',
@@ -276,6 +277,7 @@ const deps = exports.deps = {
'libgsttag-1.0.so.0': 'libgstreamer-plugins-base1.0-0',
'libgstvideo-1.0.so.0': 'libgstreamer-plugins-base1.0-0',
'libgtk-3.so.0': 'libgtk-3-0t64',
'libgtk-4.so.1': 'libgtk-4-1',
'libharfbuzz-icu.so.0': 'libharfbuzz-icu0',
'libharfbuzz.so.0': 'libharfbuzz0b',
'libhyphen.so.0': 'libhyphen0',
@@ -414,7 +416,7 @@ const deps = exports.deps = {
tools: ['xvfb', 'fonts-noto-color-emoji', 'fonts-unifont', 'libfontconfig1', 'libfreetype6', 'xfonts-scalable', 'fonts-liberation', 'fonts-ipafont-gothic', 'fonts-wqy-zenhei', 'fonts-tlwg-loma-otf', 'fonts-freefont-ttf'],
chromium: ['libasound2', 'libatk-bridge2.0-0', 'libatk1.0-0', 'libatspi2.0-0', 'libcairo2', 'libcups2', 'libdbus-1-3', 'libdrm2', 'libgbm1', 'libglib2.0-0', 'libnspr4', 'libnss3', 'libpango-1.0-0', 'libx11-6', 'libxcb1', 'libxcomposite1', 'libxdamage1', 'libxext6', 'libxfixes3', 'libxkbcommon0', 'libxrandr2'],
firefox: ['libasound2', 'libatk1.0-0', 'libcairo-gobject2', 'libcairo2', 'libdbus-1-3', 'libdbus-glib-1-2', 'libfontconfig1', 'libfreetype6', 'libgdk-pixbuf-2.0-0', 'libglib2.0-0', 'libgtk-3-0', 'libharfbuzz0b', 'libpango-1.0-0', 'libpangocairo-1.0-0', 'libx11-6', 'libx11-xcb1', 'libxcb-shm0', 'libxcb1', 'libxcomposite1', 'libxcursor1', 'libxdamage1', 'libxext6', 'libxfixes3', 'libxi6', 'libxrandr2', 'libxrender1', 'libxtst6'],
webkit: ['libsoup-3.0-0', 'gstreamer1.0-libav', 'gstreamer1.0-plugins-bad', 'gstreamer1.0-plugins-base', 'gstreamer1.0-plugins-good', 'libatk-bridge2.0-0', 'libatk1.0-0', 'libcairo2', 'libdbus-1-3', 'libdrm2', 'libegl1', 'libenchant-2-2', 'libepoxy0', 'libevdev2', 'libfontconfig1', 'libfreetype6', 'libgbm1', 'libgdk-pixbuf-2.0-0', 'libgles2', 'libglib2.0-0', 'libglx0', 'libgstreamer-gl1.0-0', 'libgstreamer-plugins-base1.0-0', 'libgstreamer1.0-0', 'libgtk-3-0', 'libgudev-1.0-0', 'libharfbuzz-icu0', 'libharfbuzz0b', 'libhyphen0', 'libicu72', 'libjpeg62-turbo', 'liblcms2-2', 'libmanette-0.2-0', 'libnotify4', 'libopengl0', 'libopenjp2-7', 'libopus0', 'libpango-1.0-0', 'libpng16-16', 'libproxy1v5', 'libsecret-1-0', 'libwayland-client0', 'libwayland-egl1', 'libwayland-server0', 'libwebp7', 'libwebpdemux2', 'libwoff1', 'libx11-6', 'libxcomposite1', 'libxdamage1', 'libxkbcommon0', 'libxml2', 'libxslt1.1', 'libatomic1', 'libevent-2.1-7', 'libavif15'],
webkit: ['libsoup-3.0-0', 'gstreamer1.0-libav', 'gstreamer1.0-plugins-bad', 'gstreamer1.0-plugins-base', 'gstreamer1.0-plugins-good', 'libatk-bridge2.0-0', 'libatk1.0-0', 'libcairo2', 'libdbus-1-3', 'libdrm2', 'libegl1', 'libenchant-2-2', 'libepoxy0', 'libevdev2', 'libfontconfig1', 'libfreetype6', 'libgbm1', 'libgdk-pixbuf-2.0-0', 'libgles2', 'libglib2.0-0', 'libglx0', 'libgstreamer-gl1.0-0', 'libgstreamer-plugins-base1.0-0', 'libgstreamer1.0-0', 'libgtk-4-1', 'libgudev-1.0-0', 'libharfbuzz-icu0', 'libharfbuzz0b', 'libhyphen0', 'libicu72', 'libjpeg62-turbo', 'liblcms2-2', 'libmanette-0.2-0', 'libnotify4', 'libopengl0', 'libopenjp2-7', 'libopus0', 'libpango-1.0-0', 'libpng16-16', 'libproxy1v5', 'libsecret-1-0', 'libwayland-client0', 'libwayland-egl1', 'libwayland-server0', 'libwebp7', 'libwebpdemux2', 'libwoff1', 'libx11-6', 'libxcomposite1', 'libxdamage1', 'libxkbcommon0', 'libxml2', 'libxslt1.1', 'libatomic1', 'libevent-2.1-7', 'libavif15'],
lib2package: {
'libavif.so.15': 'libavif15',
'libsoup-3.0.so.0': 'libsoup-3.0-0',
@@ -442,7 +444,8 @@ const deps = exports.deps = {
'libXext.so.6': 'libxext6',
'libXfixes.so.3': 'libxfixes3',
'libxkbcommon.so.0': 'libxkbcommon0',
'libXrandr.so.2': 'libxrandr2'
'libXrandr.so.2': 'libxrandr2',
'libgtk-4.so.1': 'libgtk-4-1'
}
}
};

View File

@@ -107,7 +107,7 @@ class SocksProxyConnection {
};
}
async connect() {
if (this.socksProxy.proxyAgentFromOptions) this.target = await this.socksProxy.proxyAgentFromOptions.connect(new _events.EventEmitter(), {
if (this.socksProxy.proxyAgentFromOptions) this.target = await this.socksProxy.proxyAgentFromOptions.callback(new _events.EventEmitter(), {
host: rewriteToLocalhostIfNeeded(this.host),
port: this.port,
secureEndpoint: false

View File

@@ -36,6 +36,7 @@ function frameSnapshotStreamer(snapshotStreamer, removeNoScript) {
const kCustomElementsAttribute = '__playwright_custom_elements__';
const kCurrentSrcAttribute = '__playwright_current_src__';
const kBoundingRectAttribute = '__playwright_bounding_rect__';
const kPopoverOpenAttribute = '__playwright_popover_open_';
// Symbols for our own info on Nodes/StyleSheets.
const kSnapshotFrameId = Symbol('__playwright_snapshot_frameid_');
@@ -354,18 +355,24 @@ function frameSnapshotStreamer(snapshotStreamer, removeNoScript) {
expectValue(value);
attrs[kSelectedAttribute] = value;
}
if (nodeName === 'CANVAS') {
if (nodeName === 'CANVAS' || nodeName === 'IFRAME' || nodeName === 'FRAME') {
const boundingRect = element.getBoundingClientRect();
const value = JSON.stringify({
left: boundingRect.left / window.innerWidth,
top: boundingRect.top / window.innerHeight,
right: boundingRect.right / window.innerWidth,
bottom: boundingRect.bottom / window.innerHeight
left: boundingRect.left,
top: boundingRect.top,
right: boundingRect.right,
bottom: boundingRect.bottom
});
expectValue(kBoundingRectAttribute);
expectValue(value);
attrs[kBoundingRectAttribute] = value;
}
if (element.popover && element.matches && element.matches(':popover-open')) {
const value = 'true';
expectValue(kPopoverOpenAttribute);
expectValue(value);
attrs[kPopoverOpenAttribute] = value;
}
if (element.scrollTop) {
expectValue(kScrollTopAttribute);
expectValue(element.scrollTop);

View File

@@ -94,7 +94,12 @@ async function installRootRedirect(server, traceUrls, options) {
if (options.grepInvert) params.append('grepInvert', options.grepInvert);
for (const project of options.project || []) params.append('project', project);
for (const reporter of options.reporter || []) params.append('reporter', reporter);
const urlPath = `./trace/${options.webApp || 'index.html'}?${params.toString()}`;
let baseUrl = '.';
if (process.env.PW_HMR) {
baseUrl = 'http://localhost:44223'; // port is hardcoded in build.js
params.set('server', server.urlPrefix('precise'));
}
const urlPath = `${baseUrl}/trace/${options.webApp || 'index.html'}?${params.toString()}`;
server.routePath('/', (_, response) => {
response.statusCode = 302;
response.setHeader('Location', urlPath);

View File

@@ -25,6 +25,7 @@ var _happyEyeballs = require("../utils/happy-eyeballs");
*/
const perMessageDeflate = exports.perMessageDeflate = {
clientNoContextTakeover: true,
zlibDeflateOptions: {
level: 3
},

View File

@@ -42,7 +42,7 @@ class WebKit extends _browserType.BrowserType {
}
doRewriteStartupLog(error) {
if (!error.logs) return error;
if (error.logs.includes('cannot open display')) error.logs = '\n' + (0, _utils.wrapInASCIIBox)(_browserType.kNoXServerRunningError, 1);
if (error.logs.includes('Failed to open display') || error.logs.includes('cannot open display')) error.logs = '\n' + (0, _utils.wrapInASCIIBox)(_browserType.kNoXServerRunningError, 1);
return error;
}
attemptToGracefullyCloseBrowser(transport) {

View File

@@ -108,13 +108,13 @@ class WKBrowser extends _browser.Browser {
// abort navigation that is still running. We should be able to fix this by
// instrumenting policy decision start/proceed/cancel.
page._page._frameManager.frameAbortedNavigation(payload.frameId, 'Download is starting');
let originPage = page._initializedPage;
let originPage = page._page.initializedOrUndefined();
// If it's a new window download, report it on the opener page.
if (!originPage) {
// Resume the page creation with an error. The page will automatically close right
// after the download begins.
page._firstNonInitialNavigationCommittedReject(new Error('Starting new page download'));
if (page._opener) originPage = page._opener._initializedPage;
if (page._opener) originPage = page._opener._page.initializedOrUndefined();
}
if (!originPage) return;
this._downloadCreated(originPage, payload.uuid, payload.url);
@@ -209,17 +209,17 @@ class WKBrowserContext extends _browserContext.BrowserContext {
_wkPages() {
return Array.from(this._browser._wkPages.values()).filter(wkPage => wkPage._browserContext === this);
}
pages() {
return this._wkPages().map(wkPage => wkPage._initializedPage).filter(pageOrNull => !!pageOrNull);
possiblyUninitializedPages() {
return this._wkPages().map(wkPage => wkPage._page);
}
async newPageDelegate() {
async doCreateNewPage() {
(0, _browserContext.assertBrowserContextIsNotOwned)(this);
const {
pageProxyId
} = await this._browser._browserSession.send('Playwright.createPage', {
browserContextId: this._browserContextId
});
return this._browser._wkPages.get(pageProxyId);
return this._browser._wkPages.get(pageProxyId)._page;
}
async doGetCookies(urls) {
const {

View File

@@ -111,6 +111,7 @@ function potentiallyUnserializableValue(remoteObject) {
return isUnserializable ? js.parseUnserializableValue(remoteObject.description) : value;
}
function rewriteError(error) {
if (error.message.includes('Object has too long reference chain')) throw new Error('Cannot serialize result: object reference chain is too long.');
if (!js.isJavaScriptErrorInEvaluate(error) && !(0, _protocolError.isSessionClosedError)(error)) return new Error('Execution context was destroyed, most likely because of a navigation.');
return error;
}

View File

@@ -51,11 +51,17 @@ class RawKeyboardImpl {
setSession(session) {
this._session = session;
}
async keydown(modifiers, code, keyCode, keyCodeWithoutLocation, key, location, autoRepeat, text) {
async keydown(modifiers, keyName, description, autoRepeat) {
const parts = [];
for (const modifier of ['Shift', 'Control', 'Alt', 'Meta']) {
if (modifiers.has(modifier)) parts.push(modifier);
}
const {
code,
keyCode,
key,
text
} = description;
parts.push(code);
const shortcut = parts.join('+');
let commands = _macEditingCommands.macEditingCommands[shortcut];
@@ -70,17 +76,21 @@ class RawKeyboardImpl {
unmodifiedText: text,
autoRepeat,
macCommands: commands,
isKeypad: location === input.keypadLocation
isKeypad: description.location === input.keypadLocation
});
}
async keyup(modifiers, code, keyCode, keyCodeWithoutLocation, key, location) {
async keyup(modifiers, keyName, description) {
const {
code,
key
} = description;
await this._pageProxySession.send('Input.dispatchKeyEvent', {
type: 'keyUp',
modifiers: toModifiersMask(modifiers),
key,
windowsVirtualKeyCode: keyCode,
windowsVirtualKeyCode: description.keyCode,
code,
isKeypad: location === input.keypadLocation
isKeypad: description.location === input.keypadLocation
});
}
async sendText(text) {

View File

@@ -5,7 +5,6 @@ Object.defineProperty(exports, "__esModule", {
});
exports.WKPage = void 0;
var _path = _interopRequireDefault(require("path"));
var _os = _interopRequireDefault(require("os"));
var _utilsBundle = require("../../utilsBundle");
var _stackTrace = require("../../utils/stackTrace");
var _utils = require("../../utils");
@@ -24,7 +23,6 @@ var _wkInterceptableRequest = require("./wkInterceptableRequest");
var _wkProvisionalPage = require("./wkProvisionalPage");
var _wkWorkers = require("./wkWorkers");
var _debugLogger = require("../../utils/debugLogger");
var _manualPromise = require("../../utils/manualPromise");
var _browserContext = require("../browserContext");
var _errors = require("../errors");
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
@@ -56,7 +54,6 @@ class WKPage {
this._session = void 0;
this._provisionalPage = null;
this._page = void 0;
this._pagePromise = new _manualPromise.ManualPromise();
this._pageProxySession = void 0;
this._opener = void 0;
this._requestIdToRequest = new Map();
@@ -66,7 +63,6 @@ class WKPage {
this._sessionListeners = [];
this._eventListeners = void 0;
this._browserContext = void 0;
this._initializedPage = null;
this._firstNonInitialNavigationCommittedPromise = void 0;
this._firstNonInitialNavigationCommittedFulfill = () => {};
this._firstNonInitialNavigationCommittedReject = e => {};
@@ -103,9 +99,6 @@ class WKPage {
};
}
}
potentiallyUninitializedPage() {
return this._page;
}
async _initializePageProxySession() {
if (this._page._browserContext.isSettingStorageState()) return;
const promises = [this._pageProxySession.send('Dialog.enable'), this._pageProxySession.send('Emulation.setActiveAndFocused', {
@@ -303,7 +296,7 @@ class WKPage {
this._pageProxySession.dispatchMessage(message);
}
handleProvisionalLoadFailed(event) {
if (!this._initializedPage) {
if (!this._page.initializedOrUndefined()) {
this._firstNonInitialNavigationCommittedReject(new Error('Initial load failed'));
return;
}
@@ -316,9 +309,6 @@ class WKPage {
(0, _utils.debugAssert)(!this._nextWindowOpenPopupFeatures);
this._nextWindowOpenPopupFeatures = event.windowFeatures;
}
async pageOrError() {
return this._pagePromise;
}
async _onTargetCreated(event) {
const {
targetInfo
@@ -338,7 +328,8 @@ class WKPage {
});
(0, _utils.assert)(targetInfo.type === 'page', 'Only page targets are expected in WebKit, received: ' + targetInfo.type);
if (!targetInfo.isProvisional) {
(0, _utils.assert)(!this._initializedPage);
var _this$_opener;
(0, _utils.assert)(!this._page.initializedOrUndefined());
let pageOrError;
try {
this._setSession(session);
@@ -365,12 +356,7 @@ class WKPage {
// Avoid rejection on disconnect.
this._firstNonInitialNavigationCommittedPromise.catch(() => {});
}
await this._page.initOpener(this._opener);
// Note: it is important to call |reportAsNew| before resolving pageOrError promise,
// so that anyone who awaits pageOrError got a ready and reported page.
this._initializedPage = pageOrError instanceof _page.Page ? pageOrError : null;
this._page.reportAsNew(pageOrError instanceof _page.Page ? undefined : pageOrError);
this._pagePromise.resolve(pageOrError);
this._page.reportAsNew((_this$_opener = this._opener) === null || _this$_opener === void 0 ? void 0 : _this$_opener._page, pageOrError instanceof _page.Page ? undefined : pageOrError);
} else {
(0, _utils.assert)(targetInfo.isProvisional);
(0, _utils.assert)(!this._provisionalPage);
@@ -473,7 +459,7 @@ class WKPage {
this._contextIdToContext.set(contextPayload.id, context);
}
async _onBindingCalled(contextId, argument) {
const pageOrError = await this.pageOrError();
const pageOrError = await this._page.waitForInitializedOrError();
if (!(pageOrError instanceof Error)) {
const context = this._contextIdToContext.get(contextId);
if (context) await this._page._onBindingCalled(argument, context);
@@ -692,11 +678,7 @@ class WKPage {
})];
if (options.isMobile) {
const angle = viewportSize.width > viewportSize.height ? 90 : 0;
// Special handling for macOS 12.
const useLegacySetOrientationOverrideMethod = _os.default.platform() === 'darwin' && parseInt(_os.default.release().split('.')[0], 10) <= 21;
if (useLegacySetOrientationOverrideMethod) promises.push(this._session.send('Page.setOrientationOverride', {
angle
}));else promises.push(this._pageProxySession.send('Emulation.setOrientationOverride', {
promises.push(this._pageProxySession.send('Emulation.setOrientationOverride', {
angle
}));
}
@@ -806,7 +788,7 @@ class WKPage {
toolbarHeight: this._toolbarHeight()
});
this._recordingVideoFile = options.outputFile;
this._browserContext._browser._videoStarted(this._browserContext, screencastId, options.outputFile, this.pageOrError());
this._browserContext._browser._videoStarted(this._browserContext, screencastId, options.outputFile, this._page.waitForInitializedOrError());
}
async _stopVideo() {
if (!this._recordingVideoFile) return;