Merge branch 'main' of https://github.com/microsoft/playwright
This commit is contained in:
commit
757124ad78
20
.github/workflows/tests_others.yml
vendored
20
.github/workflows/tests_others.yml
vendored
|
|
@ -88,11 +88,15 @@ jobs:
|
|||
flakiness-subscription-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_SUBSCRIPTION_ID }}
|
||||
|
||||
test_clock_frozen_time_linux:
|
||||
name: Frozen time library
|
||||
name: time library - ${{ matrix.clock }}
|
||||
environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }}
|
||||
permissions:
|
||||
id-token: write # This is required for OIDC login (azure/login) to succeed
|
||||
contents: read # This is required for actions/checkout to succeed
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
clock: [frozen, realtime]
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
|
@ -101,32 +105,36 @@ jobs:
|
|||
node-version: 20
|
||||
browsers-to-install: chromium
|
||||
command: npm run test -- --project=chromium-*
|
||||
bot-name: "frozen-time-library-chromium-linux"
|
||||
bot-name: "${{ matrix.clock }}-time-library-chromium-linux"
|
||||
flakiness-client-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_CLIENT_ID }}
|
||||
flakiness-tenant-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_TENANT_ID }}
|
||||
flakiness-subscription-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_SUBSCRIPTION_ID }}
|
||||
env:
|
||||
PW_FREEZE_TIME: 1
|
||||
PW_CLOCK: ${{ matrix.clock }}
|
||||
|
||||
test_clock_frozen_time_test_runner:
|
||||
name: Frozen time test runner
|
||||
name: time test runner - ${{ matrix.clock }}
|
||||
environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }}
|
||||
runs-on: ubuntu-22.04
|
||||
permissions:
|
||||
id-token: write # This is required for OIDC login (azure/login) to succeed
|
||||
contents: read # This is required for actions/checkout to succeed
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
clock: [frozen, realtime]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/run-test
|
||||
with:
|
||||
node-version: 20
|
||||
command: npm run ttest
|
||||
bot-name: "frozen-time-runner-chromium-linux"
|
||||
bot-name: "${{ matrix.clock }}-time-runner-chromium-linux"
|
||||
flakiness-client-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_CLIENT_ID }}
|
||||
flakiness-tenant-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_TENANT_ID }}
|
||||
flakiness-subscription-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_SUBSCRIPTION_ID }}
|
||||
env:
|
||||
PW_FREEZE_TIME: 1
|
||||
PW_CLOCK: ${{ matrix.clock }}
|
||||
|
||||
test_electron:
|
||||
name: Electron - ${{ matrix.os }}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# 🎭 Playwright
|
||||
|
||||
[](https://www.npmjs.com/package/playwright) <!-- GEN:chromium-version-badge -->[](https://www.chromium.org/Home)<!-- GEN:stop --> <!-- GEN:firefox-version-badge -->[](https://www.mozilla.org/en-US/firefox/new/)<!-- GEN:stop --> <!-- GEN:webkit-version-badge -->[](https://webkit.org/)<!-- GEN:stop -->
|
||||
[](https://www.npmjs.com/package/playwright) <!-- GEN:chromium-version-badge -->[](https://www.chromium.org/Home)<!-- GEN:stop --> <!-- GEN:firefox-version-badge -->[](https://www.mozilla.org/en-US/firefox/new/)<!-- GEN:stop --> <!-- GEN:webkit-version-badge -->[](https://webkit.org/)<!-- GEN:stop -->
|
||||
|
||||
## [Documentation](https://playwright.dev) | [API reference](https://playwright.dev/docs/api/class-playwright)
|
||||
|
||||
|
|
@ -8,7 +8,7 @@ Playwright is a framework for Web Testing and Automation. It allows testing [Chr
|
|||
|
||||
| | Linux | macOS | Windows |
|
||||
| :--- | :---: | :---: | :---: |
|
||||
| Chromium <!-- GEN:chromium-version -->127.0.6533.5<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
| Chromium <!-- GEN:chromium-version -->127.0.6533.26<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
| WebKit <!-- GEN:webkit-version -->17.4<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
| Firefox <!-- GEN:firefox-version -->127.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
REMOTE_URL="https://github.com/mozilla/gecko-dev"
|
||||
BASE_BRANCH="release"
|
||||
BASE_REVISION="f8704c84a751716bad093b9bdc482db53fe5b3ea"
|
||||
BASE_REVISION="bd7e0ac24a6fb1cddde3e45ea191b7abcc90cf56"
|
||||
|
|
|
|||
|
|
@ -146,6 +146,7 @@ class NetworkRequest {
|
|||
this._expectingInterception = false;
|
||||
this._expectingResumedRequest = undefined; // { method, headers, postData }
|
||||
this._sentOnResponse = false;
|
||||
this._fulfilled = false;
|
||||
|
||||
if (this._pageNetwork)
|
||||
appendExtraHTTPHeaders(httpChannel, this._pageNetwork.combinedExtraHTTPHeaders());
|
||||
|
|
@ -194,6 +195,7 @@ class NetworkRequest {
|
|||
|
||||
// Public interception API.
|
||||
fulfill(status, statusText, headers, base64body) {
|
||||
this._fulfilled = true;
|
||||
this._interceptedChannel.synthesizeStatus(status, statusText);
|
||||
for (const header of headers) {
|
||||
this._interceptedChannel.synthesizeHeader(header.name, header.value);
|
||||
|
|
@ -801,7 +803,8 @@ class ResponseStorage {
|
|||
return;
|
||||
}
|
||||
let encodings = [];
|
||||
if ((request.httpChannel instanceof Ci.nsIEncodedChannel) && request.httpChannel.contentEncodings && !request.httpChannel.applyConversion) {
|
||||
// Note: fulfilled request comes with decoded body right away.
|
||||
if ((request.httpChannel instanceof Ci.nsIEncodedChannel) && request.httpChannel.contentEncodings && !request.httpChannel.applyConversion && !request._fulfilled) {
|
||||
const encodingHeader = request.httpChannel.getResponseHeader("Content-Encoding");
|
||||
encodings = encodingHeader.split(/\s*\t*,\s*\t*/);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -355,6 +355,7 @@ class PageTarget {
|
|||
this._screencastRecordingInfo = undefined;
|
||||
this._dialogs = new Map();
|
||||
this.forcedColors = 'no-override';
|
||||
this.disableCache = false;
|
||||
this.mediumOverride = '';
|
||||
this.crossProcessCookie = {
|
||||
initScripts: [],
|
||||
|
|
@ -461,12 +462,26 @@ class PageTarget {
|
|||
this.updateReducedMotionOverride(browsingContext);
|
||||
this.updateForcedColorsOverride(browsingContext);
|
||||
this.updateForceOffline(browsingContext);
|
||||
this.updateCacheDisabled(browsingContext);
|
||||
}
|
||||
|
||||
updateForceOffline(browsingContext = undefined) {
|
||||
(browsingContext || this._linkedBrowser.browsingContext).forceOffline = this._browserContext.forceOffline;
|
||||
}
|
||||
|
||||
setCacheDisabled(disabled) {
|
||||
this.disableCache = disabled;
|
||||
this.updateCacheDisabled();
|
||||
}
|
||||
|
||||
updateCacheDisabled(browsingContext = this._linkedBrowser.browsingContext) {
|
||||
const enableFlags = Ci.nsIRequest.LOAD_NORMAL;
|
||||
const disableFlags = Ci.nsIRequest.LOAD_BYPASS_CACHE |
|
||||
Ci.nsIRequest.INHIBIT_CACHING;
|
||||
|
||||
browsingContext.defaultLoadFlags = (this._browserContext.disableCache || this.disableCache) ? disableFlags : enableFlags;
|
||||
}
|
||||
|
||||
updateTouchOverride(browsingContext = undefined) {
|
||||
(browsingContext || this._linkedBrowser.browsingContext).touchEventsOverride = this._browserContext.touchOverride ? 'enabled' : 'none';
|
||||
}
|
||||
|
|
@ -837,6 +852,7 @@ class BrowserContext {
|
|||
this.defaultPlatform = null;
|
||||
this.touchOverride = false;
|
||||
this.forceOffline = false;
|
||||
this.disableCache = false;
|
||||
this.colorScheme = 'none';
|
||||
this.forcedColors = 'no-override';
|
||||
this.reducedMotion = 'none';
|
||||
|
|
@ -938,6 +954,12 @@ class BrowserContext {
|
|||
page.updateForceOffline();
|
||||
}
|
||||
|
||||
setCacheDisabled(disabled) {
|
||||
this.disableCache = disabled;
|
||||
for (const page of this.pages)
|
||||
page.updateCacheDisabled();
|
||||
}
|
||||
|
||||
async setDefaultViewport(viewport) {
|
||||
this.defaultViewportSize = viewport ? viewport.viewportSize : undefined;
|
||||
this.deviceScaleFactor = viewport ? viewport.deviceScaleFactor : undefined;
|
||||
|
|
|
|||
|
|
@ -152,7 +152,6 @@ class PageAgent {
|
|||
getFullAXTree: this._getFullAXTree.bind(this),
|
||||
insertText: this._insertText.bind(this),
|
||||
scrollIntoViewIfNeeded: this._scrollIntoViewIfNeeded.bind(this),
|
||||
setCacheDisabled: this._setCacheDisabled.bind(this),
|
||||
setFileInputFiles: this._setFileInputFiles.bind(this),
|
||||
evaluate: this._runtime.evaluate.bind(this._runtime),
|
||||
callFunction: this._runtime.callFunction.bind(this._runtime),
|
||||
|
|
@ -162,15 +161,6 @@ class PageAgent {
|
|||
];
|
||||
}
|
||||
|
||||
_setCacheDisabled({cacheDisabled}) {
|
||||
const enable = Ci.nsIRequest.LOAD_NORMAL;
|
||||
const disable = Ci.nsIRequest.LOAD_BYPASS_CACHE |
|
||||
Ci.nsIRequest.INHIBIT_CACHING;
|
||||
|
||||
const docShell = this._frameTree.mainFrame().docShell();
|
||||
docShell.defaultLoadFlags = cacheDisabled ? disable : enable;
|
||||
}
|
||||
|
||||
_emitAllEvents(frame) {
|
||||
this._browserPage.emit('pageEventFired', {
|
||||
frameId: frame.id(),
|
||||
|
|
@ -519,71 +509,16 @@ class PageAgent {
|
|||
false /* aIgnoreRootScrollFrame */,
|
||||
true /* aFlushLayout */);
|
||||
|
||||
const {defaultPrevented: startPrevented} = await this._dispatchTouchEvent({
|
||||
await this._dispatchTouchEvent({
|
||||
type: 'touchstart',
|
||||
modifiers,
|
||||
touchPoints: [{x, y}]
|
||||
});
|
||||
const {defaultPrevented: endPrevented} = await this._dispatchTouchEvent({
|
||||
await this._dispatchTouchEvent({
|
||||
type: 'touchend',
|
||||
modifiers,
|
||||
touchPoints: [{x, y}]
|
||||
});
|
||||
if (startPrevented || endPrevented)
|
||||
return;
|
||||
|
||||
const frame = this._frameTree.mainFrame();
|
||||
const winUtils = frame.domWindow().windowUtils;
|
||||
winUtils.jugglerSendMouseEvent(
|
||||
'mousemove',
|
||||
x,
|
||||
y,
|
||||
0 /*button*/,
|
||||
0 /*clickCount*/,
|
||||
modifiers,
|
||||
false /*aIgnoreRootScrollFrame*/,
|
||||
0.0 /*pressure*/,
|
||||
5 /*inputSource*/,
|
||||
true /*isDOMEventSynthesized*/,
|
||||
false /*isWidgetEventSynthesized*/,
|
||||
0 /*buttons*/,
|
||||
winUtils.DEFAULT_MOUSE_POINTER_ID /* pointerIdentifier */,
|
||||
true /*disablePointerEvent*/
|
||||
);
|
||||
|
||||
winUtils.jugglerSendMouseEvent(
|
||||
'mousedown',
|
||||
x,
|
||||
y,
|
||||
0 /*button*/,
|
||||
1 /*clickCount*/,
|
||||
modifiers,
|
||||
false /*aIgnoreRootScrollFrame*/,
|
||||
0.0 /*pressure*/,
|
||||
5 /*inputSource*/,
|
||||
true /*isDOMEventSynthesized*/,
|
||||
false /*isWidgetEventSynthesized*/,
|
||||
1 /*buttons*/,
|
||||
winUtils.DEFAULT_MOUSE_POINTER_ID /*pointerIdentifier*/,
|
||||
true /*disablePointerEvent*/,
|
||||
);
|
||||
|
||||
winUtils.jugglerSendMouseEvent(
|
||||
'mouseup',
|
||||
x,
|
||||
y,
|
||||
0 /*button*/,
|
||||
1 /*clickCount*/,
|
||||
modifiers,
|
||||
false /*aIgnoreRootScrollFrame*/,
|
||||
0.0 /*pressure*/,
|
||||
5 /*inputSource*/,
|
||||
true /*isDOMEventSynthesized*/,
|
||||
false /*isWidgetEventSynthesized*/,
|
||||
0 /*buttons*/,
|
||||
winUtils.DEFAULT_MOUSE_POINTER_ID /*pointerIdentifier*/,
|
||||
true /*disablePointerEvent*/,
|
||||
);
|
||||
}
|
||||
|
||||
async _dispatchDragEvent({type, x, y, modifiers}) {
|
||||
|
|
|
|||
|
|
@ -186,6 +186,10 @@ class BrowserHandler {
|
|||
this._targetRegistry.browserContextForId(browserContextId).requestInterceptionEnabled = enabled;
|
||||
}
|
||||
|
||||
['Browser.setCacheDisabled']({browserContextId, cacheDisabled}) {
|
||||
this._targetRegistry.browserContextForId(browserContextId).setCacheDisabled(cacheDisabled);
|
||||
}
|
||||
|
||||
['Browser.setIgnoreHTTPSErrors']({browserContextId, ignoreHTTPSErrors}) {
|
||||
this._targetRegistry.browserContextForId(browserContextId).setIgnoreHTTPSErrors(nullToUndefined(ignoreHTTPSErrors));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -302,8 +302,8 @@ class PageHandler {
|
|||
await this._pageTarget.activateAndRun(() => {});
|
||||
}
|
||||
|
||||
async ['Page.setCacheDisabled'](options) {
|
||||
return await this._contentPage.send('setCacheDisabled', options);
|
||||
async ['Page.setCacheDisabled']({cacheDisabled}) {
|
||||
return await this._pageTarget.setCacheDisabled(cacheDisabled);
|
||||
}
|
||||
|
||||
async ['Page.addBinding']({ worldName, name, script }) {
|
||||
|
|
|
|||
|
|
@ -322,6 +322,12 @@ const Browser = {
|
|||
enabled: t.Boolean,
|
||||
},
|
||||
},
|
||||
'setCacheDisabled': {
|
||||
params: {
|
||||
browserContextId: t.Optional(t.String),
|
||||
cacheDisabled: t.Boolean,
|
||||
},
|
||||
},
|
||||
'setGeolocationOverride': {
|
||||
params: {
|
||||
browserContextId: t.Optional(t.String),
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ index 8e9bf2b413585b5a3db9370eee5d57fb6c6716ed..5a3b194b54e3813c89989f13a214c989
|
|||
* Return XPCOM wrapper for the internal accessible.
|
||||
*/
|
||||
diff --git a/browser/app/winlauncher/LauncherProcessWin.cpp b/browser/app/winlauncher/LauncherProcessWin.cpp
|
||||
index b40e0fceb567c0d217adf284e13f434e49cc8467..2c4e6d5fbf8da40954ad6a5b15e412493e43b14e 100644
|
||||
index 81d4ee91e9383693d794dbf68184a80b49b582c6..1a07e3511f73199fe0b248412d01d7b8a3744a66 100644
|
||||
--- a/browser/app/winlauncher/LauncherProcessWin.cpp
|
||||
+++ b/browser/app/winlauncher/LauncherProcessWin.cpp
|
||||
@@ -22,6 +22,7 @@
|
||||
|
|
@ -68,7 +68,7 @@ index b40e0fceb567c0d217adf284e13f434e49cc8467..2c4e6d5fbf8da40954ad6a5b15e41249
|
|||
#include <windows.h>
|
||||
#include <processthreadsapi.h>
|
||||
|
||||
@@ -421,8 +422,18 @@ Maybe<int> LauncherMain(int& argc, wchar_t* argv[],
|
||||
@@ -425,8 +426,18 @@ Maybe<int> LauncherMain(int& argc, wchar_t* argv[],
|
||||
HANDLE stdHandles[] = {::GetStdHandle(STD_INPUT_HANDLE),
|
||||
::GetStdHandle(STD_OUTPUT_HANDLE),
|
||||
::GetStdHandle(STD_ERROR_HANDLE)};
|
||||
|
|
@ -106,10 +106,10 @@ index f6d425f36a965f03ac82dbe3ab6cde06f12751ac..d60999ab2658b1e1e5f07a8aee530451
|
|||
browser/chrome/browser/search-extensions/amazon/favicon.ico
|
||||
browser/chrome/browser/search-extensions/amazondotcn/favicon.ico
|
||||
diff --git a/browser/installer/package-manifest.in b/browser/installer/package-manifest.in
|
||||
index 4068c0c165fcebd0a72f4d780bc0cbd680fe7a9c..fec7f8505d22485fa6ad4193d59387f493bd1ef8 100644
|
||||
index 1b87a9ab4aec939acac1da54a2b6670cc581fe86..a638dbe8e2f260d8be550fa8eb5bf6f6c2c31080 100644
|
||||
--- a/browser/installer/package-manifest.in
|
||||
+++ b/browser/installer/package-manifest.in
|
||||
@@ -197,6 +197,9 @@
|
||||
@@ -185,6 +185,9 @@
|
||||
@RESPATH@/chrome/remote.manifest
|
||||
#endif
|
||||
|
||||
|
|
@ -120,7 +120,7 @@ index 4068c0c165fcebd0a72f4d780bc0cbd680fe7a9c..fec7f8505d22485fa6ad4193d59387f4
|
|||
@RESPATH@/components/extensions-toolkit.manifest
|
||||
@RESPATH@/browser/components/extensions-browser.manifest
|
||||
diff --git a/devtools/server/socket/websocket-server.js b/devtools/server/socket/websocket-server.js
|
||||
index 4236ec2921bd57c58cfffdf1cdcf509d76fca3db..23d0cb1f06bb8c7a1cac8fcec94a99fba5bfe3f2 100644
|
||||
index d49c6fbf1bf83b832795fa674f6b41f223eef812..7ea3540947ff5f61b15f27fbf4b955649f8e9ff9 100644
|
||||
--- a/devtools/server/socket/websocket-server.js
|
||||
+++ b/devtools/server/socket/websocket-server.js
|
||||
@@ -134,13 +134,12 @@ function writeHttpResponse(output, response) {
|
||||
|
|
@ -167,31 +167,28 @@ index 4236ec2921bd57c58cfffdf1cdcf509d76fca3db..23d0cb1f06bb8c7a1cac8fcec94a99fb
|
|||
const transportProvider = {
|
||||
setListener(upgradeListener) {
|
||||
diff --git a/docshell/base/BrowsingContext.cpp b/docshell/base/BrowsingContext.cpp
|
||||
index 0ef5e02d2ae365b4e7b30fd49771e547c030bcfc..0787518696fc90bba3dce8c1e1c00387c3e7a6c9 100644
|
||||
index 6e1a1b689398fa6c3c73f2f243ae02c67a4476c8..9dcf71ce753cf11f295d83eb7653e940065c8e2c 100644
|
||||
--- a/docshell/base/BrowsingContext.cpp
|
||||
+++ b/docshell/base/BrowsingContext.cpp
|
||||
@@ -114,6 +114,20 @@ struct ParamTraits<mozilla::dom::PrefersColorSchemeOverride>
|
||||
mozilla::dom::PrefersColorSchemeOverride::None,
|
||||
mozilla::dom::PrefersColorSchemeOverride::EndGuard_> {};
|
||||
@@ -106,8 +106,15 @@ struct ParamTraits<mozilla::dom::DisplayMode>
|
||||
|
||||
template <>
|
||||
struct ParamTraits<mozilla::dom::PrefersColorSchemeOverride>
|
||||
- : public mozilla::dom::WebIDLEnumSerializer<
|
||||
- mozilla::dom::PrefersColorSchemeOverride> {};
|
||||
+ : public mozilla::dom::WebIDLEnumSerializer<mozilla::dom::PrefersColorSchemeOverride> {};
|
||||
+
|
||||
+template <>
|
||||
+struct ParamTraits<mozilla::dom::PrefersReducedMotionOverride>
|
||||
+ : public ContiguousEnumSerializer<
|
||||
+ mozilla::dom::PrefersReducedMotionOverride,
|
||||
+ mozilla::dom::PrefersReducedMotionOverride::None,
|
||||
+ mozilla::dom::PrefersReducedMotionOverride::EndGuard_> {};
|
||||
+ : public mozilla::dom::WebIDLEnumSerializer<mozilla::dom::PrefersReducedMotionOverride> {};
|
||||
+
|
||||
+template <>
|
||||
+struct ParamTraits<mozilla::dom::ForcedColorsOverride>
|
||||
+ : public ContiguousEnumSerializer<
|
||||
+ mozilla::dom::ForcedColorsOverride,
|
||||
+ mozilla::dom::ForcedColorsOverride::None,
|
||||
+ mozilla::dom::ForcedColorsOverride::EndGuard_> {};
|
||||
+
|
||||
+ : public mozilla::dom::WebIDLEnumSerializer<mozilla::dom::ForcedColorsOverride> {};
|
||||
|
||||
template <>
|
||||
struct ParamTraits<mozilla::dom::ExplicitActiveStatus>
|
||||
: public ContiguousEnumSerializer<
|
||||
@@ -2793,6 +2807,40 @@ void BrowsingContext::DidSet(FieldIndex<IDX_PrefersColorSchemeOverride>,
|
||||
@@ -2804,6 +2811,40 @@ void BrowsingContext::DidSet(FieldIndex<IDX_PrefersColorSchemeOverride>,
|
||||
PresContextAffectingFieldChanged();
|
||||
}
|
||||
|
||||
|
|
@ -233,10 +230,10 @@ index 0ef5e02d2ae365b4e7b30fd49771e547c030bcfc..0787518696fc90bba3dce8c1e1c00387
|
|||
nsString&& aOldValue) {
|
||||
MOZ_ASSERT(IsTop());
|
||||
diff --git a/docshell/base/BrowsingContext.h b/docshell/base/BrowsingContext.h
|
||||
index 7554128cf49ce929c973aeddf5f50ede01829c28..81ae7ec9523b944654c048af6a9f6844799eb4f3 100644
|
||||
index 5ec95a61e4d3af265cbe7dd9d83f6535da1d103e..f8acafe6d58c429af27a38363e06ad546dfbfddd 100644
|
||||
--- a/docshell/base/BrowsingContext.h
|
||||
+++ b/docshell/base/BrowsingContext.h
|
||||
@@ -200,10 +200,10 @@ struct EmbedderColorSchemes {
|
||||
@@ -199,10 +199,10 @@ struct EmbedderColorSchemes {
|
||||
FIELD(GVInaudibleAutoplayRequestStatus, GVAutoplayRequestStatus) \
|
||||
/* ScreenOrientation-related APIs */ \
|
||||
FIELD(CurrentOrientationAngle, float) \
|
||||
|
|
@ -249,7 +246,7 @@ index 7554128cf49ce929c973aeddf5f50ede01829c28..81ae7ec9523b944654c048af6a9f6844
|
|||
FIELD(EmbedderElementType, Maybe<nsString>) \
|
||||
FIELD(MessageManagerGroup, nsString) \
|
||||
FIELD(MaxTouchPointsOverride, uint8_t) \
|
||||
@@ -241,6 +241,10 @@ struct EmbedderColorSchemes {
|
||||
@@ -240,6 +240,10 @@ struct EmbedderColorSchemes {
|
||||
* <browser> embedder element. */ \
|
||||
FIELD(EmbedderColorSchemes, EmbedderColorSchemes) \
|
||||
FIELD(DisplayMode, dom::DisplayMode) \
|
||||
|
|
@ -260,7 +257,7 @@ index 7554128cf49ce929c973aeddf5f50ede01829c28..81ae7ec9523b944654c048af6a9f6844
|
|||
/* The number of entries added to the session history because of this \
|
||||
* browsing context. */ \
|
||||
FIELD(HistoryEntryCount, uint32_t) \
|
||||
@@ -933,6 +937,14 @@ class BrowsingContext : public nsILoadContext, public nsWrapperCache {
|
||||
@@ -926,6 +930,14 @@ class BrowsingContext : public nsILoadContext, public nsWrapperCache {
|
||||
return GetPrefersColorSchemeOverride();
|
||||
}
|
||||
|
||||
|
|
@ -275,7 +272,7 @@ index 7554128cf49ce929c973aeddf5f50ede01829c28..81ae7ec9523b944654c048af6a9f6844
|
|||
bool IsInBFCache() const;
|
||||
|
||||
bool AllowJavascript() const { return GetAllowJavascript(); }
|
||||
@@ -1097,6 +1109,23 @@ class BrowsingContext : public nsILoadContext, public nsWrapperCache {
|
||||
@@ -1090,6 +1102,23 @@ class BrowsingContext : public nsILoadContext, public nsWrapperCache {
|
||||
void WalkPresContexts(Callback&&);
|
||||
void PresContextAffectingFieldChanged();
|
||||
|
||||
|
|
@ -300,10 +297,10 @@ index 7554128cf49ce929c973aeddf5f50ede01829c28..81ae7ec9523b944654c048af6a9f6844
|
|||
|
||||
bool CanSet(FieldIndex<IDX_SuspendMediaWhenInactive>, bool, ContentParent*) {
|
||||
diff --git a/docshell/base/CanonicalBrowsingContext.cpp b/docshell/base/CanonicalBrowsingContext.cpp
|
||||
index d5f85b1e571a7840425164a3c7715e8c70ed5c8b..5ba7484b1e7f817b2bb21dd6b5b35c6ffe2c1ca5 100644
|
||||
index 84f2d2960a3fa642754e0c909f92d96865e39984..e91fc85fc7a7966d2d536839fab823ae88d1b4bd 100644
|
||||
--- a/docshell/base/CanonicalBrowsingContext.cpp
|
||||
+++ b/docshell/base/CanonicalBrowsingContext.cpp
|
||||
@@ -1466,6 +1466,12 @@ void CanonicalBrowsingContext::LoadURI(nsIURI* aURI,
|
||||
@@ -1477,6 +1477,12 @@ void CanonicalBrowsingContext::LoadURI(nsIURI* aURI,
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -317,7 +314,7 @@ index d5f85b1e571a7840425164a3c7715e8c70ed5c8b..5ba7484b1e7f817b2bb21dd6b5b35c6f
|
|||
}
|
||||
|
||||
diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp
|
||||
index 0b8212fbf3f81ef6264f17fc8e84f91cde39c0f7..99d43d662978c0418231b8ea55d670f81b5618d7 100644
|
||||
index 3404597343e0d21c42c5adc2f2849888869cf75a..558f03f30672b9f0fdb68ba8d23a0d878d33643d 100644
|
||||
--- a/docshell/base/nsDocShell.cpp
|
||||
+++ b/docshell/base/nsDocShell.cpp
|
||||
@@ -15,6 +15,12 @@
|
||||
|
|
@ -379,7 +376,7 @@ index 0b8212fbf3f81ef6264f17fc8e84f91cde39c0f7..99d43d662978c0418231b8ea55d670f8
|
|||
mAllowAuth(mItemType == typeContent),
|
||||
mAllowKeywordFixup(false),
|
||||
mDisableMetaRefreshWhenInactive(false),
|
||||
@@ -3115,6 +3132,214 @@ nsDocShell::GetMessageManager(ContentFrameMessageManager** aMessageManager) {
|
||||
@@ -3101,6 +3118,214 @@ nsDocShell::GetMessageManager(ContentFrameMessageManager** aMessageManager) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
|
@ -594,7 +591,7 @@ index 0b8212fbf3f81ef6264f17fc8e84f91cde39c0f7..99d43d662978c0418231b8ea55d670f8
|
|||
NS_IMETHODIMP
|
||||
nsDocShell::GetIsNavigating(bool* aOut) {
|
||||
*aOut = mIsNavigating;
|
||||
@@ -4803,7 +5028,7 @@ nsDocShell::GetVisibility(bool* aVisibility) {
|
||||
@@ -4789,7 +5014,7 @@ nsDocShell::GetVisibility(bool* aVisibility) {
|
||||
}
|
||||
|
||||
void nsDocShell::ActivenessMaybeChanged() {
|
||||
|
|
@ -603,7 +600,7 @@ index 0b8212fbf3f81ef6264f17fc8e84f91cde39c0f7..99d43d662978c0418231b8ea55d670f8
|
|||
if (RefPtr<PresShell> presShell = GetPresShell()) {
|
||||
presShell->ActivenessMaybeChanged();
|
||||
}
|
||||
@@ -6722,6 +6947,10 @@ bool nsDocShell::CanSavePresentation(uint32_t aLoadType,
|
||||
@@ -6711,6 +6936,10 @@ bool nsDocShell::CanSavePresentation(uint32_t aLoadType,
|
||||
return false; // no entry to save into
|
||||
}
|
||||
|
||||
|
|
@ -614,7 +611,7 @@ index 0b8212fbf3f81ef6264f17fc8e84f91cde39c0f7..99d43d662978c0418231b8ea55d670f8
|
|||
MOZ_ASSERT(!mozilla::SessionHistoryInParent(),
|
||||
"mOSHE cannot be non-null with SHIP");
|
||||
nsCOMPtr<nsIDocumentViewer> viewer = mOSHE->GetDocumentViewer();
|
||||
@@ -8454,6 +8683,12 @@ nsresult nsDocShell::PerformRetargeting(nsDocShellLoadState* aLoadState) {
|
||||
@@ -8443,6 +8672,12 @@ nsresult nsDocShell::PerformRetargeting(nsDocShellLoadState* aLoadState) {
|
||||
true, // aForceNoOpener
|
||||
getter_AddRefs(newBC));
|
||||
MOZ_ASSERT(!newBC);
|
||||
|
|
@ -627,7 +624,7 @@ index 0b8212fbf3f81ef6264f17fc8e84f91cde39c0f7..99d43d662978c0418231b8ea55d670f8
|
|||
return rv;
|
||||
}
|
||||
|
||||
@@ -9566,6 +9801,16 @@ nsresult nsDocShell::InternalLoad(nsDocShellLoadState* aLoadState,
|
||||
@@ -9569,6 +9804,16 @@ nsresult nsDocShell::InternalLoad(nsDocShellLoadState* aLoadState,
|
||||
nsINetworkPredictor::PREDICT_LOAD, attrs, nullptr);
|
||||
|
||||
nsCOMPtr<nsIRequest> req;
|
||||
|
|
@ -644,7 +641,7 @@ index 0b8212fbf3f81ef6264f17fc8e84f91cde39c0f7..99d43d662978c0418231b8ea55d670f8
|
|||
rv = DoURILoad(aLoadState, aCacheKey, getter_AddRefs(req));
|
||||
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
@@ -12714,6 +12959,9 @@ class OnLinkClickEvent : public Runnable {
|
||||
@@ -12732,6 +12977,9 @@ class OnLinkClickEvent : public Runnable {
|
||||
mHandler->OnLinkClickSync(mContent, mLoadState, mNoOpenerImplied,
|
||||
mTriggeringPrincipal);
|
||||
}
|
||||
|
|
@ -654,7 +651,7 @@ index 0b8212fbf3f81ef6264f17fc8e84f91cde39c0f7..99d43d662978c0418231b8ea55d670f8
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
@@ -12798,6 +13046,8 @@ nsresult nsDocShell::OnLinkClick(
|
||||
@@ -12816,6 +13064,8 @@ nsresult nsDocShell::OnLinkClick(
|
||||
nsCOMPtr<nsIRunnable> ev =
|
||||
new OnLinkClickEvent(this, aContent, loadState, noOpenerImplied,
|
||||
aIsTrusted, aTriggeringPrincipal);
|
||||
|
|
@ -664,7 +661,7 @@ index 0b8212fbf3f81ef6264f17fc8e84f91cde39c0f7..99d43d662978c0418231b8ea55d670f8
|
|||
}
|
||||
|
||||
diff --git a/docshell/base/nsDocShell.h b/docshell/base/nsDocShell.h
|
||||
index 9f2d9a17dc0d54be4bd09f8e04da6e85f987fe34..8ff6e67fedef0bf73297c4cfed569b8f2ced36d9 100644
|
||||
index 82ac6c9ab9dbc102a429ab0fe6cb24b8fcdf477f..f6328c25349cf39fcce973adcf908782e8721447 100644
|
||||
--- a/docshell/base/nsDocShell.h
|
||||
+++ b/docshell/base/nsDocShell.h
|
||||
@@ -15,6 +15,7 @@
|
||||
|
|
@ -699,7 +696,7 @@ index 9f2d9a17dc0d54be4bd09f8e04da6e85f987fe34..8ff6e67fedef0bf73297c4cfed569b8f
|
|||
// Create a content viewer within this nsDocShell for the given
|
||||
// `WindowGlobalChild` actor.
|
||||
nsresult CreateDocumentViewerForActor(
|
||||
@@ -1003,6 +1014,8 @@ class nsDocShell final : public nsDocLoader,
|
||||
@@ -1004,6 +1015,8 @@ class nsDocShell final : public nsDocLoader,
|
||||
|
||||
bool CSSErrorReportingEnabled() const { return mCSSErrorReportingEnabled; }
|
||||
|
||||
|
|
@ -708,7 +705,7 @@ index 9f2d9a17dc0d54be4bd09f8e04da6e85f987fe34..8ff6e67fedef0bf73297c4cfed569b8f
|
|||
// Handles retrieval of subframe session history for nsDocShell::LoadURI. If a
|
||||
// load is requested in a subframe of the current DocShell, the subframe
|
||||
// loadType may need to reflect the loadType of the parent document, or in
|
||||
@@ -1294,6 +1307,16 @@ class nsDocShell final : public nsDocLoader,
|
||||
@@ -1295,6 +1308,16 @@ class nsDocShell final : public nsDocLoader,
|
||||
bool mAllowDNSPrefetch : 1;
|
||||
bool mAllowWindowControl : 1;
|
||||
bool mCSSErrorReportingEnabled : 1;
|
||||
|
|
@ -726,7 +723,7 @@ index 9f2d9a17dc0d54be4bd09f8e04da6e85f987fe34..8ff6e67fedef0bf73297c4cfed569b8f
|
|||
bool mAllowKeywordFixup : 1;
|
||||
bool mDisableMetaRefreshWhenInactive : 1;
|
||||
diff --git a/docshell/base/nsIDocShell.idl b/docshell/base/nsIDocShell.idl
|
||||
index 9e79b6831a74c6de7c6a6c56d9a024d29c1c704b..db5cd5586b74ec65d788480f9c5fca93eb562c21 100644
|
||||
index 21f09a517e91644f81f5bb823f556c15cd06e51f..a68d30e0a60f03a0942ac1cd8a1f83804fdfd3e0 100644
|
||||
--- a/docshell/base/nsIDocShell.idl
|
||||
+++ b/docshell/base/nsIDocShell.idl
|
||||
@@ -44,6 +44,7 @@ interface nsIURI;
|
||||
|
|
@ -737,7 +734,7 @@ index 9e79b6831a74c6de7c6a6c56d9a024d29c1c704b..db5cd5586b74ec65d788480f9c5fca93
|
|||
interface nsIEditor;
|
||||
interface nsIEditingSession;
|
||||
interface nsIInputStream;
|
||||
@@ -767,6 +768,36 @@ interface nsIDocShell : nsIDocShellTreeItem
|
||||
@@ -754,6 +755,36 @@ interface nsIDocShell : nsIDocShellTreeItem
|
||||
*/
|
||||
void synchronizeLayoutHistoryState();
|
||||
|
||||
|
|
@ -775,10 +772,10 @@ index 9e79b6831a74c6de7c6a6c56d9a024d29c1c704b..db5cd5586b74ec65d788480f9c5fca93
|
|||
* This attempts to save any applicable layout history state (like
|
||||
* scroll position) in the nsISHEntry. This is normally done
|
||||
diff --git a/dom/base/Document.cpp b/dom/base/Document.cpp
|
||||
index 0a2f5be08d0dcad40cd11f4b064a1287b8141e2f..a845203c6aaea8384e8835cbe005669767a1b196 100644
|
||||
index 4e9286a91e3b0f1114aa0a7aa6ff81dde615a73d..941a0c3dac71008ca760024a1e828f75f6af5182 100644
|
||||
--- a/dom/base/Document.cpp
|
||||
+++ b/dom/base/Document.cpp
|
||||
@@ -3705,6 +3705,9 @@ void Document::SendToConsole(nsCOMArray<nsISecurityConsoleMessage>& aMessages) {
|
||||
@@ -3676,6 +3676,9 @@ void Document::SendToConsole(nsCOMArray<nsISecurityConsoleMessage>& aMessages) {
|
||||
}
|
||||
|
||||
void Document::ApplySettingsFromCSP(bool aSpeculative) {
|
||||
|
|
@ -788,7 +785,7 @@ index 0a2f5be08d0dcad40cd11f4b064a1287b8141e2f..a845203c6aaea8384e8835cbe0056697
|
|||
nsresult rv = NS_OK;
|
||||
if (!aSpeculative) {
|
||||
// 1) apply settings from regular CSP
|
||||
@@ -3762,6 +3765,11 @@ nsresult Document::InitCSP(nsIChannel* aChannel) {
|
||||
@@ -3733,6 +3736,11 @@ nsresult Document::InitCSP(nsIChannel* aChannel) {
|
||||
MOZ_ASSERT(!mScriptGlobalObject,
|
||||
"CSP must be initialized before mScriptGlobalObject is set!");
|
||||
|
||||
|
|
@ -800,7 +797,7 @@ index 0a2f5be08d0dcad40cd11f4b064a1287b8141e2f..a845203c6aaea8384e8835cbe0056697
|
|||
// If this is a data document - no need to set CSP.
|
||||
if (mLoadedAsData) {
|
||||
return NS_OK;
|
||||
@@ -4557,6 +4565,10 @@ bool Document::HasFocus(ErrorResult& rv) const {
|
||||
@@ -4500,6 +4508,10 @@ bool Document::HasFocus(ErrorResult& rv) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -811,7 +808,7 @@ index 0a2f5be08d0dcad40cd11f4b064a1287b8141e2f..a845203c6aaea8384e8835cbe0056697
|
|||
if (!fm->IsInActiveWindow(bc)) {
|
||||
return false;
|
||||
}
|
||||
@@ -18878,6 +18890,68 @@ ColorScheme Document::PreferredColorScheme(IgnoreRFP aIgnoreRFP) const {
|
||||
@@ -18849,6 +18861,66 @@ ColorScheme Document::PreferredColorScheme(IgnoreRFP aIgnoreRFP) const {
|
||||
return PreferenceSheet::PrefsFor(*this).mColorScheme;
|
||||
}
|
||||
|
||||
|
|
@ -837,7 +834,6 @@ index 0a2f5be08d0dcad40cd11f4b064a1287b8141e2f..a845203c6aaea8384e8835cbe0056697
|
|||
+ case dom::PrefersReducedMotionOverride::No_preference:
|
||||
+ return false;
|
||||
+ case dom::PrefersReducedMotionOverride::None:
|
||||
+ case dom::PrefersReducedMotionOverride::EndGuard_:
|
||||
+ break;
|
||||
+ }
|
||||
+ }
|
||||
|
|
@ -866,7 +862,6 @@ index 0a2f5be08d0dcad40cd11f4b064a1287b8141e2f..a845203c6aaea8384e8835cbe0056697
|
|||
+ case dom::ForcedColorsOverride::None:
|
||||
+ return false;
|
||||
+ case dom::ForcedColorsOverride::No_override:
|
||||
+ case dom::ForcedColorsOverride::EndGuard_:
|
||||
+ break;
|
||||
+ }
|
||||
+ }
|
||||
|
|
@ -881,10 +876,10 @@ index 0a2f5be08d0dcad40cd11f4b064a1287b8141e2f..a845203c6aaea8384e8835cbe0056697
|
|||
if (!sLoadingForegroundTopLevelContentDocument) {
|
||||
return false;
|
||||
diff --git a/dom/base/Document.h b/dom/base/Document.h
|
||||
index 3f8032594868b8aa1c8bec2d25b789518170eb0e..5017b426369378b892a224a88410f18e743da45f 100644
|
||||
index a52c61addffc4a2344fa06cb0bceebe6a7ca9075..b0f8d65e5341bf277e48bef3b88cb4cc384fbc49 100644
|
||||
--- a/dom/base/Document.h
|
||||
+++ b/dom/base/Document.h
|
||||
@@ -4051,6 +4051,9 @@ class Document : public nsINode,
|
||||
@@ -4044,6 +4044,9 @@ class Document : public nsINode,
|
||||
// color-scheme meta tag.
|
||||
ColorScheme DefaultColorScheme() const;
|
||||
|
||||
|
|
@ -895,10 +890,10 @@ index 3f8032594868b8aa1c8bec2d25b789518170eb0e..5017b426369378b892a224a88410f18e
|
|||
|
||||
static bool AutomaticStorageAccessPermissionCanBeGranted(
|
||||
diff --git a/dom/base/Navigator.cpp b/dom/base/Navigator.cpp
|
||||
index d4b04f809228fecc3e2dbf33dac42151ffc24b11..39dfddff7404e878cd9fcc8f3b9bb734ab72d743 100644
|
||||
index 14a00b8ed85f69312a89990acbb5e0f9755bd832..4b07c28615920a95a2ba59247f8575515cac4235 100644
|
||||
--- a/dom/base/Navigator.cpp
|
||||
+++ b/dom/base/Navigator.cpp
|
||||
@@ -335,14 +335,18 @@ void Navigator::GetAppName(nsAString& aAppName) const {
|
||||
@@ -338,14 +338,18 @@ void Navigator::GetAppName(nsAString& aAppName) const {
|
||||
* for more detail.
|
||||
*/
|
||||
/* static */
|
||||
|
|
@ -919,7 +914,7 @@ index d4b04f809228fecc3e2dbf33dac42151ffc24b11..39dfddff7404e878cd9fcc8f3b9bb734
|
|||
|
||||
// Split values on commas.
|
||||
for (nsDependentSubstring lang :
|
||||
@@ -394,7 +398,13 @@ void Navigator::GetLanguage(nsAString& aLanguage) {
|
||||
@@ -397,7 +401,13 @@ void Navigator::GetLanguage(nsAString& aLanguage) {
|
||||
}
|
||||
|
||||
void Navigator::GetLanguages(nsTArray<nsString>& aLanguages) {
|
||||
|
|
@ -935,10 +930,10 @@ index d4b04f809228fecc3e2dbf33dac42151ffc24b11..39dfddff7404e878cd9fcc8f3b9bb734
|
|||
// The returned value is cached by the binding code. The window listens to the
|
||||
// accept languages change and will clear the cache when needed. It has to
|
||||
diff --git a/dom/base/Navigator.h b/dom/base/Navigator.h
|
||||
index 998328cfc6f36fd579e4fe26629a92a1ceefa9fa..c73e48d8dfe5a66fb9eb7f6bf49527f88cabc884 100644
|
||||
index e559dc4d6aef61b7012a27f3d6c3186a12a15319..9798a50789ce972c4d9e94419e20a5cde4cd552a 100644
|
||||
--- a/dom/base/Navigator.h
|
||||
+++ b/dom/base/Navigator.h
|
||||
@@ -216,7 +216,7 @@ class Navigator final : public nsISupports, public nsWrapperCache {
|
||||
@@ -215,7 +215,7 @@ class Navigator final : public nsISupports, public nsWrapperCache {
|
||||
|
||||
StorageManager* Storage();
|
||||
|
||||
|
|
@ -948,10 +943,10 @@ index 998328cfc6f36fd579e4fe26629a92a1ceefa9fa..c73e48d8dfe5a66fb9eb7f6bf49527f8
|
|||
dom::MediaCapabilities* MediaCapabilities();
|
||||
dom::MediaSession* MediaSession();
|
||||
diff --git a/dom/base/nsContentUtils.cpp b/dom/base/nsContentUtils.cpp
|
||||
index 6d3a9f8937a7b37bf82a18362d7ee525e4e267f4..c598acdcda9d47cdd193011e8faa916988872a18 100644
|
||||
index c6f1687f73df6b1849a191ff8722dc9fc26fc3eb..9fdface27d5c9bd0c61b8af229a31be2356c46b5 100644
|
||||
--- a/dom/base/nsContentUtils.cpp
|
||||
+++ b/dom/base/nsContentUtils.cpp
|
||||
@@ -8697,7 +8697,8 @@ nsresult nsContentUtils::SendMouseEvent(
|
||||
@@ -8711,7 +8711,8 @@ nsresult nsContentUtils::SendMouseEvent(
|
||||
bool aIgnoreRootScrollFrame, float aPressure,
|
||||
unsigned short aInputSourceArg, uint32_t aIdentifier, bool aToWindow,
|
||||
PreventDefaultResult* aPreventDefault, bool aIsDOMEventSynthesized,
|
||||
|
|
@ -961,7 +956,7 @@ index 6d3a9f8937a7b37bf82a18362d7ee525e4e267f4..c598acdcda9d47cdd193011e8faa9169
|
|||
nsPoint offset;
|
||||
nsCOMPtr<nsIWidget> widget = GetWidget(aPresShell, &offset);
|
||||
if (!widget) return NS_ERROR_FAILURE;
|
||||
@@ -8705,6 +8706,7 @@ nsresult nsContentUtils::SendMouseEvent(
|
||||
@@ -8719,6 +8720,7 @@ nsresult nsContentUtils::SendMouseEvent(
|
||||
EventMessage msg;
|
||||
Maybe<WidgetMouseEvent::ExitFrom> exitFrom;
|
||||
bool contextMenuKey = false;
|
||||
|
|
@ -969,7 +964,7 @@ index 6d3a9f8937a7b37bf82a18362d7ee525e4e267f4..c598acdcda9d47cdd193011e8faa9169
|
|||
if (aType.EqualsLiteral("mousedown")) {
|
||||
msg = eMouseDown;
|
||||
} else if (aType.EqualsLiteral("mouseup")) {
|
||||
@@ -8729,6 +8731,12 @@ nsresult nsContentUtils::SendMouseEvent(
|
||||
@@ -8743,6 +8745,12 @@ nsresult nsContentUtils::SendMouseEvent(
|
||||
msg = eMouseHitTest;
|
||||
} else if (aType.EqualsLiteral("MozMouseExploreByTouch")) {
|
||||
msg = eMouseExploreByTouch;
|
||||
|
|
@ -982,7 +977,7 @@ index 6d3a9f8937a7b37bf82a18362d7ee525e4e267f4..c598acdcda9d47cdd193011e8faa9169
|
|||
} else {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
@@ -8737,12 +8745,21 @@ nsresult nsContentUtils::SendMouseEvent(
|
||||
@@ -8751,12 +8759,21 @@ nsresult nsContentUtils::SendMouseEvent(
|
||||
aInputSourceArg = MouseEvent_Binding::MOZ_SOURCE_MOUSE;
|
||||
}
|
||||
|
||||
|
|
@ -1006,7 +1001,7 @@ index 6d3a9f8937a7b37bf82a18362d7ee525e4e267f4..c598acdcda9d47cdd193011e8faa9169
|
|||
event.pointerId = aIdentifier;
|
||||
event.mModifiers = GetWidgetModifiers(aModifiers);
|
||||
event.mButton = aButton;
|
||||
@@ -8753,8 +8770,10 @@ nsresult nsContentUtils::SendMouseEvent(
|
||||
@@ -8767,8 +8784,10 @@ nsresult nsContentUtils::SendMouseEvent(
|
||||
event.mPressure = aPressure;
|
||||
event.mInputSource = aInputSourceArg;
|
||||
event.mClickCount = aClickCount;
|
||||
|
|
@ -1018,10 +1013,10 @@ index 6d3a9f8937a7b37bf82a18362d7ee525e4e267f4..c598acdcda9d47cdd193011e8faa9169
|
|||
nsPresContext* presContext = aPresShell->GetPresContext();
|
||||
if (!presContext) return NS_ERROR_FAILURE;
|
||||
diff --git a/dom/base/nsContentUtils.h b/dom/base/nsContentUtils.h
|
||||
index db68b67c5a3e60ac214231ac82fd9f652a697858..529b49b2c6c5f693747d847bca85e93415b9159c 100644
|
||||
index 338fc097dede02a538f240ba4cc66307086c7f56..8345e47237bc40490bd17019a053cce4c3d74a91 100644
|
||||
--- a/dom/base/nsContentUtils.h
|
||||
+++ b/dom/base/nsContentUtils.h
|
||||
@@ -2958,7 +2958,8 @@ class nsContentUtils {
|
||||
@@ -3055,7 +3055,8 @@ class nsContentUtils {
|
||||
int32_t aModifiers, bool aIgnoreRootScrollFrame, float aPressure,
|
||||
unsigned short aInputSourceArg, uint32_t aIdentifier, bool aToWindow,
|
||||
mozilla::PreventDefaultResult* aPreventDefault,
|
||||
|
|
@ -1032,7 +1027,7 @@ index db68b67c5a3e60ac214231ac82fd9f652a697858..529b49b2c6c5f693747d847bca85e934
|
|||
static void FirePageShowEventForFrameLoaderSwap(
|
||||
nsIDocShellTreeItem* aItem,
|
||||
diff --git a/dom/base/nsDOMWindowUtils.cpp b/dom/base/nsDOMWindowUtils.cpp
|
||||
index 6c547a9fd8604ac7fd7b965be473bc4120b2fdf7..c9aa1951da7af70913f77c76bb7c68ba144adaeb 100644
|
||||
index 9bc8340b9009717e0feecd5c14ff02be07a03daf..70ea04c7f11e6ccfadf72a82ec1741fac10ef5fd 100644
|
||||
--- a/dom/base/nsDOMWindowUtils.cpp
|
||||
+++ b/dom/base/nsDOMWindowUtils.cpp
|
||||
@@ -685,6 +685,26 @@ nsDOMWindowUtils::GetPresShellId(uint32_t* aPresShellId) {
|
||||
|
|
@ -1110,10 +1105,10 @@ index 63968c9b7a4e418e4c0de6e7a75fa215a36a9105..decf3ea3833ccdffd49a7aded2d600f9
|
|||
MOZ_CAN_RUN_SCRIPT
|
||||
nsresult SendTouchEventCommon(
|
||||
diff --git a/dom/base/nsFocusManager.cpp b/dom/base/nsFocusManager.cpp
|
||||
index 60f45157cdfceb7ebf4f9a1681d0e59dc1822360..964d7cc1b847cd8ef21cef29be4fdc11168b18d8 100644
|
||||
index 5a4cf78d65eee0adcbeca33787706113eca19de7..7c8016e422ccc9e86563eaa83c9acc1dad0e5967 100644
|
||||
--- a/dom/base/nsFocusManager.cpp
|
||||
+++ b/dom/base/nsFocusManager.cpp
|
||||
@@ -1673,6 +1673,10 @@ Maybe<uint64_t> nsFocusManager::SetFocusInner(Element* aNewContent,
|
||||
@@ -1675,6 +1675,10 @@ Maybe<uint64_t> nsFocusManager::SetFocusInner(Element* aNewContent,
|
||||
(GetActiveBrowsingContext() == newRootBrowsingContext);
|
||||
}
|
||||
|
||||
|
|
@ -1124,7 +1119,27 @@ index 60f45157cdfceb7ebf4f9a1681d0e59dc1822360..964d7cc1b847cd8ef21cef29be4fdc11
|
|||
// Exit fullscreen if a website focuses another window
|
||||
if (StaticPrefs::full_screen_api_exit_on_windowRaise() &&
|
||||
!isElementInActiveWindow && (aFlags & FLAG_RAISE)) {
|
||||
@@ -2946,7 +2950,9 @@ void nsFocusManager::RaiseWindow(nsPIDOMWindowOuter* aWindow,
|
||||
@@ -2242,6 +2246,7 @@ bool nsFocusManager::BlurImpl(BrowsingContext* aBrowsingContextToClear,
|
||||
bool aIsLeavingDocument, bool aAdjustWidget,
|
||||
bool aRemainActive, Element* aElementToFocus,
|
||||
uint64_t aActionId) {
|
||||
+
|
||||
LOGFOCUS(("<<Blur begin actionid: %" PRIu64 ">>", aActionId));
|
||||
|
||||
// hold a reference to the focused content, which may be null
|
||||
@@ -2288,6 +2293,11 @@ bool nsFocusManager::BlurImpl(BrowsingContext* aBrowsingContextToClear,
|
||||
return true;
|
||||
}
|
||||
|
||||
+ // Playwright: emulate focused page by never bluring when leaving document.
|
||||
+ if (XRE_IsContentProcess() && aIsLeavingDocument && docShell && nsDocShell::Cast(docShell)->ShouldOverrideHasFocus()) {
|
||||
+ return true;
|
||||
+ }
|
||||
+
|
||||
// Keep a ref to presShell since dispatching the DOM event may cause
|
||||
// the document to be destroyed.
|
||||
RefPtr<PresShell> presShell = docShell->GetPresShell();
|
||||
@@ -2947,7 +2957,9 @@ void nsFocusManager::RaiseWindow(nsPIDOMWindowOuter* aWindow,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1136,10 +1151,10 @@ index 60f45157cdfceb7ebf4f9a1681d0e59dc1822360..964d7cc1b847cd8ef21cef29be4fdc11
|
|||
// care of lowering the present active window. This happens in
|
||||
// a separate runnable to avoid touching multiple windows in
|
||||
diff --git a/dom/base/nsGlobalWindowOuter.cpp b/dom/base/nsGlobalWindowOuter.cpp
|
||||
index a10808b95d5f7c81942d2a513f63a72c7821061e..027b9a3c87811b794816bddb90ff67bc271f7faa 100644
|
||||
index e28dcdb092b23558702377af32eece5d273d4cf3..a37ae9d6ccd978d5e84562450e7039d6a75d517b 100644
|
||||
--- a/dom/base/nsGlobalWindowOuter.cpp
|
||||
+++ b/dom/base/nsGlobalWindowOuter.cpp
|
||||
@@ -2510,10 +2510,16 @@ nsresult nsGlobalWindowOuter::SetNewDocument(Document* aDocument,
|
||||
@@ -2509,10 +2509,16 @@ nsresult nsGlobalWindowOuter::SetNewDocument(Document* aDocument,
|
||||
}();
|
||||
|
||||
if (!isContentAboutBlankInChromeDocshell) {
|
||||
|
|
@ -1160,7 +1175,7 @@ index a10808b95d5f7c81942d2a513f63a72c7821061e..027b9a3c87811b794816bddb90ff67bc
|
|||
}
|
||||
}
|
||||
|
||||
@@ -2633,6 +2639,19 @@ void nsGlobalWindowOuter::DispatchDOMWindowCreated() {
|
||||
@@ -2632,6 +2638,19 @@ void nsGlobalWindowOuter::DispatchDOMWindowCreated() {
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1193,10 +1208,10 @@ index 8337a7353fb8e97372f0b57bd0fd867506a9129f..e7dedd6d26125e481e1145337a0be6ab
|
|||
// Outer windows only.
|
||||
virtual void EnsureSizeAndPositionUpToDate() override;
|
||||
diff --git a/dom/base/nsINode.cpp b/dom/base/nsINode.cpp
|
||||
index 11ca350f483458ba11f0ee170ce38e9785e8c70f..6bb310f6e96239388b4c92bd7bdf2d698fac8139 100644
|
||||
index d5455e559639aee9328905b50f02c52e94db950b..3fbd1e0459e5391066fc6b3a4e30a841594b31bf 100644
|
||||
--- a/dom/base/nsINode.cpp
|
||||
+++ b/dom/base/nsINode.cpp
|
||||
@@ -1362,6 +1362,61 @@ void nsINode::GetBoxQuadsFromWindowOrigin(const BoxQuadOptions& aOptions,
|
||||
@@ -1365,6 +1365,61 @@ void nsINode::GetBoxQuadsFromWindowOrigin(const BoxQuadOptions& aOptions,
|
||||
mozilla::GetBoxQuadsFromWindowOrigin(this, aOptions, aResult, aRv);
|
||||
}
|
||||
|
||||
|
|
@ -1259,10 +1274,10 @@ index 11ca350f483458ba11f0ee170ce38e9785e8c70f..6bb310f6e96239388b4c92bd7bdf2d69
|
|||
DOMQuad& aQuad, const GeometryNode& aFrom,
|
||||
const ConvertCoordinateOptions& aOptions, CallerType aCallerType,
|
||||
diff --git a/dom/base/nsINode.h b/dom/base/nsINode.h
|
||||
index 0ba44cb08ffffde3bf9616e7e3b1fb33317181b6..b698e309e7bf658739b3a69a1d68f657a6c84e6e 100644
|
||||
index 3a47992cc89176fe9500f7b1d5b74e4422cb9625..27e6da8498af5e4a3c37407a3a8ab592e28dc372 100644
|
||||
--- a/dom/base/nsINode.h
|
||||
+++ b/dom/base/nsINode.h
|
||||
@@ -2235,6 +2235,10 @@ class nsINode : public mozilla::dom::EventTarget {
|
||||
@@ -2248,6 +2248,10 @@ class nsINode : public mozilla::dom::EventTarget {
|
||||
nsTArray<RefPtr<DOMQuad>>& aResult,
|
||||
ErrorResult& aRv);
|
||||
|
||||
|
|
@ -1302,7 +1317,7 @@ index cceb725d393d5e5f83c8f87491089c3fa1d57cc3..e906a7fb7c3fd72554613f640dcc272e
|
|||
|
||||
static bool DumpEnabled();
|
||||
diff --git a/dom/chrome-webidl/BrowsingContext.webidl b/dom/chrome-webidl/BrowsingContext.webidl
|
||||
index 8600a844634eed78e0b8470eaf11144f8ebd230b..853a4c98b78092181ad15542ae48cd41e9c49a82 100644
|
||||
index d70f3e18cc8e8f749e5057297161206129871453..2f2be2a6539203d1957bfe580a06ab70a512c053 100644
|
||||
--- a/dom/chrome-webidl/BrowsingContext.webidl
|
||||
+++ b/dom/chrome-webidl/BrowsingContext.webidl
|
||||
@@ -53,6 +53,24 @@ enum PrefersColorSchemeOverride {
|
||||
|
|
@ -1330,7 +1345,7 @@ index 8600a844634eed78e0b8470eaf11144f8ebd230b..853a4c98b78092181ad15542ae48cd41
|
|||
/**
|
||||
* Allowed overrides of platform/pref default behaviour for touch events.
|
||||
*/
|
||||
@@ -205,6 +223,12 @@ interface BrowsingContext {
|
||||
@@ -209,6 +227,12 @@ interface BrowsingContext {
|
||||
// Color-scheme simulation, for DevTools.
|
||||
[SetterThrows] attribute PrefersColorSchemeOverride prefersColorSchemeOverride;
|
||||
|
||||
|
|
@ -1443,10 +1458,10 @@ index 7e1af00d05fbafa2d828e2c7e4dcc5c82d115f5b..e85af9718d064e4d2865bc944e9d4ba1
|
|||
~Geolocation();
|
||||
|
||||
diff --git a/dom/html/HTMLInputElement.cpp b/dom/html/HTMLInputElement.cpp
|
||||
index ae2c4af82c3827a521a79f8879a6f941ea68feca..92ea48eba6fb575ea1415d8713b49707b895561b 100644
|
||||
index d5a65a17555b7764611803f7fb298712b2c60fd7..fdee7deaf0b14e01702b902f8b73256c281e76b8 100644
|
||||
--- a/dom/html/HTMLInputElement.cpp
|
||||
+++ b/dom/html/HTMLInputElement.cpp
|
||||
@@ -58,6 +58,7 @@
|
||||
@@ -57,6 +57,7 @@
|
||||
#include "mozilla/dom/Document.h"
|
||||
#include "mozilla/dom/HTMLDataListElement.h"
|
||||
#include "mozilla/dom/HTMLOptionElement.h"
|
||||
|
|
@ -1454,11 +1469,12 @@ index ae2c4af82c3827a521a79f8879a6f941ea68feca..92ea48eba6fb575ea1415d8713b49707
|
|||
#include "nsIFormControlFrame.h"
|
||||
#include "nsITextControlFrame.h"
|
||||
#include "nsIFrame.h"
|
||||
@@ -784,6 +785,12 @@ nsresult HTMLInputElement::InitFilePicker(FilePickerType aType) {
|
||||
@@ -783,6 +784,13 @@ nsresult HTMLInputElement::InitFilePicker(FilePickerType aType) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
+ nsDocShell* docShell = static_cast<nsDocShell*>(win->GetDocShell());
|
||||
+ nsCOMPtr<nsPIDOMWindowOuter> win = doc->GetWindow();
|
||||
+ nsDocShell* docShell = win ? static_cast<nsDocShell*>(win->GetDocShell()) : nullptr;
|
||||
+ if (docShell && docShell->IsFileInputInterceptionEnabled()) {
|
||||
+ docShell->FilePickerShown(this);
|
||||
+ return NS_OK;
|
||||
|
|
@ -1468,7 +1484,7 @@ index ae2c4af82c3827a521a79f8879a6f941ea68feca..92ea48eba6fb575ea1415d8713b49707
|
|||
return NS_OK;
|
||||
}
|
||||
diff --git a/dom/interfaces/base/nsIDOMWindowUtils.idl b/dom/interfaces/base/nsIDOMWindowUtils.idl
|
||||
index 564b24e3f89e0ce6e683902a7fc4e1c3a6f5faac..e4264b251e580bb13aa2941b26227541c74d3371 100644
|
||||
index 6a0df1b435cee9cea3b7d7ca30d0aff0e31f8ac0..c17b24823dd873c025e407fcc635b7c678dfd8ed 100644
|
||||
--- a/dom/interfaces/base/nsIDOMWindowUtils.idl
|
||||
+++ b/dom/interfaces/base/nsIDOMWindowUtils.idl
|
||||
@@ -373,6 +373,26 @@ interface nsIDOMWindowUtils : nsISupports {
|
||||
|
|
@ -1499,10 +1515,10 @@ index 564b24e3f89e0ce6e683902a7fc4e1c3a6f5faac..e4264b251e580bb13aa2941b26227541
|
|||
* touchstart, touchend, touchmove, and touchcancel
|
||||
*
|
||||
diff --git a/dom/ipc/BrowserChild.cpp b/dom/ipc/BrowserChild.cpp
|
||||
index 830ad8579a39d262a1fd2c86479f2ddc77c01ae2..b840b68c9b5545fc974e1dafacac2e74372f4e41 100644
|
||||
index bdd10fdcb285e43778b55907ac05bfa973207e5e..d98808918ff8eccd6c51b4fbce1cce5e3745206a 100644
|
||||
--- a/dom/ipc/BrowserChild.cpp
|
||||
+++ b/dom/ipc/BrowserChild.cpp
|
||||
@@ -1651,6 +1651,21 @@ void BrowserChild::HandleRealMouseButtonEvent(const WidgetMouseEvent& aEvent,
|
||||
@@ -1652,6 +1652,21 @@ void BrowserChild::HandleRealMouseButtonEvent(const WidgetMouseEvent& aEvent,
|
||||
if (postLayerization) {
|
||||
postLayerization->Register();
|
||||
}
|
||||
|
|
@ -1777,7 +1793,7 @@ index 3b39538e51840cd9b1685b2efd2ff2e9ec83608a..c7bf4f2d53b58bbacb22b3ebebf6f3fc
|
|||
|
||||
return aGlobalOrNull;
|
||||
diff --git a/dom/security/nsCSPUtils.cpp b/dom/security/nsCSPUtils.cpp
|
||||
index 50730b691b06409f47cb9172570866a7bb519abc..e5ae4f31b8f93f17943c24108a82c6370470e9f2 100644
|
||||
index 11d09909f73fee425fd0f50b384c396a52e02a36..b0e668881bcd3b850de709ebf2557ae8391b8fe8 100644
|
||||
--- a/dom/security/nsCSPUtils.cpp
|
||||
+++ b/dom/security/nsCSPUtils.cpp
|
||||
@@ -22,6 +22,7 @@
|
||||
|
|
@ -1824,10 +1840,10 @@ index 2f71b284ee5f7e11f117c447834b48355784448c..2640bd57123c2b03bf4b06a2419cd020
|
|||
* returned quads are further translated relative to the window
|
||||
* origin -- which is not the layout origin. Further translation
|
||||
diff --git a/dom/workers/RuntimeService.cpp b/dom/workers/RuntimeService.cpp
|
||||
index bcfbe33925afb97e68305394fb38e42481682540..47bc46f34a93991f112292c851c539a6bc97778f 100644
|
||||
index 02efb1205382850b41c38d5f6ee47092adcdc63e..28c8d05d0b5cc415f3d13a4588248f3844faf4f2 100644
|
||||
--- a/dom/workers/RuntimeService.cpp
|
||||
+++ b/dom/workers/RuntimeService.cpp
|
||||
@@ -985,7 +985,7 @@ void PrefLanguagesChanged(const char* /* aPrefName */, void* /* aClosure */) {
|
||||
@@ -995,7 +995,7 @@ void PrefLanguagesChanged(const char* /* aPrefName */, void* /* aClosure */) {
|
||||
AssertIsOnMainThread();
|
||||
|
||||
nsTArray<nsString> languages;
|
||||
|
|
@ -1836,7 +1852,7 @@ index bcfbe33925afb97e68305394fb38e42481682540..47bc46f34a93991f112292c851c539a6
|
|||
|
||||
RuntimeService* runtime = RuntimeService::GetService();
|
||||
if (runtime) {
|
||||
@@ -1172,8 +1172,7 @@ bool RuntimeService::RegisterWorker(WorkerPrivate& aWorkerPrivate) {
|
||||
@@ -1182,8 +1182,7 @@ bool RuntimeService::RegisterWorker(WorkerPrivate& aWorkerPrivate) {
|
||||
}
|
||||
|
||||
// The navigator overridden properties should have already been read.
|
||||
|
|
@ -1846,7 +1862,7 @@ index bcfbe33925afb97e68305394fb38e42481682540..47bc46f34a93991f112292c851c539a6
|
|||
mNavigatorPropertiesLoaded = true;
|
||||
}
|
||||
|
||||
@@ -1772,6 +1771,13 @@ void RuntimeService::PropagateStorageAccessPermissionGranted(
|
||||
@@ -1789,6 +1788,13 @@ void RuntimeService::PropagateStorageAccessPermissionGranted(
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1860,7 +1876,7 @@ index bcfbe33925afb97e68305394fb38e42481682540..47bc46f34a93991f112292c851c539a6
|
|||
template <typename Func>
|
||||
void RuntimeService::BroadcastAllWorkers(const Func& aFunc) {
|
||||
AssertIsOnMainThread();
|
||||
@@ -2287,6 +2293,14 @@ void PropagateStorageAccessPermissionGrantedToWorkers(
|
||||
@@ -2304,6 +2310,14 @@ void PropagateStorageAccessPermissionGrantedToWorkers(
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1876,10 +1892,10 @@ index bcfbe33925afb97e68305394fb38e42481682540..47bc46f34a93991f112292c851c539a6
|
|||
MOZ_ASSERT(!NS_IsMainThread());
|
||||
MOZ_ASSERT(aCx);
|
||||
diff --git a/dom/workers/RuntimeService.h b/dom/workers/RuntimeService.h
|
||||
index e6deb81f357043a937d032bb4b6c38207203f4d9..ff16582af9fbf550dfb7b5639658c34199524c45 100644
|
||||
index f51076ac1480794989999d00577bc9cf1566d5f9..fe15b2e00dc8f0bf203f2af9aad86e16c996d43d 100644
|
||||
--- a/dom/workers/RuntimeService.h
|
||||
+++ b/dom/workers/RuntimeService.h
|
||||
@@ -108,6 +108,8 @@ class RuntimeService final : public nsIObserver {
|
||||
@@ -109,6 +109,8 @@ class RuntimeService final : public nsIObserver {
|
||||
void PropagateStorageAccessPermissionGranted(
|
||||
const nsPIDOMWindowInner& aWindow);
|
||||
|
||||
|
|
@ -1902,17 +1918,17 @@ index d10dabb5c5ff8e17851edf2bd2efc08e74584d8e..53c4070c5fde43b27fb8fbfdcf4c23d8
|
|||
|
||||
bool IsWorkerGlobal(JSObject* global);
|
||||
diff --git a/dom/workers/WorkerPrivate.cpp b/dom/workers/WorkerPrivate.cpp
|
||||
index acef06ba3abb006932f26f8a96fd73028f015150..940210c603d906ae0469d826b81ba8f537e7fef5 100644
|
||||
index a8643981aa966e9324a5dbdb09b4fe57210dc581..5120df2607584a7cd50ea03aa997ef5ade5c8ee2 100644
|
||||
--- a/dom/workers/WorkerPrivate.cpp
|
||||
+++ b/dom/workers/WorkerPrivate.cpp
|
||||
@@ -679,6 +679,18 @@ class UpdateContextOptionsRunnable final : public WorkerControlRunnable {
|
||||
@@ -682,6 +682,18 @@ class UpdateContextOptionsRunnable final : public WorkerControlRunnable {
|
||||
}
|
||||
};
|
||||
|
||||
+class ResetDefaultLocaleRunnable final : public WorkerControlRunnable {
|
||||
+ public:
|
||||
+ explicit ResetDefaultLocaleRunnable(WorkerPrivate* aWorkerPrivate)
|
||||
+ : WorkerControlRunnable(aWorkerPrivate, WorkerThread) {}
|
||||
+ : WorkerControlRunnable(aWorkerPrivate, "ResetDefaultLocaleRunnable", WorkerThread) {}
|
||||
+
|
||||
+ virtual bool WorkerRun(JSContext* aCx,
|
||||
+ WorkerPrivate* aWorkerPrivate) override {
|
||||
|
|
@ -1924,7 +1940,7 @@ index acef06ba3abb006932f26f8a96fd73028f015150..940210c603d906ae0469d826b81ba8f5
|
|||
class UpdateLanguagesRunnable final : public WorkerRunnable {
|
||||
nsTArray<nsString> mLanguages;
|
||||
|
||||
@@ -1977,6 +1989,16 @@ void WorkerPrivate::UpdateContextOptions(
|
||||
@@ -1993,6 +2005,16 @@ void WorkerPrivate::UpdateContextOptions(
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1941,7 +1957,7 @@ index acef06ba3abb006932f26f8a96fd73028f015150..940210c603d906ae0469d826b81ba8f5
|
|||
void WorkerPrivate::UpdateLanguages(const nsTArray<nsString>& aLanguages) {
|
||||
AssertIsOnParentThread();
|
||||
|
||||
@@ -5480,6 +5502,15 @@ void WorkerPrivate::UpdateContextOptionsInternal(
|
||||
@@ -5489,6 +5511,15 @@ void WorkerPrivate::UpdateContextOptionsInternal(
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2032,10 +2048,10 @@ index 523e84c8c93f4221701f90f2e8ee146ec8e1adbd..98d5b1176e5378431b859a2dbd4d4e77
|
|||
inline ClippedTime TimeClip(double time);
|
||||
|
||||
diff --git a/js/src/debugger/Object.cpp b/js/src/debugger/Object.cpp
|
||||
index c5a4f1f6dcf95922b5c8506d6bd24ad4fd2f1efc..a79bb0ad42d1bbd0434cda27c118cdd099013ab2 100644
|
||||
index 17528b0fd99ce8274e702746ff5674de4c3d4f2d..37fa52ae07381bec3504136b9bec0aa1ca110d6b 100644
|
||||
--- a/js/src/debugger/Object.cpp
|
||||
+++ b/js/src/debugger/Object.cpp
|
||||
@@ -2450,7 +2450,11 @@ Maybe<Completion> DebuggerObject::call(JSContext* cx,
|
||||
@@ -2468,7 +2468,11 @@ Maybe<Completion> DebuggerObject::call(JSContext* cx,
|
||||
invokeArgs[i].set(args2[i]);
|
||||
}
|
||||
|
||||
|
|
@ -2202,10 +2218,10 @@ index dac899f7558b26d6848da8b98ed8a93555c8751a..2a07d67fa1c2840b25085566e84dc3b2
|
|||
// No boxes to return
|
||||
return;
|
||||
diff --git a/layout/base/PresShell.cpp b/layout/base/PresShell.cpp
|
||||
index 4afd2b10dfdab527b63f17919c52a559b3ac3de6..787283c004d9baa7227f00c338e0322dca88f949 100644
|
||||
index 31c21c337786b76306029149a925293a44c45c28..7aa4eb0b4980bb8ecdc4e10c91b1cd70e5d0ad08 100644
|
||||
--- a/layout/base/PresShell.cpp
|
||||
+++ b/layout/base/PresShell.cpp
|
||||
@@ -10963,7 +10963,9 @@ bool PresShell::ComputeActiveness() const {
|
||||
@@ -11127,7 +11127,9 @@ bool PresShell::ComputeActiveness() const {
|
||||
if (!browserChild->IsVisible()) {
|
||||
MOZ_LOG(gLog, LogLevel::Debug,
|
||||
(" > BrowserChild %p is not visible", browserChild));
|
||||
|
|
@ -2229,10 +2245,10 @@ index 7bb839ae18835d128dc9285b7f9dc5b5e06335af..09e3979d07447522ace740daf2b818a6
|
|||
const mozilla::dom::Document*);
|
||||
mozilla::StylePrefersColorScheme Gecko_MediaFeatures_PrefersColorScheme(
|
||||
diff --git a/layout/style/nsMediaFeatures.cpp b/layout/style/nsMediaFeatures.cpp
|
||||
index c99eecb4867c623b8ccc6e6fb42986d71f795cc0..d127837d7e069818daaeb73ef42497226ff95e26 100644
|
||||
index f888c127d4423336a8f51e2011fc42eaf6c33f11..51d6e069f4a81c42b4bf270ba44ae4f82b8df592 100644
|
||||
--- a/layout/style/nsMediaFeatures.cpp
|
||||
+++ b/layout/style/nsMediaFeatures.cpp
|
||||
@@ -257,11 +257,11 @@ bool Gecko_MediaFeatures_MatchesPlatform(StylePlatform aPlatform) {
|
||||
@@ -260,11 +260,11 @@ bool Gecko_MediaFeatures_MatchesPlatform(StylePlatform aPlatform) {
|
||||
}
|
||||
|
||||
bool Gecko_MediaFeatures_PrefersReducedMotion(const Document* aDocument) {
|
||||
|
|
@ -2250,7 +2266,7 @@ index c99eecb4867c623b8ccc6e6fb42986d71f795cc0..d127837d7e069818daaeb73ef4249722
|
|||
|
||||
bool Gecko_MediaFeatures_PrefersReducedTransparency(const Document* aDocument) {
|
||||
diff --git a/netwerk/base/LoadInfo.cpp b/netwerk/base/LoadInfo.cpp
|
||||
index b15f24fffd7ea5a8a00319451fa7ebf9df32ec05..0279f6626f2ac938b0456d370fd956f2a23a036b 100644
|
||||
index eb90324c37ce515e5a0fcf75412605ae57ead9ee..6733dd6c99d77399529945386caa3926960e4176 100644
|
||||
--- a/netwerk/base/LoadInfo.cpp
|
||||
+++ b/netwerk/base/LoadInfo.cpp
|
||||
@@ -653,7 +653,8 @@ LoadInfo::LoadInfo(const LoadInfo& rhs)
|
||||
|
|
@ -2337,7 +2353,7 @@ index 155daa5cd52c4e93e1cc4559875868f4faf2c37e..02e8a2614a27a4cc9e1de760d4c48617
|
|||
/**
|
||||
* Set the status and reason for the forthcoming synthesized response.
|
||||
diff --git a/netwerk/ipc/DocumentLoadListener.cpp b/netwerk/ipc/DocumentLoadListener.cpp
|
||||
index ca1f59e8847bd399fcf3018ede95d318265c374c..d114f0bcaddedc3553780ff7b97e395e28aff91e 100644
|
||||
index d849eab7507f0665a8a79a1b11759afa3481f1ad..3a95d5201a1c1f2161a95e15beae86f2833a875f 100644
|
||||
--- a/netwerk/ipc/DocumentLoadListener.cpp
|
||||
+++ b/netwerk/ipc/DocumentLoadListener.cpp
|
||||
@@ -167,6 +167,7 @@ static auto CreateDocumentLoadInfo(CanonicalBrowsingContext* aBrowsingContext,
|
||||
|
|
@ -2387,10 +2403,10 @@ index c58fbc96391f8dcb585bd00b5ae8cba9088abd82..c121c891b61c9d60df770020c4ad0952
|
|||
if (mPump && mLoadFlags & LOAD_CALL_CONTENT_SNIFFERS) {
|
||||
mPump->PeekStream(CallTypeSniffers, static_cast<nsIChannel*>(this));
|
||||
diff --git a/parser/html/nsHtml5TreeOpExecutor.cpp b/parser/html/nsHtml5TreeOpExecutor.cpp
|
||||
index 3cb38dcf28a5cb3a8e70c336c8b1c822be59268f..4d9ae3ab666d4ba7b42730d50367720870f0183f 100644
|
||||
index f2c47c42a6b6448ede3a6fef1510a3982336d5af..9e35df57102c93238de2e4d548bbe1205d227f3b 100644
|
||||
--- a/parser/html/nsHtml5TreeOpExecutor.cpp
|
||||
+++ b/parser/html/nsHtml5TreeOpExecutor.cpp
|
||||
@@ -1381,6 +1381,10 @@ void nsHtml5TreeOpExecutor::UpdateReferrerInfoFromMeta(
|
||||
@@ -1382,6 +1382,10 @@ void nsHtml5TreeOpExecutor::UpdateReferrerInfoFromMeta(
|
||||
void nsHtml5TreeOpExecutor::AddSpeculationCSP(const nsAString& aCSP) {
|
||||
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
|
||||
|
||||
|
|
@ -2498,10 +2514,10 @@ index 73c83e526be1a3a252f995d0718e3975d50bffa7..db5977c54221e19e107a8325a0834302
|
|||
(lazy.isRunningTests || Cu.isInAutomation) &&
|
||||
this.SERVER_URL == "data:,#remote-settings-dummy/v1"
|
||||
diff --git a/servo/components/style/gecko/media_features.rs b/servo/components/style/gecko/media_features.rs
|
||||
index c9ad30b28b069a122cf01c5e97b83dc68ef022ff..f32dbcfe26fe2f6b8a0d066d6dfd208f1afc510d 100644
|
||||
index 8de45d95c2b942f069c898c93702bc7db2219369..be72e9678c3de31b1eaa72cfcb2c8be886f4a80f 100644
|
||||
--- a/servo/components/style/gecko/media_features.rs
|
||||
+++ b/servo/components/style/gecko/media_features.rs
|
||||
@@ -293,10 +293,15 @@ pub enum ForcedColors {
|
||||
@@ -292,10 +292,15 @@ pub enum ForcedColors {
|
||||
|
||||
/// https://drafts.csswg.org/mediaqueries-5/#forced-colors
|
||||
fn eval_forced_colors(context: &Context, query_value: Option<ForcedColors>) -> bool {
|
||||
|
|
@ -2597,10 +2613,10 @@ index 209af157a35cf9c4586424421ee19b3f680796b6..1feb61e2f18e9a13631addc935f00da0
|
|||
|
||||
/**
|
||||
diff --git a/toolkit/mozapps/update/UpdateService.sys.mjs b/toolkit/mozapps/update/UpdateService.sys.mjs
|
||||
index 290344c58bf488b4aa955c2c58bdaa11048e2f48..2d419b90e6c5a910d3038380cd6c819eb8b0c768 100644
|
||||
index 34bf1b9e19ae54f78d134b023af96820bc13a905..b85793edcd1996833420a026cb08706d648ece14 100644
|
||||
--- a/toolkit/mozapps/update/UpdateService.sys.mjs
|
||||
+++ b/toolkit/mozapps/update/UpdateService.sys.mjs
|
||||
@@ -3853,6 +3853,8 @@ UpdateService.prototype = {
|
||||
@@ -3852,6 +3852,8 @@ UpdateService.prototype = {
|
||||
},
|
||||
|
||||
get disabledForTesting() {
|
||||
|
|
@ -2610,7 +2626,7 @@ index 290344c58bf488b4aa955c2c58bdaa11048e2f48..2d419b90e6c5a910d3038380cd6c819e
|
|||
(Cu.isInAutomation ||
|
||||
lazy.Marionette.running ||
|
||||
diff --git a/toolkit/toolkit.mozbuild b/toolkit/toolkit.mozbuild
|
||||
index f554b692f7e874dd21be1ef783e899ba602ee7f3..dd6c94f8723ca227611c8390d9dcd4f292939703 100644
|
||||
index b697eb1e3b02b0ffcc95a6e492dc23eb888488cc..0f4059341dbb3c2ecb2c46be0850e0d56e2a7453 100644
|
||||
--- a/toolkit/toolkit.mozbuild
|
||||
+++ b/toolkit/toolkit.mozbuild
|
||||
@@ -155,6 +155,7 @@ if CONFIG["ENABLE_WEBDRIVER"]:
|
||||
|
|
@ -2622,7 +2638,7 @@ index f554b692f7e874dd21be1ef783e899ba602ee7f3..dd6c94f8723ca227611c8390d9dcd4f2
|
|||
]
|
||||
|
||||
diff --git a/toolkit/xre/nsWindowsWMain.cpp b/toolkit/xre/nsWindowsWMain.cpp
|
||||
index 7eb9e1104682d4eb47060654f43a1efa8b2a6bb2..a8315d6decf654b5302bea5beeea34140c300ded 100644
|
||||
index 2a91deec5c10f87ed09f99b659baab77b2b638f2..78f4f30a0efe314563c6405f7b0848d2c3ca0551 100644
|
||||
--- a/toolkit/xre/nsWindowsWMain.cpp
|
||||
+++ b/toolkit/xre/nsWindowsWMain.cpp
|
||||
@@ -14,8 +14,10 @@
|
||||
|
|
@ -2636,10 +2652,10 @@ index 7eb9e1104682d4eb47060654f43a1efa8b2a6bb2..a8315d6decf654b5302bea5beeea3414
|
|||
#include <windows.h>
|
||||
|
||||
#ifdef __MINGW32__
|
||||
@@ -114,6 +116,19 @@ static void FreeAllocStrings(int argc, char** argv) {
|
||||
int wmain(int argc, WCHAR** argv) {
|
||||
@@ -137,6 +139,19 @@ int wmain(int argc, WCHAR** argv) {
|
||||
SanitizeEnvironmentVariables();
|
||||
SetDllDirectoryW(L"");
|
||||
RemovePrefetchArguments(argc, argv);
|
||||
+ bool hasJugglerPipe =
|
||||
+ mozilla::CheckArg(argc, argv, "juggler-pipe", nullptr,
|
||||
+ mozilla::CheckArgFlag::None) == mozilla::ARG_FOUND;
|
||||
|
|
@ -2793,7 +2809,7 @@ index 4573e28470c5112f5ac2c5dd53e7a9d1ceedb943..b53b86d8e39f1de4b0d0f1a8d5d7295e
|
|||
// OnStartRequest)
|
||||
mDialog = nullptr;
|
||||
diff --git a/uriloader/exthandler/nsExternalHelperAppService.h b/uriloader/exthandler/nsExternalHelperAppService.h
|
||||
index 205f73cfa127e15e171854165c92551fea957e84..6399525ea0abb55655c824b086a043d022392113 100644
|
||||
index 1f77e095dbfa3acc046779114007d83fc1cfa087..2354abbab7af6f6bdc3bd628722f03ea401d236a 100644
|
||||
--- a/uriloader/exthandler/nsExternalHelperAppService.h
|
||||
+++ b/uriloader/exthandler/nsExternalHelperAppService.h
|
||||
@@ -257,6 +257,8 @@ class nsExternalHelperAppService : public nsIExternalHelperAppService,
|
||||
|
|
@ -2886,7 +2902,7 @@ index 1c25e9d9a101233f71e92288a0f93125b81ac1c5..22cf67b0f6e3ddd2b3ed725a314ba6a9
|
|||
}
|
||||
#endif
|
||||
diff --git a/widget/MouseEvents.h b/widget/MouseEvents.h
|
||||
index ea714704df53af78a55065ce7626798e0e674a23..a108e0459aeac9789adac7d7ec166abf94646454 100644
|
||||
index d29b406524c8b4afe437b559e33b4b2b5824ee58..6bef9c1657f93f90f96735d76fedb6ba3888b5c1 100644
|
||||
--- a/widget/MouseEvents.h
|
||||
+++ b/widget/MouseEvents.h
|
||||
@@ -258,6 +258,7 @@ class WidgetMouseEvent : public WidgetMouseEventBase,
|
||||
|
|
@ -2935,7 +2951,7 @@ diff --git a/widget/cocoa/NativeKeyBindings.mm b/widget/cocoa/NativeKeyBindings.
|
|||
index e4bdf715e2fb899e97a5bfeb2e147127460d6047..3554f919480278b7353617481c7ce8050630a1aa 100644
|
||||
--- a/widget/cocoa/NativeKeyBindings.mm
|
||||
+++ b/widget/cocoa/NativeKeyBindings.mm
|
||||
@@ -528,6 +528,13 @@ void NativeKeyBindings::GetEditCommandsForTests(
|
||||
@@ -528,6 +528,13 @@
|
||||
break;
|
||||
case KEY_NAME_INDEX_ArrowLeft:
|
||||
if (aEvent.IsAlt()) {
|
||||
|
|
@ -2949,7 +2965,7 @@ index e4bdf715e2fb899e97a5bfeb2e147127460d6047..3554f919480278b7353617481c7ce805
|
|||
break;
|
||||
}
|
||||
if (aEvent.IsMeta() || (aEvent.IsControl() && aEvent.IsShift())) {
|
||||
@@ -550,6 +557,13 @@ void NativeKeyBindings::GetEditCommandsForTests(
|
||||
@@ -550,6 +557,13 @@
|
||||
break;
|
||||
case KEY_NAME_INDEX_ArrowRight:
|
||||
if (aEvent.IsAlt()) {
|
||||
|
|
@ -2963,7 +2979,7 @@ index e4bdf715e2fb899e97a5bfeb2e147127460d6047..3554f919480278b7353617481c7ce805
|
|||
break;
|
||||
}
|
||||
if (aEvent.IsMeta() || (aEvent.IsControl() && aEvent.IsShift())) {
|
||||
@@ -572,6 +586,10 @@ void NativeKeyBindings::GetEditCommandsForTests(
|
||||
@@ -572,6 +586,10 @@
|
||||
break;
|
||||
case KEY_NAME_INDEX_ArrowUp:
|
||||
if (aEvent.IsControl()) {
|
||||
|
|
@ -2974,7 +2990,7 @@ index e4bdf715e2fb899e97a5bfeb2e147127460d6047..3554f919480278b7353617481c7ce805
|
|||
break;
|
||||
}
|
||||
if (aEvent.IsMeta()) {
|
||||
@@ -582,7 +600,7 @@ void NativeKeyBindings::GetEditCommandsForTests(
|
||||
@@ -582,7 +600,7 @@
|
||||
!aEvent.IsShift()
|
||||
? ToObjcSelectorPtr(@selector(moveToBeginningOfDocument:))
|
||||
: ToObjcSelectorPtr(
|
||||
|
|
@ -2983,7 +2999,7 @@ index e4bdf715e2fb899e97a5bfeb2e147127460d6047..3554f919480278b7353617481c7ce805
|
|||
aCommands);
|
||||
break;
|
||||
}
|
||||
@@ -609,6 +627,10 @@ void NativeKeyBindings::GetEditCommandsForTests(
|
||||
@@ -609,6 +627,10 @@
|
||||
break;
|
||||
case KEY_NAME_INDEX_ArrowDown:
|
||||
if (aEvent.IsControl()) {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
REMOTE_URL="https://github.com/WebKit/WebKit.git"
|
||||
BASE_BRANCH="main"
|
||||
BASE_REVISION="e225c278f4c06f451ea92cc68b12986dd2a99979"
|
||||
BASE_REVISION="b2ca06dc3d84b356d01cdf09a82049f80515fbfe"
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -344,6 +344,9 @@ If set changes the fetch method (e.g. [PUT](https://developer.mozilla.org/en-US/
|
|||
### option: APIRequestContext.fetch.maxRedirects = %%-js-python-csharp-fetch-option-maxredirects-%%
|
||||
* since: v1.26
|
||||
|
||||
### option: APIRequestContext.fetch.maxRetries = %%-js-python-csharp-fetch-option-maxretries-%%
|
||||
* since: v1.46
|
||||
|
||||
## async method: APIRequestContext.get
|
||||
* since: v1.16
|
||||
- returns: <[APIResponse]>
|
||||
|
|
@ -433,6 +436,9 @@ await request.GetAsync("https://example.com/api/getText", new() { Params = query
|
|||
### option: APIRequestContext.get.maxRedirects = %%-js-python-csharp-fetch-option-maxredirects-%%
|
||||
* since: v1.26
|
||||
|
||||
### option: APIRequestContext.get.maxRetries = %%-js-python-csharp-fetch-option-maxretries-%%
|
||||
* since: v1.46
|
||||
|
||||
## async method: APIRequestContext.head
|
||||
* since: v1.16
|
||||
- returns: <[APIResponse]>
|
||||
|
|
@ -486,6 +492,9 @@ context cookies from the response. The method will automatically follow redirect
|
|||
### option: APIRequestContext.head.maxRedirects = %%-js-python-csharp-fetch-option-maxredirects-%%
|
||||
* since: v1.26
|
||||
|
||||
### option: APIRequestContext.head.maxRetries = %%-js-python-csharp-fetch-option-maxretries-%%
|
||||
* since: v1.46
|
||||
|
||||
## async method: APIRequestContext.patch
|
||||
* since: v1.16
|
||||
- returns: <[APIResponse]>
|
||||
|
|
@ -539,6 +548,9 @@ context cookies from the response. The method will automatically follow redirect
|
|||
### option: APIRequestContext.patch.maxRedirects = %%-js-python-csharp-fetch-option-maxredirects-%%
|
||||
* since: v1.26
|
||||
|
||||
### option: APIRequestContext.patch.maxRetries = %%-js-python-csharp-fetch-option-maxretries-%%
|
||||
* since: v1.46
|
||||
|
||||
## async method: APIRequestContext.post
|
||||
* since: v1.16
|
||||
- returns: <[APIResponse]>
|
||||
|
|
@ -713,6 +725,9 @@ await request.PostAsync("https://example.com/api/uploadScript", new() { Multipar
|
|||
### option: APIRequestContext.post.maxRedirects = %%-js-python-csharp-fetch-option-maxredirects-%%
|
||||
* since: v1.26
|
||||
|
||||
### option: APIRequestContext.post.maxRetries = %%-js-python-csharp-fetch-option-maxretries-%%
|
||||
* since: v1.46
|
||||
|
||||
## async method: APIRequestContext.put
|
||||
* since: v1.16
|
||||
- returns: <[APIResponse]>
|
||||
|
|
@ -766,6 +781,9 @@ context cookies from the response. The method will automatically follow redirect
|
|||
### option: APIRequestContext.put.maxRedirects = %%-js-python-csharp-fetch-option-maxredirects-%%
|
||||
* since: v1.26
|
||||
|
||||
### option: APIRequestContext.put.maxRetries = %%-js-python-csharp-fetch-option-maxretries-%%
|
||||
* since: v1.46
|
||||
|
||||
## async method: APIRequestContext.storageState
|
||||
* since: v1.16
|
||||
- returns: <[Object]>
|
||||
|
|
|
|||
|
|
@ -357,18 +357,14 @@ await context.AddCookiesAsync(new[] { cookie1, cookie2 });
|
|||
- `cookies` <[Array]<[Object]>>
|
||||
- `name` <[string]>
|
||||
- `value` <[string]>
|
||||
- `url` ?<[string]> either url or domain / path are required. Optional.
|
||||
- `domain` ?<[string]> either url or domain / path are required Optional.
|
||||
- `path` ?<[string]> either url or domain / path are required Optional.
|
||||
- `url` ?<[string]> Either url or domain / path are required. Optional.
|
||||
- `domain` ?<[string]> For the cookie to apply to all subdomains as well, prefix domain with a dot, like this: ".example.com". Either url or domain / path are required. Optional.
|
||||
- `path` ?<[string]> Either url or domain / path are required Optional.
|
||||
- `expires` ?<[float]> Unix time in seconds. Optional.
|
||||
- `httpOnly` ?<[boolean]> Optional.
|
||||
- `secure` ?<[boolean]> Optional.
|
||||
- `sameSite` ?<[SameSiteAttribute]<"Strict"|"Lax"|"None">> Optional.
|
||||
|
||||
Adds cookies to the browser context.
|
||||
|
||||
For the cookie to apply to all subdomains as well, prefix domain with a dot, like this: ".example.com".
|
||||
|
||||
## async method: BrowserContext.addInitScript
|
||||
* since: v1.8
|
||||
|
||||
|
|
@ -748,83 +744,6 @@ await page.SetContentAsync("<script>\n" +
|
|||
await page.GetByRole(AriaRole.Button).ClickAsync();
|
||||
```
|
||||
|
||||
An example of passing an element handle:
|
||||
|
||||
```js
|
||||
await context.exposeBinding('clicked', async (source, element) => {
|
||||
console.log(await element.textContent());
|
||||
}, { handle: true });
|
||||
await page.setContent(`
|
||||
<script>
|
||||
document.addEventListener('click', event => window.clicked(event.target));
|
||||
</script>
|
||||
<div>Click me</div>
|
||||
<div>Or click me</div>
|
||||
`);
|
||||
```
|
||||
|
||||
```java
|
||||
context.exposeBinding("clicked", (source, args) -> {
|
||||
ElementHandle element = (ElementHandle) args[0];
|
||||
System.out.println(element.textContent());
|
||||
return null;
|
||||
}, new BrowserContext.ExposeBindingOptions().setHandle(true));
|
||||
page.setContent("" +
|
||||
"<script>\n" +
|
||||
" document.addEventListener('click', event => window.clicked(event.target));\n" +
|
||||
"</script>\n" +
|
||||
"<div>Click me</div>\n" +
|
||||
"<div>Or click me</div>\n");
|
||||
```
|
||||
|
||||
```python async
|
||||
async def print(source, element):
|
||||
print(await element.text_content())
|
||||
|
||||
await context.expose_binding("clicked", print, handle=true)
|
||||
await page.set_content("""
|
||||
<script>
|
||||
document.addEventListener('click', event => window.clicked(event.target));
|
||||
</script>
|
||||
<div>Click me</div>
|
||||
<div>Or click me</div>
|
||||
""")
|
||||
```
|
||||
|
||||
```python sync
|
||||
def print(source, element):
|
||||
print(element.text_content())
|
||||
|
||||
context.expose_binding("clicked", print, handle=true)
|
||||
page.set_content("""
|
||||
<script>
|
||||
document.addEventListener('click', event => window.clicked(event.target));
|
||||
</script>
|
||||
<div>Click me</div>
|
||||
<div>Or click me</div>
|
||||
""")
|
||||
```
|
||||
|
||||
```csharp
|
||||
var result = new TaskCompletionSource<string>();
|
||||
var page = await Context.NewPageAsync();
|
||||
await Context.ExposeBindingAsync("clicked", async (BindingSource _, IJSHandle t) =>
|
||||
{
|
||||
return result.TrySetResult(await t.AsElement().TextContentAsync());
|
||||
});
|
||||
|
||||
await page.SetContentAsync("<script>\n" +
|
||||
" document.addEventListener('click', event => window.clicked(event.target));\n" +
|
||||
"</script>\n" +
|
||||
"<div>Click me</div>\n" +
|
||||
"<div>Or click me</div>\n");
|
||||
|
||||
await page.ClickAsync("div");
|
||||
// Note: it makes sense to await the result here, because otherwise, the context
|
||||
// gets closed and the binding function will throw an exception.
|
||||
Assert.AreEqual("Click me", await result.Task);
|
||||
```
|
||||
|
||||
### param: BrowserContext.exposeBinding.name
|
||||
* since: v1.8
|
||||
- `name` <[string]>
|
||||
|
|
@ -839,6 +758,7 @@ Callback function that will be called in the Playwright's context.
|
|||
|
||||
### option: BrowserContext.exposeBinding.handle
|
||||
* since: v1.8
|
||||
* deprecated: This option will be removed in the future.
|
||||
- `handle` <[boolean]>
|
||||
|
||||
Whether to pass the argument as a handle, instead of passing by value. When passing a handle, only one argument is
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ await page.Clock.FastForwardAsync("30:00");
|
|||
|
||||
### param: Clock.fastForward.ticks
|
||||
* since: v1.45
|
||||
- `ticks` <[int]|[string]>
|
||||
- `ticks` <[long]|[string]>
|
||||
|
||||
Time may be the number of milliseconds to advance the clock by or a human-readable string. Valid string formats are "08" for eight seconds, "01:00" for one minute and "02:34:10" for two hours, 34 minutes and ten seconds.
|
||||
|
||||
|
|
@ -65,7 +65,7 @@ Fake timers are used to manually control the flow of time in tests. They allow y
|
|||
|
||||
### option: Clock.install.time
|
||||
* since: v1.45
|
||||
- `time` <[int]|[string]|[Date]>
|
||||
- `time` <[long]|[string]|[Date]>
|
||||
|
||||
Time to initialize with, current system time by default.
|
||||
|
||||
|
|
@ -103,7 +103,7 @@ await page.Clock.RunForAsync("30:00");
|
|||
|
||||
### param: Clock.runFor.ticks
|
||||
* since: v1.45
|
||||
- `ticks` <[int]|[string]>
|
||||
- `ticks` <[long]|[string]>
|
||||
|
||||
Time may be the number of milliseconds to advance the clock by or a human-readable string. Valid string formats are "08" for eight seconds, "01:00" for one minute and "02:34:10" for two hours, 34 minutes and ten seconds.
|
||||
|
||||
|
|
@ -136,7 +136,8 @@ page.clock.pause_at("2020-02-02")
|
|||
```
|
||||
|
||||
```java
|
||||
page.clock().pauseAt(Instant.parse("2020-02-02"));
|
||||
SimpleDateFormat format = new SimpleDateFormat("yyy-MM-dd");
|
||||
page.clock().pauseAt(format.parse("2020-02-02"));
|
||||
page.clock().pauseAt("2020-02-02");
|
||||
```
|
||||
|
||||
|
|
@ -147,7 +148,7 @@ await page.Clock.PauseAtAsync("2020-02-02");
|
|||
|
||||
### param: Clock.pauseAt.time
|
||||
* since: v1.45
|
||||
- `time` <[int]|[string]|[Date]>
|
||||
- `time` <[long]|[string]|[Date]>
|
||||
|
||||
|
||||
## async method: Clock.resume
|
||||
|
|
@ -182,8 +183,8 @@ page.clock.set_fixed_time("2020-02-02")
|
|||
```
|
||||
|
||||
```java
|
||||
page.clock().setFixedTime(Instant.now());
|
||||
page.clock().setFixedTime(Instant.parse("2020-02-02"));
|
||||
page.clock().setFixedTime(new Date());
|
||||
page.clock().setFixedTime(new SimpleDateFormat("yyy-MM-dd").parse("2020-02-02"));
|
||||
page.clock().setFixedTime("2020-02-02");
|
||||
```
|
||||
|
||||
|
|
@ -195,7 +196,7 @@ await page.Clock.SetFixedTimeAsync("2020-02-02");
|
|||
|
||||
### param: Clock.setFixedTime.time
|
||||
* since: v1.45
|
||||
- `time` <[int]|[string]|[Date]>
|
||||
- `time` <[long]|[string]|[Date]>
|
||||
|
||||
Time to be set.
|
||||
|
||||
|
|
@ -225,8 +226,8 @@ page.clock.set_system_time("2020-02-02")
|
|||
```
|
||||
|
||||
```java
|
||||
page.clock().setSystemTime(Instant.now());
|
||||
page.clock().setSystemTime(Instant.parse("2020-02-02"));
|
||||
page.clock().setSystemTime(new Date());
|
||||
page.clock().setSystemTime(new SimpleDateFormat("yyy-MM-dd").parse("2020-02-02"));
|
||||
page.clock().setSystemTime("2020-02-02");
|
||||
```
|
||||
|
||||
|
|
@ -238,4 +239,4 @@ await page.Clock.SetSystemTimeAsync("2020-02-02");
|
|||
|
||||
### param: Clock.setSystemTime.time
|
||||
* since: v1.45
|
||||
- `time` <[int]|[string]|[Date]>
|
||||
- `time` <[long]|[string]|[Date]>
|
||||
|
|
|
|||
|
|
@ -1817,80 +1817,6 @@ class PageExamples
|
|||
}
|
||||
```
|
||||
|
||||
An example of passing an element handle:
|
||||
|
||||
```js
|
||||
await page.exposeBinding('clicked', async (source, element) => {
|
||||
console.log(await element.textContent());
|
||||
}, { handle: true });
|
||||
await page.setContent(`
|
||||
<script>
|
||||
document.addEventListener('click', event => window.clicked(event.target));
|
||||
</script>
|
||||
<div>Click me</div>
|
||||
<div>Or click me</div>
|
||||
`);
|
||||
```
|
||||
|
||||
```java
|
||||
page.exposeBinding("clicked", (source, args) -> {
|
||||
ElementHandle element = (ElementHandle) args[0];
|
||||
System.out.println(element.textContent());
|
||||
return null;
|
||||
}, new Page.ExposeBindingOptions().setHandle(true));
|
||||
page.setContent("" +
|
||||
"<script>\n" +
|
||||
" document.addEventListener('click', event => window.clicked(event.target));\n" +
|
||||
"</script>\n" +
|
||||
"<div>Click me</div>\n" +
|
||||
"<div>Or click me</div>\n");
|
||||
```
|
||||
|
||||
```python async
|
||||
async def print(source, element):
|
||||
print(await element.text_content())
|
||||
|
||||
await page.expose_binding("clicked", print, handle=true)
|
||||
await page.set_content("""
|
||||
<script>
|
||||
document.addEventListener('click', event => window.clicked(event.target));
|
||||
</script>
|
||||
<div>Click me</div>
|
||||
<div>Or click me</div>
|
||||
""")
|
||||
```
|
||||
|
||||
```python sync
|
||||
def print(source, element):
|
||||
print(element.text_content())
|
||||
|
||||
page.expose_binding("clicked", print, handle=true)
|
||||
page.set_content("""
|
||||
<script>
|
||||
document.addEventListener('click', event => window.clicked(event.target));
|
||||
</script>
|
||||
<div>Click me</div>
|
||||
<div>Or click me</div>
|
||||
""")
|
||||
```
|
||||
|
||||
```csharp
|
||||
var result = new TaskCompletionSource<string>();
|
||||
await page.ExposeBindingAsync("clicked", async (BindingSource _, IJSHandle t) =>
|
||||
{
|
||||
return result.TrySetResult(await t.AsElement().TextContentAsync());
|
||||
});
|
||||
|
||||
await page.SetContentAsync("<script>\n" +
|
||||
" document.addEventListener('click', event => window.clicked(event.target));\n" +
|
||||
"</script>\n" +
|
||||
"<div>Click me</div>\n" +
|
||||
"<div>Or click me</div>\n");
|
||||
|
||||
await page.ClickAsync("div");
|
||||
Console.WriteLine(await result.Task);
|
||||
```
|
||||
|
||||
### param: Page.exposeBinding.name
|
||||
* since: v1.8
|
||||
- `name` <[string]>
|
||||
|
|
@ -1905,6 +1831,7 @@ Callback function that will be called in the Playwright's context.
|
|||
|
||||
### option: Page.exposeBinding.handle
|
||||
* since: v1.8
|
||||
* deprecated: This option will be removed in the future.
|
||||
- `handle` <[boolean]>
|
||||
|
||||
Whether to pass the argument as a handle, instead of passing by value. When passing a handle, only one argument is
|
||||
|
|
|
|||
|
|
@ -126,6 +126,16 @@ Whether to ignore HTTPS errors when sending network requests.
|
|||
Maximum number of request redirects that will be followed automatically. An error will be thrown if the number is exceeded.
|
||||
Defaults to `20`. Pass `0` to not follow redirects.
|
||||
|
||||
## method: RequestOptions.setMaxRetries
|
||||
* since: v1.46
|
||||
- returns: <[RequestOptions]>
|
||||
|
||||
### param: RequestOptions.setMaxRetries.maxRetries
|
||||
* since: v1.46
|
||||
- `maxRetries` <[int]>
|
||||
|
||||
Maximum number of times socket errors should be retried. Currently only `ECONNRESET` error is retried. An error will be thrown if the limit is exceeded. Defaults to `0` - no retries.
|
||||
|
||||
## method: RequestOptions.setMethod
|
||||
* since: v1.18
|
||||
- returns: <[RequestOptions]>
|
||||
|
|
|
|||
|
|
@ -20,3 +20,23 @@ Dispatches a `touchstart` and `touchend` event with a single touch at the positi
|
|||
### param: Touchscreen.tap.y
|
||||
* since: v1.8
|
||||
- `y` <[float]>
|
||||
|
||||
## async method: Touchscreen.touch
|
||||
* since: v1.46
|
||||
|
||||
Synthesizes a touch event.
|
||||
|
||||
### param: Touchscreen.touch.type
|
||||
* since: v1.46
|
||||
- `type` <[TouchType]<"touchstart"|"touchend"|"touchmove"|"touchcancel">>
|
||||
|
||||
Type of the touch event.
|
||||
|
||||
### param: Touchscreen.touch.touches
|
||||
* since: v1.46
|
||||
- `touchPoints` <[Array]<[Object]>>
|
||||
- `x` <[float]> x coordinate of the event in CSS pixels.
|
||||
- `y` <[float]> y coordinate of the event in CSS pixels.
|
||||
- `id` ?<[int]> Identifier used to track the touch point between events, must be unique within an event. Optional.
|
||||
|
||||
List of touch points for this event. `id` is a unique identifier of a touch point that helps identify it between touch events for the duration of its movement around the surface.
|
||||
|
|
@ -458,6 +458,12 @@ Whether to ignore HTTPS errors when sending network requests. Defaults to `false
|
|||
Maximum number of request redirects that will be followed automatically. An error will be thrown if the number is exceeded.
|
||||
Defaults to `20`. Pass `0` to not follow redirects.
|
||||
|
||||
## js-python-csharp-fetch-option-maxretries
|
||||
* langs: js, python, csharp
|
||||
- `maxRetries` <[int]>
|
||||
|
||||
Maximum number of times socket errors should be retried. Currently only `ECONNRESET` error is retried. An error will be thrown if the limit is exceeded. Defaults to `0` - no retries.
|
||||
|
||||
## evaluate-expression
|
||||
- `expression` <[string]>
|
||||
|
||||
|
|
|
|||
|
|
@ -601,6 +601,23 @@ steps:
|
|||
- 'CI=true'
|
||||
```
|
||||
|
||||
### Drone
|
||||
* langs: js
|
||||
|
||||
To run Playwright tests on Drone, use our public Docker image ([see Dockerfile](./docker.md)).
|
||||
|
||||
```yml
|
||||
kind: pipeline
|
||||
name: default
|
||||
type: docker
|
||||
|
||||
steps:
|
||||
- name: test
|
||||
image: mcr.microsoft.com/playwright:v%%VERSION%%-jammy
|
||||
commands:
|
||||
- npx playwright test
|
||||
```
|
||||
|
||||
## Caching browsers
|
||||
|
||||
Caching browser binaries is not recommended, since the amount of time it takes to restore the cache is comparable to the time it takes to download the binaries. Especially under Linux, [operating system dependencies](./browsers.md#install-system-dependencies) need to be installed, which are not cacheable.
|
||||
|
|
@ -633,7 +650,7 @@ By default, Playwright launches browsers in headless mode. See in our [Running t
|
|||
On Linux agents, headed execution requires [Xvfb](https://en.wikipedia.org/wiki/Xvfb) to be installed. Our [Docker image](./docker.md) and GitHub Action have Xvfb pre-installed. To run browsers in headed mode with Xvfb, add `xvfb-run` before the actual command.
|
||||
|
||||
```bash js
|
||||
xvfb-run npx playwrght test
|
||||
xvfb-run npx playwright test
|
||||
```
|
||||
```bash python
|
||||
xvfb-run pytest
|
||||
|
|
|
|||
|
|
@ -2,11 +2,24 @@
|
|||
id: clock
|
||||
title: "Clock"
|
||||
---
|
||||
import LiteYouTube from '@site/src/components/LiteYouTube';
|
||||
|
||||
## Introduction
|
||||
|
||||
Accurately simulating time-dependent behavior is essential for verifying the correctness of applications. Utilizing [Clock] functionality allows developers to manipulate and control time within tests, enabling the precise validation of features such as rendering time, timeouts, scheduled tasks without the delays and variability of real-time execution.
|
||||
|
||||
The [Clock] API provides the following methods to control time:
|
||||
- `setFixedTime`: Sets the fixed time for `Date.now()` and `new Date()`.
|
||||
- `install`: initializes the clock and allows you to:
|
||||
- `pauseAt`: Pauses the time at a specific time.
|
||||
- `fastForward`: Fast forwards the time.
|
||||
- `runFor`: Runs the time for a specific duration.
|
||||
- `resume`: Resumes the time.
|
||||
- `setSystemTime`: Sets the current system time.
|
||||
|
||||
The recommended approach is to use `setFixedTime` to set the time to a specific value. If that doesn't work for your use case, you can use `install` which allows you to pause time later on, fast forward it, tick it, etc. `setSystemTime` is only recommended for advanced use cases.
|
||||
|
||||
:::note
|
||||
[`property: Page.clock`] overrides native global classes and functions related to time allowing them to be manually controlled:
|
||||
- `Date`
|
||||
- `setTimeout`
|
||||
|
|
@ -18,6 +31,7 @@ Accurately simulating time-dependent behavior is essential for verifying the cor
|
|||
- `requestIdleCallback`
|
||||
- `cancelIdleCallback`
|
||||
- `performance`
|
||||
:::
|
||||
|
||||
## Test with predefined time
|
||||
|
||||
|
|
@ -118,13 +132,14 @@ expect(page.get_by_test_id("current-time")).to_have_text("2/2/2024, 10:30:00 AM"
|
|||
```java
|
||||
// Initialize clock with some time before the test time and let the page load
|
||||
// naturally. `Date.now` will progress as the timers fire.
|
||||
page.clock().install(new Clock.InstallOptions().setTime(Instant.parse("2024-02-02T08:00:00")));
|
||||
SimpleDateFormat format = new SimpleDateFormat("yyy-MM-dd'T'HH:mm:ss");
|
||||
page.clock().install(new Clock.InstallOptions().setTime(format.parse("2024-02-02T08:00:00")));
|
||||
page.navigate("http://localhost:3333");
|
||||
Locator locator = page.getByTestId("current-time");
|
||||
|
||||
// Pretend that the user closed the laptop lid and opened it again at 10am.
|
||||
// Pause the time once reached that point.
|
||||
page.clock().pauseAt(Instant.parse("2024-02-02T10:00:00"));
|
||||
page.clock().pauseAt(format.parse("2024-02-02T10:00:00"));
|
||||
|
||||
// Assert the page state.
|
||||
assertThat(locator).hasText("2/2/2024, 10:00:00 AM");
|
||||
|
|
@ -315,15 +330,16 @@ expect(locator).to_have_text("2/2/2024, 10:00:02 AM")
|
|||
```
|
||||
|
||||
```java
|
||||
SimpleDateFormat format = new SimpleDateFormat("yyy-MM-dd'T'HH:mm:ss");
|
||||
// Initialize clock with a specific time, let the page load naturally.
|
||||
page.clock().install(new Clock.InstallOptions()
|
||||
.setTime(Instant.parse("2024-02-02T08:00:00")));
|
||||
.setTime(format.parse("2024-02-02T08:00:00")));
|
||||
page.navigate("http://localhost:3333");
|
||||
Locator locator = page.getByTestId("current-time");
|
||||
|
||||
// Pause the time flow, stop the timers, you now have manual control
|
||||
// over the page time.
|
||||
page.clock().pauseAt(Instant.parse("2024-02-02T10:00:00"));
|
||||
page.clock().pauseAt(format.parse("2024-02-02T10:00:00"));
|
||||
assertThat(locator).hasText("2/2/2024, 10:00:00 AM");
|
||||
|
||||
// Tick through time manually, firing all timers in the process.
|
||||
|
|
@ -351,3 +367,10 @@ await Expect(locator).ToHaveTextAsync("2/2/2024, 10:00:00 AM");
|
|||
await Page.Clock.RunForAsync(2000);
|
||||
await Expect(locator).ToHaveTextAsync("2/2/2024, 10:00:02 AM");
|
||||
```
|
||||
|
||||
## Related Videos
|
||||
|
||||
<LiteYouTube
|
||||
id="54_aC-rVKHg"
|
||||
title="Playwright 1.45"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ title: "Dialogs"
|
|||
|
||||
## Introduction
|
||||
|
||||
Playwright can interact with the web page dialogs such as [`alert`](https://developer.mozilla.org/en-US/docs/Web/API/Window/alert), [`confirm`](https://developer.mozilla.org/en-US/docs/Web/API/Window/confirm), [`prompt`](https://developer.mozilla.org/en-US/docs/Web/API/Window/prompt) as well as [`beforeunload`](https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event) confirmation.
|
||||
Playwright can interact with the web page dialogs such as [`alert`](https://developer.mozilla.org/en-US/docs/Web/API/Window/alert), [`confirm`](https://developer.mozilla.org/en-US/docs/Web/API/Window/confirm), [`prompt`](https://developer.mozilla.org/en-US/docs/Web/API/Window/prompt) as well as [`beforeunload`](https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event) confirmation. For print dialogs, see [Print](#print-dialogs).
|
||||
|
||||
## alert(), confirm(), prompt() dialogs
|
||||
|
||||
|
|
@ -126,3 +126,55 @@ Page.Dialog += async (_, dialog) =>
|
|||
};
|
||||
await Page.CloseAsync(new() { RunBeforeUnload = true });
|
||||
```
|
||||
|
||||
## Print dialogs
|
||||
|
||||
In order to assert that a print dialog via [`window.print`](https://developer.mozilla.org/en-US/docs/Web/API/Window/print) was triggered, you can use the following snippet:
|
||||
|
||||
```js
|
||||
await page.goto('<url>');
|
||||
|
||||
await page.evaluate('(() => {window.waitForPrintDialog = new Promise(f => window.print = f);})()');
|
||||
await page.getByText('Print it!').click();
|
||||
|
||||
await page.waitForFunction('window.waitForPrintDialog');
|
||||
```
|
||||
|
||||
```java
|
||||
page.navigate("<url>");
|
||||
|
||||
page.evaluate("(() => {window.waitForPrintDialog = new Promise(f => window.print = f);})()");
|
||||
page.getByText("Print it!").click();
|
||||
|
||||
page.waitForFunction("window.waitForPrintDialog");
|
||||
```
|
||||
|
||||
```python async
|
||||
await page.goto("<url>")
|
||||
|
||||
await page.evaluate("(() => {window.waitForPrintDialog = new Promise(f => window.print = f);})()")
|
||||
await page.get_by_text("Print it!").click()
|
||||
|
||||
await page.wait_for_function("window.waitForPrintDialog")
|
||||
```
|
||||
|
||||
```python sync
|
||||
page.goto("<url>")
|
||||
|
||||
page.evaluate("(() => {window.waitForPrintDialog = new Promise(f => window.print = f);})()")
|
||||
page.get_by_text("Print it!").click()
|
||||
|
||||
page.wait_for_function("window.waitForPrintDialog")
|
||||
```
|
||||
|
||||
```csharp
|
||||
await Page.GotoAsync("<url>");
|
||||
|
||||
await Page.EvaluateAsync("(() => {window.waitForPrintDialog = new Promise(f => window.print = f);})()");
|
||||
await Page.GetByText("Print it!").ClickAsync();
|
||||
|
||||
await Page.WaitForFunctionAsync("window.waitForPrintDialog");
|
||||
```
|
||||
|
||||
This will wait for the print dialog to be opened after the button is clicked.
|
||||
Make sure to evaluate the script before clicking the button / after the page is loaded.
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ title: "Docker"
|
|||
|
||||
## Introduction
|
||||
|
||||
[Dockerfile.jammy] can be used to run Playwright scripts in Docker environment. These image includes the [Playwright browsers](./browsers.md#install-browsers) and [browser system dependencies](./browsers.md#install-system-dependencies). The Playwright package/dependency is not included in the image and should be installed separately.
|
||||
[Dockerfile.jammy] can be used to run Playwright scripts in Docker environment. This image includes the [Playwright browsers](./browsers.md#install-browsers) and [browser system dependencies](./browsers.md#install-system-dependencies). The Playwright package/dependency is not included in the image and should be installed separately.
|
||||
|
||||
## Usage
|
||||
|
||||
|
|
|
|||
|
|
@ -397,17 +397,17 @@ page.locator("#area").pressSequentially("Hello World!");
|
|||
|
||||
```python async
|
||||
# Press keys one by one
|
||||
await page.locator('#area').pressSequentially('Hello World!')
|
||||
await page.locator('#area').press_sequentially('Hello World!')
|
||||
```
|
||||
|
||||
```python sync
|
||||
# Press keys one by one
|
||||
page.locator('#area').pressSequentially('Hello World!')
|
||||
page.locator('#area').press_sequentially('Hello World!')
|
||||
```
|
||||
|
||||
```csharp
|
||||
// Press keys one by one
|
||||
await page.Locator("#area").TypeAsync("Hello World!");
|
||||
await Page.Locator("#area").PressSequentiallyAsync("Hello World!");
|
||||
```
|
||||
|
||||
This method will emit all the necessary keyboard events, with all the `keydown`, `keyup`, `keypress` events in place. You can even specify the optional `delay` between the key presses to simulate real user behavior.
|
||||
|
|
|
|||
|
|
@ -1444,14 +1444,6 @@ page.getByText("orange").click();
|
|||
await page.GetByText("orange").ClickAsync();
|
||||
```
|
||||
|
||||
```html card
|
||||
<ul>
|
||||
<li>apple</li>
|
||||
<li>banana</li>
|
||||
<li>orange</li>
|
||||
</ul>
|
||||
```
|
||||
|
||||
#### Filter by text
|
||||
Use the [`method: Locator.filter`] to locate a specific item in a list.
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,69 @@ title: "Release notes"
|
|||
toc_max_heading_level: 2
|
||||
---
|
||||
|
||||
## Version 1.45
|
||||
|
||||
### Clock
|
||||
|
||||
Utilizing the new [Clock] API allows to manipulate and control time within tests to verify time-related behavior. This API covers many common scenarios, including:
|
||||
* testing with predefined time;
|
||||
* keeping consistent time and timers;
|
||||
* monitoring inactivity;
|
||||
* ticking through time manually.
|
||||
|
||||
```csharp
|
||||
// Initialize clock with some time before the test time and let the page load naturally.
|
||||
// `Date.now` will progress as the timers fire.
|
||||
await Page.Clock.InstallAsync(new()
|
||||
{
|
||||
TimeDate = new DateTime(2024, 2, 2, 8, 0, 0)
|
||||
});
|
||||
await Page.GotoAsync("http://localhost:3333");
|
||||
|
||||
// Pretend that the user closed the laptop lid and opened it again at 10am.
|
||||
// Pause the time once reached that point.
|
||||
await Page.Clock.PauseAtAsync(new DateTime(2024, 2, 2, 10, 0, 0));
|
||||
|
||||
// Assert the page state.
|
||||
await Expect(Page.GetByTestId("current-time")).ToHaveTextAsync("2/2/2024, 10:00:00 AM");
|
||||
|
||||
// Close the laptop lid again and open it at 10:30am.
|
||||
await Page.Clock.FastForwardAsync("30:00");
|
||||
await Expect(Page.GetByTestId("current-time")).ToHaveTextAsync("2/2/2024, 10:30:00 AM");
|
||||
```
|
||||
|
||||
See [the clock guide](./clock.md) for more details.
|
||||
|
||||
### Miscellaneous
|
||||
|
||||
- Method [`method: Locator.setInputFiles`] now supports uploading a directory for `<input type=file webkitdirectory>` elements.
|
||||
```csharp
|
||||
await page.GetByLabel("Upload directory").SetInputFilesAsync("mydir");
|
||||
```
|
||||
|
||||
- Multiple methods like [`method: Locator.click`] or [`method: Locator.press`] now support a `ControlOrMeta` modifier key. This key maps to `Meta` on macOS and maps to `Control` on Windows and Linux.
|
||||
```csharp
|
||||
// Press the common keyboard shortcut Control+S or Meta+S to trigger a "Save" operation.
|
||||
await page.Keyboard.PressAsync("ControlOrMeta+S");
|
||||
```
|
||||
|
||||
- New property `httpCredentials.send` in [`method: APIRequest.newContext`] that allows to either always send the `Authorization` header or only send it in response to `401 Unauthorized`.
|
||||
|
||||
- Playwright now supports Chromium, Firefox and WebKit on Ubuntu 24.04.
|
||||
|
||||
- v1.45 is the last release to receive WebKit update for macOS 12 Monterey. Please update macOS to keep using the latest WebKit.
|
||||
|
||||
### Browser Versions
|
||||
|
||||
* Chromium 127.0.6533.5
|
||||
* Mozilla Firefox 127.0
|
||||
* WebKit 17.4
|
||||
|
||||
This version was also tested against the following stable channels:
|
||||
|
||||
* Google Chrome 126
|
||||
* Microsoft Edge 126
|
||||
|
||||
## Version 1.44
|
||||
|
||||
### New APIs
|
||||
|
|
@ -129,7 +192,7 @@ This version was also tested against the following stable channels:
|
|||
### New Locator Handler
|
||||
|
||||
New method [`method: Page.addLocatorHandler`] registers a callback that will be invoked when specified element becomes visible and may block Playwright actions. The callback can get rid of the overlay. Here is an example that closes a cookie dialog when it appears.
|
||||
|
||||
|
||||
```csharp
|
||||
// Setup the handler.
|
||||
await Page.AddLocatorHandlerAsync(
|
||||
|
|
|
|||
|
|
@ -4,6 +4,67 @@ title: "Release notes"
|
|||
toc_max_heading_level: 2
|
||||
---
|
||||
|
||||
## Version 1.45
|
||||
|
||||
### Clock
|
||||
|
||||
Utilizing the new [Clock] API allows to manipulate and control time within tests to verify time-related behavior. This API covers many common scenarios, including:
|
||||
* testing with predefined time;
|
||||
* keeping consistent time and timers;
|
||||
* monitoring inactivity;
|
||||
* ticking through time manually.
|
||||
|
||||
```java
|
||||
// Initialize clock with some time before the test time and let the page load
|
||||
// naturally. `Date.now` will progress as the timers fire.
|
||||
page.clock().install(new Clock.InstallOptions().setTime("2024-02-02T08:00:00"));
|
||||
page.navigate("http://localhost:3333");
|
||||
Locator locator = page.getByTestId("current-time");
|
||||
|
||||
// Pretend that the user closed the laptop lid and opened it again at 10am.
|
||||
// Pause the time once reached that point.
|
||||
page.clock().pauseAt("2024-02-02T10:00:00");
|
||||
|
||||
// Assert the page state.
|
||||
assertThat(locator).hasText("2/2/2024, 10:00:00 AM");
|
||||
|
||||
// Close the laptop lid again and open it at 10:30am.
|
||||
page.clock().fastForward("30:00");
|
||||
assertThat(locator).hasText("2/2/2024, 10:30:00 AM");
|
||||
```
|
||||
|
||||
See [the clock guide](./clock.md) for more details.
|
||||
|
||||
### Miscellaneous
|
||||
|
||||
- Method [`method: Locator.setInputFiles`] now supports uploading a directory for `<input type=file webkitdirectory>` elements.
|
||||
```java
|
||||
page.getByLabel("Upload directory").setInputFiles(Paths.get("mydir"));
|
||||
```
|
||||
|
||||
- Multiple methods like [`method: Locator.click`] or [`method: Locator.press`] now support a `ControlOrMeta` modifier key. This key maps to `Meta` on macOS and maps to `Control` on Windows and Linux.
|
||||
```java
|
||||
// Press the common keyboard shortcut Control+S or Meta+S to trigger a "Save" operation.
|
||||
page.keyboard.press("ControlOrMeta+S");
|
||||
```
|
||||
|
||||
- New property `httpCredentials.send` in [`method: APIRequest.newContext`] that allows to either always send the `Authorization` header or only send it in response to `401 Unauthorized`.
|
||||
|
||||
- Playwright now supports Chromium, Firefox and WebKit on Ubuntu 24.04.
|
||||
|
||||
- v1.45 is the last release to receive WebKit update for macOS 12 Monterey. Please update macOS to keep using the latest WebKit.
|
||||
|
||||
### Browser Versions
|
||||
|
||||
* Chromium 127.0.6533.5
|
||||
* Mozilla Firefox 127.0
|
||||
* WebKit 17.4
|
||||
|
||||
This version was also tested against the following stable channels:
|
||||
|
||||
* Google Chrome 126
|
||||
* Microsoft Edge 126
|
||||
|
||||
## Version 1.44
|
||||
|
||||
### New APIs
|
||||
|
|
@ -201,7 +262,7 @@ Learn more about the fixtures in our [JUnit guide](./junit.md).
|
|||
### New Locator Handler
|
||||
|
||||
New method [`method: Page.addLocatorHandler`] registers a callback that will be invoked when specified element becomes visible and may block Playwright actions. The callback can get rid of the overlay. Here is an example that closes a cookie dialog when it appears.
|
||||
|
||||
|
||||
```java
|
||||
// Setup the handler.
|
||||
page.addLocatorHandler(
|
||||
|
|
|
|||
|
|
@ -6,6 +6,103 @@ toc_max_heading_level: 2
|
|||
|
||||
import LiteYouTube from '@site/src/components/LiteYouTube';
|
||||
|
||||
## Version 1.45
|
||||
|
||||
<LiteYouTube
|
||||
id="54_aC-rVKHg"
|
||||
title="Playwright 1.45"
|
||||
/>
|
||||
|
||||
### Clock
|
||||
|
||||
Utilizing the new [Clock] API allows to manipulate and control time within tests to verify time-related behavior. This API covers many common scenarios, including:
|
||||
* testing with predefined time;
|
||||
* keeping consistent time and timers;
|
||||
* monitoring inactivity;
|
||||
* ticking through time manually.
|
||||
|
||||
```js
|
||||
// Initialize clock and let the page load naturally.
|
||||
await page.clock.install({ time: new Date('2024-02-02T08:00:00') });
|
||||
await page.goto('http://localhost:3333');
|
||||
|
||||
// Pretend that the user closed the laptop lid and opened it again at 10am,
|
||||
// Pause the time once reached that point.
|
||||
await page.clock.pauseAt(new Date('2024-02-02T10:00:00'));
|
||||
|
||||
// Assert the page state.
|
||||
await expect(page.getByTestId('current-time')).toHaveText('2/2/2024, 10:00:00 AM');
|
||||
|
||||
// Close the laptop lid again and open it at 10:30am.
|
||||
await page.clock.fastForward('30:00');
|
||||
await expect(page.getByTestId('current-time')).toHaveText('2/2/2024, 10:30:00 AM');
|
||||
```
|
||||
|
||||
See [the clock guide](./clock.md) for more details.
|
||||
|
||||
### Test runner
|
||||
|
||||
- New CLI option `--fail-on-flaky-tests` that sets exit code to `1` upon any flaky tests. Note that by default, the test runner exits with code `0` when all failed tests recovered upon a retry. With this option, the test run will fail in such case.
|
||||
|
||||
- New enviroment variable `PLAYWRIGHT_FORCE_TTY` controls whether built-in `list`, `line` and `dot` reporters assume a live terminal. For example, this could be useful to disable tty behavior when your CI environment does not handle ANSI control sequences well. Alternatively, you can enable tty behavior even when to live terminal is present, if you plan to post-process the output and handle control sequences.
|
||||
|
||||
```sh
|
||||
# Avoid TTY features that output ANSI control sequences
|
||||
PLAYWRIGHT_FORCE_TTY=0 npx playwright test
|
||||
|
||||
# Enable TTY features, assuming a terminal width 80
|
||||
PLAYWRIGHT_FORCE_TTY=80 npx playwright test
|
||||
```
|
||||
|
||||
- New options [`property: TestConfig.respectGitIgnore`] and [`property: TestProject.respectGitIgnore`] control whether files matching `.gitignore` patterns are excluded when searching for tests.
|
||||
|
||||
- New property `timeout` is now available for custom expect matchers. This property takes into account `playwright.config.ts` and `expect.configure()`.
|
||||
```ts
|
||||
import { expect as baseExpect } from '@playwright/test';
|
||||
|
||||
export const expect = baseExpect.extend({
|
||||
async toHaveAmount(locator: Locator, expected: number, options?: { timeout?: number }) {
|
||||
// When no timeout option is specified, use the config timeout.
|
||||
const timeout = options?.timeout ?? this.timeout;
|
||||
// ... implement the assertion ...
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Miscellaneous
|
||||
|
||||
- Method [`method: Locator.setInputFiles`] now supports uploading a directory for `<input type=file webkitdirectory>` elements.
|
||||
```ts
|
||||
await page.getByLabel('Upload directory').setInputFiles(path.join(__dirname, 'mydir'));
|
||||
```
|
||||
|
||||
- Multiple methods like [`method: Locator.click`] or [`method: Locator.press`] now support a `ControlOrMeta` modifier key. This key maps to `Meta` on macOS and maps to `Control` on Windows and Linux.
|
||||
```ts
|
||||
// Press the common keyboard shortcut Control+S or Meta+S to trigger a "Save" operation.
|
||||
await page.keyboard.press('ControlOrMeta+S');
|
||||
```
|
||||
|
||||
- New property `httpCredentials.send` in [`method: APIRequest.newContext`] that allows to either always send the `Authorization` header or only send it in response to `401 Unauthorized`.
|
||||
|
||||
- New option `reason` in [`method: APIRequestContext.dispose`] that will be included in the error message of ongoing operations interrupted by the context disposal.
|
||||
|
||||
- New option `host` in [`method: BrowserType.launchServer`] allows to accept websocket connections on a specific address instead of unspecified `0.0.0.0`.
|
||||
|
||||
- Playwright now supports Chromium, Firefox and WebKit on Ubuntu 24.04.
|
||||
|
||||
- v1.45 is the last release to receive WebKit update for macOS 12 Monterey. Please update macOS to keep using the latest WebKit.
|
||||
|
||||
### Browser Versions
|
||||
|
||||
* Chromium 127.0.6533.5
|
||||
* Mozilla Firefox 127.0
|
||||
* WebKit 17.4
|
||||
|
||||
This version was also tested against the following stable channels:
|
||||
|
||||
* Google Chrome 126
|
||||
* Microsoft Edge 126
|
||||
|
||||
## Version 1.44
|
||||
|
||||
<LiteYouTube
|
||||
|
|
|
|||
|
|
@ -4,6 +4,66 @@ title: "Release notes"
|
|||
toc_max_heading_level: 2
|
||||
---
|
||||
|
||||
## Version 1.45
|
||||
|
||||
### Clock
|
||||
|
||||
Utilizing the new [Clock] API allows to manipulate and control time within tests to verify time-related behavior. This API covers many common scenarios, including:
|
||||
* testing with predefined time;
|
||||
* keeping consistent time and timers;
|
||||
* monitoring inactivity;
|
||||
* ticking through time manually.
|
||||
|
||||
```python
|
||||
# Initialize clock with some time before the test time and let the page load
|
||||
# naturally. `Date.now` will progress as the timers fire.
|
||||
page.clock.install(time=datetime.datetime(2024, 2, 2, 8, 0, 0))
|
||||
page.goto("http://localhost:3333")
|
||||
|
||||
# Pretend that the user closed the laptop lid and opened it again at 10am.
|
||||
# Pause the time once reached that point.
|
||||
page.clock.pause_at(datetime.datetime(2024, 2, 2, 10, 0, 0))
|
||||
|
||||
# Assert the page state.
|
||||
expect(page.get_by_test_id("current-time")).to_have_text("2/2/2024, 10:00:00 AM")
|
||||
|
||||
# Close the laptop lid again and open it at 10:30am.
|
||||
page.clock.fast_forward("30:00")
|
||||
expect(page.get_by_test_id("current-time")).to_have_text("2/2/2024, 10:30:00 AM")
|
||||
```
|
||||
|
||||
See [the clock guide](./clock.md) for more details.
|
||||
|
||||
### Miscellaneous
|
||||
|
||||
- Method [`method: Locator.setInputFiles`] now supports uploading a directory for `<input type=file webkitdirectory>` elements.
|
||||
```python
|
||||
page.get_by_label("Upload directory").set_input_files('mydir')
|
||||
```
|
||||
|
||||
- Multiple methods like [`method: Locator.click`] or [`method: Locator.press`] now support a `ControlOrMeta` modifier key. This key maps to `Meta` on macOS and maps to `Control` on Windows and Linux.
|
||||
```python
|
||||
# Press the common keyboard shortcut Control+S or Meta+S to trigger a "Save" operation.
|
||||
page.keyboard.press("ControlOrMeta+S")
|
||||
```
|
||||
|
||||
- New property `httpCredentials.send` in [`method: APIRequest.newContext`] that allows to either always send the `Authorization` header or only send it in response to `401 Unauthorized`.
|
||||
|
||||
- Playwright now supports Chromium, Firefox and WebKit on Ubuntu 24.04.
|
||||
|
||||
- v1.45 is the last release to receive WebKit update for macOS 12 Monterey. Please update macOS to keep using the latest WebKit.
|
||||
|
||||
### Browser Versions
|
||||
|
||||
* Chromium 127.0.6533.5
|
||||
* Mozilla Firefox 127.0
|
||||
* WebKit 17.4
|
||||
|
||||
This version was also tested against the following stable channels:
|
||||
|
||||
* Google Chrome 126
|
||||
* Microsoft Edge 126
|
||||
|
||||
## Version 1.44
|
||||
|
||||
### New APIs
|
||||
|
|
@ -109,7 +169,7 @@ This version was also tested against the following stable channels:
|
|||
### New Locator Handler
|
||||
|
||||
New method [`method: Page.addLocatorHandler`] registers a callback that will be invoked when specified element becomes visible and may block Playwright actions. The callback can get rid of the overlay. Here is an example that closes a cookie dialog when it appears.
|
||||
|
||||
|
||||
```python
|
||||
# Setup the handler.
|
||||
page.add_locator_handler(
|
||||
|
|
|
|||
|
|
@ -232,12 +232,14 @@ Let's say you'd like to test following component:
|
|||
```js title="input-media.tsx"
|
||||
import React from 'react';
|
||||
|
||||
export const InputMedia: React.FC<{
|
||||
type InputMediaProps = {
|
||||
// Media is a complex browser object we can't send to Node while testing.
|
||||
onChange: (media: Media) => void,
|
||||
}> = ({ onChange }) => {
|
||||
return <></> as any;
|
||||
onChange(media: Media): void;
|
||||
};
|
||||
|
||||
export function InputMedia(props: InputMediaProps) {
|
||||
return <></> as any;
|
||||
}
|
||||
```
|
||||
|
||||
Create a story file for your component:
|
||||
|
|
@ -246,12 +248,14 @@ Create a story file for your component:
|
|||
import React from 'react';
|
||||
import InputMedia from './import-media';
|
||||
|
||||
export const InputMediaForTest: React.FC<{
|
||||
onMediaChange: (mediaName: string) => void,
|
||||
}> = ({ onMediaChange }) => {
|
||||
// Instead of sending a complex `media` object to the test, send the media name.
|
||||
return <InputMedia onChange={media => onMediaChange(media.name)} />;
|
||||
type InputMediaForTestProps = {
|
||||
onMediaChange(mediaName: string): void;
|
||||
};
|
||||
|
||||
export function InputMediaForTest(props: InputMediaForTestProps) {
|
||||
// Instead of sending a complex `media` object to the test, send the media name.
|
||||
return <InputMedia onChange={media => props.onMediaChange(media.name)} />;
|
||||
}
|
||||
// Export more stories here.
|
||||
```
|
||||
|
||||
|
|
@ -265,7 +269,6 @@ test('changes the image', async ({ mount }) => {
|
|||
<InputMediaForTest
|
||||
onMediaChange={mediaName => {
|
||||
mediaSelected = mediaName;
|
||||
console.log({ mediaName });
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -43,48 +43,7 @@ Here is how typical test environment setup differs between traditional test styl
|
|||
<summary>Click to expand the code for the <code>TodoPage</code></summary>
|
||||
<div>
|
||||
|
||||
```js tab=js-js title="todo-page.js"
|
||||
export class TodoPage {
|
||||
/**
|
||||
* @param {import('@playwright/test').Page} page
|
||||
*/
|
||||
constructor(page) {
|
||||
this.page = page;
|
||||
this.inputBox = this.page.locator('input.new-todo');
|
||||
this.todoItems = this.page.getByTestId('todo-item');
|
||||
}
|
||||
|
||||
async goto() {
|
||||
await this.page.goto('https://demo.playwright.dev/todomvc/');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} text
|
||||
*/
|
||||
async addToDo(text) {
|
||||
await this.inputBox.fill(text);
|
||||
await this.inputBox.press('Enter');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} text
|
||||
*/
|
||||
async remove(text) {
|
||||
const todo = this.todoItems.filter({ hasText: text });
|
||||
await todo.hover();
|
||||
await todo.getByLabel('Delete').click();
|
||||
}
|
||||
|
||||
async removeAll() {
|
||||
while ((await this.todoItems.count()) > 0) {
|
||||
await this.todoItems.first().hover();
|
||||
await this.todoItems.getByLabel('Delete').first().click();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js tab=js-ts title="todo-page.ts"
|
||||
```js title="todo-page.ts"
|
||||
import type { Page, Locator } from '@playwright/test';
|
||||
|
||||
export class TodoPage {
|
||||
|
|
@ -167,48 +126,7 @@ Fixtures have a number of advantages over before/after hooks:
|
|||
<summary>Click to expand the code for the <code>TodoPage</code></summary>
|
||||
<div>
|
||||
|
||||
```js tab=js-js title="todo-page.js"
|
||||
export class TodoPage {
|
||||
/**
|
||||
* @param {import('@playwright/test').Page} page
|
||||
*/
|
||||
constructor(page) {
|
||||
this.page = page;
|
||||
this.inputBox = this.page.locator('input.new-todo');
|
||||
this.todoItems = this.page.getByTestId('todo-item');
|
||||
}
|
||||
|
||||
async goto() {
|
||||
await this.page.goto('https://demo.playwright.dev/todomvc/');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} text
|
||||
*/
|
||||
async addToDo(text) {
|
||||
await this.inputBox.fill(text);
|
||||
await this.inputBox.press('Enter');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} text
|
||||
*/
|
||||
async remove(text) {
|
||||
const todo = this.todoItems.filter({ hasText: text });
|
||||
await todo.hover();
|
||||
await todo.getByLabel('Delete').click();
|
||||
}
|
||||
|
||||
async removeAll() {
|
||||
while ((await this.todoItems.count()) > 0) {
|
||||
await this.todoItems.first().hover();
|
||||
await this.todoItems.getByLabel('Delete').first().click();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js tab=js-ts title="todo-page.ts"
|
||||
```js title="todo-page.ts"
|
||||
import type { Page, Locator } from '@playwright/test';
|
||||
|
||||
export class TodoPage {
|
||||
|
|
@ -246,34 +164,7 @@ export class TodoPage {
|
|||
</div>
|
||||
</details>
|
||||
|
||||
```js tab=js-js title="todo.spec.js"
|
||||
const base = require('@playwright/test');
|
||||
const { TodoPage } = require('./todo-page');
|
||||
|
||||
// Extend basic test by providing a "todoPage" fixture.
|
||||
const test = base.test.extend({
|
||||
todoPage: async ({ page }, use) => {
|
||||
const todoPage = new TodoPage(page);
|
||||
await todoPage.goto();
|
||||
await todoPage.addToDo('item1');
|
||||
await todoPage.addToDo('item2');
|
||||
await use(todoPage);
|
||||
await todoPage.removeAll();
|
||||
},
|
||||
});
|
||||
|
||||
test('should add an item', async ({ todoPage }) => {
|
||||
await todoPage.addToDo('my item');
|
||||
// ...
|
||||
});
|
||||
|
||||
test('should remove an item', async ({ todoPage }) => {
|
||||
await todoPage.remove('item1');
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
```js tab=js-ts title="example.spec.ts"
|
||||
```js title="example.spec.ts"
|
||||
import { test as base } from '@playwright/test';
|
||||
import { TodoPage } from './todo-page';
|
||||
|
||||
|
|
@ -309,48 +200,8 @@ Below we create two fixtures `todoPage` and `settingsPage` that follow the [Page
|
|||
<details>
|
||||
<summary>Click to expand the code for the <code>TodoPage</code> and <code>SettingsPage</code></summary>
|
||||
<div>
|
||||
```js tab=js-js title="todo-page.js"
|
||||
export class TodoPage {
|
||||
/**
|
||||
* @param {import('@playwright/test').Page} page
|
||||
*/
|
||||
constructor(page) {
|
||||
this.page = page;
|
||||
this.inputBox = this.page.locator('input.new-todo');
|
||||
this.todoItems = this.page.getByTestId('todo-item');
|
||||
}
|
||||
|
||||
async goto() {
|
||||
await this.page.goto('https://demo.playwright.dev/todomvc/');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} text
|
||||
*/
|
||||
async addToDo(text) {
|
||||
await this.inputBox.fill(text);
|
||||
await this.inputBox.press('Enter');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} text
|
||||
*/
|
||||
async remove(text) {
|
||||
const todo = this.todoItems.filter({ hasText: text });
|
||||
await todo.hover();
|
||||
await todo.getByLabel('Delete').click();
|
||||
}
|
||||
|
||||
async removeAll() {
|
||||
while ((await this.todoItems.count()) > 0) {
|
||||
await this.todoItems.first().hover();
|
||||
await this.todoItems.getByLabel('Delete').first().click();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js tab=js-ts title="todo-page.ts"
|
||||
```js title="todo-page.ts"
|
||||
import type { Page, Locator } from '@playwright/test';
|
||||
|
||||
export class TodoPage {
|
||||
|
|
@ -388,22 +239,7 @@ export class TodoPage {
|
|||
|
||||
SettingsPage is similar:
|
||||
|
||||
```js tab=js-js title="settings-page.js"
|
||||
export class SettingsPage {
|
||||
/**
|
||||
* @param {import('@playwright/test').Page} page
|
||||
*/
|
||||
constructor(page) {
|
||||
this.page = page;
|
||||
}
|
||||
|
||||
async switchToDarkMode() {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js tab=js-ts title="settings-page.ts"
|
||||
```js title="settings-page.ts"
|
||||
import type { Page } from '@playwright/test';
|
||||
|
||||
export class SettingsPage {
|
||||
|
|
@ -419,36 +255,7 @@ export class SettingsPage {
|
|||
</div>
|
||||
</details>
|
||||
|
||||
```js tab=js-js title="my-test.js"
|
||||
const base = require('@playwright/test');
|
||||
const { TodoPage } = require('./todo-page');
|
||||
const { SettingsPage } = require('./settings-page');
|
||||
|
||||
// Extend base test by providing "todoPage" and "settingsPage".
|
||||
// This new "test" can be used in multiple test files, and each of them will get the fixtures.
|
||||
exports.test = base.test.extend({
|
||||
todoPage: async ({ page }, use) => {
|
||||
// Set up the fixture.
|
||||
const todoPage = new TodoPage(page);
|
||||
await todoPage.goto();
|
||||
await todoPage.addToDo('item1');
|
||||
await todoPage.addToDo('item2');
|
||||
|
||||
// Use the fixture value in the test.
|
||||
await use(todoPage);
|
||||
|
||||
// Clean up the fixture.
|
||||
await todoPage.removeAll();
|
||||
},
|
||||
|
||||
settingsPage: async ({ page }, use) => {
|
||||
await use(new SettingsPage(page));
|
||||
},
|
||||
});
|
||||
exports.expect = base.expect;
|
||||
```
|
||||
|
||||
```js tab=js-ts title="my-test.ts"
|
||||
```js title="my-test.ts"
|
||||
import { test as base } from '@playwright/test';
|
||||
import { TodoPage } from './todo-page';
|
||||
import { SettingsPage } from './settings-page';
|
||||
|
|
@ -493,20 +300,7 @@ Just mention fixture in your test function argument, and test runner will take c
|
|||
|
||||
Below we use the `todoPage` and `settingsPage` fixtures defined above.
|
||||
|
||||
```js tab=js-js
|
||||
const { test, expect } = require('./my-test');
|
||||
|
||||
test.beforeEach(async ({ settingsPage }) => {
|
||||
await settingsPage.switchToDarkMode();
|
||||
});
|
||||
|
||||
test('basic test', async ({ todoPage, page }) => {
|
||||
await todoPage.addToDo('something nice');
|
||||
await expect(page.getByTestId('todo-title')).toContainText(['something nice']);
|
||||
});
|
||||
```
|
||||
|
||||
```js tab=js-ts
|
||||
```js
|
||||
import { test, expect } from './my-test';
|
||||
|
||||
test.beforeEach(async ({ settingsPage }) => {
|
||||
|
|
@ -560,47 +354,7 @@ Playwright Test uses [worker processes](./test-parallel.md) to run test files. S
|
|||
|
||||
Below we'll create an `account` fixture that will be shared by all tests in the same worker, and override the `page` fixture to login into this account for each test. To generate unique accounts, we'll use the [`property: WorkerInfo.workerIndex`] that is available to any test or fixture. Note the tuple-like syntax for the worker fixture - we have to pass `{scope: 'worker'}` so that test runner sets up this fixture once per worker.
|
||||
|
||||
```js tab=js-js title="my-test.js"
|
||||
const base = require('@playwright/test');
|
||||
|
||||
exports.test = base.test.extend({
|
||||
account: [async ({ browser }, use, workerInfo) => {
|
||||
// Unique username.
|
||||
const username = 'user' + workerInfo.workerIndex;
|
||||
const password = 'verysecure';
|
||||
|
||||
// Create the account with Playwright.
|
||||
const page = await browser.newPage();
|
||||
await page.goto('/signup');
|
||||
await page.getByLabel('User Name').fill(username);
|
||||
await page.getByLabel('Password').fill(password);
|
||||
await page.getByText('Sign up').click();
|
||||
// Make sure everything is ok.
|
||||
await expect(page.locator('#result')).toHaveText('Success');
|
||||
// Do not forget to cleanup.
|
||||
await page.close();
|
||||
|
||||
// Use the account value.
|
||||
await use({ username, password });
|
||||
}, { scope: 'worker' }],
|
||||
|
||||
page: async ({ page, account }, use) => {
|
||||
// Sign in with our account.
|
||||
const { username, password } = account;
|
||||
await page.goto('/signin');
|
||||
await page.getByLabel('User Name').fill(username);
|
||||
await page.getByLabel('Password').fill(password);
|
||||
await page.getByText('Sign in').click();
|
||||
await expect(page.getByTestId('userinfo')).toHaveText(username);
|
||||
|
||||
// Use signed-in page in the test.
|
||||
await use(page);
|
||||
},
|
||||
});
|
||||
exports.expect = base.expect;
|
||||
```
|
||||
|
||||
```js tab=js-ts title="my-test.ts"
|
||||
```js title="my-test.ts"
|
||||
import { test as base } from '@playwright/test';
|
||||
|
||||
type Account = {
|
||||
|
|
@ -652,32 +406,7 @@ Automatic fixtures are set up for each test/worker, even when the test does not
|
|||
|
||||
Here is an example fixture that automatically attaches debug logs when the test fails, so we can later review the logs in the reporter. Note how it uses [TestInfo] object that is available in each test/fixture to retrieve metadata about the test being run.
|
||||
|
||||
```js tab=js-js title="my-test.js"
|
||||
const debug = require('debug');
|
||||
const fs = require('fs');
|
||||
const base = require('@playwright/test');
|
||||
|
||||
exports.test = base.test.extend({
|
||||
saveLogs: [async ({}, use, testInfo) => {
|
||||
// Collecting logs during the test.
|
||||
const logs = [];
|
||||
debug.log = (...args) => logs.push(args.map(String).join(''));
|
||||
debug.enable('myserver');
|
||||
|
||||
await use();
|
||||
|
||||
// After the test we can check whether the test passed or failed.
|
||||
if (testInfo.status !== testInfo.expectedStatus) {
|
||||
// outputPath() API guarantees a unique file name.
|
||||
const logFile = testInfo.outputPath('logs.txt');
|
||||
await fs.promises.writeFile(logFile, logs.join('\n'), 'utf8');
|
||||
testInfo.attachments.push({ name: 'logs', contentType: 'text/plain', path: logFile });
|
||||
}
|
||||
}, { auto: true }],
|
||||
});
|
||||
```
|
||||
|
||||
```js tab=js-ts title="my-test.ts"
|
||||
```js title="my-test.ts"
|
||||
import * as debug from 'debug';
|
||||
import * as fs from 'fs';
|
||||
import { test as base } from '@playwright/test';
|
||||
|
|
@ -707,22 +436,7 @@ export { expect } from '@playwright/test';
|
|||
|
||||
By default, fixture shares timeout with the test. However, for slow fixtures, especially [worker-scoped](#worker-scoped-fixtures) ones, it is convenient to have a separate timeout. This way you can keep the overall test timeout small, and give the slow fixture more time.
|
||||
|
||||
```js tab=js-js
|
||||
const { test: base, expect } = require('@playwright/test');
|
||||
|
||||
const test = base.extend({
|
||||
slowFixture: [async ({}, use) => {
|
||||
// ... perform a slow operation ...
|
||||
await use('hello');
|
||||
}, { timeout: 60000 }]
|
||||
});
|
||||
|
||||
test('example test', async ({ slowFixture }) => {
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
```js tab=js-ts
|
||||
```js
|
||||
import { test as base, expect } from '@playwright/test';
|
||||
|
||||
const test = base.extend<{ slowFixture: string }>({
|
||||
|
|
@ -752,48 +466,7 @@ Below we'll create a `defaultItem` option in addition to the `todoPage` fixture
|
|||
<summary>Click to expand the code for the <code>TodoPage</code></summary>
|
||||
<div>
|
||||
|
||||
```js tab=js-js title="todo-page.js"
|
||||
export class TodoPage {
|
||||
/**
|
||||
* @param {import('@playwright/test').Page} page
|
||||
*/
|
||||
constructor(page) {
|
||||
this.page = page;
|
||||
this.inputBox = this.page.locator('input.new-todo');
|
||||
this.todoItems = this.page.getByTestId('todo-item');
|
||||
}
|
||||
|
||||
async goto() {
|
||||
await this.page.goto('https://demo.playwright.dev/todomvc/');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} text
|
||||
*/
|
||||
async addToDo(text) {
|
||||
await this.inputBox.fill(text);
|
||||
await this.inputBox.press('Enter');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} text
|
||||
*/
|
||||
async remove(text) {
|
||||
const todo = this.todoItems.filter({ hasText: text });
|
||||
await todo.hover();
|
||||
await todo.getByLabel('Delete').click();
|
||||
}
|
||||
|
||||
async removeAll() {
|
||||
while ((await this.todoItems.count()) > 0) {
|
||||
await this.todoItems.first().hover();
|
||||
await this.todoItems.getByLabel('Delete').first().click();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js tab=js-ts title="todo-page.ts"
|
||||
```js title="todo-page.ts"
|
||||
import type { Page, Locator } from '@playwright/test';
|
||||
|
||||
export class TodoPage {
|
||||
|
|
@ -832,28 +505,7 @@ export class TodoPage {
|
|||
</div>
|
||||
</details>
|
||||
|
||||
```js tab=js-js title="my-test.js"
|
||||
const base = require('@playwright/test');
|
||||
const { TodoPage } = require('./todo-page');
|
||||
|
||||
exports.test = base.test.extend({
|
||||
// Define an option and provide a default value.
|
||||
// We can later override it in the config.
|
||||
defaultItem: ['Something nice', { option: true }],
|
||||
|
||||
// Our "todoPage" fixture depends on the option.
|
||||
todoPage: async ({ page, defaultItem }, use) => {
|
||||
const todoPage = new TodoPage(page);
|
||||
await todoPage.goto();
|
||||
await todoPage.addToDo(defaultItem);
|
||||
await use(todoPage);
|
||||
await todoPage.removeAll();
|
||||
},
|
||||
});
|
||||
exports.expect = base.expect;
|
||||
```
|
||||
|
||||
```js tab=js-ts title="my-test.ts"
|
||||
```js title="my-test.ts"
|
||||
import { test as base } from '@playwright/test';
|
||||
import { TodoPage } from './todo-page';
|
||||
|
||||
|
|
@ -885,25 +537,7 @@ export { expect } from '@playwright/test';
|
|||
|
||||
We can now use `todoPage` fixture as usual, and set the `defaultItem` option in the config file.
|
||||
|
||||
```js tab=js-js title="playwright.config.ts"
|
||||
// @ts-check
|
||||
|
||||
const { defineConfig } = require('@playwright/test');
|
||||
module.exports = defineConfig({
|
||||
projects: [
|
||||
{
|
||||
name: 'shopping',
|
||||
use: { defaultItem: 'Buy milk' },
|
||||
},
|
||||
{
|
||||
name: 'wellbeing',
|
||||
use: { defaultItem: 'Exercise!' },
|
||||
},
|
||||
]
|
||||
});
|
||||
```
|
||||
|
||||
```js tab=js-ts title="playwright.config.ts"
|
||||
```js title="playwright.config.ts"
|
||||
import { defineConfig } from '@playwright/test';
|
||||
import type { MyOptions } from './my-test';
|
||||
|
||||
|
|
@ -932,50 +566,7 @@ Fixtures follow these rules to determine the execution order:
|
|||
|
||||
Consider the following example:
|
||||
|
||||
```js tab=js-js
|
||||
const { test: base } = require('@playwright/test');
|
||||
|
||||
const test = base.extend({
|
||||
workerFixture: [async ({ browser }) => {
|
||||
// workerFixture setup...
|
||||
await use('workerFixture');
|
||||
// workerFixture teardown...
|
||||
}, { scope: 'worker' }],
|
||||
|
||||
autoWorkerFixture: [async ({ browser }) => {
|
||||
// autoWorkerFixture setup...
|
||||
await use('autoWorkerFixture');
|
||||
// autoWorkerFixture teardown...
|
||||
}, { scope: 'worker', auto: true }],
|
||||
|
||||
testFixture: [async ({ page, workerFixture }) => {
|
||||
// testFixture setup...
|
||||
await use('testFixture');
|
||||
// testFixture teardown...
|
||||
}, { scope: 'test' }],
|
||||
|
||||
autoTestFixture: [async () => {
|
||||
// autoTestFixture setup...
|
||||
await use('autoTestFixture');
|
||||
// autoTestFixture teardown...
|
||||
}, { scope: 'test', auto: true }],
|
||||
|
||||
unusedFixture: [async ({ page }) => {
|
||||
// unusedFixture setup...
|
||||
await use('unusedFixture');
|
||||
// unusedFixture teardown...
|
||||
}, { scope: 'test' }],
|
||||
});
|
||||
|
||||
test.beforeAll(async () => { /* ... */ });
|
||||
test.beforeEach(async ({ page }) => { /* ... */ });
|
||||
test('first test', async ({ page }) => { /* ... */ });
|
||||
test('second test', async ({ testFixture }) => { /* ... */ });
|
||||
test.afterEach(async () => { /* ... */ });
|
||||
test.afterAll(async () => { /* ... */ });
|
||||
```
|
||||
|
||||
```js tab=js-ts
|
||||
```js
|
||||
import { test as base } from '@playwright/test';
|
||||
|
||||
const test = base.extend<{
|
||||
|
|
@ -1081,3 +672,30 @@ test('passes', async ({ database, page, a11y }) => {
|
|||
// use database and a11y fixtures.
|
||||
});
|
||||
```
|
||||
|
||||
## Box fixtures
|
||||
|
||||
You can minimize the fixture exposure to the reporters UI and error messages via boxing it:
|
||||
|
||||
```js
|
||||
import { test as base } from '@playwright/test';
|
||||
|
||||
export const test = base.extend({
|
||||
_helperFixture: [async ({}, use, testInfo) => {
|
||||
}, { box: true }],
|
||||
});
|
||||
```
|
||||
|
||||
## Custom fixture title
|
||||
|
||||
You can assign a custom title to a fixture to be used in error messages and in the
|
||||
reporters UI:
|
||||
|
||||
```js
|
||||
import { test as base } from '@playwright/test';
|
||||
|
||||
export const test = base.extend({
|
||||
_innerFixture: [async ({}, use, testInfo) => {
|
||||
}, { title: 'my fixture' }],
|
||||
});
|
||||
```
|
||||
|
|
|
|||
|
|
@ -9,13 +9,61 @@ You can either parameterize tests on a test level or on a project level.
|
|||
## Parameterized Tests
|
||||
|
||||
```js title="example.spec.ts"
|
||||
const people = ['Alice', 'Bob'];
|
||||
for (const name of people) {
|
||||
test(`testing with ${name}`, async () => {
|
||||
// ...
|
||||
});
|
||||
[
|
||||
{ name: 'Alice', expected: 'Hello, Alice!' },
|
||||
{ name: 'Bob', expected: 'Hello, Bob!' },
|
||||
{ name: 'Charlie', expected: 'Hello, Charlie!' },
|
||||
].forEach(({ name, expected }) => {
|
||||
// You can also do it with test.describe() or with multiple tests as long the test name is unique.
|
||||
}
|
||||
test(`testing with ${name}`, async ({ page }) => {
|
||||
await page.goto(`https://example.com/greet?name=${name}`);
|
||||
await expect(page.getByRole('heading')).toHaveText(expected);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Before and after hooks
|
||||
|
||||
Most of the time you should put `beforeEach`, `beforeAll`, `afterEach` and `afterAll` hooks outside of `forEach`, so that hooks are executed just once:
|
||||
|
||||
```js title="example.spec.ts"
|
||||
test.beforeEach(async ({ page }) => {
|
||||
// ...
|
||||
});
|
||||
|
||||
test.afterEach(async ({ page }) => {
|
||||
// ...
|
||||
});
|
||||
|
||||
[
|
||||
{ name: 'Alice', expected: 'Hello, Alice!' },
|
||||
{ name: 'Bob', expected: 'Hello, Bob!' },
|
||||
{ name: 'Charlie', expected: 'Hello, Charlie!' },
|
||||
].forEach(({ name, expected }) => {
|
||||
test(`testing with ${name}`, async ({ page }) => {
|
||||
await page.goto(`https://example.com/greet?name=${name}`);
|
||||
await expect(page.getByRole('heading')).toHaveText(expected);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
If you want to have hooks for each test, you can put them inside a `describe()` - so they are executed for each iteration / each invidual test:
|
||||
|
||||
```js title="example.spec.ts"
|
||||
[
|
||||
{ name: 'Alice', expected: 'Hello, Alice!' },
|
||||
{ name: 'Bob', expected: 'Hello, Bob!' },
|
||||
{ name: 'Charlie', expected: 'Hello, Charlie!' },
|
||||
].forEach(({ name, expected }) => {
|
||||
test.describe(() => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto(`https://example.com/greet?name=${name}`);
|
||||
});
|
||||
test(`testing with ${expected}`, async ({ page }) => {
|
||||
await expect(page.getByRole('heading')).toHaveText(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## Parameterized Projects
|
||||
|
|
|
|||
|
|
@ -5,7 +5,11 @@ title: "Sharding"
|
|||
|
||||
## Introduction
|
||||
|
||||
By default, Playwright runs test files in [parallel](./test-parallel.md) and strives for optimal utilization of CPU cores on your machine. In order to achieve even greater parallelisation, you can further scale Playwright test execution by running tests on multiple machines simultaneously. We call this mode of operation "sharding".
|
||||
By default, Playwright runs test files in [parallel](./test-parallel.md) and strives for optimal utilization of CPU cores on your machine. In order to achieve even greater parallelisation, you can further scale Playwright test execution by running tests on multiple machines simultaneously. We call this mode of operation "sharding". Sharding in Playwright means splitting your tests into smaller parts called "shards". Each shard is like a separate job that can run independently. The whole purpose is to divide your tests to speed up test runtime.
|
||||
|
||||
When you shard your tests, each shard can run on its own, utilizing the available CPU cores. This helps speed up the testing process by doing tasks simultaneously.
|
||||
|
||||
In a CI pipeline, each shard can run as a separate job, making use of the hardware resources available in your CI pipeline, like CPU cores, to run tests faster.
|
||||
|
||||
## Sharding tests between multiple machines
|
||||
|
||||
|
|
@ -18,7 +22,7 @@ npx playwright test --shard=3/4
|
|||
npx playwright test --shard=4/4
|
||||
```
|
||||
|
||||
Now, if you run these shards in parallel on different computers, your test suite completes four times faster.
|
||||
Now, if you run these shards in parallel on different jobs, your test suite completes four times faster.
|
||||
|
||||
Note that Playwright can only shard tests that can be run in parallel. By default, this means Playwright will shard test files. Learn about other options in the [parallelism guide](./test-parallel.md).
|
||||
|
||||
|
|
|
|||
632
package-lock.json
generated
632
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "playwright-internal",
|
||||
"private": true,
|
||||
"version": "1.45.0-next",
|
||||
"version": "1.46.0-next",
|
||||
"description": "A high-level API to automate web browsers",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
@ -83,7 +83,7 @@
|
|||
"concurrently": "^6.2.1",
|
||||
"cross-env": "^7.0.3",
|
||||
"dotenv": "^16.0.0",
|
||||
"electron": "19.0.11",
|
||||
"electron": "^30.1.2",
|
||||
"enquirer": "^2.3.6",
|
||||
"esbuild": "^0.18.11",
|
||||
"eslint": "^8.55.0",
|
||||
|
|
@ -101,7 +101,7 @@
|
|||
"ssim.js": "^3.5.0",
|
||||
"typescript": "^5.3.2",
|
||||
"vite": "^5.0.13",
|
||||
"ws": "^8.5.0",
|
||||
"ws": "^8.17.1",
|
||||
"xml2js": "^0.5.0",
|
||||
"yaml": "^2.2.2"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@playwright/browser-chromium",
|
||||
"version": "1.45.0-next",
|
||||
"version": "1.46.0-next",
|
||||
"description": "Playwright package that automatically installs Chromium",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
@ -27,6 +27,6 @@
|
|||
"install": "node install.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"playwright-core": "1.45.0-next"
|
||||
"playwright-core": "1.46.0-next"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@playwright/browser-firefox",
|
||||
"version": "1.45.0-next",
|
||||
"version": "1.46.0-next",
|
||||
"description": "Playwright package that automatically installs Firefox",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
@ -27,6 +27,6 @@
|
|||
"install": "node install.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"playwright-core": "1.45.0-next"
|
||||
"playwright-core": "1.46.0-next"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@playwright/browser-webkit",
|
||||
"version": "1.45.0-next",
|
||||
"version": "1.46.0-next",
|
||||
"description": "Playwright package that automatically installs WebKit",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
@ -27,6 +27,6 @@
|
|||
"install": "node install.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"playwright-core": "1.45.0-next"
|
||||
"playwright-core": "1.46.0-next"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "playwright-chromium",
|
||||
"version": "1.45.0-next",
|
||||
"version": "1.46.0-next",
|
||||
"description": "A high-level API to automate Chromium",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
@ -30,6 +30,6 @@
|
|||
"install": "node install.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"playwright-core": "1.45.0-next"
|
||||
"playwright-core": "1.46.0-next"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ This project incorporates components from the projects listed below. The origina
|
|||
- sprintf-js@1.1.3 (https://github.com/alexei/sprintf.js)
|
||||
- stack-utils@2.0.5 (https://github.com/tapjs/stack-utils)
|
||||
- wrappy@1.0.2 (https://github.com/npm/wrappy)
|
||||
- ws@8.4.2 (https://github.com/websockets/ws)
|
||||
- ws@8.17.1 (https://github.com/websockets/ws)
|
||||
- yauzl@2.10.0 (https://github.com/thejoshwolfe/yauzl)
|
||||
- yazl@2.5.1 (https://github.com/thejoshwolfe/yazl)
|
||||
|
||||
|
|
@ -1435,29 +1435,30 @@ IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|||
=========================================
|
||||
END OF wrappy@1.0.2 AND INFORMATION
|
||||
|
||||
%% ws@8.4.2 NOTICES AND INFORMATION BEGIN HERE
|
||||
%% ws@8.17.1 NOTICES AND INFORMATION BEGIN HERE
|
||||
=========================================
|
||||
Copyright (c) 2011 Einar Otto Stangvik <einaros@gmail.com>
|
||||
Copyright (c) 2013 Arnout Kazemier and contributors
|
||||
Copyright (c) 2016 Luigi Pinca and contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
=========================================
|
||||
END OF ws@8.4.2 AND INFORMATION
|
||||
END OF ws@8.17.1 AND INFORMATION
|
||||
|
||||
%% yauzl@2.10.0 NOTICES AND INFORMATION BEGIN HERE
|
||||
=========================================
|
||||
|
|
|
|||
|
|
@ -3,31 +3,31 @@
|
|||
"browsers": [
|
||||
{
|
||||
"name": "chromium",
|
||||
"revision": "1123",
|
||||
"revision": "1125",
|
||||
"installByDefault": true,
|
||||
"browserVersion": "127.0.6533.5"
|
||||
"browserVersion": "127.0.6533.26"
|
||||
},
|
||||
{
|
||||
"name": "chromium-tip-of-tree",
|
||||
"revision": "1231",
|
||||
"revision": "1236",
|
||||
"installByDefault": false,
|
||||
"browserVersion": "128.0.6536.0"
|
||||
"browserVersion": "128.0.6561.0"
|
||||
},
|
||||
{
|
||||
"name": "firefox",
|
||||
"revision": "1454",
|
||||
"revision": "1456",
|
||||
"installByDefault": true,
|
||||
"browserVersion": "127.0"
|
||||
},
|
||||
{
|
||||
"name": "firefox-beta",
|
||||
"revision": "1453",
|
||||
"revision": "1456",
|
||||
"installByDefault": false,
|
||||
"browserVersion": "127.0b3"
|
||||
"browserVersion": "128.0b3"
|
||||
},
|
||||
{
|
||||
"name": "webkit",
|
||||
"revision": "2035",
|
||||
"revision": "2041",
|
||||
"installByDefault": true,
|
||||
"revisionOverrides": {
|
||||
"mac10.14": "1446",
|
||||
|
|
@ -46,7 +46,7 @@
|
|||
},
|
||||
{
|
||||
"name": "android",
|
||||
"revision": "1000",
|
||||
"revision": "1001",
|
||||
"installByDefault": false
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@
|
|||
"signal-exit": "3.0.7",
|
||||
"socks-proxy-agent": "6.1.1",
|
||||
"stack-utils": "2.0.5",
|
||||
"ws": "8.4.2"
|
||||
"ws": "8.17.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/debug": "^4.1.7",
|
||||
|
|
@ -399,15 +399,15 @@
|
|||
}
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "8.4.2",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.4.2.tgz",
|
||||
"integrity": "sha512-Kbk4Nxyq7/ZWqr/tarI9yIt/+iNNFOjBXEWgTb4ydaNHBNGgvf2QHbS9fdfsndfjFlFwEd4Al+mw83YkaD10ZA==",
|
||||
"version": "8.17.1",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
|
||||
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"bufferutil": "^4.0.1",
|
||||
"utf-8-validate": "^5.0.2"
|
||||
"utf-8-validate": ">=5.0.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"bufferutil": {
|
||||
|
|
@ -702,9 +702,9 @@
|
|||
}
|
||||
},
|
||||
"ws": {
|
||||
"version": "8.4.2",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.4.2.tgz",
|
||||
"integrity": "sha512-Kbk4Nxyq7/ZWqr/tarI9yIt/+iNNFOjBXEWgTb4ydaNHBNGgvf2QHbS9fdfsndfjFlFwEd4Al+mw83YkaD10ZA==",
|
||||
"version": "8.17.1",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
|
||||
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
|
||||
"requires": {}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@
|
|||
"signal-exit": "3.0.7",
|
||||
"socks-proxy-agent": "6.1.1",
|
||||
"stack-utils": "2.0.5",
|
||||
"ws": "8.4.2"
|
||||
"ws": "8.17.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/debug": "^4.1.7",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "playwright-core",
|
||||
"version": "1.45.0-next",
|
||||
"version": "1.46.0-next",
|
||||
"description": "A high-level API to automate web browsers",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
|
|||
|
|
@ -85,12 +85,6 @@ export class Browser extends ChannelOwner<channels.BrowserChannel> implements ap
|
|||
const response = forReuse ? await this._channel.newContextForReuse(contextOptions) : await this._channel.newContext(contextOptions);
|
||||
const context = BrowserContext.from(response.context);
|
||||
await this._browserType._didCreateContext(context, contextOptions, this._options, options.logger || this._logger);
|
||||
if (!forReuse && !!process.env.PW_FREEZE_TIME) {
|
||||
await this._wrapApiCall(async () => {
|
||||
await context.clock.install({ time: 0 });
|
||||
await context.clock.pauseAt(1000);
|
||||
}, true);
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -58,6 +58,8 @@ function parseTime(time: string | number | Date): { timeNumber?: number, timeStr
|
|||
return { timeNumber: time };
|
||||
if (typeof time === 'string')
|
||||
return { timeString: time };
|
||||
if (!isFinite(time.getTime()))
|
||||
throw new Error(`Invalid date: ${time}`);
|
||||
return { timeNumber: time.getTime() };
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ import type { Page } from './page';
|
|||
import { ConsoleMessage } from './consoleMessage';
|
||||
import type { Env, WaitForEventOptions, Headers, BrowserContextOptions } from './types';
|
||||
import { Waiter } from './waiter';
|
||||
import { TargetClosedError } from './errors';
|
||||
import { TargetClosedError, isTargetClosedError } from './errors';
|
||||
|
||||
type ElectronOptions = Omit<channels.ElectronLaunchOptions, 'env'|'extraHTTPHeaders'|'recordHar'|'colorScheme'|'acceptDownloads'> & {
|
||||
env?: Env,
|
||||
|
|
@ -116,7 +116,13 @@ export class ElectronApplication extends ChannelOwner<channels.ElectronApplicati
|
|||
}
|
||||
|
||||
async close() {
|
||||
await this._context.close().catch(() => {});
|
||||
try {
|
||||
await this._context.close();
|
||||
} catch (e) {
|
||||
if (isTargetClosedError(e))
|
||||
return;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
async waitForEvent(event: string, optionsOrPredicate: WaitForEventOptions = {}): Promise<any> {
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ export type FetchOptions = {
|
|||
failOnStatusCode?: boolean,
|
||||
ignoreHTTPSErrors?: boolean,
|
||||
maxRedirects?: number,
|
||||
maxRetries?: number,
|
||||
};
|
||||
|
||||
type NewContextOptions = Omit<channels.PlaywrightNewRequestOptions, 'extraHTTPHeaders' | 'storageState' | 'tracesDir'> & {
|
||||
|
|
@ -168,11 +169,11 @@ export class APIRequestContext extends ChannelOwner<channels.APIRequestContextCh
|
|||
throw new TargetClosedError(this._closeReason);
|
||||
assert(options.request || typeof options.url === 'string', 'First argument must be either URL string or Request');
|
||||
assert((options.data === undefined ? 0 : 1) + (options.form === undefined ? 0 : 1) + (options.multipart === undefined ? 0 : 1) <= 1, `Only one of 'data', 'form' or 'multipart' can be specified`);
|
||||
assert(options.maxRedirects === undefined || options.maxRedirects >= 0, `'maxRedirects' should be greater than or equal to '0'`);
|
||||
assert(options.maxRedirects === undefined || options.maxRedirects >= 0, `'maxRedirects' must be greater than or equal to '0'`);
|
||||
assert(options.maxRetries === undefined || options.maxRetries >= 0, `'maxRetries' must be greater than or equal to '0'`);
|
||||
const url = options.url !== undefined ? options.url : options.request!.url();
|
||||
const params = objectToArray(options.params);
|
||||
const method = options.method || options.request?.method();
|
||||
const maxRedirects = options.maxRedirects;
|
||||
// Cannot call allHeaders() here as the request may be paused inside route handler.
|
||||
const headersObj = options.headers || options.request?.headers() ;
|
||||
const headers = headersObj ? headersObjectToArray(headersObj) : undefined;
|
||||
|
|
@ -234,7 +235,8 @@ export class APIRequestContext extends ChannelOwner<channels.APIRequestContextCh
|
|||
timeout: options.timeout,
|
||||
failOnStatusCode: options.failOnStatusCode,
|
||||
ignoreHTTPSErrors: options.ignoreHTTPSErrors,
|
||||
maxRedirects: maxRedirects,
|
||||
maxRedirects: options.maxRedirects,
|
||||
maxRetries: options.maxRetries,
|
||||
...fixtures
|
||||
});
|
||||
return new APIResponse(this, result.response);
|
||||
|
|
|
|||
|
|
@ -89,4 +89,8 @@ export class Touchscreen implements api.Touchscreen {
|
|||
async tap(x: number, y: number) {
|
||||
await this._page._channel.touchscreenTap({ x, y });
|
||||
}
|
||||
|
||||
async touch(type: 'touchstart'|'touchmove'|'touchend'|'touchcancel', touchPoints: { x: number, y: number, id?: number }[]) {
|
||||
await this._page._channel.touchscreenTouch({ type, touchPoints });
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -336,7 +336,7 @@ export class Route extends ChannelOwner<channels.RouteChannel> implements api.Ro
|
|||
});
|
||||
}
|
||||
|
||||
async fetch(options: FallbackOverrides & { maxRedirects?: number, timeout?: number } = {}): Promise<APIResponse> {
|
||||
async fetch(options: FallbackOverrides & { maxRedirects?: number, maxRetries?: number, timeout?: number } = {}): Promise<APIResponse> {
|
||||
return await this._wrapApiCall(async () => {
|
||||
return await this._context.request._innerFetch({ request: this.request(), data: options.postData, ...options });
|
||||
});
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ export const slowMoActions = new Set([
|
|||
'Page.mouseClick',
|
||||
'Page.mouseWheel',
|
||||
'Page.touchscreenTap',
|
||||
'Page.touchscreenTouch',
|
||||
'Frame.blur',
|
||||
'Frame.check',
|
||||
'Frame.click',
|
||||
|
|
@ -89,6 +90,7 @@ export const commandsWithTracingSnapshots = new Set([
|
|||
'Page.mouseClick',
|
||||
'Page.mouseWheel',
|
||||
'Page.touchscreenTap',
|
||||
'Page.touchscreenTouch',
|
||||
'Frame.evalOnSelector',
|
||||
'Frame.evalOnSelectorAll',
|
||||
'Frame.addScriptTag',
|
||||
|
|
|
|||
|
|
@ -182,6 +182,7 @@ scheme.APIRequestContextFetchParams = tObject({
|
|||
failOnStatusCode: tOptional(tBoolean),
|
||||
ignoreHTTPSErrors: tOptional(tBoolean),
|
||||
maxRedirects: tOptional(tNumber),
|
||||
maxRetries: tOptional(tNumber),
|
||||
});
|
||||
scheme.APIRequestContextFetchResult = tObject({
|
||||
response: tType('APIResponse'),
|
||||
|
|
@ -1236,6 +1237,15 @@ scheme.PageTouchscreenTapParams = tObject({
|
|||
y: tNumber,
|
||||
});
|
||||
scheme.PageTouchscreenTapResult = tOptional(tObject({}));
|
||||
scheme.PageTouchscreenTouchParams = tObject({
|
||||
type: tEnum(['touchstart', 'touchend', 'touchmove', 'touchcancel']),
|
||||
touchPoints: tArray(tObject({
|
||||
x: tNumber,
|
||||
y: tNumber,
|
||||
id: tOptional(tNumber),
|
||||
})),
|
||||
});
|
||||
scheme.PageTouchscreenTouchResult = tOptional(tObject({}));
|
||||
scheme.PageAccessibilitySnapshotParams = tObject({
|
||||
interestingOnly: tOptional(tBoolean),
|
||||
root: tOptional(tChannel(['ElementHandle'])),
|
||||
|
|
|
|||
|
|
@ -13,3 +13,4 @@
|
|||
.externalNativeBuild
|
||||
.cxx
|
||||
local.properties
|
||||
build/
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
/build
|
||||
|
|
@ -8,6 +8,7 @@ android {
|
|||
defaultConfig {
|
||||
applicationId "com.microsoft.playwright.androiddriver"
|
||||
minSdkVersion 21
|
||||
// noinspection ExpiredTargetSdkVersion
|
||||
targetSdkVersion 29
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
|
|
@ -25,10 +26,11 @@ android {
|
|||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
namespace 'com.microsoft.playwright.androiddriver'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
|
||||
androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.microsoft.playwright.androiddriver">
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
|
|
@ -9,4 +8,4 @@
|
|||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true" />
|
||||
|
||||
</manifest>
|
||||
</manifest>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ buildscript {
|
|||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath "com.android.tools.build:gradle:4.1.0"
|
||||
classpath 'com.android.tools.build:gradle:8.5.0'
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
|
|
@ -21,4 +21,4 @@ allprojects {
|
|||
|
||||
task clean(type: Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,4 +16,7 @@ org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
|||
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
||||
android.useAndroidX=true
|
||||
# Automatically convert third-party libraries to use AndroidX
|
||||
android.enableJetifier=true
|
||||
android.enableJetifier=true
|
||||
android.defaults.buildfeatures.buildconfig=true
|
||||
android.nonTransitiveRClass=false
|
||||
android.nonFinalResIds=false
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -1,6 +1,5 @@
|
|||
#Tue Dec 08 09:38:33 PST 2020
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip
|
||||
|
|
|
|||
|
|
@ -1,78 +1,129 @@
|
|||
#!/usr/bin/env sh
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
#
|
||||
# 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
|
||||
#
|
||||
# https://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.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||
# noncompliant, but you have some other compliant shell such as ksh or
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
# * functions;
|
||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||
# * compound commands having a testable exit status, especially «case»;
|
||||
# * various built-in commands including «command», «set», and «ulimit».
|
||||
#
|
||||
# Important for patching:
|
||||
#
|
||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||
#
|
||||
# The "traditional" practice of packing multiple parameters into a
|
||||
# space-separated string is a well documented source of bugs and security
|
||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||
# options in "$@", and eventually passing that to Java.
|
||||
#
|
||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||
# see the in-line comments for details.
|
||||
#
|
||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
APP_BASE_NAME=${0##*/}
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
MAX_FD=maximum
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
} >&2
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
} >&2
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
JAVACMD=$JAVA_HOME/bin/java
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
|
@ -81,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the
|
|||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
JAVACMD=java
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
|
|
@ -89,84 +140,101 @@ location of your Java installation."
|
|||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=$((i+1))
|
||||
done
|
||||
case $i in
|
||||
(0) set -- ;;
|
||||
(1) set -- "$args0" ;;
|
||||
(2) set -- "$args0" "$args1" ;;
|
||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=$(save "$@")
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --module-path (only if needed)
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
||||
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
||||
cd "$(dirname "$0")"
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
for arg do
|
||||
if
|
||||
case $arg in #(
|
||||
-*) false ;; # don't mess with options #(
|
||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||
[ -e "$t" ] ;; #(
|
||||
*) false ;;
|
||||
esac
|
||||
then
|
||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||
fi
|
||||
# Roll the args list around exactly as many times as the number of
|
||||
# args, so each arg winds up back in the position where it started, but
|
||||
# possibly modified.
|
||||
#
|
||||
# NB: a `for` loop captures its iteration list before it begins, so
|
||||
# changing the positional parameters here affects neither the number of
|
||||
# iterations, nor the values presented in `arg`.
|
||||
shift # remove old arg
|
||||
set -- "$@" "$arg" # push replacement arg
|
||||
done
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command;
|
||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||
# shell script including quotes and variable substitutions, so put them in
|
||||
# double quotes to make sure that they get re-expanded; and
|
||||
# * put everything else in single quotes, so that it's not re-expanded.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
if ! command -v xargs >/dev/null 2>&1
|
||||
then
|
||||
die "xargs is not available"
|
||||
fi
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
#
|
||||
# In Bash we could simply go:
|
||||
#
|
||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||
# set -- "${ARGS[@]}" "$@"
|
||||
#
|
||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||
# character that might be a shell metacharacter, then use eval to reverse
|
||||
# that process (while maintaining the separation between arguments), and wrap
|
||||
# the whole thing up as a single "set" statement.
|
||||
#
|
||||
# This will of course break if any of these variables contains a newline or
|
||||
# an unmatched quote.
|
||||
#
|
||||
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,20 @@
|
|||
@if "%DEBUG%" == "" @echo off
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
|
|
@ -9,19 +25,22 @@
|
|||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS=
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
|
@ -35,7 +54,7 @@ goto fail
|
|||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
|
|
@ -45,38 +64,26 @@ echo location of your Java installation.
|
|||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windows variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import type { Download } from './download';
|
|||
import type * as frames from './frames';
|
||||
import { helper } from './helper';
|
||||
import * as network from './network';
|
||||
import { InitScript } from './page';
|
||||
import type { PageDelegate } from './page';
|
||||
import { Page, PageBinding } from './page';
|
||||
import type { Progress, ProgressController } from './progress';
|
||||
|
|
@ -84,7 +85,7 @@ export abstract class BrowserContext extends SdkObject {
|
|||
private _customCloseHandler?: () => Promise<any>;
|
||||
readonly _tempDirs: string[] = [];
|
||||
private _settingStorageState = false;
|
||||
readonly initScripts: string[] = [];
|
||||
readonly initScripts: InitScript[] = [];
|
||||
private _routesInFlight = new Set<network.Route>();
|
||||
private _debugger!: Debugger;
|
||||
_closeReason: string | undefined;
|
||||
|
|
@ -216,6 +217,7 @@ export abstract class BrowserContext extends SdkObject {
|
|||
await this._resetStorage();
|
||||
await this._removeExposedBindings();
|
||||
await this._removeInitScripts();
|
||||
this.clock.markAsUninstalled();
|
||||
// TODO: following can be optimized to not perform noops.
|
||||
if (this._options.permissions)
|
||||
await this.grantPermissions(this._options.permissions);
|
||||
|
|
@ -265,7 +267,7 @@ export abstract class BrowserContext extends SdkObject {
|
|||
protected abstract doGrantPermissions(origin: string, permissions: string[]): Promise<void>;
|
||||
protected abstract doClearPermissions(): Promise<void>;
|
||||
protected abstract doSetHTTPCredentials(httpCredentials?: types.Credentials): Promise<void>;
|
||||
protected abstract doAddInitScript(expression: string): Promise<void>;
|
||||
protected abstract doAddInitScript(initScript: InitScript): Promise<void>;
|
||||
protected abstract doRemoveInitScripts(): Promise<void>;
|
||||
protected abstract doExposeBinding(binding: PageBinding): Promise<void>;
|
||||
protected abstract doRemoveExposedBindings(): Promise<void>;
|
||||
|
|
@ -402,9 +404,10 @@ export abstract class BrowserContext extends SdkObject {
|
|||
this._options.httpCredentials = { username, password: password || '' };
|
||||
}
|
||||
|
||||
async addInitScript(script: string) {
|
||||
this.initScripts.push(script);
|
||||
await this.doAddInitScript(script);
|
||||
async addInitScript(source: string) {
|
||||
const initScript = new InitScript(source);
|
||||
this.initScripts.push(initScript);
|
||||
await this.doAddInitScript(initScript);
|
||||
}
|
||||
|
||||
async _removeInitScripts(): Promise<void> {
|
||||
|
|
@ -653,8 +656,13 @@ export function validateBrowserContextOptions(options: channels.BrowserNewContex
|
|||
throw new Error(`"deviceScaleFactor" option is not supported with null "viewport"`);
|
||||
if (options.noDefaultViewport && !!options.isMobile)
|
||||
throw new Error(`"isMobile" option is not supported with null "viewport"`);
|
||||
if (options.acceptDownloads === undefined)
|
||||
if (options.acceptDownloads === undefined && browserOptions.name !== 'electron')
|
||||
options.acceptDownloads = 'accept';
|
||||
// Electron requires explicit acceptDownloads: true since we wait for
|
||||
// https://github.com/electron/electron/pull/41718 to be widely shipped.
|
||||
// In 6-12 months, we can remove this check.
|
||||
else if (options.acceptDownloads === undefined && browserOptions.name === 'electron')
|
||||
options.acceptDownloads = 'internal-browser-default';
|
||||
if (!options.viewport && !options.noDefaultViewport)
|
||||
options.viewport = { width: 1280, height: 720 };
|
||||
if (options.recordVideo) {
|
||||
|
|
|
|||
|
|
@ -319,7 +319,7 @@ export class Chromium extends BrowserType {
|
|||
if (process.env.PLAYWRIGHT_CHROMIUM_USE_HEADLESS_NEW)
|
||||
chromeArguments.push('--headless=new');
|
||||
else
|
||||
chromeArguments.push('--headless');
|
||||
chromeArguments.push('--headless=old');
|
||||
|
||||
chromeArguments.push(
|
||||
'--hide-scrollbars',
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import { Browser } from '../browser';
|
|||
import { assertBrowserContextIsNotOwned, BrowserContext, verifyGeolocation } from '../browserContext';
|
||||
import { assert, createGuid } from '../../utils';
|
||||
import * as network from '../network';
|
||||
import type { PageBinding, PageDelegate, Worker } from '../page';
|
||||
import type { InitScript, PageBinding, PageDelegate, Worker } from '../page';
|
||||
import { Page } from '../page';
|
||||
import { Frame } from '../frames';
|
||||
import type { Dialog } from '../dialog';
|
||||
|
|
@ -348,7 +348,7 @@ export class CRBrowserContext extends BrowserContext {
|
|||
override async _initialize() {
|
||||
assert(!Array.from(this._browser._crPages.values()).some(page => page._browserContext === this));
|
||||
const promises: Promise<any>[] = [super._initialize()];
|
||||
if (this._browser.options.name !== 'electron' && this._browser.options.name !== 'clank' && this._options.acceptDownloads !== 'internal-browser-default') {
|
||||
if (this._browser.options.name !== 'clank' && this._options.acceptDownloads !== 'internal-browser-default') {
|
||||
promises.push(this._browser._session.send('Browser.setDownloadBehavior', {
|
||||
behavior: this._options.acceptDownloads === 'accept' ? 'allowAndName' : 'deny',
|
||||
browserContextId: this._browserContextId,
|
||||
|
|
@ -486,9 +486,9 @@ export class CRBrowserContext extends BrowserContext {
|
|||
await (sw as CRServiceWorker).updateHttpCredentials();
|
||||
}
|
||||
|
||||
async doAddInitScript(source: string) {
|
||||
async doAddInitScript(initScript: InitScript) {
|
||||
for (const page of this.pages())
|
||||
await (page._delegate as CRPage).addInitScript(source);
|
||||
await (page._delegate as CRPage).addInitScript(initScript);
|
||||
}
|
||||
|
||||
async doRemoveInitScripts() {
|
||||
|
|
|
|||
|
|
@ -179,4 +179,19 @@ export class RawTouchscreenImpl implements input.RawTouchscreen {
|
|||
}),
|
||||
]);
|
||||
}
|
||||
async touch(eventType: 'touchstart'|'touchmove'|'touchend'|'touchcancel', touchPoints: { x: number, y: number, id?: number }[], modifiers: Set<types.KeyboardModifier>) {
|
||||
let type: 'touchStart' | 'touchMove' | 'touchEnd' | 'touchCancel';
|
||||
switch (eventType) {
|
||||
case 'touchstart': type = 'touchStart'; break;
|
||||
case 'touchmove': type = 'touchMove'; break;
|
||||
case 'touchend': type = 'touchEnd'; break;
|
||||
case 'touchcancel': type = 'touchCancel'; break;
|
||||
default: throw new Error('Invalid eventType: ' + eventType);
|
||||
}
|
||||
await this._client.send('Input.dispatchTouchEvent', {
|
||||
type,
|
||||
touchPoints,
|
||||
modifiers: toModifiersMask(modifiers)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -332,12 +332,13 @@ export class CRNetworkManager {
|
|||
}
|
||||
|
||||
let route = null;
|
||||
let headersOverride: types.HeadersArray | undefined;
|
||||
if (requestPausedEvent) {
|
||||
// We do not support intercepting redirects.
|
||||
if (redirectedFrom || (!this._userRequestInterceptionEnabled && this._protocolRequestInterceptionEnabled)) {
|
||||
// Chromium does not preserve header overrides between redirects, so we have to do it ourselves.
|
||||
const headers = redirectedFrom?._originalRequestRoute?._alreadyContinuedParams?.headers;
|
||||
requestPausedSessionInfo!.session._sendMayFail('Fetch.continueRequest', { requestId: requestPausedEvent.requestId, headers });
|
||||
headersOverride = redirectedFrom?._originalRequestRoute?._alreadyContinuedParams?.headers;
|
||||
requestPausedSessionInfo!.session._sendMayFail('Fetch.continueRequest', { requestId: requestPausedEvent.requestId, headers: headersOverride });
|
||||
} else {
|
||||
route = new RouteImpl(requestPausedSessionInfo!.session, requestPausedEvent.requestId);
|
||||
}
|
||||
|
|
@ -353,15 +354,16 @@ export class CRNetworkManager {
|
|||
route,
|
||||
requestWillBeSentEvent,
|
||||
requestPausedEvent,
|
||||
redirectedFrom
|
||||
redirectedFrom,
|
||||
headersOverride: headersOverride || null,
|
||||
});
|
||||
this._requestIdToRequest.set(requestWillBeSentEvent.requestId, request);
|
||||
|
||||
if (requestPausedEvent) {
|
||||
// We will not receive extra info when intercepting the request.
|
||||
if (route) {
|
||||
// We may not receive extra info when intercepting the request.
|
||||
// Use the headers from the Fetch.requestPausedPayload and release the allHeaders()
|
||||
// right away, so that client can call it from the route handler.
|
||||
request.request.setRawRequestHeaders(headersObjectToArray(requestPausedEvent.request.headers, '\n'));
|
||||
request.request.setRawRequestHeaders(headersObjectToArray(requestPausedEvent!.request.headers, '\n'));
|
||||
}
|
||||
(this._page?._frameManager || this._serviceWorker)!.requestStarted(request.request, route || undefined);
|
||||
}
|
||||
|
|
@ -568,8 +570,9 @@ class InterceptableRequest {
|
|||
requestWillBeSentEvent: Protocol.Network.requestWillBeSentPayload;
|
||||
requestPausedEvent: Protocol.Fetch.requestPausedPayload | undefined;
|
||||
redirectedFrom: InterceptableRequest | null;
|
||||
headersOverride: types.HeadersArray | null;
|
||||
}) {
|
||||
const { session, context, frame, documentId, route, requestWillBeSentEvent, requestPausedEvent, redirectedFrom, serviceWorker } = options;
|
||||
const { session, context, frame, documentId, route, requestWillBeSentEvent, requestPausedEvent, redirectedFrom, serviceWorker, headersOverride } = options;
|
||||
this.session = session;
|
||||
this._timestamp = requestWillBeSentEvent.timestamp;
|
||||
this._wallTime = requestWillBeSentEvent.wallTime;
|
||||
|
|
@ -591,7 +594,7 @@ class InterceptableRequest {
|
|||
if (entries && entries.length)
|
||||
postDataBuffer = Buffer.concat(entries.map(entry => Buffer.from(entry.bytes!, 'base64')));
|
||||
|
||||
this.request = new network.Request(context, frame, serviceWorker, redirectedFrom?.request || null, documentId, url, type, method, postDataBuffer, headersObjectToArray(headers));
|
||||
this.request = new network.Request(context, frame, serviceWorker, redirectedFrom?.request || null, documentId, url, type, method, postDataBuffer, headersOverride || headersObjectToArray(headers));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ import * as dom from '../dom';
|
|||
import * as frames from '../frames';
|
||||
import { helper } from '../helper';
|
||||
import * as network from '../network';
|
||||
import type { PageBinding, PageDelegate } from '../page';
|
||||
import type { InitScript, PageBinding, PageDelegate } from '../page';
|
||||
import { Page, Worker } from '../page';
|
||||
import type { Progress } from '../progress';
|
||||
import type * as types from '../types';
|
||||
|
|
@ -256,8 +256,8 @@ export class CRPage implements PageDelegate {
|
|||
return this._go(+1);
|
||||
}
|
||||
|
||||
async addInitScript(source: string, world: types.World = 'main'): Promise<void> {
|
||||
await this._forAllFrameSessions(frame => frame._evaluateOnNewDocument(source, world));
|
||||
async addInitScript(initScript: InitScript, world: types.World = 'main'): Promise<void> {
|
||||
await this._forAllFrameSessions(frame => frame._evaluateOnNewDocument(initScript, world));
|
||||
}
|
||||
|
||||
async removeInitScripts() {
|
||||
|
|
@ -511,6 +511,20 @@ class FrameSession {
|
|||
this._addRendererListeners();
|
||||
}
|
||||
|
||||
const localFrames = this._isMainFrame() ? this._page.frames() : [this._page._frameManager.frame(this._targetId)!];
|
||||
for (const frame of localFrames) {
|
||||
// Note: frames might be removed before we send these.
|
||||
this._client._sendMayFail('Page.createIsolatedWorld', {
|
||||
frameId: frame._id,
|
||||
grantUniveralAccess: true,
|
||||
worldName: UTILITY_WORLD_NAME,
|
||||
});
|
||||
for (const binding of this._crPage._browserContext._pageBindings.values())
|
||||
frame.evaluateExpression(binding.source).catch(e => {});
|
||||
for (const initScript of this._crPage._browserContext.initScripts)
|
||||
frame.evaluateExpression(initScript.source).catch(e => {});
|
||||
}
|
||||
|
||||
const isInitialEmptyPage = this._isMainFrame() && this._page.mainFrame().url() === ':';
|
||||
if (isInitialEmptyPage) {
|
||||
// Ignore lifecycle events, worlds and bindings for the initial empty page. It is never the final page
|
||||
|
|
@ -520,20 +534,6 @@ class FrameSession {
|
|||
this._eventListeners.push(eventsHelper.addEventListener(this._client, 'Page.lifecycleEvent', event => this._onLifecycleEvent(event)));
|
||||
});
|
||||
} else {
|
||||
const localFrames = this._isMainFrame() ? this._page.frames() : [this._page._frameManager.frame(this._targetId)!];
|
||||
for (const frame of localFrames) {
|
||||
// Note: frames might be removed before we send these.
|
||||
this._client._sendMayFail('Page.createIsolatedWorld', {
|
||||
frameId: frame._id,
|
||||
grantUniveralAccess: true,
|
||||
worldName: UTILITY_WORLD_NAME,
|
||||
});
|
||||
for (const binding of this._crPage._browserContext._pageBindings.values())
|
||||
frame.evaluateExpression(binding.source).catch(e => {});
|
||||
for (const source of this._crPage._browserContext.initScripts)
|
||||
frame.evaluateExpression(source).catch(e => {});
|
||||
}
|
||||
|
||||
this._firstNonInitialNavigationCommittedFulfill();
|
||||
this._eventListeners.push(eventsHelper.addEventListener(this._client, 'Page.lifecycleEvent', event => this._onLifecycleEvent(event)));
|
||||
}
|
||||
|
|
@ -575,10 +575,10 @@ class FrameSession {
|
|||
promises.push(this._updateFileChooserInterception(true));
|
||||
for (const binding of this._crPage._page.allBindings())
|
||||
promises.push(this._initBinding(binding));
|
||||
for (const source of this._crPage._browserContext.initScripts)
|
||||
promises.push(this._evaluateOnNewDocument(source, 'main'));
|
||||
for (const source of this._crPage._page.initScripts)
|
||||
promises.push(this._evaluateOnNewDocument(source, 'main'));
|
||||
for (const initScript of this._crPage._browserContext.initScripts)
|
||||
promises.push(this._evaluateOnNewDocument(initScript, 'main'));
|
||||
for (const initScript of this._crPage._page.initScripts)
|
||||
promises.push(this._evaluateOnNewDocument(initScript, 'main'));
|
||||
if (screencastOptions)
|
||||
promises.push(this._startVideoRecording(screencastOptions));
|
||||
}
|
||||
|
|
@ -1099,9 +1099,9 @@ class FrameSession {
|
|||
await this._client.send('Page.setInterceptFileChooserDialog', { enabled }).catch(() => {}); // target can be closed.
|
||||
}
|
||||
|
||||
async _evaluateOnNewDocument(source: string, world: types.World): Promise<void> {
|
||||
async _evaluateOnNewDocument(initScript: InitScript, world: types.World): Promise<void> {
|
||||
const worldName = world === 'utility' ? UTILITY_WORLD_NAME : undefined;
|
||||
const { identifier } = await this._client.send('Page.addScriptToEvaluateOnNewDocument', { source, worldName });
|
||||
const { identifier } = await this._client.send('Page.addScriptToEvaluateOnNewDocument', { source: initScript.source, worldName });
|
||||
this._evaluateOnNewDocumentIdentifiers.push(identifier);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ export function getExceptionMessage(exceptionDetails: Protocol.Runtime.Exception
|
|||
}
|
||||
|
||||
export async function releaseObject(client: CRSession, objectId: string) {
|
||||
await client.send('Runtime.releaseObject', { objectId }).catch(error => {});
|
||||
await client.send('Runtime.releaseObject', { objectId }).catch(error => { });
|
||||
}
|
||||
|
||||
export async function saveProtocolStream(client: CRSession, handle: string, path: string) {
|
||||
|
|
@ -91,7 +91,8 @@ export function exceptionToError(exceptionDetails: Protocol.Runtime.ExceptionDet
|
|||
|
||||
const err = new Error(message);
|
||||
err.stack = stack;
|
||||
err.name = name;
|
||||
const nameOverride = exceptionDetails.exception?.preview?.properties.find(o => o.name === 'name');
|
||||
err.name = nameOverride ? nameOverride.value ?? 'Error' : name;
|
||||
return err;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -26,6 +26,10 @@ export class Clock {
|
|||
this._browserContext = browserContext;
|
||||
}
|
||||
|
||||
markAsUninstalled() {
|
||||
this._scriptInstalled = false;
|
||||
}
|
||||
|
||||
async fastForward(ticks: number | string) {
|
||||
await this._installIfNeeded();
|
||||
const ticksMillis = parseTicks(ticks);
|
||||
|
|
@ -144,5 +148,8 @@ function parseTime(epoch: string | number | undefined): number {
|
|||
return 0;
|
||||
if (typeof epoch === 'number')
|
||||
return epoch;
|
||||
return new Date(epoch).getTime();
|
||||
const parsed = new Date(epoch);
|
||||
if (!isFinite(parsed.getTime()))
|
||||
throw new Error(`Invalid date: ${epoch}`);
|
||||
return parsed.getTime();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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/127.0.6533.5 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.26 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/127.0.6533.5 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.26 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/127.0.6533.5 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.26 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/127.0.6533.5 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.26 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/127.0.6533.5 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.26 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/127.0.6533.5 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.26 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/127.0.6533.5 Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.26 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/127.0.6533.5 Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.26 Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 1138,
|
||||
"height": 712
|
||||
|
|
@ -978,7 +978,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/127.0.6533.5 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/127.0.6533.26 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 384,
|
||||
"height": 640
|
||||
|
|
@ -989,7 +989,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/127.0.6533.5 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/127.0.6533.26 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 640,
|
||||
"height": 384
|
||||
|
|
@ -1000,7 +1000,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/127.0.6533.5 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/127.0.6533.26 Mobile Safari/537.36 Edge/14.14263",
|
||||
"viewport": {
|
||||
"width": 640,
|
||||
"height": 360
|
||||
|
|
@ -1011,7 +1011,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/127.0.6533.5 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/127.0.6533.26 Mobile Safari/537.36 Edge/14.14263",
|
||||
"viewport": {
|
||||
"width": 360,
|
||||
"height": 640
|
||||
|
|
@ -1022,7 +1022,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/127.0.6533.5 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/127.0.6533.26 Mobile Safari/537.36 Edge/14.14263",
|
||||
"viewport": {
|
||||
"width": 360,
|
||||
"height": 640
|
||||
|
|
@ -1033,7 +1033,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/127.0.6533.5 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/127.0.6533.26 Mobile Safari/537.36 Edge/14.14263",
|
||||
"viewport": {
|
||||
"width": 640,
|
||||
"height": 360
|
||||
|
|
@ -1044,7 +1044,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/127.0.6533.5 Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.26 Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 800,
|
||||
"height": 1280
|
||||
|
|
@ -1055,7 +1055,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/127.0.6533.5 Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.26 Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 1280,
|
||||
"height": 800
|
||||
|
|
@ -1066,7 +1066,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/127.0.6533.5 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.26 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 384,
|
||||
"height": 640
|
||||
|
|
@ -1077,7 +1077,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/127.0.6533.5 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.26 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 640,
|
||||
"height": 384
|
||||
|
|
@ -1088,7 +1088,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Nexus 5": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.5 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.26 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 360,
|
||||
"height": 640
|
||||
|
|
@ -1099,7 +1099,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/127.0.6533.5 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.26 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 640,
|
||||
"height": 360
|
||||
|
|
@ -1110,7 +1110,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/127.0.6533.5 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/127.0.6533.26 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 412,
|
||||
"height": 732
|
||||
|
|
@ -1121,7 +1121,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/127.0.6533.5 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/127.0.6533.26 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 732,
|
||||
"height": 412
|
||||
|
|
@ -1132,7 +1132,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/127.0.6533.5 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.26 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 412,
|
||||
"height": 732
|
||||
|
|
@ -1143,7 +1143,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/127.0.6533.5 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.26 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 732,
|
||||
"height": 412
|
||||
|
|
@ -1154,7 +1154,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/127.0.6533.5 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/127.0.6533.26 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 412,
|
||||
"height": 732
|
||||
|
|
@ -1165,7 +1165,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/127.0.6533.5 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/127.0.6533.26 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 732,
|
||||
"height": 412
|
||||
|
|
@ -1176,7 +1176,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/127.0.6533.5 Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.26 Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 600,
|
||||
"height": 960
|
||||
|
|
@ -1187,7 +1187,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/127.0.6533.5 Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.26 Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 960,
|
||||
"height": 600
|
||||
|
|
@ -1242,7 +1242,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/127.0.6533.5 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/127.0.6533.26 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 411,
|
||||
"height": 731
|
||||
|
|
@ -1253,7 +1253,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/127.0.6533.5 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/127.0.6533.26 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 731,
|
||||
"height": 411
|
||||
|
|
@ -1264,7 +1264,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/127.0.6533.5 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/127.0.6533.26 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 411,
|
||||
"height": 823
|
||||
|
|
@ -1275,7 +1275,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/127.0.6533.5 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/127.0.6533.26 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 823,
|
||||
"height": 411
|
||||
|
|
@ -1286,7 +1286,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/127.0.6533.5 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/127.0.6533.26 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 393,
|
||||
"height": 786
|
||||
|
|
@ -1297,7 +1297,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/127.0.6533.5 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/127.0.6533.26 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 786,
|
||||
"height": 393
|
||||
|
|
@ -1308,7 +1308,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Pixel 4": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.5 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.26 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 353,
|
||||
"height": 745
|
||||
|
|
@ -1319,7 +1319,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Pixel 4 landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.5 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.26 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 745,
|
||||
"height": 353
|
||||
|
|
@ -1330,7 +1330,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Pixel 4a (5G)": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.5 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.26 Mobile Safari/537.36",
|
||||
"screen": {
|
||||
"width": 412,
|
||||
"height": 892
|
||||
|
|
@ -1345,7 +1345,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Pixel 4a (5G) landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.5 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.26 Mobile Safari/537.36",
|
||||
"screen": {
|
||||
"height": 892,
|
||||
"width": 412
|
||||
|
|
@ -1360,7 +1360,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Pixel 5": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.5 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.26 Mobile Safari/537.36",
|
||||
"screen": {
|
||||
"width": 393,
|
||||
"height": 851
|
||||
|
|
@ -1375,7 +1375,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Pixel 5 landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.5 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.26 Mobile Safari/537.36",
|
||||
"screen": {
|
||||
"width": 851,
|
||||
"height": 393
|
||||
|
|
@ -1390,7 +1390,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Pixel 7": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.5 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.26 Mobile Safari/537.36",
|
||||
"screen": {
|
||||
"width": 412,
|
||||
"height": 915
|
||||
|
|
@ -1405,7 +1405,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Pixel 7 landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.5 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.26 Mobile Safari/537.36",
|
||||
"screen": {
|
||||
"width": 915,
|
||||
"height": 412
|
||||
|
|
@ -1420,7 +1420,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Moto G4": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.5 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.26 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 360,
|
||||
"height": 640
|
||||
|
|
@ -1431,7 +1431,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Moto G4 landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.5 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.26 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 640,
|
||||
"height": 360
|
||||
|
|
@ -1442,7 +1442,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Desktop Chrome HiDPI": {
|
||||
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.5 Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.26 Safari/537.36",
|
||||
"screen": {
|
||||
"width": 1792,
|
||||
"height": 1120
|
||||
|
|
@ -1457,7 +1457,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Desktop Edge HiDPI": {
|
||||
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.5 Safari/537.36 Edg/127.0.6533.5",
|
||||
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.26 Safari/537.36 Edg/127.0.6533.26",
|
||||
"screen": {
|
||||
"width": 1792,
|
||||
"height": 1120
|
||||
|
|
@ -1502,7 +1502,7 @@
|
|||
"defaultBrowserType": "webkit"
|
||||
},
|
||||
"Desktop Chrome": {
|
||||
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.5 Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.26 Safari/537.36",
|
||||
"screen": {
|
||||
"width": 1920,
|
||||
"height": 1080
|
||||
|
|
@ -1517,7 +1517,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Desktop Edge": {
|
||||
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.5 Safari/537.36 Edg/127.0.6533.5",
|
||||
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.26 Safari/537.36 Edg/127.0.6533.26",
|
||||
"screen": {
|
||||
"width": 1920,
|
||||
"height": 1080
|
||||
|
|
|
|||
|
|
@ -265,6 +265,10 @@ export class PageDispatcher extends Dispatcher<Page, channels.PageChannel, Brows
|
|||
await this._page.touchscreen.tap(params.x, params.y, metadata);
|
||||
}
|
||||
|
||||
async touchscreenTouch(params: channels.PageTouchscreenTouchParams, metadata: CallMetadata) {
|
||||
await this._page.touchscreen.touch(params.type, params.touchPoints, metadata);
|
||||
}
|
||||
|
||||
async accessibilitySnapshot(params: channels.PageAccessibilitySnapshotParams, metadata: CallMetadata): Promise<channels.PageAccessibilitySnapshotResult> {
|
||||
const rootAXNode = await this._page.accessibility.snapshot({
|
||||
interestingOnly: params.interestingOnly,
|
||||
|
|
|
|||
|
|
@ -201,7 +201,7 @@ export abstract class APIRequestContext extends SdkObject {
|
|||
setHeader(headers, 'content-length', String(postData.byteLength));
|
||||
const controller = new ProgressController(metadata, this);
|
||||
const fetchResponse = await controller.run(progress => {
|
||||
return this._sendRequest(progress, requestUrl, options, postData);
|
||||
return this._sendRequestWithRetries(progress, requestUrl, options, postData, params.maxRetries);
|
||||
});
|
||||
const fetchUid = this._storeResponseBody(fetchResponse.body);
|
||||
this.fetchLog.set(fetchUid, controller.metadata.log);
|
||||
|
|
@ -247,6 +247,28 @@ export abstract class APIRequestContext extends SdkObject {
|
|||
}
|
||||
}
|
||||
|
||||
private async _sendRequestWithRetries(progress: Progress, url: URL, options: SendRequestOptions, postData?: Buffer, maxRetries?: number): Promise<Omit<channels.APIResponse, 'fetchUid'> & { body: Buffer }>{
|
||||
maxRetries ??= 0;
|
||||
let backoff = 250;
|
||||
for (let i = 0; i <= maxRetries; i++) {
|
||||
try {
|
||||
return await this._sendRequest(progress, url, options, postData);
|
||||
} catch (e) {
|
||||
if (maxRetries === 0)
|
||||
throw e;
|
||||
if (i === maxRetries || (options.deadline && monotonicTime() + backoff > options.deadline))
|
||||
throw new Error(`Failed after ${i + 1} attempt(s): ${e}`);
|
||||
// Retry on connection reset only.
|
||||
if (e.code !== 'ECONNRESET')
|
||||
throw e;
|
||||
progress.log(` Received ECONNRESET, will retry after ${backoff}ms.`);
|
||||
await new Promise(f => setTimeout(f, backoff));
|
||||
backoff *= 2;
|
||||
}
|
||||
}
|
||||
throw new Error('Unreachable');
|
||||
}
|
||||
|
||||
private async _sendRequest(progress: Progress, url: URL, options: SendRequestOptions, postData?: Buffer): Promise<Omit<channels.APIResponse, 'fetchUid'> & { body: Buffer }>{
|
||||
await this._updateRequestCookieHeader(url, options.headers);
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import type { BrowserOptions } from '../browser';
|
|||
import { Browser } from '../browser';
|
||||
import { assertBrowserContextIsNotOwned, BrowserContext, verifyGeolocation } from '../browserContext';
|
||||
import * as network from '../network';
|
||||
import type { Page, PageBinding, PageDelegate } from '../page';
|
||||
import type { InitScript, Page, PageBinding, PageDelegate } from '../page';
|
||||
import type { ConnectionTransport } from '../transport';
|
||||
import type * as types from '../types';
|
||||
import type * as channels from '@protocol/channels';
|
||||
|
|
@ -352,8 +352,8 @@ export class FFBrowserContext extends BrowserContext {
|
|||
await this._browser.session.send('Browser.setHTTPCredentials', { browserContextId: this._browserContextId, credentials });
|
||||
}
|
||||
|
||||
async doAddInitScript(source: string) {
|
||||
await this._browser.session.send('Browser.setInitScripts', { browserContextId: this._browserContextId, scripts: this.initScripts.map(script => ({ script })) });
|
||||
async doAddInitScript(initScript: InitScript) {
|
||||
await this._browser.session.send('Browser.setInitScripts', { browserContextId: this._browserContextId, scripts: this.initScripts.map(script => ({ script: script.source })) });
|
||||
}
|
||||
|
||||
async doRemoveInitScripts() {
|
||||
|
|
|
|||
|
|
@ -166,4 +166,8 @@ export class RawTouchscreenImpl implements input.RawTouchscreen {
|
|||
modifiers: toModifiersMask(modifiers),
|
||||
});
|
||||
}
|
||||
|
||||
async touch(eventType: 'touchstart'|'touchmove'|'touchend'|'touchcancel', touchPoints: { x: number, y: number, id?: number }[], modifiers: Set<types.KeyboardModifier>) {
|
||||
throw new Error('Not implemented yet.');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import type * as frames from '../frames';
|
|||
import type { RegisteredListener } from '../../utils/eventsHelper';
|
||||
import { eventsHelper } from '../../utils/eventsHelper';
|
||||
import type { PageBinding, PageDelegate } from '../page';
|
||||
import { InitScript } from '../page';
|
||||
import { Page, Worker } from '../page';
|
||||
import type * as types from '../types';
|
||||
import { getAccessibilityTree } from './ffAccessibility';
|
||||
|
|
@ -56,7 +57,7 @@ export class FFPage implements PageDelegate {
|
|||
private _eventListeners: RegisteredListener[];
|
||||
private _workers = new Map<string, { frameId: string, session: FFSession }>();
|
||||
private _screencastId: string | undefined;
|
||||
private _initScripts: { script: string, worldName?: string }[] = [];
|
||||
private _initScripts: { initScript: InitScript, worldName?: string }[] = [];
|
||||
|
||||
constructor(session: FFSession, browserContext: FFBrowserContext, opener: FFPage | null) {
|
||||
this._session = session;
|
||||
|
|
@ -113,7 +114,7 @@ export class FFPage implements PageDelegate {
|
|||
});
|
||||
// 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('', UTILITY_WORLD_NAME).catch(e => this._markAsError(e));
|
||||
this.addInitScript(new InitScript(''), UTILITY_WORLD_NAME).catch(e => this._markAsError(e));
|
||||
}
|
||||
|
||||
potentiallyUninitializedPage(): Page {
|
||||
|
|
@ -406,9 +407,9 @@ export class FFPage implements PageDelegate {
|
|||
return success;
|
||||
}
|
||||
|
||||
async addInitScript(script: string, worldName?: string): Promise<void> {
|
||||
this._initScripts.push({ script, worldName });
|
||||
await this._session.send('Page.setInitScripts', { scripts: this._initScripts });
|
||||
async addInitScript(initScript: InitScript, worldName?: string): Promise<void> {
|
||||
this._initScripts.push({ initScript, worldName });
|
||||
await this._session.send('Page.setInitScripts', { scripts: this._initScripts.map(s => ({ script: s.initScript.source, worldName: s.worldName })) });
|
||||
}
|
||||
|
||||
async removeInitScripts() {
|
||||
|
|
|
|||
|
|
@ -434,12 +434,6 @@ export class HarTracer {
|
|||
const pageEntry = this._createPageEntryIfNeeded(page);
|
||||
const request = response.request();
|
||||
|
||||
// Prefer "response received" time over "request sent" time
|
||||
// for the purpose of matching requests that were used in a particular snapshot.
|
||||
// Note that both snapshot time and request time are taken here in the Node process.
|
||||
if (this._options.includeTraceInfo)
|
||||
harEntry._monotonicTime = monotonicTime();
|
||||
|
||||
harEntry.response = {
|
||||
status: response.status(),
|
||||
statusText: response.statusText(),
|
||||
|
|
|
|||
|
|
@ -705,11 +705,12 @@ export function install(globalObject: WindowOrWorkerGlobalScope, config: Install
|
|||
}
|
||||
|
||||
export function inject(globalObject: WindowOrWorkerGlobalScope) {
|
||||
const builtin = platformOriginals(globalObject).bound;
|
||||
const { clock: controller } = install(globalObject);
|
||||
controller.resume();
|
||||
return {
|
||||
controller,
|
||||
builtin: platformOriginals(globalObject).bound,
|
||||
builtin,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ import { getChecked, getAriaDisabled, getAriaRole, getElementAccessibleName, get
|
|||
import { kLayoutSelectorNames, type LayoutSelectorName, layoutSelectorScore } from './layoutSelectorUtils';
|
||||
import { asLocator } from '../../utils/isomorphic/locatorGenerators';
|
||||
import type { Language } from '../../utils/isomorphic/locatorGenerators';
|
||||
import { normalizeWhiteSpace, trimStringWithEllipsis } from '../../utils/isomorphic/stringUtils';
|
||||
import { cacheNormalizedWhitespaces, normalizeWhiteSpace, trimStringWithEllipsis } from '../../utils/isomorphic/stringUtils';
|
||||
|
||||
export type FrameExpectParams = Omit<channels.FrameExpectParams, 'expectedValue'> & { expectedValue?: any };
|
||||
|
||||
|
|
@ -66,7 +66,7 @@ export class InjectedScript {
|
|||
// eslint-disable-next-line no-restricted-globals
|
||||
readonly window: Window & typeof globalThis;
|
||||
readonly document: Document;
|
||||
readonly utils = { isInsideScope, elementText, asLocator, normalizeWhiteSpace };
|
||||
readonly utils = { isInsideScope, elementText, asLocator, normalizeWhiteSpace, cacheNormalizedWhitespaces };
|
||||
|
||||
// eslint-disable-next-line no-restricted-globals
|
||||
constructor(window: Window & typeof globalThis, isUnderTest: boolean, sdkLanguage: Language, testIdAttributeNameForStrictErrorAndConsoleCodegen: string, stableRafCount: number, browserName: string, customEngines: { name: string, engine: SelectorEngine }[]) {
|
||||
|
|
@ -478,7 +478,7 @@ export class InjectedScript {
|
|||
element = element.closest('button, [role=button], [role=checkbox], [role=radio]') || element;
|
||||
}
|
||||
if (behavior === 'follow-label') {
|
||||
if (!element.matches('input, textarea, button, select, [role=button], [role=checkbox], [role=radio]') &&
|
||||
if (!element.matches('a, input, textarea, button, select, [role=link], [role=button], [role=checkbox], [role=radio]') &&
|
||||
!(element as any).isContentEditable) {
|
||||
// Go up to the label that might be connected to the input/textarea.
|
||||
element = element.closest('label') || element;
|
||||
|
|
|
|||
|
|
@ -356,7 +356,7 @@ class RecordActionTool implements RecorderTool {
|
|||
return;
|
||||
this._performAction({
|
||||
name: 'select',
|
||||
selector: this._hoveredModel!.selector,
|
||||
selector: this._activeModel!.selector,
|
||||
options: [...selectElement.selectedOptions].map(option => option.value),
|
||||
signals: []
|
||||
});
|
||||
|
|
@ -578,8 +578,8 @@ class TextAssertionTool implements RecorderTool {
|
|||
|
||||
onPointerUp(event: PointerEvent) {
|
||||
const target = this._hoverHighlight?.elements[0];
|
||||
if (this._kind === 'value' && target && target.nodeName === 'INPUT' && (target as HTMLInputElement).disabled) {
|
||||
// Click on a disabled input does not produce a "click" event, but we still want
|
||||
if (this._kind === 'value' && target && (target.nodeName === 'INPUT' || target.nodeName === 'SELECT') && (target as HTMLInputElement).disabled) {
|
||||
// Click on a disabled input (or select) does not produce a "click" event, but we still want
|
||||
// to assert the value.
|
||||
this._commitAssertValue();
|
||||
}
|
||||
|
|
@ -973,7 +973,7 @@ export class Recorder {
|
|||
body[data-pw-cursor=text] *, body[data-pw-cursor=text] *::after { cursor: text !important; }
|
||||
`);
|
||||
this.installListeners();
|
||||
|
||||
injectedScript.utils.cacheNormalizedWhitespaces();
|
||||
if (injectedScript.isUnderTest)
|
||||
console.error('Recorder script ready for test'); // eslint-disable-line no-console
|
||||
}
|
||||
|
|
|
|||
|
|
@ -528,7 +528,7 @@ function suitableTextAlternatives(text: string) {
|
|||
const match = text.match(/^([\d.,]+)[^.,\w]/);
|
||||
const leadingNumberLength = match ? match[1].length : 0;
|
||||
if (leadingNumberLength) {
|
||||
const alt = text.substring(leadingNumberLength).trimStart();
|
||||
const alt = trimWordBoundary(text.substring(leadingNumberLength).trimStart(), 80);
|
||||
result.push({ text: alt, scoreBouns: alt.length <= 30 ? 2 : 1 });
|
||||
}
|
||||
}
|
||||
|
|
@ -537,7 +537,7 @@ function suitableTextAlternatives(text: string) {
|
|||
const match = text.match(/[^.,\w]([\d.,]+)$/);
|
||||
const trailingNumberLength = match ? match[1].length : 0;
|
||||
if (trailingNumberLength) {
|
||||
const alt = text.substring(0, text.length - trailingNumberLength).trimEnd();
|
||||
const alt = trimWordBoundary(text.substring(0, text.length - trailingNumberLength).trimEnd(), 80);
|
||||
result.push({ text: alt, scoreBouns: alt.length <= 30 ? 2 : 1 });
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -308,6 +308,7 @@ function buildLayoutClosure(layout: keyboardLayout.KeyboardLayout): Map<string,
|
|||
|
||||
export interface RawTouchscreen {
|
||||
tap(x: number, y: number, modifiers: Set<types.KeyboardModifier>): Promise<void>;
|
||||
touch(type: 'touchstart'|'touchmove'|'touchend'|'touchcancel', touchPoints: { x: number, y: number, id?: number }[], modifiers: Set<types.KeyboardModifier>): Promise<void>;
|
||||
}
|
||||
|
||||
export class Touchscreen {
|
||||
|
|
@ -326,4 +327,19 @@ export class Touchscreen {
|
|||
throw new Error('hasTouch must be enabled on the browser context before using the touchscreen.');
|
||||
await this._raw.tap(x, y, this._page.keyboard._modifiers());
|
||||
}
|
||||
async touch(type: 'touchstart'|'touchmove'|'touchend'|'touchcancel', touchPoints: { x: number, y: number, id?: number }[], metadata?: CallMetadata) {
|
||||
if (metadata && touchPoints.length === 1)
|
||||
metadata.point = { x: touchPoints[0].x, y: touchPoints[0].y };
|
||||
if (!this._page._browserContext._options.hasTouch)
|
||||
throw new Error('hasTouch must be enabled on the browser context before using the touchscreen.');
|
||||
const ids = new Set<number>();
|
||||
for (const point of touchPoints) {
|
||||
if (point.id !== undefined) {
|
||||
if (ids.has(point.id))
|
||||
throw new Error(`Duplicate touch point id: ${point.id}`);
|
||||
ids.add(point.id);
|
||||
}
|
||||
}
|
||||
await this._raw.touch(type, touchPoints, this._page.keyboard._modifiers());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ import * as accessibility from './accessibility';
|
|||
import { FileChooser } from './fileChooser';
|
||||
import type { Progress } from './progress';
|
||||
import { ProgressController } from './progress';
|
||||
import { LongStandingScope, assert, isError } from '../utils';
|
||||
import { LongStandingScope, assert, createGuid, isError } from '../utils';
|
||||
import { ManualPromise } from '../utils/manualPromise';
|
||||
import { debugLogger } from '../utils/debugLogger';
|
||||
import type { ImageComparatorOptions } from '../utils/comparators';
|
||||
|
|
@ -56,7 +56,7 @@ export interface PageDelegate {
|
|||
goForward(): Promise<boolean>;
|
||||
exposeBinding(binding: PageBinding): Promise<void>;
|
||||
removeExposedBindings(): Promise<void>;
|
||||
addInitScript(source: string): Promise<void>;
|
||||
addInitScript(initScript: InitScript): Promise<void>;
|
||||
removeInitScripts(): Promise<void>;
|
||||
closePage(runBeforeUnload: boolean): Promise<void>;
|
||||
potentiallyUninitializedPage(): Page;
|
||||
|
|
@ -154,7 +154,7 @@ export class Page extends SdkObject {
|
|||
private _emulatedMedia: Partial<EmulatedMedia> = {};
|
||||
private _interceptFileChooser = false;
|
||||
private readonly _pageBindings = new Map<string, PageBinding>();
|
||||
readonly initScripts: string[] = [];
|
||||
readonly initScripts: InitScript[] = [];
|
||||
readonly _screenshotter: Screenshotter;
|
||||
readonly _frameManager: frames.FrameManager;
|
||||
readonly accessibility: accessibility.Accessibility;
|
||||
|
|
@ -527,8 +527,9 @@ export class Page extends SdkObject {
|
|||
}
|
||||
|
||||
async addInitScript(source: string) {
|
||||
this.initScripts.push(source);
|
||||
await this._delegate.addInitScript(source);
|
||||
const initScript = new InitScript(source);
|
||||
this.initScripts.push(initScript);
|
||||
await this._delegate.addInitScript(initScript);
|
||||
}
|
||||
|
||||
async _removeInitScripts() {
|
||||
|
|
@ -905,6 +906,22 @@ function addPageBinding(bindingName: string, needsHandle: boolean, utilityScript
|
|||
(globalThis as any)[bindingName].__installed = true;
|
||||
}
|
||||
|
||||
export class InitScript {
|
||||
readonly source: string;
|
||||
|
||||
constructor(source: string) {
|
||||
const guid = createGuid();
|
||||
this.source = `(() => {
|
||||
globalThis.__pwInitScripts = globalThis.__pwInitScripts || {};
|
||||
const hasInitScript = globalThis.__pwInitScripts[${JSON.stringify(guid)}];
|
||||
if (hasInitScript)
|
||||
return;
|
||||
globalThis.__pwInitScripts[${JSON.stringify(guid)}] = true;
|
||||
${source}
|
||||
})();`;
|
||||
}
|
||||
}
|
||||
|
||||
class FrameThrottler {
|
||||
private _acks: (() => void)[] = [];
|
||||
private _defaultInterval: number;
|
||||
|
|
|
|||
|
|
@ -2122,6 +2122,10 @@ export module Protocol {
|
|||
* Array of <code>DOMNode</code> ids of any children marked as selected.
|
||||
*/
|
||||
selectedChildNodeIds?: NodeId[];
|
||||
/**
|
||||
* On / off state of switch form controls.
|
||||
*/
|
||||
switchState?: "off"|"on";
|
||||
}
|
||||
/**
|
||||
* A structure holding an RGBA color.
|
||||
|
|
@ -5009,6 +5013,23 @@ might return multiple quads for inline nodes.
|
|||
* UTC time in seconds, counted from January 1, 1970.
|
||||
*/
|
||||
export type TimeSinceEpoch = number;
|
||||
/**
|
||||
* Touch point.
|
||||
*/
|
||||
export interface TouchPoint {
|
||||
/**
|
||||
* X coordinate of the event relative to the main frame's viewport in CSS pixels.
|
||||
*/
|
||||
x: number;
|
||||
/**
|
||||
* Y coordinate of the event relative to the main frame's viewport in CSS pixels.
|
||||
*/
|
||||
y: number;
|
||||
/**
|
||||
* Identifier used to track touch sources between events, must be unique within an event.
|
||||
*/
|
||||
id: number;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
|
|
@ -5165,6 +5186,26 @@ the top of the viewport and Y increases as it proceeds towards the bottom of the
|
|||
}
|
||||
export type dispatchTapEventReturnValue = {
|
||||
}
|
||||
/**
|
||||
* Dispatches a touch event to the page.
|
||||
*/
|
||||
export type dispatchTouchEventParameters = {
|
||||
/**
|
||||
* Type of the touch event.
|
||||
*/
|
||||
type: "touchStart"|"touchMove"|"touchEnd"|"touchCancel";
|
||||
/**
|
||||
* Bit field representing pressed modifier keys. Alt=1, Ctrl=2, Meta/Command=4, Shift=8
|
||||
(default: 0).
|
||||
*/
|
||||
modifiers?: number;
|
||||
/**
|
||||
* List of touch points
|
||||
*/
|
||||
touchPoints?: TouchPoint[];
|
||||
}
|
||||
export type dispatchTouchEventReturnValue = {
|
||||
}
|
||||
}
|
||||
|
||||
export module Inspector {
|
||||
|
|
@ -9528,6 +9569,7 @@ the top of the viewport and Y increases as it proceeds towards the bottom of the
|
|||
"Input.dispatchMouseEvent": Input.dispatchMouseEventParameters;
|
||||
"Input.dispatchWheelEvent": Input.dispatchWheelEventParameters;
|
||||
"Input.dispatchTapEvent": Input.dispatchTapEventParameters;
|
||||
"Input.dispatchTouchEvent": Input.dispatchTouchEventParameters;
|
||||
"Inspector.enable": Inspector.enableParameters;
|
||||
"Inspector.disable": Inspector.disableParameters;
|
||||
"Inspector.initialized": Inspector.initializedParameters;
|
||||
|
|
@ -9839,6 +9881,7 @@ the top of the viewport and Y increases as it proceeds towards the bottom of the
|
|||
"Input.dispatchMouseEvent": Input.dispatchMouseEventReturnValue;
|
||||
"Input.dispatchWheelEvent": Input.dispatchWheelEventReturnValue;
|
||||
"Input.dispatchTapEvent": Input.dispatchTapEventReturnValue;
|
||||
"Input.dispatchTouchEvent": Input.dispatchTouchEventReturnValue;
|
||||
"Inspector.enable": Inspector.enableReturnValue;
|
||||
"Inspector.disable": Inspector.disableReturnValue;
|
||||
"Inspector.initialized": Inspector.initializedReturnValue;
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import type { RegisteredListener } from '../../utils/eventsHelper';
|
|||
import { assert } from '../../utils';
|
||||
import { eventsHelper } from '../../utils/eventsHelper';
|
||||
import * as network from '../network';
|
||||
import type { Page, PageBinding, PageDelegate } from '../page';
|
||||
import type { InitScript, Page, PageBinding, PageDelegate } from '../page';
|
||||
import type { ConnectionTransport } from '../transport';
|
||||
import type * as types from '../types';
|
||||
import type * as channels from '@protocol/channels';
|
||||
|
|
@ -315,7 +315,7 @@ export class WKBrowserContext extends BrowserContext {
|
|||
await (page._delegate as WKPage).updateHttpCredentials();
|
||||
}
|
||||
|
||||
async doAddInitScript(source: string) {
|
||||
async doAddInitScript(initScript: InitScript) {
|
||||
for (const page of this.pages())
|
||||
await (page._delegate as WKPage)._updateBootstrapScript();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -182,4 +182,20 @@ export class RawTouchscreenImpl implements input.RawTouchscreen {
|
|||
modifiers: toModifiersMask(modifiers),
|
||||
});
|
||||
}
|
||||
|
||||
async touch(eventType: 'touchstart'|'touchmove'|'touchend'|'touchcancel', touchPoints: { x: number, y: number, id?: number }[], modifiers: Set<types.KeyboardModifier>) {
|
||||
let type: 'touchStart' | 'touchMove' | 'touchEnd' | 'touchCancel';
|
||||
switch (eventType) {
|
||||
case 'touchstart': type = 'touchStart'; break;
|
||||
case 'touchmove': type = 'touchMove'; break;
|
||||
case 'touchend': type = 'touchEnd'; break;
|
||||
case 'touchcancel': type = 'touchCancel'; break;
|
||||
default: throw new Error('Invalid eventType: ' + eventType);
|
||||
}
|
||||
await this._pageProxySession.send('Input.dispatchTouchEvent', {
|
||||
type,
|
||||
touchPoints: touchPoints.map(p => ({ ...p, id: p.id || 0 })),
|
||||
modifiers: toModifiersMask(modifiers)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ import { eventsHelper } from '../../utils/eventsHelper';
|
|||
import { helper } from '../helper';
|
||||
import type { JSHandle } from '../javascript';
|
||||
import * as network from '../network';
|
||||
import type { PageBinding, PageDelegate } from '../page';
|
||||
import type { InitScript, PageBinding, PageDelegate } from '../page';
|
||||
import { Page } from '../page';
|
||||
import type { Progress } from '../progress';
|
||||
import type * as types from '../types';
|
||||
|
|
@ -777,7 +777,7 @@ export class WKPage implements PageDelegate {
|
|||
await this._updateBootstrapScript();
|
||||
}
|
||||
|
||||
async addInitScript(script: string): Promise<void> {
|
||||
async addInitScript(initScript: InitScript): Promise<void> {
|
||||
await this._updateBootstrapScript();
|
||||
}
|
||||
|
||||
|
|
@ -797,8 +797,8 @@ export class WKPage implements PageDelegate {
|
|||
|
||||
for (const binding of this._page.allBindings())
|
||||
scripts.push(binding.source);
|
||||
scripts.push(...this._browserContext.initScripts);
|
||||
scripts.push(...this._page.initScripts);
|
||||
scripts.push(...this._browserContext.initScripts.map(s => s.source));
|
||||
scripts.push(...this._page.initScripts.map(s => s.source));
|
||||
return scripts.join(';\n');
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -45,8 +45,9 @@ class HttpsHappyEyeballsAgent extends https.Agent {
|
|||
}
|
||||
}
|
||||
|
||||
export const httpsHappyEyeballsAgent = new HttpsHappyEyeballsAgent();
|
||||
export const httpHappyEyeballsAgent = new HttpHappyEyeballsAgent();
|
||||
// These options are aligned with the default Node.js globalAgent options.
|
||||
export const httpsHappyEyeballsAgent = new HttpsHappyEyeballsAgent({ keepAlive: true });
|
||||
export const httpHappyEyeballsAgent = new HttpHappyEyeballsAgent({ keepAlive: true });
|
||||
|
||||
export async function createSocket(host: string, port: number): Promise<net.Socket> {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
|
|
|||
|
|
@ -67,8 +67,19 @@ function cssEscapeOne(s: string, i: number): string {
|
|||
return '\\' + s.charAt(i);
|
||||
}
|
||||
|
||||
let normalizedWhitespaceCache: Map<string, string> | undefined;
|
||||
|
||||
export function cacheNormalizedWhitespaces() {
|
||||
normalizedWhitespaceCache = new Map();
|
||||
}
|
||||
|
||||
export function normalizeWhiteSpace(text: string): string {
|
||||
return text.replace(/\u200b/g, '').trim().replace(/\s+/g, ' ');
|
||||
let result = normalizedWhitespaceCache?.get(text);
|
||||
if (result === undefined) {
|
||||
result = text.replace(/\u200b/g, '').trim().replace(/\s+/g, ' ');
|
||||
normalizedWhitespaceCache?.set(text, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function normalizeEscapedRegexQuotes(source: string) {
|
||||
|
|
|
|||
130
packages/playwright-core/types/types.d.ts
vendored
130
packages/playwright-core/types/types.d.ts
vendored
|
|
@ -814,21 +814,6 @@ export interface Page {
|
|||
* })();
|
||||
* ```
|
||||
*
|
||||
* An example of passing an element handle:
|
||||
*
|
||||
* ```js
|
||||
* await page.exposeBinding('clicked', async (source, element) => {
|
||||
* console.log(await element.textContent());
|
||||
* }, { handle: true });
|
||||
* await page.setContent(`
|
||||
* <script>
|
||||
* document.addEventListener('click', event => window.clicked(event.target));
|
||||
* </script>
|
||||
* <div>Click me</div>
|
||||
* <div>Or click me</div>
|
||||
* `);
|
||||
* ```
|
||||
*
|
||||
* @param name Name of the function on the window object.
|
||||
* @param callback Callback function that will be called in the Playwright's context.
|
||||
* @param options
|
||||
|
|
@ -875,21 +860,6 @@ export interface Page {
|
|||
* })();
|
||||
* ```
|
||||
*
|
||||
* An example of passing an element handle:
|
||||
*
|
||||
* ```js
|
||||
* await page.exposeBinding('clicked', async (source, element) => {
|
||||
* console.log(await element.textContent());
|
||||
* }, { handle: true });
|
||||
* await page.setContent(`
|
||||
* <script>
|
||||
* document.addEventListener('click', event => window.clicked(event.target));
|
||||
* </script>
|
||||
* <div>Click me</div>
|
||||
* <div>Or click me</div>
|
||||
* `);
|
||||
* ```
|
||||
*
|
||||
* @param name Name of the function on the window object.
|
||||
* @param callback Callback function that will be called in the Playwright's context.
|
||||
* @param options
|
||||
|
|
@ -7637,21 +7607,6 @@ export interface BrowserContext {
|
|||
* })();
|
||||
* ```
|
||||
*
|
||||
* An example of passing an element handle:
|
||||
*
|
||||
* ```js
|
||||
* await context.exposeBinding('clicked', async (source, element) => {
|
||||
* console.log(await element.textContent());
|
||||
* }, { handle: true });
|
||||
* await page.setContent(`
|
||||
* <script>
|
||||
* document.addEventListener('click', event => window.clicked(event.target));
|
||||
* </script>
|
||||
* <div>Click me</div>
|
||||
* <div>Or click me</div>
|
||||
* `);
|
||||
* ```
|
||||
*
|
||||
* @param name Name of the function on the window object.
|
||||
* @param callback Callback function that will be called in the Playwright's context.
|
||||
* @param options
|
||||
|
|
@ -7693,21 +7648,6 @@ export interface BrowserContext {
|
|||
* })();
|
||||
* ```
|
||||
*
|
||||
* An example of passing an element handle:
|
||||
*
|
||||
* ```js
|
||||
* await context.exposeBinding('clicked', async (source, element) => {
|
||||
* console.log(await element.textContent());
|
||||
* }, { handle: true });
|
||||
* await page.setContent(`
|
||||
* <script>
|
||||
* document.addEventListener('click', event => window.clicked(event.target));
|
||||
* </script>
|
||||
* <div>Click me</div>
|
||||
* <div>Or click me</div>
|
||||
* `);
|
||||
* ```
|
||||
*
|
||||
* @param name Name of the function on the window object.
|
||||
* @param callback Callback function that will be called in the Playwright's context.
|
||||
* @param options
|
||||
|
|
@ -8346,9 +8286,7 @@ export interface BrowserContext {
|
|||
* await browserContext.addCookies([cookieObject1, cookieObject2]);
|
||||
* ```
|
||||
*
|
||||
* @param cookies Adds cookies to the browser context.
|
||||
*
|
||||
* For the cookie to apply to all subdomains as well, prefix domain with a dot, like this: ".example.com".
|
||||
* @param cookies
|
||||
*/
|
||||
addCookies(cookies: ReadonlyArray<{
|
||||
name: string;
|
||||
|
|
@ -8356,17 +8294,18 @@ export interface BrowserContext {
|
|||
value: string;
|
||||
|
||||
/**
|
||||
* either url or domain / path are required. Optional.
|
||||
* Either url or domain / path are required. Optional.
|
||||
*/
|
||||
url?: string;
|
||||
|
||||
/**
|
||||
* either url or domain / path are required Optional.
|
||||
* For the cookie to apply to all subdomains as well, prefix domain with a dot, like this: ".example.com". Either url
|
||||
* or domain / path are required. Optional.
|
||||
*/
|
||||
domain?: string;
|
||||
|
||||
/**
|
||||
* either url or domain / path are required Optional.
|
||||
* Either url or domain / path are required Optional.
|
||||
*/
|
||||
path?: string;
|
||||
|
||||
|
|
@ -15963,6 +15902,12 @@ export interface APIRequestContext {
|
|||
*/
|
||||
maxRedirects?: number;
|
||||
|
||||
/**
|
||||
* Maximum number of times socket errors should be retried. Currently only `ECONNRESET` error is retried. An error
|
||||
* will be thrown if the limit is exceeded. Defaults to `0` - no retries.
|
||||
*/
|
||||
maxRetries?: number;
|
||||
|
||||
/**
|
||||
* If set changes the fetch method (e.g. [PUT](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PUT) or
|
||||
* [POST](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST)). If not specified, GET method is used.
|
||||
|
|
@ -16063,6 +16008,12 @@ export interface APIRequestContext {
|
|||
*/
|
||||
maxRedirects?: number;
|
||||
|
||||
/**
|
||||
* Maximum number of times socket errors should be retried. Currently only `ECONNRESET` error is retried. An error
|
||||
* will be thrown if the limit is exceeded. Defaults to `0` - no retries.
|
||||
*/
|
||||
maxRetries?: number;
|
||||
|
||||
/**
|
||||
* Provides an object that will be serialized as html form using `multipart/form-data` encoding and sent as this
|
||||
* request body. If this parameter is specified `content-type` header will be set to `multipart/form-data` unless
|
||||
|
|
@ -16143,6 +16094,12 @@ export interface APIRequestContext {
|
|||
*/
|
||||
maxRedirects?: number;
|
||||
|
||||
/**
|
||||
* Maximum number of times socket errors should be retried. Currently only `ECONNRESET` error is retried. An error
|
||||
* will be thrown if the limit is exceeded. Defaults to `0` - no retries.
|
||||
*/
|
||||
maxRetries?: number;
|
||||
|
||||
/**
|
||||
* Provides an object that will be serialized as html form using `multipart/form-data` encoding and sent as this
|
||||
* request body. If this parameter is specified `content-type` header will be set to `multipart/form-data` unless
|
||||
|
|
@ -16223,6 +16180,12 @@ export interface APIRequestContext {
|
|||
*/
|
||||
maxRedirects?: number;
|
||||
|
||||
/**
|
||||
* Maximum number of times socket errors should be retried. Currently only `ECONNRESET` error is retried. An error
|
||||
* will be thrown if the limit is exceeded. Defaults to `0` - no retries.
|
||||
*/
|
||||
maxRetries?: number;
|
||||
|
||||
/**
|
||||
* Provides an object that will be serialized as html form using `multipart/form-data` encoding and sent as this
|
||||
* request body. If this parameter is specified `content-type` header will be set to `multipart/form-data` unless
|
||||
|
|
@ -16345,6 +16308,12 @@ export interface APIRequestContext {
|
|||
*/
|
||||
maxRedirects?: number;
|
||||
|
||||
/**
|
||||
* Maximum number of times socket errors should be retried. Currently only `ECONNRESET` error is retried. An error
|
||||
* will be thrown if the limit is exceeded. Defaults to `0` - no retries.
|
||||
*/
|
||||
maxRetries?: number;
|
||||
|
||||
/**
|
||||
* Provides an object that will be serialized as html form using `multipart/form-data` encoding and sent as this
|
||||
* request body. If this parameter is specified `content-type` header will be set to `multipart/form-data` unless
|
||||
|
|
@ -16425,6 +16394,12 @@ export interface APIRequestContext {
|
|||
*/
|
||||
maxRedirects?: number;
|
||||
|
||||
/**
|
||||
* Maximum number of times socket errors should be retried. Currently only `ECONNRESET` error is retried. An error
|
||||
* will be thrown if the limit is exceeded. Defaults to `0` - no retries.
|
||||
*/
|
||||
maxRetries?: number;
|
||||
|
||||
/**
|
||||
* Provides an object that will be serialized as html form using `multipart/form-data` encoding and sent as this
|
||||
* request body. If this parameter is specified `content-type` header will be set to `multipart/form-data` unless
|
||||
|
|
@ -19707,6 +19682,29 @@ export interface Touchscreen {
|
|||
* @param y
|
||||
*/
|
||||
tap(x: number, y: number): Promise<void>;
|
||||
|
||||
/**
|
||||
* Synthesizes a touch event.
|
||||
* @param type Type of the touch event.
|
||||
* @param touchPoints List of touch points for this event. `id` is a unique identifier of a touch point that helps identify it between
|
||||
* touch events for the duration of its movement around the surface.
|
||||
*/
|
||||
touch(type: "touchstart"|"touchend"|"touchmove"|"touchcancel", touchPoints: ReadonlyArray<{
|
||||
/**
|
||||
* x coordinate of the event in CSS pixels.
|
||||
*/
|
||||
x: number;
|
||||
|
||||
/**
|
||||
* y coordinate of the event in CSS pixels.
|
||||
*/
|
||||
y: number;
|
||||
|
||||
/**
|
||||
* Identifier used to track the touch point between events, must be unique within an event. Optional.
|
||||
*/
|
||||
id?: number;
|
||||
}>): Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@playwright/experimental-ct-core",
|
||||
"version": "1.45.0-next",
|
||||
"version": "1.46.0-next",
|
||||
"description": "Playwright Component Testing Helpers",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
@ -26,8 +26,8 @@
|
|||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"playwright-core": "1.45.0-next",
|
||||
"playwright-core": "1.46.0-next",
|
||||
"vite": "^5.2.8",
|
||||
"playwright": "1.45.0-next"
|
||||
"playwright": "1.46.0-next"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@playwright/experimental-ct-react",
|
||||
"version": "1.45.0-next",
|
||||
"version": "1.46.0-next",
|
||||
"description": "Playwright Component Testing for React",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
@ -26,10 +26,11 @@
|
|||
"./hooks": {
|
||||
"types": "./hooks.d.ts",
|
||||
"default": "./hooks.mjs"
|
||||
}
|
||||
},
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@playwright/experimental-ct-core": "1.45.0-next",
|
||||
"@playwright/experimental-ct-core": "1.46.0-next",
|
||||
"@vitejs/plugin-react": "^4.2.1"
|
||||
},
|
||||
"bin": {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@playwright/experimental-ct-react17",
|
||||
"version": "1.45.0-next",
|
||||
"version": "1.46.0-next",
|
||||
"description": "Playwright Component Testing for React",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
@ -26,10 +26,11 @@
|
|||
"./hooks": {
|
||||
"types": "./hooks.d.ts",
|
||||
"default": "./hooks.mjs"
|
||||
}
|
||||
},
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@playwright/experimental-ct-core": "1.45.0-next",
|
||||
"@playwright/experimental-ct-core": "1.46.0-next",
|
||||
"@vitejs/plugin-react": "^4.2.1"
|
||||
},
|
||||
"bin": {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@playwright/experimental-ct-solid",
|
||||
"version": "1.45.0-next",
|
||||
"version": "1.46.0-next",
|
||||
"description": "Playwright Component Testing for Solid",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
@ -26,10 +26,11 @@
|
|||
"./hooks": {
|
||||
"types": "./hooks.d.ts",
|
||||
"default": "./hooks.mjs"
|
||||
}
|
||||
},
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@playwright/experimental-ct-core": "1.45.0-next",
|
||||
"@playwright/experimental-ct-core": "1.46.0-next",
|
||||
"vite-plugin-solid": "^2.7.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@playwright/experimental-ct-svelte",
|
||||
"version": "1.45.0-next",
|
||||
"version": "1.46.0-next",
|
||||
"description": "Playwright Component Testing for Svelte",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
@ -26,10 +26,11 @@
|
|||
"./hooks": {
|
||||
"types": "./hooks.d.ts",
|
||||
"default": "./hooks.mjs"
|
||||
}
|
||||
},
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@playwright/experimental-ct-core": "1.45.0-next",
|
||||
"@playwright/experimental-ct-core": "1.46.0-next",
|
||||
"@sveltejs/vite-plugin-svelte": "^3.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@playwright/experimental-ct-vue",
|
||||
"version": "1.45.0-next",
|
||||
"version": "1.46.0-next",
|
||||
"description": "Playwright Component Testing for Vue",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
@ -26,10 +26,11 @@
|
|||
"./hooks": {
|
||||
"types": "./hooks.d.ts",
|
||||
"default": "./hooks.mjs"
|
||||
}
|
||||
},
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@playwright/experimental-ct-core": "1.45.0-next",
|
||||
"@playwright/experimental-ct-core": "1.46.0-next",
|
||||
"@vitejs/plugin-vue": "^4.2.1"
|
||||
},
|
||||
"bin": {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@playwright/experimental-ct-vue2",
|
||||
"version": "1.45.0-next",
|
||||
"version": "1.46.0-next",
|
||||
"description": "Playwright Component Testing for Vue2",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
@ -26,10 +26,11 @@
|
|||
"./hooks": {
|
||||
"types": "./hooks.d.ts",
|
||||
"default": "./hooks.mjs"
|
||||
}
|
||||
},
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@playwright/experimental-ct-core": "1.45.0-next",
|
||||
"@playwright/experimental-ct-core": "1.46.0-next",
|
||||
"@vitejs/plugin-vue2": "^2.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "playwright-firefox",
|
||||
"version": "1.45.0-next",
|
||||
"version": "1.46.0-next",
|
||||
"description": "A high-level API to automate Firefox",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
@ -30,6 +30,6 @@
|
|||
"install": "node install.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"playwright-core": "1.45.0-next"
|
||||
"playwright-core": "1.46.0-next"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@playwright/test",
|
||||
"version": "1.45.0-next",
|
||||
"version": "1.46.0-next",
|
||||
"description": "A high-level API to automate web browsers",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
@ -30,6 +30,6 @@
|
|||
},
|
||||
"scripts": {},
|
||||
"dependencies": {
|
||||
"playwright": "1.45.0-next"
|
||||
"playwright": "1.46.0-next"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "playwright-webkit",
|
||||
"version": "1.45.0-next",
|
||||
"version": "1.46.0-next",
|
||||
"description": "A high-level API to automate WebKit",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
@ -30,6 +30,6 @@
|
|||
"install": "node install.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"playwright-core": "1.45.0-next"
|
||||
"playwright-core": "1.46.0-next"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue