Compare commits
30 Commits
Author | SHA1 | Date |
---|---|---|
![]() |
7ec70c61d7 | |
![]() |
bd89ddaca4 | |
![]() |
fcfb61ab26 | |
![]() |
303901d7a5 | |
![]() |
3032513e92 | |
![]() |
2dda48c9f0 | |
![]() |
5ceb61c497 | |
![]() |
939aa16f77 | |
![]() |
7bf770143c | |
![]() |
aef47744df | |
![]() |
36a628d902 | |
![]() |
2ece112cb3 | |
![]() |
53abf161b0 | |
![]() |
5e3e29f7d2 | |
![]() |
b1a72febc1 | |
![]() |
68cfaacd39 | |
![]() |
0109882a66 | |
![]() |
039e87f5cb | |
![]() |
37a9535a27 | |
![]() |
e4a9684ffe | |
![]() |
cd0b053165 | |
![]() |
8772389000 | |
![]() |
7c65d5e81e | |
![]() |
9b6176d709 | |
![]() |
61fc320053 | |
![]() |
224667e9ff | |
![]() |
09529e9275 | |
![]() |
cb2d94e467 | |
![]() |
78600c60f8 | |
![]() |
47ad692952 |
|
@ -1,3 +1,3 @@
|
|||
REMOTE_URL="https://github.com/mozilla/gecko-dev"
|
||||
BASE_BRANCH="release"
|
||||
BASE_REVISION="4764531b2bb14867dde520d74f6a3c88690e0751"
|
||||
BASE_REVISION="5e1efb776a56e399f6810204a2eca13f18a3eba6"
|
||||
|
|
|
@ -204,11 +204,13 @@ class NetworkRequest {
|
|||
this._interceptedChannel.synthesizeHeader(header.name, header.value);
|
||||
if (header.name.toLowerCase() === 'set-cookie') {
|
||||
Services.cookies.QueryInterface(Ci.nsICookieService);
|
||||
Services.cookies.setCookieStringFromHttp(this.httpChannel.URI, header.value, this.httpChannel);
|
||||
for (const cookieString of header.value.split('\n'))
|
||||
Services.cookies.setCookieStringFromHttp(this.httpChannel.URI, cookieString, this.httpChannel);
|
||||
}
|
||||
}
|
||||
const synthesized = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(Ci.nsIStringInputStream);
|
||||
synthesized.data = base64body ? atob(base64body) : '';
|
||||
if (base64body)
|
||||
synthesized.setByteStringData(atob(base64body));
|
||||
this._interceptedChannel.startSynthesizedResponse(synthesized, null, null, '', false);
|
||||
this._interceptedChannel.finishSynthesizedResponse();
|
||||
this._interceptedChannel = undefined;
|
||||
|
@ -870,7 +872,7 @@ function setPostData(httpChannel, postData, headers) {
|
|||
return;
|
||||
const synthesized = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(Ci.nsIStringInputStream);
|
||||
const body = atob(postData);
|
||||
synthesized.setData(body, body.length);
|
||||
synthesized.setByteStringData(body);
|
||||
|
||||
const overriddenHeader = (lowerCaseName) => {
|
||||
if (headers) {
|
||||
|
@ -902,7 +904,7 @@ function convertString(s, source, dest) {
|
|||
const is = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
|
||||
Ci.nsIStringInputStream
|
||||
);
|
||||
is.setData(s, s.length);
|
||||
is.setByteStringData(s);
|
||||
const listener = Cc["@mozilla.org/network/stream-loader;1"].createInstance(
|
||||
Ci.nsIStreamLoader
|
||||
);
|
||||
|
|
|
@ -22,6 +22,8 @@ const ALL_PERMISSIONS = [
|
|||
];
|
||||
|
||||
let globalTabAndWindowActivationChain = Promise.resolve();
|
||||
// This is a workaround for https://github.com/microsoft/playwright/issues/34586
|
||||
let globalNewPageChain = Promise.resolve();
|
||||
|
||||
class DownloadInterceptor {
|
||||
constructor(registry) {
|
||||
|
@ -308,55 +310,59 @@ class TargetRegistry {
|
|||
}
|
||||
|
||||
async newPage({browserContextId}) {
|
||||
const browserContext = this.browserContextForId(browserContextId);
|
||||
const features = "chrome,dialog=no,all";
|
||||
// See _callWithURIToLoad in browser.js for the structure of window.arguments
|
||||
// window.arguments[1]: unused (bug 871161)
|
||||
// [2]: referrerInfo (nsIReferrerInfo)
|
||||
// [3]: postData (nsIInputStream)
|
||||
// [4]: allowThirdPartyFixup (bool)
|
||||
// [5]: userContextId (int)
|
||||
// [6]: originPrincipal (nsIPrincipal)
|
||||
// [7]: originStoragePrincipal (nsIPrincipal)
|
||||
// [8]: triggeringPrincipal (nsIPrincipal)
|
||||
// [9]: allowInheritPrincipal (bool)
|
||||
// [10]: csp (nsIContentSecurityPolicy)
|
||||
// [11]: nsOpenWindowInfo
|
||||
const args = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
|
||||
const urlSupports = Cc["@mozilla.org/supports-string;1"].createInstance(
|
||||
Ci.nsISupportsString
|
||||
);
|
||||
urlSupports.data = 'about:blank';
|
||||
args.appendElement(urlSupports); // 0
|
||||
args.appendElement(undefined); // 1
|
||||
args.appendElement(undefined); // 2
|
||||
args.appendElement(undefined); // 3
|
||||
args.appendElement(undefined); // 4
|
||||
const userContextIdSupports = Cc[
|
||||
"@mozilla.org/supports-PRUint32;1"
|
||||
].createInstance(Ci.nsISupportsPRUint32);
|
||||
userContextIdSupports.data = browserContext.userContextId;
|
||||
args.appendElement(userContextIdSupports); // 5
|
||||
args.appendElement(undefined); // 6
|
||||
args.appendElement(undefined); // 7
|
||||
args.appendElement(Services.scriptSecurityManager.getSystemPrincipal()); // 8
|
||||
const result = globalNewPageChain.then(async () => {
|
||||
const browserContext = this.browserContextForId(browserContextId);
|
||||
const features = "chrome,dialog=no,all";
|
||||
// See _callWithURIToLoad in browser.js for the structure of window.arguments
|
||||
// window.arguments[1]: unused (bug 871161)
|
||||
// [2]: referrerInfo (nsIReferrerInfo)
|
||||
// [3]: postData (nsIInputStream)
|
||||
// [4]: allowThirdPartyFixup (bool)
|
||||
// [5]: userContextId (int)
|
||||
// [6]: originPrincipal (nsIPrincipal)
|
||||
// [7]: originStoragePrincipal (nsIPrincipal)
|
||||
// [8]: triggeringPrincipal (nsIPrincipal)
|
||||
// [9]: allowInheritPrincipal (bool)
|
||||
// [10]: csp (nsIContentSecurityPolicy)
|
||||
// [11]: nsOpenWindowInfo
|
||||
const args = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
|
||||
const urlSupports = Cc["@mozilla.org/supports-string;1"].createInstance(
|
||||
Ci.nsISupportsString
|
||||
);
|
||||
urlSupports.data = 'about:blank';
|
||||
args.appendElement(urlSupports); // 0
|
||||
args.appendElement(undefined); // 1
|
||||
args.appendElement(undefined); // 2
|
||||
args.appendElement(undefined); // 3
|
||||
args.appendElement(undefined); // 4
|
||||
const userContextIdSupports = Cc[
|
||||
"@mozilla.org/supports-PRUint32;1"
|
||||
].createInstance(Ci.nsISupportsPRUint32);
|
||||
userContextIdSupports.data = browserContext.userContextId;
|
||||
args.appendElement(userContextIdSupports); // 5
|
||||
args.appendElement(undefined); // 6
|
||||
args.appendElement(undefined); // 7
|
||||
args.appendElement(Services.scriptSecurityManager.getSystemPrincipal()); // 8
|
||||
|
||||
const window = Services.ww.openWindow(null, AppConstants.BROWSER_CHROME_URL, '_blank', features, args);
|
||||
await waitForWindowReady(window);
|
||||
if (window.gBrowser.browsers.length !== 1)
|
||||
throw new Error(`Unexpected number of tabs in the new window: ${window.gBrowser.browsers.length}`);
|
||||
const browser = window.gBrowser.browsers[0];
|
||||
let target = this._browserToTarget.get(browser);
|
||||
while (!target) {
|
||||
await helper.awaitEvent(this, TargetRegistry.Events.TargetCreated);
|
||||
target = this._browserToTarget.get(browser);
|
||||
}
|
||||
browser.focus();
|
||||
if (browserContext.crossProcessCookie.settings.timezoneId) {
|
||||
if (await target.hasFailedToOverrideTimezone())
|
||||
throw new Error('Failed to override timezone');
|
||||
}
|
||||
return target.id();
|
||||
const window = Services.ww.openWindow(null, AppConstants.BROWSER_CHROME_URL, '_blank', features, args);
|
||||
await waitForWindowReady(window);
|
||||
if (window.gBrowser.browsers.length !== 1)
|
||||
throw new Error(`Unexpected number of tabs in the new window: ${window.gBrowser.browsers.length}`);
|
||||
const browser = window.gBrowser.browsers[0];
|
||||
let target = this._browserToTarget.get(browser);
|
||||
while (!target) {
|
||||
await helper.awaitEvent(this, TargetRegistry.Events.TargetCreated);
|
||||
target = this._browserToTarget.get(browser);
|
||||
}
|
||||
browser.focus();
|
||||
if (browserContext.crossProcessCookie.settings.timezoneId) {
|
||||
if (await target.hasFailedToOverrideTimezone())
|
||||
throw new Error('Failed to override timezone');
|
||||
}
|
||||
return target.id();
|
||||
});
|
||||
globalNewPageChain = result.catch(error => { /* swallow errors to keep chain running */ });
|
||||
return result;
|
||||
}
|
||||
|
||||
targets() {
|
||||
|
@ -501,6 +507,7 @@ class PageTarget {
|
|||
this.updateEmulatedMedia(browsingContext);
|
||||
this.updateColorSchemeOverride(browsingContext);
|
||||
this.updateReducedMotionOverride(browsingContext);
|
||||
this.updateContrastOverride(browsingContext);
|
||||
this.updateForcedColorsOverride(browsingContext);
|
||||
this.updateForceOffline(browsingContext);
|
||||
this.updateCacheDisabled(browsingContext);
|
||||
|
@ -640,6 +647,15 @@ class PageTarget {
|
|||
(browsingContext || this._linkedBrowser.browsingContext).prefersReducedMotionOverride = this.reducedMotion || this._browserContext.reducedMotion || 'none';
|
||||
}
|
||||
|
||||
setContrast(contrast) {
|
||||
this.contrast = fromProtocolContrast(contrast);
|
||||
this.updateContrastOverride();
|
||||
}
|
||||
|
||||
updateContrastOverride(browsingContext = undefined) {
|
||||
(browsingContext || this._linkedBrowser.browsingContext).prefersContrastOverride = this.contrast || this._browserContext.contrast || 'none';
|
||||
}
|
||||
|
||||
setForcedColors(forcedColors) {
|
||||
this.forcedColors = fromProtocolForcedColors(forcedColors);
|
||||
this.updateForcedColorsOverride();
|
||||
|
@ -875,6 +891,14 @@ function fromProtocolReducedMotion(reducedMotion) {
|
|||
throw new Error('Unknown reduced motion: ' + reducedMotion);
|
||||
}
|
||||
|
||||
function fromProtocolContrast(contrast) {
|
||||
if (contrast === 'more' || contrast === 'less' || contrast === 'custom' || contrast === 'no-preference')
|
||||
return contrast;
|
||||
if (contrast === null)
|
||||
return undefined;
|
||||
throw new Error('Unknown contrast: ' + contrast);
|
||||
}
|
||||
|
||||
function fromProtocolForcedColors(forcedColors) {
|
||||
if (forcedColors === 'active' || forcedColors === 'none')
|
||||
return forcedColors;
|
||||
|
@ -915,6 +939,7 @@ class BrowserContext {
|
|||
this.colorScheme = 'none';
|
||||
this.forcedColors = 'none';
|
||||
this.reducedMotion = 'none';
|
||||
this.contrast = 'none';
|
||||
this.videoRecordingOptions = undefined;
|
||||
this.crossProcessCookie = {
|
||||
initScripts: [],
|
||||
|
@ -941,6 +966,12 @@ class BrowserContext {
|
|||
page.updateReducedMotionOverride();
|
||||
}
|
||||
|
||||
setContrast(contrast) {
|
||||
this.contrast = fromProtocolContrast(contrast);
|
||||
for (const page of this.pages)
|
||||
page.updateContrastOverride();
|
||||
}
|
||||
|
||||
setForcedColors(forcedColors) {
|
||||
this.forcedColors = fromProtocolForcedColors(forcedColors);
|
||||
for (const page of this.pages)
|
||||
|
|
|
@ -219,6 +219,10 @@ class BrowserHandler {
|
|||
await this._targetRegistry.browserContextForId(browserContextId).setForcedColors(nullToUndefined(forcedColors));
|
||||
}
|
||||
|
||||
async ['Browser.setContrast']({browserContextId, contrast}) {
|
||||
await this._targetRegistry.browserContextForId(browserContextId).setContrast(nullToUndefined(contrast));
|
||||
}
|
||||
|
||||
async ['Browser.setVideoRecordingOptions']({browserContextId, options}) {
|
||||
await this._targetRegistry.browserContextForId(browserContextId).setVideoRecordingOptions(options);
|
||||
}
|
||||
|
|
|
@ -302,10 +302,11 @@ class PageHandler {
|
|||
return await this._contentPage.send('setFileInputFiles', options);
|
||||
}
|
||||
|
||||
async ['Page.setEmulatedMedia']({colorScheme, type, reducedMotion, forcedColors}) {
|
||||
async ['Page.setEmulatedMedia']({colorScheme, type, reducedMotion, forcedColors, contrast}) {
|
||||
this._pageTarget.setColorScheme(colorScheme || null);
|
||||
this._pageTarget.setReducedMotion(reducedMotion || null);
|
||||
this._pageTarget.setForcedColors(forcedColors || null);
|
||||
this._pageTarget.setContrast(contrast || null);
|
||||
this._pageTarget.setEmulatedMedia(type);
|
||||
}
|
||||
|
||||
|
|
|
@ -463,6 +463,12 @@ const Browser = {
|
|||
forcedColors: t.Nullable(t.Enum(['active', 'none'])),
|
||||
},
|
||||
},
|
||||
'setContrast': {
|
||||
params: {
|
||||
browserContextId: t.Optional(t.String),
|
||||
contrast: t.Nullable(t.Enum(['less', 'more', 'custom', 'no-preference'])),
|
||||
},
|
||||
},
|
||||
'setVideoRecordingOptions': {
|
||||
params: {
|
||||
browserContextId: t.Optional(t.String),
|
||||
|
@ -809,6 +815,7 @@ const Page = {
|
|||
colorScheme: t.Optional(t.Enum(['dark', 'light', 'no-preference'])),
|
||||
reducedMotion: t.Optional(t.Enum(['reduce', 'no-preference'])),
|
||||
forcedColors: t.Optional(t.Enum(['active', 'none'])),
|
||||
contrast: t.Optional(t.Enum(['less', 'more', 'custom', 'no-preference'])),
|
||||
},
|
||||
},
|
||||
'setCacheDisabled': {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
diff --git a/accessible/base/NotificationController.h b/accessible/base/NotificationController.h
|
||||
index 137963f1170927ae0262e0dc26ef721d496376f4..41fa27bc4a3da41814a7f326792990df3424e81f 100644
|
||||
index 1bcd464d3bd6b8465f78c62b074b0d57dbc6a082..f878ac9db2d800542dabcc2f48e8ae4727ec4b9a 100644
|
||||
--- a/accessible/base/NotificationController.h
|
||||
+++ b/accessible/base/NotificationController.h
|
||||
@@ -244,6 +244,8 @@ class NotificationController final : public EventQueue,
|
||||
|
@ -106,7 +106,7 @@ index 213a99ed433d5219c2b9a64baad82d14cdbcd432..ee4f6484cdfe80899c28a1d9607494e5
|
|||
browser/chrome/browser/content/activity-stream/data/content/tippytop/favicons/allegro-pl.ico
|
||||
browser/defaults/settings/main/search-config-icons/96327a73-c433-5eb4-a16d-b090cadfb80b
|
||||
diff --git a/browser/installer/package-manifest.in b/browser/installer/package-manifest.in
|
||||
index 90c2e7a69076df56392f6d14aacadada7f413e89..9ed24d7d690eca04ef07e9715444227ecf32a008 100644
|
||||
index beae3018bb533529555496433b90403827ba07fc..3f3c750f4cef768b5429492c0077616505262cb9 100644
|
||||
--- a/browser/installer/package-manifest.in
|
||||
+++ b/browser/installer/package-manifest.in
|
||||
@@ -196,6 +196,9 @@
|
||||
|
@ -167,10 +167,10 @@ index d49c6fbf1bf83b832795fa674f6b41f223eef812..7ea3540947ff5f61b15f27fbf4b95564
|
|||
const transportProvider = {
|
||||
setListener(upgradeListener) {
|
||||
diff --git a/docshell/base/BrowsingContext.cpp b/docshell/base/BrowsingContext.cpp
|
||||
index b30dab9ecdee37d7db842acaad30ded30fc326b2..534a89916e34f3d4d960dfcf660c38e2d1000e0c 100644
|
||||
index 28e9d14bd7979798025f9fc30d9a45527488c34c..d6abf1c3e7e2b2cdfd51351ced4aa6fb0d51327b 100644
|
||||
--- a/docshell/base/BrowsingContext.cpp
|
||||
+++ b/docshell/base/BrowsingContext.cpp
|
||||
@@ -106,8 +106,11 @@ struct ParamTraits<mozilla::dom::DisplayMode>
|
||||
@@ -106,8 +106,15 @@ struct ParamTraits<mozilla::dom::DisplayMode>
|
||||
|
||||
template <>
|
||||
struct ParamTraits<mozilla::dom::PrefersColorSchemeOverride>
|
||||
|
@ -181,13 +181,26 @@ index b30dab9ecdee37d7db842acaad30ded30fc326b2..534a89916e34f3d4d960dfcf660c38e2
|
|||
+template <>
|
||||
+struct ParamTraits<mozilla::dom::PrefersReducedMotionOverride>
|
||||
+ : public mozilla::dom::WebIDLEnumSerializer<mozilla::dom::PrefersReducedMotionOverride> {};
|
||||
+
|
||||
+template <>
|
||||
+struct ParamTraits<mozilla::dom::PrefersContrastOverride>
|
||||
+ : public mozilla::dom::WebIDLEnumSerializer<mozilla::dom::PrefersContrastOverride> {};
|
||||
|
||||
template <>
|
||||
struct ParamTraits<mozilla::dom::ForcedColorsOverride>
|
||||
@@ -2887,6 +2890,23 @@ void BrowsingContext::DidSet(FieldIndex<IDX_ForcedColorsOverride>,
|
||||
@@ -2887,6 +2894,32 @@ void BrowsingContext::DidSet(FieldIndex<IDX_ForcedColorsOverride>,
|
||||
PresContextAffectingFieldChanged();
|
||||
}
|
||||
|
||||
+void BrowsingContext::DidSet(FieldIndex<IDX_PrefersContrastOverride>,
|
||||
+ dom::PrefersContrastOverride aOldValue) {
|
||||
+ MOZ_ASSERT(IsTop());
|
||||
+ if (PrefersContrastOverride() == aOldValue) {
|
||||
+ return;
|
||||
+ }
|
||||
+ PresContextAffectingFieldChanged();
|
||||
+}
|
||||
+
|
||||
+void BrowsingContext::DidSet(FieldIndex<IDX_PrefersReducedMotionOverride>,
|
||||
+ dom::PrefersReducedMotionOverride aOldValue) {
|
||||
+ MOZ_ASSERT(IsTop());
|
||||
|
@ -209,7 +222,7 @@ index b30dab9ecdee37d7db842acaad30ded30fc326b2..534a89916e34f3d4d960dfcf660c38e2
|
|||
nsString&& aOldValue) {
|
||||
MOZ_ASSERT(IsTop());
|
||||
diff --git a/docshell/base/BrowsingContext.h b/docshell/base/BrowsingContext.h
|
||||
index e0310bbed02a805a247e04bdc88ae142bcbe5637..19fe77791b055f7f907a0ab00b03e100a0aac1e8 100644
|
||||
index 13815ddf7e13825970cafda19ca24412a5150b53..71c7ae2fce1d92614a60b2aba85bbd70629f682f 100644
|
||||
--- a/docshell/base/BrowsingContext.h
|
||||
+++ b/docshell/base/BrowsingContext.h
|
||||
@@ -203,10 +203,10 @@ struct EmbedderColorSchemes {
|
||||
|
@ -225,27 +238,51 @@ index e0310bbed02a805a247e04bdc88ae142bcbe5637..19fe77791b055f7f907a0ab00b03e100
|
|||
FIELD(EmbedderElementType, Maybe<nsString>) \
|
||||
FIELD(MessageManagerGroup, nsString) \
|
||||
FIELD(MaxTouchPointsOverride, uint8_t) \
|
||||
@@ -246,6 +246,8 @@ struct EmbedderColorSchemes {
|
||||
@@ -246,6 +246,9 @@ struct EmbedderColorSchemes {
|
||||
* <browser> embedder element. */ \
|
||||
FIELD(EmbedderColorSchemes, EmbedderColorSchemes) \
|
||||
FIELD(DisplayMode, dom::DisplayMode) \
|
||||
+ /* playwright addition */ \
|
||||
+ FIELD(PrefersReducedMotionOverride, dom::PrefersReducedMotionOverride) \
|
||||
+ FIELD(PrefersContrastOverride, dom::PrefersContrastOverride) \
|
||||
/* The number of entries added to the session history because of this \
|
||||
* browsing context. */ \
|
||||
FIELD(HistoryEntryCount, uint32_t) \
|
||||
@@ -947,6 +949,10 @@ class BrowsingContext : public nsILoadContext, public nsWrapperCache {
|
||||
@@ -948,6 +951,14 @@ class BrowsingContext : public nsILoadContext, public nsWrapperCache {
|
||||
return GetForcedColorsOverride();
|
||||
}
|
||||
|
||||
+ dom::PrefersReducedMotionOverride PrefersReducedMotionOverride() const {
|
||||
+ return GetPrefersReducedMotionOverride();
|
||||
+ }
|
||||
+
|
||||
+ dom::PrefersContrastOverride PrefersContrastOverride() const {
|
||||
+ return GetPrefersContrastOverride();
|
||||
+ }
|
||||
+
|
||||
bool IsInBFCache() const;
|
||||
|
||||
bool AllowJavascript() const { return GetAllowJavascript(); }
|
||||
@@ -1128,6 +1134,15 @@ class BrowsingContext : public nsILoadContext, public nsWrapperCache {
|
||||
@@ -1107,6 +1118,11 @@ class BrowsingContext : public nsILoadContext, public nsWrapperCache {
|
||||
return IsTop();
|
||||
}
|
||||
|
||||
+ bool CanSet(FieldIndex<IDX_PrefersContrastOverride>,
|
||||
+ dom::PrefersContrastOverride, ContentParent*) {
|
||||
+ return IsTop();
|
||||
+ }
|
||||
+
|
||||
bool CanSet(FieldIndex<IDX_ForcedColorsOverride>, dom::ForcedColorsOverride,
|
||||
ContentParent*) {
|
||||
return IsTop();
|
||||
@@ -1125,10 +1141,22 @@ class BrowsingContext : public nsILoadContext, public nsWrapperCache {
|
||||
void DidSet(FieldIndex<IDX_ForcedColorsOverride>,
|
||||
dom::ForcedColorsOverride aOldValue);
|
||||
|
||||
+ void DidSet(FieldIndex<IDX_PrefersContrastOverride>,
|
||||
+ dom::PrefersContrastOverride aOldValue);
|
||||
+
|
||||
template <typename Callback>
|
||||
void WalkPresContexts(Callback&&);
|
||||
void PresContextAffectingFieldChanged();
|
||||
|
||||
|
@ -262,7 +299,7 @@ index e0310bbed02a805a247e04bdc88ae142bcbe5637..19fe77791b055f7f907a0ab00b03e100
|
|||
|
||||
bool CanSet(FieldIndex<IDX_SuspendMediaWhenInactive>, bool, ContentParent*) {
|
||||
diff --git a/docshell/base/CanonicalBrowsingContext.cpp b/docshell/base/CanonicalBrowsingContext.cpp
|
||||
index e73d56380ff6802103896bd63d2e11c26c8ee3f5..2ce283bcc4e6690a26a6483b6b50b24093797e45 100644
|
||||
index 671a12de84d3b01c4331dbbb2fac050ce061cda1..afcd95e77185e6d606503b7c01d6ab48cc236cc8 100644
|
||||
--- a/docshell/base/CanonicalBrowsingContext.cpp
|
||||
+++ b/docshell/base/CanonicalBrowsingContext.cpp
|
||||
@@ -323,6 +323,8 @@ void CanonicalBrowsingContext::ReplacedBy(
|
||||
|
@ -274,7 +311,7 @@ index e73d56380ff6802103896bd63d2e11c26c8ee3f5..2ce283bcc4e6690a26a6483b6b50b240
|
|||
|
||||
// Propagate some settings on BrowsingContext replacement so they're not lost
|
||||
// on bfcached navigations. These are important for GeckoView (see bug
|
||||
@@ -1608,6 +1610,12 @@ void CanonicalBrowsingContext::LoadURI(nsIURI* aURI,
|
||||
@@ -1600,6 +1602,12 @@ void CanonicalBrowsingContext::LoadURI(nsIURI* aURI,
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -288,7 +325,7 @@ index e73d56380ff6802103896bd63d2e11c26c8ee3f5..2ce283bcc4e6690a26a6483b6b50b240
|
|||
}
|
||||
|
||||
diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp
|
||||
index 4851148d889bdfc12a4b9a463c05d3fb9f1f593f..9f4e3fd0cdfc66b49b1650a05f4b296603846d84 100644
|
||||
index f3069febc1bd9f3ff6acbe162b5475963e004e6c..65ab560c9f13c837c9ed6af15704738700b0e14f 100644
|
||||
--- a/docshell/base/nsDocShell.cpp
|
||||
+++ b/docshell/base/nsDocShell.cpp
|
||||
@@ -15,6 +15,12 @@
|
||||
|
@ -336,7 +373,7 @@ index 4851148d889bdfc12a4b9a463c05d3fb9f1f593f..9f4e3fd0cdfc66b49b1650a05f4b2966
|
|||
#include "nsNetCID.h"
|
||||
#include "nsNetUtil.h"
|
||||
#include "nsObjectLoadingContent.h"
|
||||
@@ -347,6 +357,13 @@ nsDocShell::nsDocShell(BrowsingContext* aBrowsingContext,
|
||||
@@ -347,6 +357,14 @@ nsDocShell::nsDocShell(BrowsingContext* aBrowsingContext,
|
||||
mAllowDNSPrefetch(true),
|
||||
mAllowWindowControl(true),
|
||||
mCSSErrorReportingEnabled(false),
|
||||
|
@ -347,10 +384,11 @@ index 4851148d889bdfc12a4b9a463c05d3fb9f1f593f..9f4e3fd0cdfc66b49b1650a05f4b2966
|
|||
+ mDisallowBFCache(false),
|
||||
+ mReducedMotionOverride(REDUCED_MOTION_OVERRIDE_NONE),
|
||||
+ mForcedColorsOverride(FORCED_COLORS_OVERRIDE_NO_OVERRIDE),
|
||||
+ mContrastOverride(CONTRAST_OVERRIDE_NONE),
|
||||
mAllowAuth(mItemType == typeContent),
|
||||
mAllowKeywordFixup(false),
|
||||
mDisableMetaRefreshWhenInactive(false),
|
||||
@@ -3019,6 +3036,214 @@ nsDocShell::GetMessageManager(ContentFrameMessageManager** aMessageManager) {
|
||||
@@ -3019,6 +3037,232 @@ nsDocShell::GetMessageManager(ContentFrameMessageManager** aMessageManager) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -560,12 +598,30 @@ index 4851148d889bdfc12a4b9a463c05d3fb9f1f593f..9f4e3fd0cdfc66b49b1650a05f4b2966
|
|||
+ return NS_OK;
|
||||
+}
|
||||
+
|
||||
+NS_IMETHODIMP
|
||||
+nsDocShell::GetContrastOverride(ContrastOverride* aContrastOverride) {
|
||||
+ *aContrastOverride = GetRootDocShell()->mContrastOverride;
|
||||
+ return NS_OK;
|
||||
+}
|
||||
+
|
||||
+NS_IMETHODIMP
|
||||
+nsDocShell::SetContrastOverride(ContrastOverride aContrastOverride) {
|
||||
+ mContrastOverride = aContrastOverride;
|
||||
+ RefPtr<nsPresContext> presContext = GetPresContext();
|
||||
+ if (presContext) {
|
||||
+ presContext->MediaFeatureValuesChanged(
|
||||
+ {MediaFeatureChangeReason::SystemMetricsChange},
|
||||
+ MediaFeatureChangePropagation::JustThisDocument);
|
||||
+ }
|
||||
+ return NS_OK;
|
||||
+}
|
||||
+
|
||||
+// =============== Juggler End =======================
|
||||
+
|
||||
NS_IMETHODIMP
|
||||
nsDocShell::GetIsNavigating(bool* aOut) {
|
||||
*aOut = mIsNavigating;
|
||||
@@ -4695,7 +4920,7 @@ nsDocShell::GetVisibility(bool* aVisibility) {
|
||||
@@ -4715,7 +4959,7 @@ nsDocShell::GetVisibility(bool* aVisibility) {
|
||||
}
|
||||
|
||||
void nsDocShell::ActivenessMaybeChanged() {
|
||||
|
@ -574,7 +630,7 @@ index 4851148d889bdfc12a4b9a463c05d3fb9f1f593f..9f4e3fd0cdfc66b49b1650a05f4b2966
|
|||
if (RefPtr<PresShell> presShell = GetPresShell()) {
|
||||
presShell->ActivenessMaybeChanged();
|
||||
}
|
||||
@@ -6619,6 +6844,10 @@ bool nsDocShell::CanSavePresentation(uint32_t aLoadType,
|
||||
@@ -6639,6 +6883,10 @@ bool nsDocShell::CanSavePresentation(uint32_t aLoadType,
|
||||
return false; // no entry to save into
|
||||
}
|
||||
|
||||
|
@ -585,7 +641,7 @@ index 4851148d889bdfc12a4b9a463c05d3fb9f1f593f..9f4e3fd0cdfc66b49b1650a05f4b2966
|
|||
MOZ_ASSERT(!mozilla::SessionHistoryInParent(),
|
||||
"mOSHE cannot be non-null with SHIP");
|
||||
nsCOMPtr<nsIDocumentViewer> viewer = mOSHE->GetDocumentViewer();
|
||||
@@ -8352,6 +8581,12 @@ nsresult nsDocShell::PerformRetargeting(nsDocShellLoadState* aLoadState) {
|
||||
@@ -8377,6 +8625,12 @@ nsresult nsDocShell::PerformRetargeting(nsDocShellLoadState* aLoadState) {
|
||||
true, // aForceNoOpener
|
||||
getter_AddRefs(newBC));
|
||||
MOZ_ASSERT(!newBC);
|
||||
|
@ -598,7 +654,7 @@ index 4851148d889bdfc12a4b9a463c05d3fb9f1f593f..9f4e3fd0cdfc66b49b1650a05f4b2966
|
|||
return rv;
|
||||
}
|
||||
|
||||
@@ -9506,6 +9741,16 @@ nsresult nsDocShell::InternalLoad(nsDocShellLoadState* aLoadState,
|
||||
@@ -9531,6 +9785,16 @@ nsresult nsDocShell::InternalLoad(nsDocShellLoadState* aLoadState,
|
||||
nsINetworkPredictor::PREDICT_LOAD, attrs, nullptr);
|
||||
|
||||
nsCOMPtr<nsIRequest> req;
|
||||
|
@ -615,7 +671,7 @@ index 4851148d889bdfc12a4b9a463c05d3fb9f1f593f..9f4e3fd0cdfc66b49b1650a05f4b2966
|
|||
rv = DoURILoad(aLoadState, aCacheKey, getter_AddRefs(req));
|
||||
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
@@ -12708,6 +12953,9 @@ class OnLinkClickEvent : public Runnable {
|
||||
@@ -12733,6 +12997,9 @@ class OnLinkClickEvent : public Runnable {
|
||||
mHandler->OnLinkClickSync(mContent, mLoadState, mNoOpenerImplied,
|
||||
mTriggeringPrincipal);
|
||||
}
|
||||
|
@ -625,7 +681,7 @@ index 4851148d889bdfc12a4b9a463c05d3fb9f1f593f..9f4e3fd0cdfc66b49b1650a05f4b2966
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
@@ -12794,6 +13042,8 @@ nsresult nsDocShell::OnLinkClick(
|
||||
@@ -12819,6 +13086,8 @@ nsresult nsDocShell::OnLinkClick(
|
||||
|
||||
nsCOMPtr<nsIRunnable> ev = new OnLinkClickEvent(
|
||||
this, aContent, loadState, noOpenerImplied, aTriggeringPrincipal);
|
||||
|
@ -635,7 +691,7 @@ index 4851148d889bdfc12a4b9a463c05d3fb9f1f593f..9f4e3fd0cdfc66b49b1650a05f4b2966
|
|||
}
|
||||
|
||||
diff --git a/docshell/base/nsDocShell.h b/docshell/base/nsDocShell.h
|
||||
index 888741f8490d6f0e885ed0ce73115c16e7bbe821..0958824d57a082fecae6dde1eb568f457c42f443 100644
|
||||
index 888741f8490d6f0e885ed0ce73115c16e7bbe821..9cb32a1ec61bc4cb67f5fc5bb1fa723055bb1eaa 100644
|
||||
--- a/docshell/base/nsDocShell.h
|
||||
+++ b/docshell/base/nsDocShell.h
|
||||
@@ -15,6 +15,7 @@
|
||||
|
@ -679,7 +735,7 @@ index 888741f8490d6f0e885ed0ce73115c16e7bbe821..0958824d57a082fecae6dde1eb568f45
|
|||
// 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
|
||||
@@ -1282,6 +1295,16 @@ class nsDocShell final : public nsDocLoader,
|
||||
@@ -1282,6 +1295,17 @@ class nsDocShell final : public nsDocLoader,
|
||||
bool mAllowDNSPrefetch : 1;
|
||||
bool mAllowWindowControl : 1;
|
||||
bool mCSSErrorReportingEnabled : 1;
|
||||
|
@ -692,12 +748,13 @@ index 888741f8490d6f0e885ed0ce73115c16e7bbe821..0958824d57a082fecae6dde1eb568f45
|
|||
+ RefPtr<nsGeolocationService> mGeolocationServiceOverride;
|
||||
+ ReducedMotionOverride mReducedMotionOverride;
|
||||
+ ForcedColorsOverride mForcedColorsOverride;
|
||||
+ ContrastOverride mContrastOverride;
|
||||
+
|
||||
bool mAllowAuth : 1;
|
||||
bool mAllowKeywordFixup : 1;
|
||||
bool mDisableMetaRefreshWhenInactive : 1;
|
||||
diff --git a/docshell/base/nsIDocShell.idl b/docshell/base/nsIDocShell.idl
|
||||
index 84e821e33e8164829dfee4f05340784e189b90ee..397742551b98d0d9e6fcf94f55efda5ea628b9ac 100644
|
||||
index 84e821e33e8164829dfee4f05340784e189b90ee..aa690eb747cb73bc6bff40a62546037c2e64c485 100644
|
||||
--- a/docshell/base/nsIDocShell.idl
|
||||
+++ b/docshell/base/nsIDocShell.idl
|
||||
@@ -44,6 +44,7 @@ interface nsIURI;
|
||||
|
@ -708,7 +765,7 @@ index 84e821e33e8164829dfee4f05340784e189b90ee..397742551b98d0d9e6fcf94f55efda5e
|
|||
interface nsIEditor;
|
||||
interface nsIEditingSession;
|
||||
interface nsIInputStream;
|
||||
@@ -719,6 +720,36 @@ interface nsIDocShell : nsIDocShellTreeItem
|
||||
@@ -719,6 +720,45 @@ interface nsIDocShell : nsIDocShellTreeItem
|
||||
*/
|
||||
void synchronizeLayoutHistoryState();
|
||||
|
||||
|
@ -740,16 +797,25 @@ index 84e821e33e8164829dfee4f05340784e189b90ee..397742551b98d0d9e6fcf94f55efda5e
|
|||
+ };
|
||||
+ [infallible] attribute nsIDocShell_ForcedColorsOverride forcedColorsOverride;
|
||||
+
|
||||
+ cenum ContrastOverride : 8 {
|
||||
+ CONTRAST_OVERRIDE_LESS,
|
||||
+ CONTRAST_OVERRIDE_MORE,
|
||||
+ CONTRAST_OVERRIDE_CUSTOM,
|
||||
+ CONTRAST_OVERRIDE_NO_PREFERENCE,
|
||||
+ CONTRAST_OVERRIDE_NONE, /* This clears the override. */
|
||||
+ };
|
||||
+ [infallible] attribute nsIDocShell_ContrastOverride contrastOverride;
|
||||
+
|
||||
+ void setGeolocationOverride(in nsIDOMGeoPosition position);
|
||||
+
|
||||
/**
|
||||
* 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 8680bd4e64abc48233b5babb3b4123fea0e76f3b..44ea1267771cd925df74c716cc26d7ab51b2b985 100644
|
||||
index 46de7024c0e148c5bc9b90553f0ab7c961acdaae..4ce29d24b940940ba7eefab60ac2be55bb888f78 100644
|
||||
--- a/dom/base/Document.cpp
|
||||
+++ b/dom/base/Document.cpp
|
||||
@@ -3741,6 +3741,9 @@ void Document::SendToConsole(nsCOMArray<nsISecurityConsoleMessage>& aMessages) {
|
||||
@@ -3765,6 +3765,9 @@ void Document::SendToConsole(nsCOMArray<nsISecurityConsoleMessage>& aMessages) {
|
||||
}
|
||||
|
||||
void Document::ApplySettingsFromCSP(bool aSpeculative) {
|
||||
|
@ -759,7 +825,7 @@ index 8680bd4e64abc48233b5babb3b4123fea0e76f3b..44ea1267771cd925df74c716cc26d7ab
|
|||
nsresult rv = NS_OK;
|
||||
if (!aSpeculative) {
|
||||
// 1) apply settings from regular CSP
|
||||
@@ -3798,6 +3801,11 @@ nsresult Document::InitCSP(nsIChannel* aChannel) {
|
||||
@@ -3822,6 +3825,11 @@ nsresult Document::InitCSP(nsIChannel* aChannel) {
|
||||
MOZ_ASSERT(!mScriptGlobalObject,
|
||||
"CSP must be initialized before mScriptGlobalObject is set!");
|
||||
|
||||
|
@ -771,7 +837,7 @@ index 8680bd4e64abc48233b5babb3b4123fea0e76f3b..44ea1267771cd925df74c716cc26d7ab
|
|||
// If this is a data document - no need to set CSP.
|
||||
if (mLoadedAsData) {
|
||||
return NS_OK;
|
||||
@@ -4605,6 +4613,10 @@ bool Document::HasFocus(ErrorResult& rv) const {
|
||||
@@ -4629,6 +4637,10 @@ bool Document::HasFocus(ErrorResult& rv) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -782,7 +848,7 @@ index 8680bd4e64abc48233b5babb3b4123fea0e76f3b..44ea1267771cd925df74c716cc26d7ab
|
|||
if (!fm->IsInActiveWindow(bc)) {
|
||||
return false;
|
||||
}
|
||||
@@ -19488,6 +19500,35 @@ ColorScheme Document::PreferredColorScheme(IgnoreRFP aIgnoreRFP) const {
|
||||
@@ -19580,6 +19592,35 @@ ColorScheme Document::PreferredColorScheme(IgnoreRFP aIgnoreRFP) const {
|
||||
return PreferenceSheet::PrefsFor(*this).mColorScheme;
|
||||
}
|
||||
|
||||
|
@ -819,10 +885,10 @@ index 8680bd4e64abc48233b5babb3b4123fea0e76f3b..44ea1267771cd925df74c716cc26d7ab
|
|||
if (!sLoadingForegroundTopLevelContentDocument) {
|
||||
return false;
|
||||
diff --git a/dom/base/Document.h b/dom/base/Document.h
|
||||
index ee5800c51cacc7ac3517c03274ed4e0a35e57f9f..91a8ea64eb40dc0e642e8ab515b78c93e11d7d5a 100644
|
||||
index 7eb244d65eb55322fb16ed185be83cb731fc268c..3cfb3c17c1a769e4d0fc3fa76288c570822dc48f 100644
|
||||
--- a/dom/base/Document.h
|
||||
+++ b/dom/base/Document.h
|
||||
@@ -4123,6 +4123,8 @@ class Document : public nsINode,
|
||||
@@ -4134,6 +4134,8 @@ class Document : public nsINode,
|
||||
// color-scheme meta tag.
|
||||
ColorScheme DefaultColorScheme() const;
|
||||
|
||||
|
@ -895,10 +961,10 @@ index 6abf6cef230c97815f17f6b7abf9f1b1de274a6f..46ead1f32e0d710b5b32e61dff72a4f7
|
|||
dom::MediaCapabilities* MediaCapabilities();
|
||||
dom::MediaSession* MediaSession();
|
||||
diff --git a/dom/base/nsContentUtils.cpp b/dom/base/nsContentUtils.cpp
|
||||
index 13ba7fa81c67f41e419a13c74f9c382193e0eb27..f438d58b594e1434d090eecc89accc66b9f62fcd 100644
|
||||
index 3793f9845d12dc6d88604c43baca744fe559ed21..6f2ec74a8613b72be0ed27cb566bfdbc388a692b 100644
|
||||
--- a/dom/base/nsContentUtils.cpp
|
||||
+++ b/dom/base/nsContentUtils.cpp
|
||||
@@ -8773,7 +8773,8 @@ nsresult nsContentUtils::SendMouseEvent(
|
||||
@@ -8740,7 +8740,8 @@ nsresult nsContentUtils::SendMouseEvent(
|
||||
bool aIgnoreRootScrollFrame, float aPressure,
|
||||
unsigned short aInputSourceArg, uint32_t aIdentifier, bool aToWindow,
|
||||
PreventDefaultResult* aPreventDefault, bool aIsDOMEventSynthesized,
|
||||
|
@ -908,7 +974,7 @@ index 13ba7fa81c67f41e419a13c74f9c382193e0eb27..f438d58b594e1434d090eecc89accc66
|
|||
nsPoint offset;
|
||||
nsCOMPtr<nsIWidget> widget = GetWidget(aPresShell, &offset);
|
||||
if (!widget) return NS_ERROR_FAILURE;
|
||||
@@ -8781,6 +8782,7 @@ nsresult nsContentUtils::SendMouseEvent(
|
||||
@@ -8748,6 +8749,7 @@ nsresult nsContentUtils::SendMouseEvent(
|
||||
EventMessage msg;
|
||||
Maybe<WidgetMouseEvent::ExitFrom> exitFrom;
|
||||
bool contextMenuKey = false;
|
||||
|
@ -916,7 +982,7 @@ index 13ba7fa81c67f41e419a13c74f9c382193e0eb27..f438d58b594e1434d090eecc89accc66
|
|||
if (aType.EqualsLiteral("mousedown")) {
|
||||
msg = eMouseDown;
|
||||
} else if (aType.EqualsLiteral("mouseup")) {
|
||||
@@ -8806,6 +8808,12 @@ nsresult nsContentUtils::SendMouseEvent(
|
||||
@@ -8773,6 +8775,12 @@ nsresult nsContentUtils::SendMouseEvent(
|
||||
msg = eMouseHitTest;
|
||||
} else if (aType.EqualsLiteral("MozMouseExploreByTouch")) {
|
||||
msg = eMouseExploreByTouch;
|
||||
|
@ -929,7 +995,7 @@ index 13ba7fa81c67f41e419a13c74f9c382193e0eb27..f438d58b594e1434d090eecc89accc66
|
|||
} else {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
@@ -8816,7 +8824,14 @@ nsresult nsContentUtils::SendMouseEvent(
|
||||
@@ -8783,7 +8791,14 @@ nsresult nsContentUtils::SendMouseEvent(
|
||||
|
||||
Maybe<WidgetPointerEvent> pointerEvent;
|
||||
Maybe<WidgetMouseEvent> mouseEvent;
|
||||
|
@ -945,7 +1011,7 @@ index 13ba7fa81c67f41e419a13c74f9c382193e0eb27..f438d58b594e1434d090eecc89accc66
|
|||
MOZ_ASSERT(!aIsWidgetEventSynthesized,
|
||||
"The event shouldn't be dispatched as a synthesized event");
|
||||
if (MOZ_UNLIKELY(aIsWidgetEventSynthesized)) {
|
||||
@@ -8835,8 +8850,11 @@ nsresult nsContentUtils::SendMouseEvent(
|
||||
@@ -8802,8 +8817,11 @@ nsresult nsContentUtils::SendMouseEvent(
|
||||
contextMenuKey ? WidgetMouseEvent::eContextMenuKey
|
||||
: WidgetMouseEvent::eNormal);
|
||||
}
|
||||
|
@ -957,7 +1023,7 @@ index 13ba7fa81c67f41e419a13c74f9c382193e0eb27..f438d58b594e1434d090eecc89accc66
|
|||
mouseOrPointerEvent.pointerId = aIdentifier;
|
||||
mouseOrPointerEvent.mModifiers = GetWidgetModifiers(aModifiers);
|
||||
mouseOrPointerEvent.mButton = aButton;
|
||||
@@ -8849,6 +8867,8 @@ nsresult nsContentUtils::SendMouseEvent(
|
||||
@@ -8816,6 +8834,8 @@ nsresult nsContentUtils::SendMouseEvent(
|
||||
mouseOrPointerEvent.mClickCount = aClickCount;
|
||||
mouseOrPointerEvent.mFlags.mIsSynthesizedForTests = aIsDOMEventSynthesized;
|
||||
mouseOrPointerEvent.mExitFrom = exitFrom;
|
||||
|
@ -967,10 +1033,10 @@ index 13ba7fa81c67f41e419a13c74f9c382193e0eb27..f438d58b594e1434d090eecc89accc66
|
|||
nsPresContext* presContext = aPresShell->GetPresContext();
|
||||
if (!presContext) return NS_ERROR_FAILURE;
|
||||
diff --git a/dom/base/nsContentUtils.h b/dom/base/nsContentUtils.h
|
||||
index a1efe3efc7f48f9ff8e2b1a1a50e5dd7bf81f263..caf55fa9dae74ce4ce9ad0369c8cfc8eea061778 100644
|
||||
index ed822f10425272124dd33a74d1cdac5011b1ba6a..0f29ce787f9cebc068d2e5faa9907d52b177283a 100644
|
||||
--- a/dom/base/nsContentUtils.h
|
||||
+++ b/dom/base/nsContentUtils.h
|
||||
@@ -3033,7 +3033,8 @@ class nsContentUtils {
|
||||
@@ -3019,7 +3019,8 @@ class nsContentUtils {
|
||||
int32_t aModifiers, bool aIgnoreRootScrollFrame, float aPressure,
|
||||
unsigned short aInputSourceArg, uint32_t aIdentifier, bool aToWindow,
|
||||
mozilla::PreventDefaultResult* aPreventDefault,
|
||||
|
@ -981,10 +1047,10 @@ index a1efe3efc7f48f9ff8e2b1a1a50e5dd7bf81f263..caf55fa9dae74ce4ce9ad0369c8cfc8e
|
|||
static void FirePageShowEventForFrameLoaderSwap(
|
||||
nsIDocShellTreeItem* aItem,
|
||||
diff --git a/dom/base/nsDOMWindowUtils.cpp b/dom/base/nsDOMWindowUtils.cpp
|
||||
index aae46b9bd2e7756fc025c0597db221c579cac679..5e8ae13a9d1747312cd7df9d80cbb1677530465c 100644
|
||||
index fe23028691d0d06f6b036fd9da2c466730f58cb7..40cfc256510cbddea50574b531ce4369462fd956 100644
|
||||
--- a/dom/base/nsDOMWindowUtils.cpp
|
||||
+++ b/dom/base/nsDOMWindowUtils.cpp
|
||||
@@ -686,6 +686,26 @@ nsDOMWindowUtils::GetPresShellId(uint32_t* aPresShellId) {
|
||||
@@ -710,6 +710,26 @@ nsDOMWindowUtils::GetPresShellId(uint32_t* aPresShellId) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
|
@ -1011,7 +1077,7 @@ index aae46b9bd2e7756fc025c0597db221c579cac679..5e8ae13a9d1747312cd7df9d80cbb167
|
|||
NS_IMETHODIMP
|
||||
nsDOMWindowUtils::SendMouseEvent(
|
||||
const nsAString& aType, float aX, float aY, int32_t aButton,
|
||||
@@ -700,7 +720,7 @@ nsDOMWindowUtils::SendMouseEvent(
|
||||
@@ -724,7 +744,7 @@ nsDOMWindowUtils::SendMouseEvent(
|
||||
aOptionalArgCount >= 7 ? aIdentifier : DEFAULT_MOUSE_POINTER_ID, false,
|
||||
aPreventDefault, aOptionalArgCount >= 4 ? aIsDOMEventSynthesized : true,
|
||||
aOptionalArgCount >= 5 ? aIsWidgetEventSynthesized : false,
|
||||
|
@ -1020,7 +1086,7 @@ index aae46b9bd2e7756fc025c0597db221c579cac679..5e8ae13a9d1747312cd7df9d80cbb167
|
|||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
@@ -718,7 +738,7 @@ nsDOMWindowUtils::SendMouseEventToWindow(
|
||||
@@ -742,7 +762,7 @@ nsDOMWindowUtils::SendMouseEventToWindow(
|
||||
aOptionalArgCount >= 7 ? aIdentifier : DEFAULT_MOUSE_POINTER_ID, true,
|
||||
nullptr, aOptionalArgCount >= 4 ? aIsDOMEventSynthesized : true,
|
||||
aOptionalArgCount >= 5 ? aIsWidgetEventSynthesized : false,
|
||||
|
@ -1029,7 +1095,7 @@ index aae46b9bd2e7756fc025c0597db221c579cac679..5e8ae13a9d1747312cd7df9d80cbb167
|
|||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
@@ -727,13 +747,13 @@ nsDOMWindowUtils::SendMouseEventCommon(
|
||||
@@ -751,13 +771,13 @@ nsDOMWindowUtils::SendMouseEventCommon(
|
||||
int32_t aClickCount, int32_t aModifiers, bool aIgnoreRootScrollFrame,
|
||||
float aPressure, unsigned short aInputSourceArg, uint32_t aPointerId,
|
||||
bool aToWindow, bool* aPreventDefault, bool aIsDOMEventSynthesized,
|
||||
|
@ -1059,10 +1125,10 @@ index 47ff326b202266b1d7d6af8bdfb72776df8a6a93..b8e084b0c788c46345b1455b8257f171
|
|||
MOZ_CAN_RUN_SCRIPT
|
||||
nsresult SendTouchEventCommon(
|
||||
diff --git a/dom/base/nsFocusManager.cpp b/dom/base/nsFocusManager.cpp
|
||||
index 54cf5e2647a8fa097481e972e0ab67928b2abc8b..37ff283278cebc0e058b0345a157036283847de8 100644
|
||||
index a1d3ae3f3cb8d916a9a8bcca4cb515a2ab496ccb..72326b41064f6a86d76aba7b1902851e966365a8 100644
|
||||
--- a/dom/base/nsFocusManager.cpp
|
||||
+++ b/dom/base/nsFocusManager.cpp
|
||||
@@ -1712,6 +1712,10 @@ Maybe<uint64_t> nsFocusManager::SetFocusInner(Element* aNewContent,
|
||||
@@ -1719,6 +1719,10 @@ Maybe<uint64_t> nsFocusManager::SetFocusInner(Element* aNewContent,
|
||||
(GetActiveBrowsingContext() == newRootBrowsingContext);
|
||||
}
|
||||
|
||||
|
@ -1073,7 +1139,7 @@ index 54cf5e2647a8fa097481e972e0ab67928b2abc8b..37ff283278cebc0e058b0345a1570362
|
|||
// Exit fullscreen if a website focuses another window
|
||||
if (StaticPrefs::full_screen_api_exit_on_windowRaise() &&
|
||||
!isElementInActiveWindow && (aFlags & FLAG_RAISE)) {
|
||||
@@ -2297,6 +2301,7 @@ bool nsFocusManager::BlurImpl(BrowsingContext* aBrowsingContextToClear,
|
||||
@@ -2304,6 +2308,7 @@ bool nsFocusManager::BlurImpl(BrowsingContext* aBrowsingContextToClear,
|
||||
bool aIsLeavingDocument, bool aAdjustWidget,
|
||||
bool aRemainActive, Element* aElementToFocus,
|
||||
uint64_t aActionId) {
|
||||
|
@ -1081,7 +1147,7 @@ index 54cf5e2647a8fa097481e972e0ab67928b2abc8b..37ff283278cebc0e058b0345a1570362
|
|||
LOGFOCUS(("<<Blur begin actionid: %" PRIu64 ">>", aActionId));
|
||||
|
||||
// hold a reference to the focused content, which may be null
|
||||
@@ -2343,6 +2348,11 @@ bool nsFocusManager::BlurImpl(BrowsingContext* aBrowsingContextToClear,
|
||||
@@ -2350,6 +2355,11 @@ bool nsFocusManager::BlurImpl(BrowsingContext* aBrowsingContextToClear,
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1093,7 +1159,7 @@ index 54cf5e2647a8fa097481e972e0ab67928b2abc8b..37ff283278cebc0e058b0345a1570362
|
|||
// Keep a ref to presShell since dispatching the DOM event may cause
|
||||
// the document to be destroyed.
|
||||
RefPtr<PresShell> presShell = docShell->GetPresShell();
|
||||
@@ -3020,7 +3030,9 @@ void nsFocusManager::RaiseWindow(nsPIDOMWindowOuter* aWindow,
|
||||
@@ -3064,7 +3074,9 @@ void nsFocusManager::RaiseWindow(nsPIDOMWindowOuter* aWindow,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1105,7 +1171,7 @@ index 54cf5e2647a8fa097481e972e0ab67928b2abc8b..37ff283278cebc0e058b0345a1570362
|
|||
// 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 d8ba9e36c98197a7dd7e1b0781f2477c451fc5fd..f3b5b0417ed9433678c7379d304de263c7bc1f80 100644
|
||||
index 4d0483fb6cc4b7ed6cc9633c72413017e7b49249..9eb60397c94f567cc76fcbeec4585bf92b2fd0f7 100644
|
||||
--- a/dom/base/nsGlobalWindowOuter.cpp
|
||||
+++ b/dom/base/nsGlobalWindowOuter.cpp
|
||||
@@ -2516,10 +2516,16 @@ nsresult nsGlobalWindowOuter::SetNewDocument(Document* aDocument,
|
||||
|
@ -1162,10 +1228,10 @@ index d4347e7a508742986f634e97394a9e3dd435d170..5088520537c8c5c6cc79c1dffd91a68d
|
|||
// Outer windows only.
|
||||
virtual void EnsureSizeAndPositionUpToDate() override;
|
||||
diff --git a/dom/base/nsINode.cpp b/dom/base/nsINode.cpp
|
||||
index 855ba87b5b6db67d7dd207cb54001190c494c61a..f96eb49056dafcbd0ca244dac6d1d4a38e7819ce 100644
|
||||
index 4c1efbb1edf0e7d34a333e2724baf055eefa7e6e..c5d64fba742187c8ebaad8f45e868ab65722d07a 100644
|
||||
--- a/dom/base/nsINode.cpp
|
||||
+++ b/dom/base/nsINode.cpp
|
||||
@@ -1438,6 +1438,61 @@ void nsINode::GetBoxQuadsFromWindowOrigin(const BoxQuadOptions& aOptions,
|
||||
@@ -1437,6 +1437,61 @@ void nsINode::GetBoxQuadsFromWindowOrigin(const BoxQuadOptions& aOptions,
|
||||
mozilla::GetBoxQuadsFromWindowOrigin(this, aOptions, aResult, aRv);
|
||||
}
|
||||
|
||||
|
@ -1271,10 +1337,10 @@ index f32e21752d5013bf143eb45391ab9218debab08e..83763d2354dade2f8d2b7930ba18ae91
|
|||
|
||||
static bool DumpEnabled();
|
||||
diff --git a/dom/chrome-webidl/BrowsingContext.webidl b/dom/chrome-webidl/BrowsingContext.webidl
|
||||
index 28e8d8cb9c61ff8362b2d191d47c3630d2cb0b34..0058e60aaab21f8003bbe1bf3f271c63ed78f29a 100644
|
||||
index 28e8d8cb9c61ff8362b2d191d47c3630d2cb0b34..58c12d597978100507dbc21e88c49c5642a3ba1f 100644
|
||||
--- a/dom/chrome-webidl/BrowsingContext.webidl
|
||||
+++ b/dom/chrome-webidl/BrowsingContext.webidl
|
||||
@@ -61,6 +61,15 @@ enum ForcedColorsOverride {
|
||||
@@ -61,6 +61,26 @@ enum ForcedColorsOverride {
|
||||
"active",
|
||||
};
|
||||
|
||||
|
@ -1286,22 +1352,72 @@ index 28e8d8cb9c61ff8362b2d191d47c3630d2cb0b34..0058e60aaab21f8003bbe1bf3f271c63
|
|||
+ "reduce",
|
||||
+ "no-preference",
|
||||
+};
|
||||
+
|
||||
+/**
|
||||
+ * CSS prefers-contrast values.
|
||||
+ */
|
||||
+enum PrefersContrastOverride {
|
||||
+ "none",
|
||||
+ "no-preference",
|
||||
+ "more",
|
||||
+ "less",
|
||||
+ "custom",
|
||||
+};
|
||||
+
|
||||
/**
|
||||
* Allowed overrides of platform/pref default behaviour for touch events.
|
||||
*/
|
||||
@@ -220,6 +229,9 @@ interface BrowsingContext {
|
||||
@@ -220,6 +240,12 @@ interface BrowsingContext {
|
||||
// Forced-colors simulation, for DevTools
|
||||
[SetterThrows] attribute ForcedColorsOverride forcedColorsOverride;
|
||||
|
||||
+ // Reduced-Motion simulation, for DevTools.
|
||||
+ [SetterThrows] attribute PrefersReducedMotionOverride prefersReducedMotionOverride;
|
||||
+
|
||||
+ // Contrast simulation, for DevTools.
|
||||
+ [SetterThrows] attribute PrefersContrastOverride prefersContrastOverride;
|
||||
+
|
||||
/**
|
||||
* A unique identifier for the browser element that is hosting this
|
||||
* BrowsingContext tree. Every BrowsingContext in the element's tree will
|
||||
diff --git a/dom/fetch/Fetch.cpp b/dom/fetch/Fetch.cpp
|
||||
index a030f3f07b9f4a676f87ac482507056bc911edf5..68bd39affdbbe2e35fdac62b1e159ebc7ffd031c 100644
|
||||
--- a/dom/fetch/Fetch.cpp
|
||||
+++ b/dom/fetch/Fetch.cpp
|
||||
@@ -689,6 +689,12 @@ already_AddRefed<Promise> FetchRequest(nsIGlobalObject* aGlobal,
|
||||
ipcArgs.hasCSPEventListener() = false;
|
||||
ipcArgs.isWorkerRequest() = false;
|
||||
|
||||
+ /* --> Playwright: associate keep-alive fetch with the window */
|
||||
+ BrowsingContext* bc = window ? window->GetBrowsingContext() : nullptr;
|
||||
+ if (bc)
|
||||
+ ipcArgs.associatedBrowsingContextID() = bc->Id();
|
||||
+ /* <-- Playwright */
|
||||
+
|
||||
actor->DoFetchOp(ipcArgs);
|
||||
|
||||
mozilla::glean::networking::fetch_keepalive_request_count.Get("main"_ns)
|
||||
diff --git a/dom/fetch/FetchService.cpp b/dom/fetch/FetchService.cpp
|
||||
index 6fc05bf6cc8341e7cfac63789b79f610bdbe9641..112db3c2e6d2f854103ddacde896904018f5ea90 100644
|
||||
--- a/dom/fetch/FetchService.cpp
|
||||
+++ b/dom/fetch/FetchService.cpp
|
||||
@@ -265,6 +265,14 @@ RefPtr<FetchServicePromises> FetchService::FetchInstance::Fetch() {
|
||||
false // IsTrackingFetch
|
||||
);
|
||||
|
||||
+ /* --> Playwright: associate keep-alive fetch with the window */
|
||||
+ if (mArgsType == FetchArgsType::MainThreadFetch) {
|
||||
+ auto& args = mArgs.as<MainThreadFetchArgs>();
|
||||
+ mFetchDriver->SetAssociatedBrowsingContextID(
|
||||
+ args.mAssociatedBrowsingContextID);
|
||||
+ }
|
||||
+ /* <-- Playwright */
|
||||
+
|
||||
if (mArgsType == FetchArgsType::WorkerFetch) {
|
||||
auto& args = mArgs.as<WorkerFetchArgs>();
|
||||
mFetchDriver->SetWorkerScript(args.mWorkerScript);
|
||||
diff --git a/dom/geolocation/Geolocation.cpp b/dom/geolocation/Geolocation.cpp
|
||||
index 6a624e4c0f5fb8ffff06419a29f6e6acc6108773..370625634f7846d0545ec7ee964d7fa1a15b3ac0 100644
|
||||
index e67cb4efce43b42fa876c906f7f1927c65d34ea7..a42da20dc6aaa1915c611d4bc339a8db974c75eb 100644
|
||||
--- a/dom/geolocation/Geolocation.cpp
|
||||
+++ b/dom/geolocation/Geolocation.cpp
|
||||
@@ -29,6 +29,7 @@
|
||||
|
@ -1400,10 +1516,10 @@ index 992de29b5d2d09c19e55ebb2502215ec9d05a171..cdc20567b693283b0fd5a5923f7ea542
|
|||
~Geolocation();
|
||||
|
||||
diff --git a/dom/html/HTMLInputElement.cpp b/dom/html/HTMLInputElement.cpp
|
||||
index 4b6686880b9fca9bb7c08e850918a4c0f15dac88..825a006b05947ede8ffdfefa4cd12dd5663fb98f 100644
|
||||
index 2e6ef116be73d0794683189c07afc8a629859154..33f4add0cb7736edd416d31d0feca4fd5afc4526 100644
|
||||
--- a/dom/html/HTMLInputElement.cpp
|
||||
+++ b/dom/html/HTMLInputElement.cpp
|
||||
@@ -62,6 +62,7 @@
|
||||
@@ -64,6 +64,7 @@
|
||||
#include "mozilla/dom/Document.h"
|
||||
#include "mozilla/dom/HTMLDataListElement.h"
|
||||
#include "mozilla/dom/HTMLOptionElement.h"
|
||||
|
@ -1426,7 +1542,7 @@ index 4b6686880b9fca9bb7c08e850918a4c0f15dac88..825a006b05947ede8ffdfefa4cd12dd5
|
|||
return NS_OK;
|
||||
}
|
||||
diff --git a/dom/interfaces/base/nsIDOMWindowUtils.idl b/dom/interfaces/base/nsIDOMWindowUtils.idl
|
||||
index b4d065ff3197404f92e099afea9a0dcb4ff79bf1..59448d3c4f1054a8a1c8cb415f36fdeb2983040d 100644
|
||||
index 8ddde6e5de319142ce0898dc3667c08f1f24cce9..9e530a727f06b924e3d0bcf4ba52507231778257 100644
|
||||
--- a/dom/interfaces/base/nsIDOMWindowUtils.idl
|
||||
+++ b/dom/interfaces/base/nsIDOMWindowUtils.idl
|
||||
@@ -374,6 +374,26 @@ interface nsIDOMWindowUtils : nsISupports {
|
||||
|
@ -1457,10 +1573,10 @@ index b4d065ff3197404f92e099afea9a0dcb4ff79bf1..59448d3c4f1054a8a1c8cb415f36fdeb
|
|||
* touchstart, touchend, touchmove, and touchcancel
|
||||
*
|
||||
diff --git a/dom/ipc/BrowserChild.cpp b/dom/ipc/BrowserChild.cpp
|
||||
index 067e8551685497a8d7681296bbb7d7eae1d7587b..5667fbbfff99b77992eac181304093d8afbff367 100644
|
||||
index 93f20a36acef74947d5377df5ff916546218d8b8..22b706b985d22a8c0c278a12ab4944e26ded51a4 100644
|
||||
--- a/dom/ipc/BrowserChild.cpp
|
||||
+++ b/dom/ipc/BrowserChild.cpp
|
||||
@@ -1674,6 +1674,21 @@ void BrowserChild::HandleRealMouseButtonEvent(const WidgetMouseEvent& aEvent,
|
||||
@@ -1676,6 +1676,21 @@ void BrowserChild::HandleRealMouseButtonEvent(const WidgetMouseEvent& aEvent,
|
||||
if (postLayerization) {
|
||||
postLayerization->Register();
|
||||
}
|
||||
|
@ -1730,7 +1846,7 @@ index 3b39538e51840cd9b1685b2efd2ff2e9ec83608a..c7bf4f2d53b58bbacb22b3ebebf6f3fc
|
|||
|
||||
return aGlobalOrNull;
|
||||
diff --git a/dom/security/nsCSPUtils.cpp b/dom/security/nsCSPUtils.cpp
|
||||
index 8c4364190dadd1a58bfd99e2c0dae1524a4e2c0c..ffadb3b4665a804320724b5a12e09cb29ef31498 100644
|
||||
index 32a8d9496e674e752dd3ac41afc7f22ed534dce3..e57690654be4ae18f14d3171fa4eab9ec8aa991f 100644
|
||||
--- a/dom/security/nsCSPUtils.cpp
|
||||
+++ b/dom/security/nsCSPUtils.cpp
|
||||
@@ -23,6 +23,7 @@
|
||||
|
@ -1777,10 +1893,10 @@ index aee376e971ae01ac1e512c3920b115bfaf06afa8..1701311534bf77e6cd9bafc0e3a28361
|
|||
* 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 996b5699166711901ff0d11fe64352b6c9c97d1e..5b08ea6145e6052058a55d6a678fd7539f70baa1 100644
|
||||
index f528bec20afef533e4c6b99c5e9d1680fd0b636e..a0d22d38657f672d865f35c02975e7b611571353 100644
|
||||
--- a/dom/workers/RuntimeService.cpp
|
||||
+++ b/dom/workers/RuntimeService.cpp
|
||||
@@ -1005,7 +1005,7 @@ void PrefLanguagesChanged(const char* /* aPrefName */, void* /* aClosure */) {
|
||||
@@ -1027,7 +1027,7 @@ void PrefLanguagesChanged(const char* /* aPrefName */, void* /* aClosure */) {
|
||||
AssertIsOnMainThread();
|
||||
|
||||
nsTArray<nsString> languages;
|
||||
|
@ -1789,7 +1905,7 @@ index 996b5699166711901ff0d11fe64352b6c9c97d1e..5b08ea6145e6052058a55d6a678fd753
|
|||
|
||||
RuntimeService* runtime = RuntimeService::GetService();
|
||||
if (runtime) {
|
||||
@@ -1193,8 +1193,7 @@ bool RuntimeService::RegisterWorker(WorkerPrivate& aWorkerPrivate) {
|
||||
@@ -1215,8 +1215,7 @@ bool RuntimeService::RegisterWorker(WorkerPrivate& aWorkerPrivate) {
|
||||
}
|
||||
|
||||
// The navigator overridden properties should have already been read.
|
||||
|
@ -1799,7 +1915,7 @@ index 996b5699166711901ff0d11fe64352b6c9c97d1e..5b08ea6145e6052058a55d6a678fd753
|
|||
mNavigatorPropertiesLoaded = true;
|
||||
}
|
||||
|
||||
@@ -1815,6 +1814,13 @@ void RuntimeService::PropagateStorageAccessPermissionGranted(
|
||||
@@ -1837,6 +1836,13 @@ void RuntimeService::PropagateStorageAccessPermissionGranted(
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1813,7 +1929,7 @@ index 996b5699166711901ff0d11fe64352b6c9c97d1e..5b08ea6145e6052058a55d6a678fd753
|
|||
template <typename Func>
|
||||
void RuntimeService::BroadcastAllWorkers(const Func& aFunc) {
|
||||
AssertIsOnMainThread();
|
||||
@@ -2340,6 +2346,14 @@ void PropagateStorageAccessPermissionGrantedToWorkers(
|
||||
@@ -2362,6 +2368,14 @@ void PropagateStorageAccessPermissionGrantedToWorkers(
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2156,10 +2272,10 @@ index 4bfd336ddcbee8004ac538ca7b7d8216d04a61c3..cd22351c4aeacea8afc9828972222aca
|
|||
// No boxes to return
|
||||
return;
|
||||
diff --git a/layout/base/PresShell.cpp b/layout/base/PresShell.cpp
|
||||
index c533494e49d59904b839ea770475ec726c4c897e..1da4eeb774dadb4e3463cbeb17d857ccb6ef76ea 100644
|
||||
index ede24b9c7ac3569d6467ac88bc491d2987ac0bca..a45ebcdf3a3caaad15a8dff0b8ebbec971aac8a6 100644
|
||||
--- a/layout/base/PresShell.cpp
|
||||
+++ b/layout/base/PresShell.cpp
|
||||
@@ -11278,7 +11278,9 @@ bool PresShell::ComputeActiveness() const {
|
||||
@@ -11265,7 +11265,9 @@ bool PresShell::ComputeActiveness() const {
|
||||
if (!browserChild->IsVisible()) {
|
||||
MOZ_LOG(gLog, LogLevel::Debug,
|
||||
(" > BrowserChild %p is not visible", browserChild));
|
||||
|
@ -2171,7 +2287,7 @@ index c533494e49d59904b839ea770475ec726c4c897e..1da4eeb774dadb4e3463cbeb17d857cc
|
|||
|
||||
// If the browser is visible but just due to be preserving layers
|
||||
diff --git a/layout/base/nsLayoutUtils.cpp b/layout/base/nsLayoutUtils.cpp
|
||||
index 1fba8697d48f35e20a69ff861f5da9689c6d6769..77d0de76f346c0563d9b74b667c8400e26a98694 100644
|
||||
index b9f06daa19e5aecb976ad198990a315bc39f736d..1600435c406e2e652abdab72a7947add7266c75a 100644
|
||||
--- a/layout/base/nsLayoutUtils.cpp
|
||||
+++ b/layout/base/nsLayoutUtils.cpp
|
||||
@@ -708,6 +708,10 @@ bool nsLayoutUtils::AllowZoomingForDocument(
|
||||
|
@ -2185,7 +2301,7 @@ index 1fba8697d48f35e20a69ff861f5da9689c6d6769..77d0de76f346c0563d9b74b667c8400e
|
|||
// True if we allow zooming for all documents on this platform, or if we are
|
||||
// in RDM.
|
||||
BrowsingContext* bc = aDocument->GetBrowsingContext();
|
||||
@@ -9709,6 +9713,9 @@ void nsLayoutUtils::ComputeSystemFont(nsFont* aSystemFont,
|
||||
@@ -9748,6 +9752,9 @@ void nsLayoutUtils::ComputeSystemFont(nsFont* aSystemFont,
|
||||
|
||||
/* static */
|
||||
bool nsLayoutUtils::ShouldHandleMetaViewport(const Document* aDocument) {
|
||||
|
@ -2196,10 +2312,10 @@ index 1fba8697d48f35e20a69ff861f5da9689c6d6769..77d0de76f346c0563d9b74b667c8400e
|
|||
return StaticPrefs::dom_meta_viewport_enabled() || (bc && bc->InRDMPane());
|
||||
}
|
||||
diff --git a/layout/style/GeckoBindings.h b/layout/style/GeckoBindings.h
|
||||
index acb5b24776c8591933d1abcbcc7b254cf2ceb4e4..191ddd1f43bd704294727555c3d5137d69c1460c 100644
|
||||
index 3f97c46ee5721c9f5bb9b86e2c0ece552ed00568..52f6c4d600baccc846503373af3476816f4c9fdc 100644
|
||||
--- a/layout/style/GeckoBindings.h
|
||||
+++ b/layout/style/GeckoBindings.h
|
||||
@@ -593,6 +593,7 @@ float Gecko_MediaFeatures_GetResolution(const mozilla::dom::Document*);
|
||||
@@ -595,6 +595,7 @@ float Gecko_MediaFeatures_GetResolution(const mozilla::dom::Document*);
|
||||
bool Gecko_MediaFeatures_PrefersReducedMotion(const mozilla::dom::Document*);
|
||||
bool Gecko_MediaFeatures_PrefersReducedTransparency(
|
||||
const mozilla::dom::Document*);
|
||||
|
@ -2208,7 +2324,7 @@ index acb5b24776c8591933d1abcbcc7b254cf2ceb4e4..191ddd1f43bd704294727555c3d5137d
|
|||
const mozilla::dom::Document*);
|
||||
mozilla::StylePrefersColorScheme Gecko_MediaFeatures_PrefersColorScheme(
|
||||
diff --git a/layout/style/nsMediaFeatures.cpp b/layout/style/nsMediaFeatures.cpp
|
||||
index ca382a3cfba8ce5839890d6e4cb3cf9789287e3b..b1f1b579d7609c6ab93cc0bc52417ea54ab4aeed 100644
|
||||
index ca382a3cfba8ce5839890d6e4cb3cf9789287e3b..5800fc23dc77ee5764beddd6fa48a7fd701d2939 100644
|
||||
--- a/layout/style/nsMediaFeatures.cpp
|
||||
+++ b/layout/style/nsMediaFeatures.cpp
|
||||
@@ -264,11 +264,7 @@ bool Gecko_MediaFeatures_MatchesPlatform(StylePlatform aPlatform) {
|
||||
|
@ -2224,6 +2340,27 @@ index ca382a3cfba8ce5839890d6e4cb3cf9789287e3b..b1f1b579d7609c6ab93cc0bc52417ea5
|
|||
}
|
||||
|
||||
bool Gecko_MediaFeatures_PrefersReducedTransparency(const Document* aDocument) {
|
||||
@@ -293,6 +289,20 @@ StylePrefersColorScheme Gecko_MediaFeatures_PrefersColorScheme(
|
||||
// as a signal.
|
||||
StylePrefersContrast Gecko_MediaFeatures_PrefersContrast(
|
||||
const Document* aDocument) {
|
||||
+ if (auto* bc = aDocument->GetBrowsingContext()) {
|
||||
+ switch (bc->Top()->PrefersContrastOverride()) {
|
||||
+ case dom::PrefersContrastOverride::No_preference:
|
||||
+ return StylePrefersContrast::NoPreference;
|
||||
+ case dom::PrefersContrastOverride::Less:
|
||||
+ return StylePrefersContrast::Less;
|
||||
+ case dom::PrefersContrastOverride::More:
|
||||
+ return StylePrefersContrast::More;
|
||||
+ case dom::PrefersContrastOverride::Custom:
|
||||
+ return StylePrefersContrast::Custom;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+
|
||||
if (aDocument->ShouldResistFingerprinting(RFPTarget::CSSPrefersContrast)) {
|
||||
return StylePrefersContrast::NoPreference;
|
||||
}
|
||||
diff --git a/netwerk/base/LoadInfo.cpp b/netwerk/base/LoadInfo.cpp
|
||||
index 06acdc629c2b6ee0e29c50d8edc5a96d343b1ef2..6c263edf54117fd9cbf4a77abc396f1238730880 100644
|
||||
--- a/netwerk/base/LoadInfo.cpp
|
||||
|
@ -2289,10 +2426,10 @@ index 5984a0a196615cca5544de052874cbb163a8233b..3617816a06651ae65c214ebd5f0affed
|
|||
} // namespace net
|
||||
} // namespace mozilla
|
||||
diff --git a/netwerk/base/nsILoadInfo.idl b/netwerk/base/nsILoadInfo.idl
|
||||
index 50dfc8767a99eef5e8748d648995f3cd7cc81a73..32a171eac26376144482bc7d90e8662a0e719f47 100644
|
||||
index 2d77b8aa8799ec6bb7f38722e837d070f9057ea6..0261e58afd17c78a1484ec55e45bf34442929200 100644
|
||||
--- a/netwerk/base/nsILoadInfo.idl
|
||||
+++ b/netwerk/base/nsILoadInfo.idl
|
||||
@@ -1616,4 +1616,6 @@ interface nsILoadInfo : nsISupports
|
||||
@@ -1609,4 +1609,6 @@ interface nsILoadInfo : nsISupports
|
||||
* When true, this load will never be upgraded to HTTPS.
|
||||
*/
|
||||
[infallible] attribute boolean skipHTTPSUpgrade;
|
||||
|
@ -2312,10 +2449,10 @@ index 7f91d2df6f8bb4020c75c132dc8f6bf26625fa1e..ba6569f4be8fc54ec96ee44d5de45a09
|
|||
/**
|
||||
* Set the status and reason for the forthcoming synthesized response.
|
||||
diff --git a/netwerk/ipc/DocumentLoadListener.cpp b/netwerk/ipc/DocumentLoadListener.cpp
|
||||
index 8b392845d07f50dddf016770836107614b6b9753..b0817d1b660dbb2dd856daf30ec9ec0fcb3d2aeb 100644
|
||||
index cdf9ad443ebf49eabc362fd555ae54c09502395c..8bca3d81fef7b59334b2ab0cdccdd80ef19c675d 100644
|
||||
--- a/netwerk/ipc/DocumentLoadListener.cpp
|
||||
+++ b/netwerk/ipc/DocumentLoadListener.cpp
|
||||
@@ -172,6 +172,7 @@ static auto CreateDocumentLoadInfo(CanonicalBrowsingContext* aBrowsingContext,
|
||||
@@ -175,6 +175,7 @@ static auto CreateDocumentLoadInfo(CanonicalBrowsingContext* aBrowsingContext,
|
||||
loadInfo->SetTextDirectiveUserActivation(
|
||||
aLoadState->GetTextDirectiveUserActivation());
|
||||
loadInfo->SetIsMetaRefresh(aLoadState->IsMetaRefresh());
|
||||
|
@ -2324,10 +2461,10 @@ index 8b392845d07f50dddf016770836107614b6b9753..b0817d1b660dbb2dd856daf30ec9ec0f
|
|||
return loadInfo.forget();
|
||||
}
|
||||
diff --git a/netwerk/protocol/http/InterceptedHttpChannel.cpp b/netwerk/protocol/http/InterceptedHttpChannel.cpp
|
||||
index b7c129dcc21cb5d5478765f6aa06ed047aee6de0..6f3881c002c5f6d3e48865d253515ffd4d24bcf6 100644
|
||||
index 6d8d65c9335583d28aa3be8c05c065fa5aede908..6ab815111954b53b4dc722a1babe6b49a78bbedc 100644
|
||||
--- a/netwerk/protocol/http/InterceptedHttpChannel.cpp
|
||||
+++ b/netwerk/protocol/http/InterceptedHttpChannel.cpp
|
||||
@@ -726,6 +726,14 @@ NS_IMPL_ISUPPORTS(ResetInterceptionHeaderVisitor, nsIHttpHeaderVisitor)
|
||||
@@ -727,6 +727,14 @@ NS_IMPL_ISUPPORTS(ResetInterceptionHeaderVisitor, nsIHttpHeaderVisitor)
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
|
@ -2342,7 +2479,7 @@ index b7c129dcc21cb5d5478765f6aa06ed047aee6de0..6f3881c002c5f6d3e48865d253515ffd
|
|||
NS_IMETHODIMP
|
||||
InterceptedHttpChannel::ResetInterception(bool aBypass) {
|
||||
INTERCEPTED_LOG(("InterceptedHttpChannel::ResetInterception [%p] bypass: %s",
|
||||
@@ -1139,11 +1147,18 @@ InterceptedHttpChannel::OnStartRequest(nsIRequest* aRequest) {
|
||||
@@ -1140,11 +1148,18 @@ InterceptedHttpChannel::OnStartRequest(nsIRequest* aRequest) {
|
||||
GetCallback(mProgressSink);
|
||||
}
|
||||
|
||||
|
@ -2377,10 +2514,10 @@ index d05b06c3f9ddba3b40d5969730474eaf0d843cb1..9b2cc35c504e1044ac681c62c107f8fe
|
|||
nsCOMPtr<nsIContentSecurityPolicy> preloadCsp = mDocument->GetPreloadCsp();
|
||||
if (!preloadCsp) {
|
||||
diff --git a/security/manager/ssl/nsCertOverrideService.cpp b/security/manager/ssl/nsCertOverrideService.cpp
|
||||
index b2e328e7c7d7a89be34b84fd176c306a3620c77c..54f24b213bcdc78c702e15d4d45a3943bc082281 100644
|
||||
index 1b9f32fc97bf3c5000db95567eaab85b518fe03a..3a39859d58b05b373e9db7ebb2b7ae37166d74e7 100644
|
||||
--- a/security/manager/ssl/nsCertOverrideService.cpp
|
||||
+++ b/security/manager/ssl/nsCertOverrideService.cpp
|
||||
@@ -439,7 +439,12 @@ nsCertOverrideService::HasMatchingOverride(
|
||||
@@ -433,7 +433,12 @@ nsCertOverrideService::HasMatchingOverride(
|
||||
bool disableAllSecurityCheck = false;
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
|
@ -2394,7 +2531,7 @@ index b2e328e7c7d7a89be34b84fd176c306a3620c77c..54f24b213bcdc78c702e15d4d45a3943
|
|||
}
|
||||
if (disableAllSecurityCheck) {
|
||||
*aIsTemporary = false;
|
||||
@@ -651,14 +656,24 @@ static bool IsDebugger() {
|
||||
@@ -645,14 +650,24 @@ static bool IsDebugger() {
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsCertOverrideService::
|
||||
|
@ -2450,7 +2587,7 @@ index 6dfd07d6b676a99993408921de8dea9d561f201d..e3c6794363cd6336effbeac83a179f37
|
|||
readonly attribute boolean securityCheckDisabled;
|
||||
};
|
||||
diff --git a/services/settings/Utils.sys.mjs b/services/settings/Utils.sys.mjs
|
||||
index 12fef6cde815a9301944c399a58f27a7e4c4d5d7..0f7f06d1002a089547d1b15d7d8ddf264f28b529 100644
|
||||
index d3643aedf21a26594268a47bc0f6ac53e3977f75..795c6f3b28278b9f65a596799d4e424880fcffa7 100644
|
||||
--- a/services/settings/Utils.sys.mjs
|
||||
+++ b/services/settings/Utils.sys.mjs
|
||||
@@ -97,7 +97,7 @@ const _cdnURLs = {};
|
||||
|
@ -2504,10 +2641,10 @@ index 8b975a8b11bcf2eabbb7fa51a431ff99ff69a5bc..0eeb5924c43a21b8561dd4b68fa89228
|
|||
|
||||
if (provider.failed) {
|
||||
diff --git a/toolkit/components/resistfingerprinting/nsUserCharacteristics.cpp b/toolkit/components/resistfingerprinting/nsUserCharacteristics.cpp
|
||||
index 77496e700eebbf286e8c5175ea1f77f8576fde1f..3d13e9b316284eaef5d4c82087117020ca8aba71 100644
|
||||
index cb235ed5b39fe5092a17b12976121ee3952d2062..649bba4c0faab1745f3040eba5925ef48b8f34f9 100644
|
||||
--- a/toolkit/components/resistfingerprinting/nsUserCharacteristics.cpp
|
||||
+++ b/toolkit/components/resistfingerprinting/nsUserCharacteristics.cpp
|
||||
@@ -488,7 +488,7 @@ void PopulateLanguages() {
|
||||
@@ -489,7 +489,7 @@ void PopulateLanguages() {
|
||||
// sufficient to only collect this information as the other properties are
|
||||
// just reformats of Navigator::GetAcceptLanguages.
|
||||
nsTArray<nsString> languages;
|
||||
|
@ -2517,10 +2654,10 @@ index 77496e700eebbf286e8c5175ea1f77f8576fde1f..3d13e9b316284eaef5d4c82087117020
|
|||
|
||||
for (const auto& language : languages) {
|
||||
diff --git a/toolkit/components/startup/nsAppStartup.cpp b/toolkit/components/startup/nsAppStartup.cpp
|
||||
index 9297e5eacd65658289dda764ad39e182c22d192b..15926f106850637a5bbd27e56834dc5c82791250 100644
|
||||
index dc0826f72134b91482e30d183ddf52e95146e12f..119a324e162b6965ddd3d6b2d53bd2856a174452 100644
|
||||
--- a/toolkit/components/startup/nsAppStartup.cpp
|
||||
+++ b/toolkit/components/startup/nsAppStartup.cpp
|
||||
@@ -365,7 +365,7 @@ nsAppStartup::Quit(uint32_t aMode, int aExitCode, bool* aUserAllowedQuit) {
|
||||
@@ -361,7 +361,7 @@ nsAppStartup::Quit(uint32_t aMode, int aExitCode, bool* aUserAllowedQuit) {
|
||||
nsCOMPtr<nsISimpleEnumerator> windowEnumerator;
|
||||
nsCOMPtr<nsIWindowMediator> mediator(
|
||||
do_GetService(NS_WINDOWMEDIATOR_CONTRACTID));
|
||||
|
@ -2562,10 +2699,10 @@ index 585a957fd8a1467dc262bd1ca2058584fd8762c9..16ad38c3b7d753c386e091af700d1beb
|
|||
|
||||
/**
|
||||
diff --git a/toolkit/mozapps/update/UpdateService.sys.mjs b/toolkit/mozapps/update/UpdateService.sys.mjs
|
||||
index eeec31f4d77de0f9622692eeb761392ed54b90ad..8907773fb6212f4e9cc184f87b840c91a0db67b6 100644
|
||||
index b050236a46af8dc5f1b72559065c813db9343088..85873915d92d49fddff3b7c4330cbe2d39719f39 100644
|
||||
--- a/toolkit/mozapps/update/UpdateService.sys.mjs
|
||||
+++ b/toolkit/mozapps/update/UpdateService.sys.mjs
|
||||
@@ -3811,6 +3811,8 @@ export class UpdateService {
|
||||
@@ -3752,6 +3752,8 @@ export class UpdateService {
|
||||
}
|
||||
|
||||
get disabledForTesting() {
|
||||
|
@ -2587,10 +2724,10 @@ index c50b7f3932e18da9fad4b673e353974a001e78c4..708e0d75594ddcd62276d4e08c4bd5c6
|
|||
]
|
||||
|
||||
diff --git a/toolkit/xre/nsAppRunner.cpp b/toolkit/xre/nsAppRunner.cpp
|
||||
index 795fc8669cc6f03a57139745f58963ffefe5aeff..46651bb0b53be9a522e1deb90140bf386a9f8b51 100644
|
||||
index 7a16ea20770dd945e743871e15e5b02da4842c60..e608a81c3b069b4a020da2d7807556975f603d33 100644
|
||||
--- a/toolkit/xre/nsAppRunner.cpp
|
||||
+++ b/toolkit/xre/nsAppRunner.cpp
|
||||
@@ -5633,7 +5633,10 @@ nsresult XREMain::XRE_mainRun() {
|
||||
@@ -5669,7 +5669,10 @@ nsresult XREMain::XRE_mainRun() {
|
||||
|
||||
if (!AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) {
|
||||
#ifdef XP_MACOSX
|
||||
|
@ -2655,7 +2792,7 @@ index e5cc386651e192710b61858ab5625c97a02b92da..e560ad4fef232a26ce1e1b244f4ccea0
|
|||
// nsDocumentViewer::LoadComplete that doesn't do various things
|
||||
// that are not relevant here because this wasn't an actual
|
||||
diff --git a/uriloader/exthandler/nsExternalHelperAppService.cpp b/uriloader/exthandler/nsExternalHelperAppService.cpp
|
||||
index c4dc15918032a34d8be9f1cda94a9375466980f6..20756dc9166f9665d408cd007e9df55b5937b73c 100644
|
||||
index 418902fc1d00a2f0bc06bd68402e8bb342485c0a..101a7b63128743862d404c3fcadaa2aa886a7f8a 100644
|
||||
--- a/uriloader/exthandler/nsExternalHelperAppService.cpp
|
||||
+++ b/uriloader/exthandler/nsExternalHelperAppService.cpp
|
||||
@@ -112,6 +112,7 @@
|
||||
|
@ -2666,7 +2803,7 @@ index c4dc15918032a34d8be9f1cda94a9375466980f6..20756dc9166f9665d408cd007e9df55b
|
|||
#include "mozilla/Preferences.h"
|
||||
#include "mozilla/ipc/URIUtils.h"
|
||||
|
||||
@@ -864,6 +865,12 @@ NS_IMETHODIMP nsExternalHelperAppService::ApplyDecodingForExtension(
|
||||
@@ -865,6 +866,12 @@ NS_IMETHODIMP nsExternalHelperAppService::ApplyDecodingForExtension(
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -2679,7 +2816,7 @@ index c4dc15918032a34d8be9f1cda94a9375466980f6..20756dc9166f9665d408cd007e9df55b
|
|||
nsresult nsExternalHelperAppService::GetFileTokenForPath(
|
||||
const char16_t* aPlatformAppPath, nsIFile** aFile) {
|
||||
nsDependentString platformAppPath(aPlatformAppPath);
|
||||
@@ -1485,7 +1492,12 @@ nsresult nsExternalAppHandler::SetUpTempFile(nsIChannel* aChannel) {
|
||||
@@ -1486,7 +1493,12 @@ nsresult nsExternalAppHandler::SetUpTempFile(nsIChannel* aChannel) {
|
||||
// Strip off the ".part" from mTempLeafName
|
||||
mTempLeafName.Truncate(mTempLeafName.Length() - std::size(".part") + 1);
|
||||
|
||||
|
@ -2692,7 +2829,7 @@ index c4dc15918032a34d8be9f1cda94a9375466980f6..20756dc9166f9665d408cd007e9df55b
|
|||
mSaver =
|
||||
do_CreateInstance(NS_BACKGROUNDFILESAVERSTREAMLISTENER_CONTRACTID, &rv);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
@@ -1671,7 +1683,36 @@ NS_IMETHODIMP nsExternalAppHandler::OnStartRequest(nsIRequest* request) {
|
||||
@@ -1672,7 +1684,36 @@ NS_IMETHODIMP nsExternalAppHandler::OnStartRequest(nsIRequest* request) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -2730,7 +2867,7 @@ index c4dc15918032a34d8be9f1cda94a9375466980f6..20756dc9166f9665d408cd007e9df55b
|
|||
if (NS_FAILED(rv)) {
|
||||
nsresult transferError = rv;
|
||||
|
||||
@@ -1732,6 +1773,9 @@ NS_IMETHODIMP nsExternalAppHandler::OnStartRequest(nsIRequest* request) {
|
||||
@@ -1733,6 +1774,9 @@ NS_IMETHODIMP nsExternalAppHandler::OnStartRequest(nsIRequest* request) {
|
||||
|
||||
bool alwaysAsk = true;
|
||||
mMimeInfo->GetAlwaysAskBeforeHandling(&alwaysAsk);
|
||||
|
@ -2740,7 +2877,7 @@ index c4dc15918032a34d8be9f1cda94a9375466980f6..20756dc9166f9665d408cd007e9df55b
|
|||
if (alwaysAsk) {
|
||||
// But we *don't* ask if this mimeInfo didn't come from
|
||||
// our user configuration datastore and the user has said
|
||||
@@ -2248,6 +2292,16 @@ nsExternalAppHandler::OnSaveComplete(nsIBackgroundFileSaver* aSaver,
|
||||
@@ -2249,6 +2293,16 @@ nsExternalAppHandler::OnSaveComplete(nsIBackgroundFileSaver* aSaver,
|
||||
NotifyTransfer(aStatus);
|
||||
}
|
||||
|
||||
|
@ -2757,7 +2894,7 @@ index c4dc15918032a34d8be9f1cda94a9375466980f6..20756dc9166f9665d408cd007e9df55b
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
@@ -2731,6 +2785,15 @@ NS_IMETHODIMP nsExternalAppHandler::Cancel(nsresult aReason) {
|
||||
@@ -2732,6 +2786,15 @@ NS_IMETHODIMP nsExternalAppHandler::Cancel(nsresult aReason) {
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2868,10 +3005,10 @@ index 1c25e9d9a101233f71e92288a0f93125b81ac1c5..22cf67b0f6e3ddd2b3ed725a314ba6a9
|
|||
}
|
||||
#endif
|
||||
diff --git a/widget/MouseEvents.h b/widget/MouseEvents.h
|
||||
index c4e510441cf6329b2ad898034fbe39fa1a701ad4..e34d797ee3e3f2985e6d4472afce133e97a0caa4 100644
|
||||
index 5ca1a6fa13233b1bd00ee0467732c5875c51d343..0d3b8ebe127e59516802e8819f4bbed961f0992b 100644
|
||||
--- a/widget/MouseEvents.h
|
||||
+++ b/widget/MouseEvents.h
|
||||
@@ -363,6 +363,9 @@ class WidgetMouseEvent : public WidgetMouseEventBase,
|
||||
@@ -368,6 +368,9 @@ class WidgetMouseEvent : public WidgetMouseEventBase,
|
||||
// Otherwise, this must be 0.
|
||||
uint32_t mClickCount = 0;
|
||||
|
||||
|
@ -2881,7 +3018,7 @@ index c4e510441cf6329b2ad898034fbe39fa1a701ad4..e34d797ee3e3f2985e6d4472afce133e
|
|||
// Whether the event should ignore scroll frame bounds during dispatch.
|
||||
bool mIgnoreRootScrollFrame = false;
|
||||
|
||||
@@ -386,6 +389,7 @@ class WidgetMouseEvent : public WidgetMouseEventBase,
|
||||
@@ -391,6 +394,7 @@ class WidgetMouseEvent : public WidgetMouseEventBase,
|
||||
mContextMenuTrigger = aEvent.mContextMenuTrigger;
|
||||
mExitFrom = aEvent.mExitFrom;
|
||||
mClickCount = aEvent.mClickCount;
|
||||
|
@ -2953,7 +3090,7 @@ index e4bdf715e2fb899e97a5bfeb2e147127460d6047..3554f919480278b7353617481c7ce805
|
|||
}
|
||||
if (aEvent.IsMeta()) {
|
||||
diff --git a/widget/gtk/nsFilePicker.cpp b/widget/gtk/nsFilePicker.cpp
|
||||
index ad56ab325bb3b3c348259f852453eec1190d892b..272731c25d19e83a2da988ec3176e5227dc4da5b 100644
|
||||
index f4bded345e95674c66e4d4ad56b50844fce0871b..321e22d334a8bbc6057ee78e77e139a2804b2403 100644
|
||||
--- a/widget/gtk/nsFilePicker.cpp
|
||||
+++ b/widget/gtk/nsFilePicker.cpp
|
||||
@@ -21,6 +21,7 @@
|
||||
|
@ -3114,7 +3251,7 @@ index facd2bc65afab8ec1aa322faa20a67464964dfb9..d6dea95472bec6006411753c3dfdab2e
|
|||
|
||||
} // namespace widget
|
||||
diff --git a/widget/headless/HeadlessWidget.cpp b/widget/headless/HeadlessWidget.cpp
|
||||
index b8b3f6a09f3fd480f67c28a2d3c6daa960946324..8b9ea637e18c404254ca8a72dabf860452699096 100644
|
||||
index daa2d455374fd9f75a5c6ac9f7b91696d88b065c..f45184137b52db0a5774bf3365b15f784532fbdf 100644
|
||||
--- a/widget/headless/HeadlessWidget.cpp
|
||||
+++ b/widget/headless/HeadlessWidget.cpp
|
||||
@@ -111,6 +111,8 @@ void HeadlessWidget::Destroy() {
|
||||
|
@ -3126,7 +3263,7 @@ index b8b3f6a09f3fd480f67c28a2d3c6daa960946324..8b9ea637e18c404254ca8a72dabf8604
|
|||
nsBaseWidget::OnDestroy();
|
||||
|
||||
nsBaseWidget::Destroy();
|
||||
@@ -591,5 +593,14 @@ nsresult HeadlessWidget::SynthesizeNativeTouchpadPan(
|
||||
@@ -593,5 +595,14 @@ nsresult HeadlessWidget::SynthesizeNativeTouchpadPan(
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -3142,10 +3279,10 @@ index b8b3f6a09f3fd480f67c28a2d3c6daa960946324..8b9ea637e18c404254ca8a72dabf8604
|
|||
} // namespace widget
|
||||
} // namespace mozilla
|
||||
diff --git a/widget/headless/HeadlessWidget.h b/widget/headless/HeadlessWidget.h
|
||||
index f07f7284495cf5e48409866aaef6fd4d529568be..daefaa8f58c3c8392ce229c814807bc5fff77dc7 100644
|
||||
index 39833c28e40c61e354119cde429b8389056bafac..a638fb7520b857219ce58fcbf9ca0ed939528924 100644
|
||||
--- a/widget/headless/HeadlessWidget.h
|
||||
+++ b/widget/headless/HeadlessWidget.h
|
||||
@@ -136,6 +136,9 @@ class HeadlessWidget : public nsBaseWidget {
|
||||
@@ -132,6 +132,9 @@ class HeadlessWidget final : public nsBaseWidget {
|
||||
int32_t aModifierFlags,
|
||||
nsIObserver* aObserver) override;
|
||||
|
||||
|
|
|
@ -8,6 +8,10 @@ pref("dom.input_events.security.minTimeElapsedInMS", 0);
|
|||
|
||||
pref("dom.iframe_lazy_loading.enabled", false);
|
||||
|
||||
// This setting is experimental and is only enabled on early betas.
|
||||
// Disable it unconditionally since it breaks proxy tests.
|
||||
pref("dom.security.https_first", false);
|
||||
|
||||
pref("datareporting.policy.dataSubmissionEnabled", false);
|
||||
pref("datareporting.policy.dataSubmissionPolicyAccepted", false);
|
||||
pref("datareporting.policy.dataSubmissionPolicyBypassNotification", true);
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
REMOTE_URL="https://github.com/WebKit/WebKit.git"
|
||||
BASE_BRANCH="main"
|
||||
BASE_REVISION="71b1107b4656f116936a278cb4948cc2db56998c"
|
||||
BASE_REVISION="ba8bcf39b0a89706b998447abba82590ad50fc36"
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,2 +1 @@
|
|||
winldd-win64.zip
|
||||
winldd-win64.tar.br
|
||||
|
|
|
@ -206,6 +206,12 @@ Below is the HTML markup and the respective ARIA snapshot:
|
|||
- link "About"
|
||||
```
|
||||
|
||||
### option: Locator.ariaSnapshot.emitGeneric
|
||||
* since: v1.53
|
||||
- `emitGeneric` <[boolean]>
|
||||
|
||||
Generate `generic` aria nodes for elements w/o roles (similar to Chrome DevTools).
|
||||
|
||||
### option: Locator.ariaSnapshot.ref
|
||||
* since: v1.52
|
||||
- `ref` <[boolean]>
|
||||
|
@ -871,6 +877,37 @@ If [`param: expression`] throws or rejects, this method throws.
|
|||
|
||||
**Usage**
|
||||
|
||||
Passing argument to [`param: expression`]:
|
||||
|
||||
```js
|
||||
const result = await page.getByTestId('myId').evaluate((element, [x, y]) => {
|
||||
return element.textContent + ' ' + x * y;
|
||||
}, [7, 8]);
|
||||
console.log(result); // prints "myId text 56"
|
||||
```
|
||||
|
||||
```java
|
||||
Object result = page.getByTestId("myId").evaluate("(element, [x, y]) => {\n" +
|
||||
" return element.textContent + ' ' + x * y;\n" +
|
||||
"}", Arrays.asList(7, 8));
|
||||
System.out.println(result); // prints "myId text 56"
|
||||
```
|
||||
|
||||
```python async
|
||||
result = await page.get_by_testid("myId").evaluate("(element, [x, y]) => element.textContent + ' ' + x * y", [7, 8])
|
||||
print(result) # prints "myId text 56"
|
||||
```
|
||||
|
||||
```python sync
|
||||
result = page.get_by_testid("myId").evaluate("(element, [x, y]) => element.textContent + ' ' + x * y", [7, 8])
|
||||
print(result) # prints "myId text 56"
|
||||
```
|
||||
|
||||
```csharp
|
||||
var result = await page.GetByTestId("myId").EvaluateAsync<string>("(element, [x, y]) => element.textContent + ' ' + x * y)", new[] { 7, 8 });
|
||||
Console.WriteLine(result); // prints "myId text 56"
|
||||
```
|
||||
|
||||
### param: Locator.evaluate.expression = %%-evaluate-expression-%%
|
||||
* since: v1.14
|
||||
|
||||
|
|
|
@ -284,7 +284,7 @@ By default, a template containing the subset of children will be matched:
|
|||
|
||||
|
||||
The `/children` property can be used to control how child elements are matched:
|
||||
- `contain` (default): Matches if all specified children are present in any order
|
||||
- `contain` (default): Matches if all specified children are present in order
|
||||
- `equal`: Matches if the children exactly match the specified list in order
|
||||
- `deep-equal`: Matches if the children exactly match the specified list in order, including nested children
|
||||
|
||||
|
@ -296,7 +296,7 @@ The `/children` property can be used to control how child elements are matched:
|
|||
</ul>
|
||||
```
|
||||
|
||||
*aria snapshot will fail due Feature C not being in the template*
|
||||
*aria snapshot will fail due to Feature C not being in the template*
|
||||
|
||||
```yaml
|
||||
- list
|
||||
|
|
|
@ -81,7 +81,7 @@ The `tests` folder contains a basic example test to help you get started with te
|
|||
|
||||
## Running the Example Test
|
||||
|
||||
By default tests will be run on all 3 browsers, Chromium, Firefox and WebKit using 3 workers. This can be configured in the [playwright.config file](./test-configuration.md). Tests are run in headless mode meaning no browser will open up when running the tests. Results of the tests and test logs will be shown in the terminal.
|
||||
By default tests will be run on all 3 browsers, Chromium, Firefox and WebKit using several workers. This can be configured in the [playwright.config file](./test-configuration.md). Tests are run in headless mode meaning no browser will open up when running the tests. Results of the tests and test logs will be shown in the terminal.
|
||||
|
||||
<Tabs
|
||||
groupId="js-package-manager"
|
||||
|
|
|
@ -4,6 +4,53 @@ title: "Release notes"
|
|||
toc_max_heading_level: 2
|
||||
---
|
||||
|
||||
## Version 1.52
|
||||
|
||||
### Highlights
|
||||
|
||||
- New method [`method: LocatorAssertions.toContainClass`] to ergonomically assert individual class names on the element.
|
||||
|
||||
```csharp
|
||||
await Expect(Page.GetByRole(AriaRole.Listitem, new() { Name = "Ship v1.52" })).ToContainClassAsync("done");
|
||||
```
|
||||
|
||||
- [Aria Snapshots](./aria-snapshots.md) got two new properties: [`/children`](./aria-snapshots.md#strict-matching) for strict matching and `/url` for links.
|
||||
|
||||
```csharp
|
||||
await Expect(locator).ToMatchAriaSnapshotAsync(@"
|
||||
- list
|
||||
- /children: equal
|
||||
- listitem: Feature A
|
||||
- listitem:
|
||||
- link ""Feature B"":
|
||||
- /url: ""https://playwright.dev""
|
||||
");
|
||||
```
|
||||
|
||||
### Miscellaneous
|
||||
|
||||
- New option [`option: APIRequest.newContext.maxRedirects`] in [`method: APIRequest.newContext`] to control the maximum number of redirects.
|
||||
- New option [`option: Locator.ariaSnapshot.ref`] in [`method: Locator.ariaSnapshot`] to generate reference for each element in the snapshot which can later be used to locate the element.
|
||||
- HTML reporter now supports *NOT filtering* via `!@my-tag` or `!my-file.spec.ts` or `!p:my-project`.
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
- Base URL matching is not supported in [`method: Page.frame`] anymore. We recommend migrating to [`method: Page.frameLocator`] instead for having a more convenient API.
|
||||
- Glob URL patterns in methods like [`method: Page.route`] do not support `?` and `[]` anymore. We recommend using regular expressions instead.
|
||||
- Method [`method: Route.continue`] does not allow to override the `Cookie` header anymore. If a `Cookie` header is provided, it will be ignored, and the cookie will be loaded from the browser's cookie store. To set custom cookies, use [`method: BrowserContext.addCookies`].
|
||||
- macOS 13 is now deprecated and will no longer receive WebKit updates. Please upgrade to a more recent macOS version to continue benefiting from the latest WebKit improvements.
|
||||
|
||||
### Browser Versions
|
||||
|
||||
- Chromium 136.0.7103.25
|
||||
- Mozilla Firefox 137.0
|
||||
- WebKit 18.4
|
||||
|
||||
This version was also tested against the following stable channels:
|
||||
|
||||
- Google Chrome 135
|
||||
- Microsoft Edge 135
|
||||
|
||||
## Version 1.51
|
||||
|
||||
### Highlights
|
||||
|
|
|
@ -4,6 +4,53 @@ title: "Release notes"
|
|||
toc_max_heading_level: 2
|
||||
---
|
||||
|
||||
## Version 1.52
|
||||
|
||||
### Highlights
|
||||
|
||||
- New method [`method: LocatorAssertions.toContainClass`] to ergonomically assert individual class names on the element.
|
||||
|
||||
```java
|
||||
assertThat(page.getByRole(AriaRole.LISTITEM, new Page.GetByRoleOptions().setName("Ship v1.52"))).containsClass("done");
|
||||
```
|
||||
|
||||
- [Aria Snapshots](./aria-snapshots.md) got two new properties: [`/children`](./aria-snapshots.md#strict-matching) for strict matching and `/url` for links.
|
||||
|
||||
```java
|
||||
assertThat(locator).toMatchAriaSnapshot("""
|
||||
- list
|
||||
- /children: equal
|
||||
- listitem: Feature A
|
||||
- listitem:
|
||||
- link "Feature B":
|
||||
- /url: "https://playwright.dev"
|
||||
""");
|
||||
```
|
||||
|
||||
### Miscellaneous
|
||||
|
||||
- New option [`option: APIRequest.newContext.maxRedirects`] in [`method: APIRequest.newContext`] to control the maximum number of redirects.
|
||||
- New option [`option: Locator.ariaSnapshot.ref`] in [`method: Locator.ariaSnapshot`] to generate reference for each element in the snapshot which can later be used to locate the element.
|
||||
- HTML reporter now supports *NOT filtering* via `!@my-tag` or `!my-file.spec.ts` or `!p:my-project`.
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
- Base URL matching is not supported in [`method: Page.frame`] anymore. We recommend migrating to [`method: Page.frameLocator`] instead for having a more convenient API.
|
||||
- Glob URL patterns in methods like [`method: Page.route`] do not support `?` and `[]` anymore. We recommend using regular expressions instead.
|
||||
- Method [`method: Route.continue`] does not allow to override the `Cookie` header anymore. If a `Cookie` header is provided, it will be ignored, and the cookie will be loaded from the browser's cookie store. To set custom cookies, use [`method: BrowserContext.addCookies`].
|
||||
- macOS 13 is now deprecated and will no longer receive WebKit updates. Please upgrade to a more recent macOS version to continue benefiting from the latest WebKit improvements.
|
||||
|
||||
### Browser Versions
|
||||
|
||||
- Chromium 136.0.7103.25
|
||||
- Mozilla Firefox 137.0
|
||||
- WebKit 18.4
|
||||
|
||||
This version was also tested against the following stable channels:
|
||||
|
||||
- Google Chrome 135
|
||||
- Microsoft Edge 135
|
||||
|
||||
## Version 1.51
|
||||
|
||||
### Highlights
|
||||
|
|
|
@ -6,6 +6,58 @@ toc_max_heading_level: 2
|
|||
|
||||
import LiteYouTube from '@site/src/components/LiteYouTube';
|
||||
|
||||
## Version 1.52
|
||||
|
||||
### Highlights
|
||||
|
||||
- New method [`method: LocatorAssertions.toContainClass`] to ergonomically assert individual class names on the element.
|
||||
|
||||
```ts
|
||||
await expect(page.getByRole('listitem', { name: 'Ship v1.52' })).toContainClass('done');
|
||||
```
|
||||
|
||||
- [Aria Snapshots](./aria-snapshots.md) got two new properties: [`/children`](./aria-snapshots.md#strict-matching) for strict matching and `/url` for links.
|
||||
|
||||
```ts
|
||||
await expect(locator).toMatchAriaSnapshot(`
|
||||
- list
|
||||
- /children: equal
|
||||
- listitem: Feature A
|
||||
- listitem:
|
||||
- link "Feature B":
|
||||
- /url: "https://playwright.dev"
|
||||
`);
|
||||
```
|
||||
|
||||
### Test Runner
|
||||
|
||||
- New property [`property: TestProject.workers`] allows to specify the number of concurrent worker processes to use for a test project. The global limit of property [`property: TestConfig.workers`] still applies.
|
||||
- New [`property: TestConfig.failOnFlakyTests`] option to fail the test run if any flaky tests are detected, similarly to `--fail-on-flaky-tests`. This is useful for CI/CD environments where you want to ensure that all tests are stable before deploying.
|
||||
- New property [`property: TestResult.annotations`] contains annotations for each test retry.
|
||||
|
||||
### Miscellaneous
|
||||
|
||||
- New option [`option: APIRequest.newContext.maxRedirects`] in [`method: APIRequest.newContext`] to control the maximum number of redirects.
|
||||
- New option [`option: Locator.ariaSnapshot.ref`] in [`method: Locator.ariaSnapshot`] to generate reference for each element in the snapshot which can later be used to locate the element.
|
||||
- HTML reporter now supports *NOT filtering* via `!@my-tag` or `!my-file.spec.ts` or `!p:my-project`.
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
- Glob URL patterns in methods like [`method: Page.route`] do not support `?` and `[]` anymore. We recommend using regular expressions instead.
|
||||
- Method [`method: Route.continue`] does not allow to override the `Cookie` header anymore. If a `Cookie` header is provided, it will be ignored, and the cookie will be loaded from the browser's cookie store. To set custom cookies, use [`method: BrowserContext.addCookies`].
|
||||
- macOS 13 is now deprecated and will no longer receive WebKit updates. Please upgrade to a more recent macOS version to continue benefiting from the latest WebKit improvements.
|
||||
|
||||
### Browser Versions
|
||||
|
||||
- Chromium 136.0.7103.25
|
||||
- Mozilla Firefox 137.0
|
||||
- WebKit 18.4
|
||||
|
||||
This version was also tested against the following stable channels:
|
||||
|
||||
- Google Chrome 135
|
||||
- Microsoft Edge 135
|
||||
|
||||
## Version 1.51
|
||||
|
||||
### StorageState for indexedDB
|
||||
|
|
|
@ -4,6 +4,53 @@ title: "Release notes"
|
|||
toc_max_heading_level: 2
|
||||
---
|
||||
|
||||
## Version 1.52
|
||||
|
||||
### Highlights
|
||||
|
||||
- New method [`method: LocatorAssertions.toContainClass`] to ergonomically assert individual class names on the element.
|
||||
|
||||
```python
|
||||
expect(page.get_by_role('listitem', name='Ship v1.52')).to_contain_class('done')
|
||||
```
|
||||
|
||||
- [Aria Snapshots](./aria-snapshots.md) got two new properties: [`/children`](./aria-snapshots.md#strict-matching) for strict matching and `/url` for links.
|
||||
|
||||
```python
|
||||
expect(locator).to_match_aria_snapshot("""
|
||||
- list
|
||||
- /children: equal
|
||||
- listitem: Feature A
|
||||
- listitem:
|
||||
- link "Feature B":
|
||||
- /url: "https://playwright.dev"
|
||||
""")
|
||||
```
|
||||
|
||||
### Miscellaneous
|
||||
|
||||
- New option [`option: APIRequest.newContext.maxRedirects`] in [`method: APIRequest.newContext`] to control the maximum number of redirects.
|
||||
- New option [`option: Locator.ariaSnapshot.ref`] in [`method: Locator.ariaSnapshot`] to generate reference for each element in the snapshot which can later be used to locate the element.
|
||||
- HTML reporter now supports *NOT filtering* via `!@my-tag` or `!my-file.spec.ts` or `!p:my-project`.
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
- Base URL matching is not supported in [`method: Page.frame`] anymore. We recommend migrating to [`method: Page.frameLocator`] instead for having a more convenient API.
|
||||
- Glob URL patterns in methods like [`method: Page.route`] do not support `?` and `[]` anymore. We recommend using regular expressions instead.
|
||||
- Method [`method: Route.continue`] does not allow to override the `Cookie` header anymore. If a `Cookie` header is provided, it will be ignored, and the cookie will be loaded from the browser's cookie store. To set custom cookies, use [`method: BrowserContext.addCookies`].
|
||||
- macOS 13 is now deprecated and will no longer receive WebKit updates. Please upgrade to a more recent macOS version to continue benefiting from the latest WebKit improvements.
|
||||
|
||||
### Browser Versions
|
||||
|
||||
- Chromium 136.0.7103.25
|
||||
- Mozilla Firefox 137.0
|
||||
- WebKit 18.4
|
||||
|
||||
This version was also tested against the following stable channels:
|
||||
|
||||
- Google Chrome 135
|
||||
- Microsoft Edge 135
|
||||
|
||||
## Version 1.51
|
||||
|
||||
### Highlights
|
||||
|
|
|
@ -19,7 +19,6 @@ test('basic test', async ({ page }, testInfo) => {
|
|||
- type: <[Array]<[Object]>>
|
||||
- `type` <[string]> Annotation type, for example `'skip'` or `'fail'`.
|
||||
- `description` ?<[string]> Optional description.
|
||||
- `location` ?<[Location]> Optional location in the source where the annotation is added.
|
||||
|
||||
The list of annotations applicable to the current test. Includes annotations from the test, annotations from all [`method: Test.describe`] groups the test belongs to and file-level annotations for the test file.
|
||||
|
||||
|
|
|
@ -223,6 +223,17 @@ await expect.poll(async () => {
|
|||
}).toBe(200);
|
||||
```
|
||||
|
||||
You can combine `expect.configure({ soft: true })` with expect.poll to perform soft assertions in polling logic.
|
||||
|
||||
```js
|
||||
const softExpect = expect.configure({ soft: true });
|
||||
await softExpect.poll(async () => {
|
||||
const response = await page.request.get('https://api.example.com');
|
||||
return response.status();
|
||||
}, {}).toBe(200);
|
||||
```
|
||||
This allows the test to continue even if the assertion inside poll fails.
|
||||
|
||||
## expect.toPass
|
||||
|
||||
You can retry blocks of code until they are passing successfully.
|
||||
|
|
|
@ -21,7 +21,7 @@ test('basic test', async ({ page }) => {
|
|||
});
|
||||
```
|
||||
|
||||
The `{ page }` argument tells Playwright Test to setup the `page` fixture and provide it to your test function.
|
||||
The `{ page }` argument tells Playwright Test to set up the `page` fixture and provide it to your test function.
|
||||
|
||||
Here is a list of the pre-defined fixtures that you are likely to use most of the time:
|
||||
|
||||
|
@ -29,15 +29,15 @@ Here is a list of the pre-defined fixtures that you are likely to use most of th
|
|||
|:----------|:------------------|:--------------------------------|
|
||||
|page |[Page] |Isolated page for this test run. |
|
||||
|context |[BrowserContext] |Isolated context for this test run. The `page` fixture belongs to this context as well. Learn how to [configure context](./test-configuration.md). |
|
||||
|browser |[Browser] |Browsers are shared across tests to optimize resources. Learn how to [configure browser](./test-configuration.md). |
|
||||
|browser |[Browser] |Browsers are shared across tests to optimize resources. Learn how to [configure browsers](./test-configuration.md). |
|
||||
|browserName|[string] |The name of the browser currently running the test. Either `chromium`, `firefox` or `webkit`.|
|
||||
|request |[APIRequestContext]|Isolated [APIRequestContext](./api/class-apirequestcontext.md) instance for this test run.|
|
||||
|
||||
### Without fixtures
|
||||
|
||||
Here is how typical test environment setup differs between traditional test style and the fixture-based one.
|
||||
Here is how a typical test environment setup differs between the traditional test style and the fixture-based one.
|
||||
|
||||
`TodoPage` is a class that helps interacting with a "todo list" page of the web app, following the [Page Object Model](./pom.md) pattern. It uses Playwright's `page` internally.
|
||||
`TodoPage` is a class that helps us interact with a "todo list" page of the web app, following the [Page Object Model](./pom.md) pattern. It uses Playwright's `page` internally.
|
||||
|
||||
<details>
|
||||
<summary>Click to expand the code for the <code>TodoPage</code></summary>
|
||||
|
@ -116,11 +116,11 @@ test.describe('todo tests', () => {
|
|||
|
||||
Fixtures have a number of advantages over before/after hooks:
|
||||
- Fixtures **encapsulate** setup and teardown in the same place so it is easier to write. So if you have an after hook that tears down what was created in a before hook, consider turning them into a fixture.
|
||||
- Fixtures are **reusable** between test files - you can define them once and use in all your tests. That's how Playwright's built-in `page` fixture works. So if you have a helper function that is used in multiple tests, consider turning it into a fixture.
|
||||
- Fixtures are **reusable** between test files - you can define them once and use them in all your tests. That's how Playwright's built-in `page` fixture works. So if you have a helper function that is used in multiple tests, consider turning it into a fixture.
|
||||
- Fixtures are **on-demand** - you can define as many fixtures as you'd like, and Playwright Test will setup only the ones needed by your test and nothing else.
|
||||
- Fixtures are **composable** - they can depend on each other to provide complex behaviors.
|
||||
- Fixtures are **flexible**. Tests can use any combinations of the fixtures to tailor precise environment they need, without affecting other tests.
|
||||
- Fixtures simplify **grouping**. You no longer need to wrap tests in `describe`s that set up environment, and are free to group your tests by their meaning instead.
|
||||
- Fixtures are **flexible**. Tests can use any combination of fixtures to precisely tailor the environment to their needs, without affecting other tests.
|
||||
- Fixtures simplify **grouping**. You no longer need to wrap tests in `describe`s that set up their environment, and are free to group your tests by their meaning instead.
|
||||
|
||||
<details>
|
||||
<summary>Click to expand the code for the <code>TodoPage</code></summary>
|
||||
|
@ -291,14 +291,14 @@ export { expect } from '@playwright/test';
|
|||
```
|
||||
|
||||
:::note
|
||||
Custom fixture names should start with a letter or underscore, and can contain only letters, numbers, underscores.
|
||||
Custom fixture names should start with a letter or underscore, and can contain only letters, numbers, and underscores.
|
||||
:::
|
||||
|
||||
## Using a fixture
|
||||
|
||||
Just mention fixture in your test function argument, and test runner will take care of it. Fixtures are also available in hooks and other fixtures. If you use TypeScript, fixtures will have the right type.
|
||||
Just mention a fixture in your test function argument, and the test runner will take care of it. Fixtures are also available in hooks and other fixtures. If you use TypeScript, fixtures will be type safe.
|
||||
|
||||
Below we use the `todoPage` and `settingsPage` fixtures defined above.
|
||||
Below we use the `todoPage` and `settingsPage` fixtures that we defined above.
|
||||
|
||||
```js
|
||||
import { test, expect } from './my-test';
|
||||
|
@ -315,7 +315,7 @@ test('basic test', async ({ todoPage, page }) => {
|
|||
|
||||
## Overriding fixtures
|
||||
|
||||
In addition to creating your own fixtures, you can also override existing fixtures to fit your needs. Consider the following example which overrides the `page` fixture by automatically navigating to some `baseURL`:
|
||||
In addition to creating your own fixtures, you can also override existing fixtures to fit your needs. Consider the following example which overrides the `page` fixture by automatically navigating to the `baseURL`:
|
||||
|
||||
```js
|
||||
import { test as base } from '@playwright/test';
|
||||
|
@ -335,7 +335,7 @@ Notice that in this example, the `page` fixture is able to depend on other built
|
|||
test.use({ baseURL: 'https://playwright.dev' });
|
||||
```
|
||||
|
||||
Fixtures can also be overridden where the base fixture is completely replaced with something different. For example, we could override the [`property: TestOptions.storageState`] fixture to provide our own data.
|
||||
Fixtures can also be overridden, causing the base fixture to be completely replaced with something different. For example, we could override the [`property: TestOptions.storageState`] fixture to provide our own data.
|
||||
|
||||
```js
|
||||
import { test as base } from '@playwright/test';
|
||||
|
@ -350,9 +350,9 @@ export const test = base.extend({
|
|||
|
||||
## Worker-scoped fixtures
|
||||
|
||||
Playwright Test uses [worker processes](./test-parallel.md) to run test files. Similarly to how test fixtures are set up for individual test runs, worker fixtures are set up for each worker process. That's where you can set up services, run servers, etc. Playwright Test will reuse the worker process for as many test files as it can, provided their worker fixtures match and hence environments are identical.
|
||||
Playwright Test uses [worker processes](./test-parallel.md) to run test files. Similar to how test fixtures are set up for individual test runs, worker fixtures are set up for each worker process. That's where you can set up services, run servers, etc. Playwright Test will reuse the worker process for as many test files as it can, provided their worker fixtures match and hence environments are identical.
|
||||
|
||||
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.
|
||||
Below we'll create an `account` fixture that will be shared by all tests in the same worker, and override the `page` fixture to log in to 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 this fixture up once per worker.
|
||||
|
||||
```js title="my-test.ts"
|
||||
import { test as base } from '@playwright/test';
|
||||
|
@ -404,7 +404,7 @@ export { expect } from '@playwright/test';
|
|||
|
||||
Automatic fixtures are set up for each test/worker, even when the test does not list them directly. To create an automatic fixture, use the tuple syntax and pass `{ auto: true }`.
|
||||
|
||||
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.
|
||||
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 the [TestInfo] object that is available in each test/fixture to retrieve metadata about the test being run.
|
||||
|
||||
```js title="my-test.ts"
|
||||
import debug from 'debug';
|
||||
|
@ -434,7 +434,7 @@ export { expect } from '@playwright/test';
|
|||
|
||||
## Fixture timeout
|
||||
|
||||
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.
|
||||
By default, the fixture inherits the timeout value of 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
|
||||
import { test as base, expect } from '@playwright/test';
|
||||
|
@ -454,9 +454,9 @@ test('example test', async ({ slowFixture }) => {
|
|||
|
||||
## Fixtures-options
|
||||
|
||||
Playwright Test supports running multiple test projects that can be separately configured. You can use "option" fixtures to make your configuration options declarative and type-checked. Learn more about [parametrizing tests](./test-parameterize.md).
|
||||
Playwright Test supports running multiple test projects that can be configured separately. You can use "option" fixtures to make your configuration options declarative and type safe. Learn more about [parameterizing tests](./test-parameterize.md).
|
||||
|
||||
Below we'll create a `defaultItem` option in addition to the `todoPage` fixture from other examples. This option will be set in configuration file. Note the tuple syntax and `{ option: true }` argument.
|
||||
Below we'll create a `defaultItem` option in addition to the `todoPage` fixture from other examples. This option will be set in the configuration file. Note the tuple syntax and `{ option: true }` argument.
|
||||
|
||||
<details>
|
||||
<summary>Click to expand the code for the <code>TodoPage</code></summary>
|
||||
|
@ -531,7 +531,7 @@ export const test = base.extend<MyOptions & MyFixtures>({
|
|||
export { expect } from '@playwright/test';
|
||||
```
|
||||
|
||||
We can now use `todoPage` fixture as usual, and set the `defaultItem` option in the config file.
|
||||
We can now use the `todoPage` fixture as usual, and set the `defaultItem` option in the configuration file.
|
||||
|
||||
```js title="playwright.config.ts"
|
||||
import { defineConfig } from '@playwright/test';
|
||||
|
@ -577,12 +577,12 @@ test.use({
|
|||
|
||||
## Execution order
|
||||
|
||||
Each fixture has a setup and teardown phase separated by the `await use()` call in the fixture. Setup is executed before the fixture is used by the test/hook, and teardown is executed when the fixture will not be used by the test/hook anymore.
|
||||
Each fixture has a setup and teardown phase before and after the `await use()` call in the fixture. Setup is executed before the test/hook requiring it is run, and teardown is executed when the fixture is no longer being used by the test/hook.
|
||||
|
||||
Fixtures follow these rules to determine the execution order:
|
||||
* When fixture A depends on fixture B: B is always set up before A and torn down after A.
|
||||
* Non-automatic fixtures are executed lazily, only when the test/hook needs them.
|
||||
* Test-scoped fixtures are torn down after each test, while worker-scoped fixtures are only torn down when the worker process executing tests is shutdown.
|
||||
* Test-scoped fixtures are torn down after each test, while worker-scoped fixtures are only torn down when the worker process executing tests is torn down.
|
||||
|
||||
Consider the following example:
|
||||
|
||||
|
@ -695,7 +695,7 @@ test('passes', async ({ database, page, a11y }) => {
|
|||
|
||||
## Box fixtures
|
||||
|
||||
Usually, custom fixtures are reported as separate steps in the UI mode, Trace Viewer and various test reports. They also appear in error messages from the test runner. For frequently-used fixtures, this can mean lots of noise. You can stop the fixtures steps from being shown in the UI by "boxing" it.
|
||||
Usually, custom fixtures are reported as separate steps in the UI mode, Trace Viewer and various test reports. They also appear in error messages from the test runner. For frequently used fixtures, this can mean lots of noise. You can stop the fixtures steps from being shown in the UI by "boxing" it.
|
||||
|
||||
```js
|
||||
import { test as base } from '@playwright/test';
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
- type: <[Array]<[Object]>>
|
||||
- `type` <[string]> Annotation type, for example `'skip'` or `'fail'`.
|
||||
- `description` ?<[string]> Optional description.
|
||||
- `location` ?<[Location]> Optional location in the source where the annotation is added.
|
||||
|
||||
[`property: TestResult.annotations`] of the last test run.
|
||||
|
||||
|
|
|
@ -19,7 +19,6 @@ The list of files or buffers attached during the test execution through [`proper
|
|||
- type: <[Array]<[Object]>>
|
||||
- `type` <[string]> Annotation type, for example `'skip'` or `'fail'`.
|
||||
- `description` ?<[string]> Optional description.
|
||||
- `location` ?<[Location]> Optional location in the source where the annotation is added.
|
||||
|
||||
The list of annotations applicable to the current test. Includes:
|
||||
* annotations defined on the test or suite via [`method: Test.(call)`] and [`method: Test.describe`];
|
||||
|
|
|
@ -55,7 +55,6 @@ List of steps inside this step.
|
|||
- type: <[Array]<[Object]>>
|
||||
- `type` <[string]> Annotation type, for example `'skip'`.
|
||||
- `description` ?<[string]> Optional description.
|
||||
- `location` ?<[Location]> Optional location in the source where the annotation is added.
|
||||
|
||||
The list of annotations applicable to the current test step.
|
||||
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "playwright-internal",
|
||||
"version": "1.52.0-next",
|
||||
"version": "1.53.0-next",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "playwright-internal",
|
||||
"version": "1.52.0-next",
|
||||
"version": "1.53.0-next",
|
||||
"license": "Apache-2.0",
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
|
@ -7906,10 +7906,10 @@
|
|||
"version": "0.0.0"
|
||||
},
|
||||
"packages/playwright": {
|
||||
"version": "1.52.0-next",
|
||||
"version": "1.53.0-next",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright-core": "1.52.0-next"
|
||||
"playwright-core": "1.53.0-next"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
|
@ -7923,11 +7923,11 @@
|
|||
},
|
||||
"packages/playwright-browser-chromium": {
|
||||
"name": "@playwright/browser-chromium",
|
||||
"version": "1.52.0-next",
|
||||
"version": "1.53.0-next",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright-core": "1.52.0-next"
|
||||
"playwright-core": "1.53.0-next"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
|
@ -7935,11 +7935,11 @@
|
|||
},
|
||||
"packages/playwright-browser-firefox": {
|
||||
"name": "@playwright/browser-firefox",
|
||||
"version": "1.52.0-next",
|
||||
"version": "1.53.0-next",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright-core": "1.52.0-next"
|
||||
"playwright-core": "1.53.0-next"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
|
@ -7947,22 +7947,22 @@
|
|||
},
|
||||
"packages/playwright-browser-webkit": {
|
||||
"name": "@playwright/browser-webkit",
|
||||
"version": "1.52.0-next",
|
||||
"version": "1.53.0-next",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright-core": "1.52.0-next"
|
||||
"playwright-core": "1.53.0-next"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"packages/playwright-chromium": {
|
||||
"version": "1.52.0-next",
|
||||
"version": "1.53.0-next",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright-core": "1.52.0-next"
|
||||
"playwright-core": "1.53.0-next"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
|
@ -7976,14 +7976,14 @@
|
|||
"version": "0.0.0",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright-core": "1.52.0-next"
|
||||
"playwright-core": "1.53.0-next"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"packages/playwright-core": {
|
||||
"version": "1.52.0-next",
|
||||
"version": "1.53.0-next",
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"playwright-core": "cli.js"
|
||||
|
@ -7994,11 +7994,11 @@
|
|||
},
|
||||
"packages/playwright-ct-core": {
|
||||
"name": "@playwright/experimental-ct-core",
|
||||
"version": "1.52.0-next",
|
||||
"version": "1.53.0-next",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright": "1.52.0-next",
|
||||
"playwright-core": "1.52.0-next",
|
||||
"playwright": "1.53.0-next",
|
||||
"playwright-core": "1.53.0-next",
|
||||
"vite": "^6.2.6"
|
||||
},
|
||||
"engines": {
|
||||
|
@ -8007,10 +8007,10 @@
|
|||
},
|
||||
"packages/playwright-ct-react": {
|
||||
"name": "@playwright/experimental-ct-react",
|
||||
"version": "1.52.0-next",
|
||||
"version": "1.53.0-next",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@playwright/experimental-ct-core": "1.52.0-next",
|
||||
"@playwright/experimental-ct-core": "1.53.0-next",
|
||||
"@vitejs/plugin-react": "^4.2.1"
|
||||
},
|
||||
"bin": {
|
||||
|
@ -8022,10 +8022,10 @@
|
|||
},
|
||||
"packages/playwright-ct-react17": {
|
||||
"name": "@playwright/experimental-ct-react17",
|
||||
"version": "1.52.0-next",
|
||||
"version": "1.53.0-next",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@playwright/experimental-ct-core": "1.52.0-next",
|
||||
"@playwright/experimental-ct-core": "1.53.0-next",
|
||||
"@vitejs/plugin-react": "^4.2.1"
|
||||
},
|
||||
"bin": {
|
||||
|
@ -8037,10 +8037,10 @@
|
|||
},
|
||||
"packages/playwright-ct-svelte": {
|
||||
"name": "@playwright/experimental-ct-svelte",
|
||||
"version": "1.52.0-next",
|
||||
"version": "1.53.0-next",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@playwright/experimental-ct-core": "1.52.0-next",
|
||||
"@playwright/experimental-ct-core": "1.53.0-next",
|
||||
"@sveltejs/vite-plugin-svelte": "^3.0.1"
|
||||
},
|
||||
"bin": {
|
||||
|
@ -8598,10 +8598,10 @@
|
|||
},
|
||||
"packages/playwright-ct-vue": {
|
||||
"name": "@playwright/experimental-ct-vue",
|
||||
"version": "1.52.0-next",
|
||||
"version": "1.53.0-next",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@playwright/experimental-ct-core": "1.52.0-next",
|
||||
"@playwright/experimental-ct-core": "1.53.0-next",
|
||||
"@vitejs/plugin-vue": "^5.2.0"
|
||||
},
|
||||
"bin": {
|
||||
|
@ -8621,18 +8621,18 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@octokit/graphql-schema": "^15.26.0",
|
||||
"@playwright/test": "1.52.0-next"
|
||||
"@playwright/test": "1.53.0-next"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"packages/playwright-firefox": {
|
||||
"version": "1.52.0-next",
|
||||
"version": "1.53.0-next",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright-core": "1.52.0-next"
|
||||
"playwright-core": "1.53.0-next"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
|
@ -8664,10 +8664,10 @@
|
|||
},
|
||||
"packages/playwright-test": {
|
||||
"name": "@playwright/test",
|
||||
"version": "1.52.0-next",
|
||||
"version": "1.53.0-next",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright": "1.52.0-next"
|
||||
"playwright": "1.53.0-next"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
|
@ -8694,11 +8694,11 @@
|
|||
}
|
||||
},
|
||||
"packages/playwright-webkit": {
|
||||
"version": "1.52.0-next",
|
||||
"version": "1.53.0-next",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright-core": "1.52.0-next"
|
||||
"playwright-core": "1.53.0-next"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "playwright-internal",
|
||||
"private": true,
|
||||
"version": "1.52.0-next",
|
||||
"version": "1.53.0-next",
|
||||
"description": "A high-level API to automate web browsers",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
|
@ -61,12 +61,14 @@ class ZipReport implements LoadedReport {
|
|||
if (window.playwrightReportBase64)
|
||||
return resolve(window.playwrightReportBase64);
|
||||
if (window.opener) {
|
||||
window.addEventListener('message', event => {
|
||||
const listener = (event: MessageEvent) => {
|
||||
if (event.source === window.opener) {
|
||||
localStorage.setItem(kPlaywrightReportStorageForHMR, event.data);
|
||||
resolve(event.data);
|
||||
window.removeEventListener('message', listener);
|
||||
}
|
||||
}, { once: true });
|
||||
};
|
||||
window.addEventListener('message', listener);
|
||||
window.opener.postMessage('ready', '*');
|
||||
} else {
|
||||
const oldReport = localStorage.getItem(kPlaywrightReportStorageForHMR);
|
||||
|
|
|
@ -14,8 +14,7 @@
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import type { TestAnnotation } from '@playwright/test';
|
||||
import type { TestCase, TestCaseSummary } from './types';
|
||||
import type { TestCase, TestAnnotation, TestCaseSummary } from './types';
|
||||
import * as React from 'react';
|
||||
import { TabbedPane } from './tabbedPane';
|
||||
import { AutoChip } from './chip';
|
||||
|
|
|
@ -20,17 +20,18 @@ import './testErrorView.css';
|
|||
import type { ImageDiff } from '@web/shared/imageDiffView';
|
||||
import { ImageDiffView } from '@web/shared/imageDiffView';
|
||||
import { TestAttachment } from './types';
|
||||
import { fixTestInstructions } from '@web/prompts';
|
||||
|
||||
export const TestErrorView: React.FC<{
|
||||
error: string;
|
||||
testId?: string;
|
||||
prompt?: TestAttachment;
|
||||
}> = ({ error, testId, prompt }) => {
|
||||
context?: TestAttachment;
|
||||
}> = ({ error, testId, context }) => {
|
||||
return (
|
||||
<CodeSnippet code={error} testId={testId}>
|
||||
{prompt && (
|
||||
{context && (
|
||||
<div style={{ position: 'absolute', right: 0, padding: '10px' }}>
|
||||
<PromptButton prompt={prompt} />
|
||||
<PromptButton context={context} />
|
||||
</div>
|
||||
)}
|
||||
</CodeSnippet>
|
||||
|
@ -47,14 +48,14 @@ export const CodeSnippet = ({ code, children, testId }: React.PropsWithChildren<
|
|||
);
|
||||
};
|
||||
|
||||
const PromptButton: React.FC<{ prompt: TestAttachment }> = ({ prompt }) => {
|
||||
const PromptButton: React.FC<{ context: TestAttachment }> = ({ context }) => {
|
||||
const [copied, setCopied] = React.useState(false);
|
||||
return <button
|
||||
className='button'
|
||||
style={{ minWidth: 100 }}
|
||||
onClick={async () => {
|
||||
const text = prompt.body ? prompt.body : await fetch(prompt.path!).then(r => r.text());
|
||||
await navigator.clipboard.writeText(text);
|
||||
const text = context.body ? context.body : await fetch(context.path!).then(r => r.text());
|
||||
await navigator.clipboard.writeText(fixTestInstructions + text);
|
||||
setCopied(true);
|
||||
setTimeout(() => {
|
||||
setCopied(false);
|
||||
|
|
|
@ -90,7 +90,7 @@ export const TestResultView: React.FC<{
|
|||
{errors.map((error, index) => {
|
||||
if (error.type === 'screenshot')
|
||||
return <TestScreenshotErrorView key={'test-result-error-message-' + index} errorPrefix={error.errorPrefix} diff={error.diff!} errorSuffix={error.errorSuffix}></TestScreenshotErrorView>;
|
||||
return <TestErrorView key={'test-result-error-message-' + index} error={error.error!} prompt={error.prompt}></TestErrorView>;
|
||||
return <TestErrorView key={'test-result-error-message-' + index} error={error.error!} context={error.context}></TestErrorView>;
|
||||
})}
|
||||
</AutoChip>}
|
||||
{!!result.steps.length && <AutoChip header='Test Steps'>
|
||||
|
@ -165,8 +165,8 @@ function classifyErrors(testErrors: string[], diffs: ImageDiff[], attachments: T
|
|||
}
|
||||
}
|
||||
|
||||
const prompt = attachments.find(a => a.name === `_prompt-${i}`);
|
||||
return { type: 'regular', error, prompt };
|
||||
const context = attachments.find(a => a.name === `_error-context-${i}`);
|
||||
return { type: 'regular', error, context };
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { TestAnnotation, Metadata } from '@playwright/test';
|
||||
import type { Metadata } from '@playwright/test';
|
||||
|
||||
export type Stats = {
|
||||
total: number;
|
||||
|
@ -59,6 +59,8 @@ export type TestFileSummary = {
|
|||
stats: Stats;
|
||||
};
|
||||
|
||||
export type TestAnnotation = { type: string, description?: string };
|
||||
|
||||
export type TestCaseSummary = {
|
||||
testId: string,
|
||||
title: string;
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
import { Map, Set } from '@isomorphic/builtins';
|
||||
import { escapeRegExp, longestCommonSubstring, normalizeWhiteSpace } from '@isomorphic/stringUtils';
|
||||
|
||||
import { getElementComputedStyle, getGlobalOptions } from './domUtils';
|
||||
import { getElementComputedStyle, getGlobalOptions, isElementVisible } from './domUtils';
|
||||
import * as roleUtils from './roleUtils';
|
||||
import { yamlEscapeKeyIfNeeded, yamlEscapeValueIfNeeded } from './yaml';
|
||||
|
||||
|
@ -38,7 +38,7 @@ export type AriaSnapshot = {
|
|||
ids: Map<Element, number>;
|
||||
};
|
||||
|
||||
export function generateAriaTree(rootElement: Element, generation: number): AriaSnapshot {
|
||||
export function generateAriaTree(rootElement: Element, generation: number, options?: { emitGeneric?: boolean }): AriaSnapshot {
|
||||
const visited = new Set<Node>();
|
||||
|
||||
const snapshot: AriaSnapshot = {
|
||||
|
@ -86,8 +86,12 @@ export function generateAriaTree(rootElement: Element, generation: number): Aria
|
|||
}
|
||||
}
|
||||
|
||||
// Skip all the leaf nodes that are not visible as they can't be interacted with.
|
||||
if (!ariaChildren.length && !isElementVisible(element))
|
||||
return;
|
||||
|
||||
addElement(element);
|
||||
const childAriaNode = toAriaNode(element);
|
||||
const childAriaNode = toAriaNode(element, options);
|
||||
if (childAriaNode)
|
||||
ariaNode.children.push(childAriaNode);
|
||||
processElement(childAriaNode || ariaNode, element, ariaChildren);
|
||||
|
@ -141,14 +145,16 @@ export function generateAriaTree(rootElement: Element, generation: number): Aria
|
|||
}
|
||||
|
||||
normalizeStringChildren(snapshot.root);
|
||||
normalizeGenericRoles(snapshot.root);
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
function toAriaNode(element: Element): AriaNode | null {
|
||||
function toAriaNode(element: Element, options?: { emitGeneric?: boolean }): AriaNode | null {
|
||||
if (element.nodeName === 'IFRAME')
|
||||
return { role: 'iframe', name: '', children: [], props: {}, element };
|
||||
|
||||
const role = roleUtils.getAriaRole(element);
|
||||
const defaultRole = options?.emitGeneric ? 'generic' : null;
|
||||
const role = roleUtils.getAriaRole(element) ?? defaultRole;
|
||||
if (!role || role === 'presentation' || role === 'none')
|
||||
return null;
|
||||
|
||||
|
@ -181,6 +187,33 @@ function toAriaNode(element: Element): AriaNode | null {
|
|||
return result;
|
||||
}
|
||||
|
||||
function normalizeGenericRoles(rootA11yNode: AriaNode) {
|
||||
const visit = (ariaNode: AriaNode) => {
|
||||
const newChildren: (AriaNode | string)[] = [];
|
||||
for (const child of ariaNode.children) {
|
||||
if (typeof child === 'string') {
|
||||
newChildren.push(child);
|
||||
continue;
|
||||
}
|
||||
const isEmptyGeneric = child.role === 'generic' && child.children.length === 0;
|
||||
const isSingleGenericChild = child.role === 'generic' && child.children.length === 1;
|
||||
if (isSingleGenericChild) {
|
||||
// Inline single child chains.
|
||||
const newChild = child.children[0];
|
||||
newChildren.push(newChild);
|
||||
if (typeof newChild !== 'string')
|
||||
visit(newChild);
|
||||
} else if (!isEmptyGeneric) {
|
||||
// Empty div
|
||||
newChildren.push(child);
|
||||
visit(child);
|
||||
}
|
||||
}
|
||||
ariaNode.children = newChildren;
|
||||
};
|
||||
visit(rootA11yNode);
|
||||
}
|
||||
|
||||
function normalizeStringChildren(rootA11yNode: AriaNode) {
|
||||
const flushChildren = (buffer: string[], normalizedChildren: (AriaNode | string)[]) => {
|
||||
if (!buffer.length)
|
||||
|
|
|
@ -281,11 +281,11 @@ export class InjectedScript {
|
|||
return new Set<Element>(result.map(r => r.element));
|
||||
}
|
||||
|
||||
ariaSnapshot(node: Node, options?: { mode?: 'raw' | 'regex', ref?: boolean }): string {
|
||||
ariaSnapshot(node: Node, options?: { mode?: 'raw' | 'regex', ref?: boolean, emitGeneric?: boolean }): string {
|
||||
if (node.nodeType !== Node.ELEMENT_NODE)
|
||||
throw this.createStacklessError('Can only capture aria snapshot of Element nodes.');
|
||||
const generation = (this._lastAriaSnapshot?.generation || 0) + 1;
|
||||
this._lastAriaSnapshot = generateAriaTree(node as Element, generation);
|
||||
this._lastAriaSnapshot = generateAriaTree(node as Element, generation, options);
|
||||
return renderAriaTree(this._lastAriaSnapshot, options);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@playwright/browser-chromium",
|
||||
"version": "1.52.0-next",
|
||||
"version": "1.53.0-next",
|
||||
"description": "Playwright package that automatically installs Chromium",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -27,6 +27,6 @@
|
|||
"install": "node install.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"playwright-core": "1.52.0-next"
|
||||
"playwright-core": "1.53.0-next"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@playwright/browser-firefox",
|
||||
"version": "1.52.0-next",
|
||||
"version": "1.53.0-next",
|
||||
"description": "Playwright package that automatically installs Firefox",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -27,6 +27,6 @@
|
|||
"install": "node install.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"playwright-core": "1.52.0-next"
|
||||
"playwright-core": "1.53.0-next"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@playwright/browser-webkit",
|
||||
"version": "1.52.0-next",
|
||||
"version": "1.53.0-next",
|
||||
"description": "Playwright package that automatically installs WebKit",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -27,6 +27,6 @@
|
|||
"install": "node install.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"playwright-core": "1.52.0-next"
|
||||
"playwright-core": "1.53.0-next"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "playwright-chromium",
|
||||
"version": "1.52.0-next",
|
||||
"version": "1.53.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.52.0-next"
|
||||
"playwright-core": "1.53.0-next"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,6 +29,6 @@
|
|||
"watch": "esbuild ./src/index.ts --outdir=lib --format=cjs --bundle --platform=node --target=ES2019 --watch"
|
||||
},
|
||||
"dependencies": {
|
||||
"playwright-core": "1.52.0-next"
|
||||
"playwright-core": "1.53.0-next"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12197,6 +12197,17 @@ export interface Locator {
|
|||
* rejects, this method throws.
|
||||
*
|
||||
* **Usage**
|
||||
*
|
||||
* Passing argument to
|
||||
* [`pageFunction`](https://playwright.dev/docs/api/class-locator#locator-evaluate-option-expression):
|
||||
*
|
||||
* ```js
|
||||
* const result = await page.getByTestId('myId').evaluate((element, [x, y]) => {
|
||||
* return element.textContent + ' ' + x * y;
|
||||
* }, [7, 8]);
|
||||
* console.log(result); // prints "myId text 56"
|
||||
* ```
|
||||
*
|
||||
* @param pageFunction Function to be evaluated in the page context.
|
||||
* @param arg Optional argument to pass to
|
||||
* [`pageFunction`](https://playwright.dev/docs/api/class-locator#locator-evaluate-option-expression).
|
||||
|
@ -12222,6 +12233,17 @@ export interface Locator {
|
|||
* rejects, this method throws.
|
||||
*
|
||||
* **Usage**
|
||||
*
|
||||
* Passing argument to
|
||||
* [`pageFunction`](https://playwright.dev/docs/api/class-locator#locator-evaluate-option-expression):
|
||||
*
|
||||
* ```js
|
||||
* const result = await page.getByTestId('myId').evaluate((element, [x, y]) => {
|
||||
* return element.textContent + ' ' + x * y;
|
||||
* }, [7, 8]);
|
||||
* console.log(result); // prints "myId text 56"
|
||||
* ```
|
||||
*
|
||||
* @param pageFunction Function to be evaluated in the page context.
|
||||
* @param arg Optional argument to pass to
|
||||
* [`pageFunction`](https://playwright.dev/docs/api/class-locator#locator-evaluate-option-expression).
|
||||
|
@ -12478,6 +12500,11 @@ export interface Locator {
|
|||
* @param options
|
||||
*/
|
||||
ariaSnapshot(options?: {
|
||||
/**
|
||||
* Generate `generic` aria nodes for elements w/o roles (similar to Chrome DevTools).
|
||||
*/
|
||||
emitGeneric?: boolean;
|
||||
|
||||
/**
|
||||
* Generate symbolic reference for each element. One can use `aria-ref=<ref>` locator immediately after capturing the
|
||||
* snapshot to perform actions on the element.
|
||||
|
|
|
@ -15,15 +15,15 @@
|
|||
},
|
||||
{
|
||||
"name": "chromium-tip-of-tree",
|
||||
"revision": "1320",
|
||||
"revision": "1322",
|
||||
"installByDefault": false,
|
||||
"browserVersion": "137.0.7105.0"
|
||||
"browserVersion": "137.0.7119.0"
|
||||
},
|
||||
{
|
||||
"name": "chromium-tip-of-tree-headless-shell",
|
||||
"revision": "1320",
|
||||
"revision": "1322",
|
||||
"installByDefault": false,
|
||||
"browserVersion": "137.0.7105.0"
|
||||
"browserVersion": "137.0.7119.0"
|
||||
},
|
||||
{
|
||||
"name": "firefox",
|
||||
|
@ -39,7 +39,7 @@
|
|||
},
|
||||
{
|
||||
"name": "webkit",
|
||||
"revision": "2158",
|
||||
"revision": "2160",
|
||||
"installByDefault": true,
|
||||
"revisionOverrides": {
|
||||
"debian11-x64": "2105",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "playwright-core",
|
||||
"version": "1.52.0-next",
|
||||
"version": "1.53.0-next",
|
||||
"description": "A high-level API to automate web browsers",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
|
@ -303,7 +303,7 @@ export class Locator implements api.Locator {
|
|||
return await this._withElement((h, timeout) => h.screenshot({ ...options, mask, timeout }), options.timeout);
|
||||
}
|
||||
|
||||
async ariaSnapshot(options?: { ref?: boolean, _mode?: 'raw' | 'regex' } & TimeoutOptions): Promise<string> {
|
||||
async ariaSnapshot(options?: { ref?: boolean, emitGeneric?: boolean, _mode?: 'raw' | 'regex' } & TimeoutOptions): Promise<string> {
|
||||
const result = await this._frame._channel.ariaSnapshot({ ...options, mode: options?._mode, selector: this._selector });
|
||||
return result.snapshot;
|
||||
}
|
||||
|
|
|
@ -1481,6 +1481,7 @@ scheme.FrameAddStyleTagResult = tObject({
|
|||
scheme.FrameAriaSnapshotParams = tObject({
|
||||
selector: tString,
|
||||
ref: tOptional(tBoolean),
|
||||
emitGeneric: tOptional(tBoolean),
|
||||
mode: tOptional(tEnum(['raw', 'regex'])),
|
||||
timeout: tOptional(tNumber),
|
||||
});
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
import { eventsHelper } from '../utils/eventsHelper';
|
||||
import { Browser } from '../browser';
|
||||
import { BrowserContext, assertBrowserContextIsNotOwned } from '../browserContext';
|
||||
import { BrowserContext, assertBrowserContextIsNotOwned, verifyGeolocation } from '../browserContext';
|
||||
import * as network from '../network';
|
||||
import { BidiConnection } from './bidiConnection';
|
||||
import { bidiBytesValueToString } from './bidiNetworkManager';
|
||||
|
@ -205,6 +205,7 @@ export class BidiBrowser extends Browser {
|
|||
export class BidiBrowserContext extends BrowserContext {
|
||||
declare readonly _browser: BidiBrowser;
|
||||
private _initScriptIds: bidi.Script.PreloadScript[] = [];
|
||||
private _originToPermissions = new Map<string, string[]>();
|
||||
|
||||
constructor(browser: BidiBrowser, browserContextId: string | undefined, options: types.BrowserContextOptions) {
|
||||
super(browser, options, browserContextId);
|
||||
|
@ -230,6 +231,8 @@ export class BidiBrowserContext extends BrowserContext {
|
|||
userContexts: [this._userContextId()],
|
||||
}));
|
||||
}
|
||||
if (this._options.geolocation)
|
||||
promises.push(this.setGeolocation(this._options.geolocation));
|
||||
await Promise.all(promises);
|
||||
}
|
||||
|
||||
|
@ -306,12 +309,56 @@ export class BidiBrowserContext extends BrowserContext {
|
|||
}
|
||||
|
||||
async doGrantPermissions(origin: string, permissions: string[]) {
|
||||
const currentPermissions = this._originToPermissions.get(origin) || [];
|
||||
const toGrant = permissions.filter(permission => !currentPermissions.includes(permission));
|
||||
this._originToPermissions.set(origin, [...currentPermissions, ...toGrant]);
|
||||
await Promise.all(toGrant.map(permission => this._setPermission(origin, permission, bidi.Permissions.PermissionState.Granted)));
|
||||
}
|
||||
|
||||
async doClearPermissions() {
|
||||
const currentPermissions = [...this._originToPermissions.entries()];
|
||||
this._originToPermissions = new Map();
|
||||
await Promise.all(currentPermissions.map(([origin, permissions]) => permissions.map(
|
||||
p => this._setPermission(origin, p, bidi.Permissions.PermissionState.Prompt))));
|
||||
}
|
||||
|
||||
private async _setPermission(origin: string, permission: string, state: bidi.Permissions.PermissionState) {
|
||||
await this._browser._browserSession.send('permissions.setPermission', {
|
||||
descriptor: {
|
||||
name: permission,
|
||||
},
|
||||
state,
|
||||
origin,
|
||||
userContext: this._browserContextId || 'default',
|
||||
});
|
||||
}
|
||||
|
||||
async setGeolocation(geolocation?: types.Geolocation): Promise<void> {
|
||||
verifyGeolocation(geolocation);
|
||||
this._options.geolocation = geolocation;
|
||||
const promises: Promise<unknown>[] = [
|
||||
this._browser._browserSession.send('emulation.setGeolocationOverride', {
|
||||
coordinates: {
|
||||
latitude: geolocation?.latitude,
|
||||
longitude: geolocation?.longitude,
|
||||
accuracy: geolocation?.accuracy,
|
||||
},
|
||||
userContexts: [this._browserContextId || 'default'],
|
||||
}),
|
||||
];
|
||||
const pageIds = this.pages().map(page => (page._delegate as BidiPage)._session.sessionId);
|
||||
if (pageIds.length) {
|
||||
// TODO: we can't specify userContexts and contexts at the same time in Firefox.
|
||||
promises.push(this._browser._browserSession.send('emulation.setGeolocationOverride', {
|
||||
coordinates: {
|
||||
latitude: geolocation?.latitude,
|
||||
longitude: geolocation?.longitude,
|
||||
accuracy: geolocation?.accuracy,
|
||||
},
|
||||
contexts: pageIds as [string, ...string[]],
|
||||
}));
|
||||
}
|
||||
await Promise.all(promises);
|
||||
}
|
||||
|
||||
async setExtraHTTPHeaders(headers: types.HeadersArray): Promise<void> {
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
// Copied from upstream: https://github.com/puppeteer/puppeteer/blob/main/packages/puppeteer-core/src/bidi/core/Connection.ts
|
||||
|
||||
import * as Bidi from './bidiProtocol';
|
||||
import type * as Bidi from './bidiProtocol';
|
||||
|
||||
export interface Commands {
|
||||
'script.evaluate': {
|
||||
|
@ -113,6 +113,16 @@ export interface Commands {
|
|||
returnType: Bidi.EmptyResult;
|
||||
};
|
||||
|
||||
'emulation.setGeolocationOverride': {
|
||||
params: Bidi.Emulation.SetGeolocationOverrideParameters;
|
||||
returnType: Bidi.EmptyResult;
|
||||
};
|
||||
|
||||
'permissions.setPermission': {
|
||||
params: Bidi.Permissions.SetPermissionParameters;
|
||||
returnType: Bidi.EmptyResult;
|
||||
};
|
||||
|
||||
'session.end': {
|
||||
params: Bidi.EmptyParams;
|
||||
returnType: Bidi.EmptyResult;
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
42
packages/playwright-core/src/server/bidi/third_party/bidiProtocolPermissions.ts
vendored
Normal file
42
packages/playwright-core/src/server/bidi/third_party/bidiProtocolPermissions.ts
vendored
Normal file
|
@ -0,0 +1,42 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright 2024 Google Inc.
|
||||
* Modifications copyright (c) Microsoft Corporation.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
// Copied from upstream: https://github.com/GoogleChromeLabs/chromium-bidi/blob/main/src/protocol/generated/webdriver-bidi-permissions.ts
|
||||
|
||||
/**
|
||||
* THIS FILE IS AUTOGENERATED by cddlconv 0.1.6.
|
||||
* Run `node tools/generate-bidi-types.mjs` to regenerate.
|
||||
* @see https://github.com/w3c/webdriver-bidi/blob/master/index.bs
|
||||
*/
|
||||
|
||||
export type PermissionsCommand = Permissions.SetPermission;
|
||||
export namespace Permissions {
|
||||
export type PermissionDescriptor = {
|
||||
name: string;
|
||||
};
|
||||
}
|
||||
export namespace Permissions {
|
||||
export const enum PermissionState {
|
||||
Granted = 'granted',
|
||||
Denied = 'denied',
|
||||
Prompt = 'prompt',
|
||||
}
|
||||
}
|
||||
export namespace Permissions {
|
||||
export type SetPermission = {
|
||||
method: 'permissions.setPermission';
|
||||
params: Permissions.SetPermissionParameters;
|
||||
};
|
||||
}
|
||||
export namespace Permissions {
|
||||
export type SetPermissionParameters = {
|
||||
descriptor: Permissions.PermissionDescriptor;
|
||||
state: Permissions.PermissionState;
|
||||
origin: string;
|
||||
userContext?: string;
|
||||
};
|
||||
}
|
|
@ -703,7 +703,7 @@ export function validateBrowserContextOptions(options: types.BrowserContextOptio
|
|||
verifyGeolocation(options.geolocation);
|
||||
}
|
||||
|
||||
export function verifyGeolocation(geolocation?: types.Geolocation) {
|
||||
export function verifyGeolocation(geolocation?: types.Geolocation): asserts geolocation is types.Geolocation {
|
||||
if (!geolocation)
|
||||
return;
|
||||
geolocation.accuracy = geolocation.accuracy || 0;
|
||||
|
|
|
@ -813,7 +813,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
|||
return this._page._delegate.getBoundingBox(this);
|
||||
}
|
||||
|
||||
async ariaSnapshot(options: { id?: boolean, mode?: 'raw' | 'regex' }): Promise<string> {
|
||||
async ariaSnapshot(options: { ref?: boolean, emitGeneric?: boolean, mode?: 'raw' | 'regex' }): Promise<string> {
|
||||
return await this.evaluateInUtility(([injected, element, options]) => injected.ariaSnapshot(element, options), options);
|
||||
}
|
||||
|
||||
|
|
|
@ -1411,7 +1411,7 @@ export class Frame extends SdkObject {
|
|||
});
|
||||
}
|
||||
|
||||
async ariaSnapshot(metadata: CallMetadata, selector: string, options: { ref?: boolean, mode?: 'raw' | 'regex' } & types.TimeoutOptions = {}): Promise<string> {
|
||||
async ariaSnapshot(metadata: CallMetadata, selector: string, options: { ref?: boolean, emitGeneric?: boolean, mode?: 'raw' | 'regex' } & types.TimeoutOptions = {}): Promise<string> {
|
||||
const controller = new ProgressController(metadata, this);
|
||||
return controller.run(async progress => {
|
||||
return await this._retryWithProgressIfNotConnected(progress, selector, true /* strict */, true /* performActionPreChecks */, handle => handle.ariaSnapshot(options));
|
||||
|
|
|
@ -12197,6 +12197,17 @@ export interface Locator {
|
|||
* rejects, this method throws.
|
||||
*
|
||||
* **Usage**
|
||||
*
|
||||
* Passing argument to
|
||||
* [`pageFunction`](https://playwright.dev/docs/api/class-locator#locator-evaluate-option-expression):
|
||||
*
|
||||
* ```js
|
||||
* const result = await page.getByTestId('myId').evaluate((element, [x, y]) => {
|
||||
* return element.textContent + ' ' + x * y;
|
||||
* }, [7, 8]);
|
||||
* console.log(result); // prints "myId text 56"
|
||||
* ```
|
||||
*
|
||||
* @param pageFunction Function to be evaluated in the page context.
|
||||
* @param arg Optional argument to pass to
|
||||
* [`pageFunction`](https://playwright.dev/docs/api/class-locator#locator-evaluate-option-expression).
|
||||
|
@ -12222,6 +12233,17 @@ export interface Locator {
|
|||
* rejects, this method throws.
|
||||
*
|
||||
* **Usage**
|
||||
*
|
||||
* Passing argument to
|
||||
* [`pageFunction`](https://playwright.dev/docs/api/class-locator#locator-evaluate-option-expression):
|
||||
*
|
||||
* ```js
|
||||
* const result = await page.getByTestId('myId').evaluate((element, [x, y]) => {
|
||||
* return element.textContent + ' ' + x * y;
|
||||
* }, [7, 8]);
|
||||
* console.log(result); // prints "myId text 56"
|
||||
* ```
|
||||
*
|
||||
* @param pageFunction Function to be evaluated in the page context.
|
||||
* @param arg Optional argument to pass to
|
||||
* [`pageFunction`](https://playwright.dev/docs/api/class-locator#locator-evaluate-option-expression).
|
||||
|
@ -12478,6 +12500,11 @@ export interface Locator {
|
|||
* @param options
|
||||
*/
|
||||
ariaSnapshot(options?: {
|
||||
/**
|
||||
* Generate `generic` aria nodes for elements w/o roles (similar to Chrome DevTools).
|
||||
*/
|
||||
emitGeneric?: boolean;
|
||||
|
||||
/**
|
||||
* Generate symbolic reference for each element. One can use `aria-ref=<ref>` locator immediately after capturing the
|
||||
* snapshot to perform actions on the element.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@playwright/experimental-ct-core",
|
||||
"version": "1.52.0-next",
|
||||
"version": "1.53.0-next",
|
||||
"description": "Playwright Component Testing Helpers",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -26,8 +26,8 @@
|
|||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"playwright-core": "1.52.0-next",
|
||||
"playwright-core": "1.53.0-next",
|
||||
"vite": "^6.2.6",
|
||||
"playwright": "1.52.0-next"
|
||||
"playwright": "1.53.0-next"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@playwright/experimental-ct-react",
|
||||
"version": "1.52.0-next",
|
||||
"version": "1.53.0-next",
|
||||
"description": "Playwright Component Testing for React",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -30,7 +30,7 @@
|
|||
"./package.json": "./package.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@playwright/experimental-ct-core": "1.52.0-next",
|
||||
"@playwright/experimental-ct-core": "1.53.0-next",
|
||||
"@vitejs/plugin-react": "^4.2.1"
|
||||
},
|
||||
"bin": {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@playwright/experimental-ct-react17",
|
||||
"version": "1.52.0-next",
|
||||
"version": "1.53.0-next",
|
||||
"description": "Playwright Component Testing for React",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -30,7 +30,7 @@
|
|||
"./package.json": "./package.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@playwright/experimental-ct-core": "1.52.0-next",
|
||||
"@playwright/experimental-ct-core": "1.53.0-next",
|
||||
"@vitejs/plugin-react": "^4.2.1"
|
||||
},
|
||||
"bin": {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@playwright/experimental-ct-svelte",
|
||||
"version": "1.52.0-next",
|
||||
"version": "1.53.0-next",
|
||||
"description": "Playwright Component Testing for Svelte",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -30,7 +30,7 @@
|
|||
"./package.json": "./package.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@playwright/experimental-ct-core": "1.52.0-next",
|
||||
"@playwright/experimental-ct-core": "1.53.0-next",
|
||||
"@sveltejs/vite-plugin-svelte": "^3.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@playwright/experimental-ct-vue",
|
||||
"version": "1.52.0-next",
|
||||
"version": "1.53.0-next",
|
||||
"description": "Playwright Component Testing for Vue",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -30,7 +30,7 @@
|
|||
"./package.json": "./package.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@playwright/experimental-ct-core": "1.52.0-next",
|
||||
"@playwright/experimental-ct-core": "1.53.0-next",
|
||||
"@vitejs/plugin-vue": "^5.2.0"
|
||||
},
|
||||
"bin": {
|
||||
|
|
|
@ -24,6 +24,6 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@octokit/graphql-schema": "^15.26.0",
|
||||
"@playwright/test": "1.52.0-next"
|
||||
"@playwright/test": "1.53.0-next"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,8 +33,6 @@ function getGithubToken() {
|
|||
|
||||
const octokit = getOctokit(getGithubToken());
|
||||
|
||||
const magicComment = '<!-- Generated by Playwright markdown reporter -->';
|
||||
|
||||
class GHAMarkdownReporter extends MarkdownReporter {
|
||||
override async publishReport(report: string) {
|
||||
core.info('Publishing report to PR.');
|
||||
|
@ -65,6 +63,7 @@ class GHAMarkdownReporter extends MarkdownReporter {
|
|||
id
|
||||
body
|
||||
author {
|
||||
__typename
|
||||
login
|
||||
}
|
||||
}
|
||||
|
@ -73,8 +72,10 @@ class GHAMarkdownReporter extends MarkdownReporter {
|
|||
}
|
||||
}
|
||||
`);
|
||||
const comments = data.repository.pullRequest?.comments.nodes?.filter(
|
||||
comment => comment?.author?.login === 'github-actions[bot]' && comment.body?.includes(magicComment));
|
||||
const comments = data.repository.pullRequest?.comments.nodes?.filter(comment =>
|
||||
comment?.author?.__typename === 'Bot' &&
|
||||
comment?.author?.login === 'github-actions' &&
|
||||
comment.body?.includes(this._magicComment()));
|
||||
const prId = data.repository.pullRequest?.id;
|
||||
if (!comments?.length)
|
||||
return prId;
|
||||
|
@ -88,13 +89,27 @@ class GHAMarkdownReporter extends MarkdownReporter {
|
|||
return prId;
|
||||
}
|
||||
|
||||
private _magicComment() {
|
||||
return `<!-- Generated by Playwright markdown reporter for ${this._workflowRunName()} in job ${process.env.GITHUB_JOB} -->`;
|
||||
}
|
||||
|
||||
private _workflowRunName() {
|
||||
// When used via 'workflow_run' event.
|
||||
const workflowRunName = context.payload.workflow_run?.name;
|
||||
if (workflowRunName)
|
||||
return workflowRunName;
|
||||
// When used via 'pull_request'/'push' event.
|
||||
// This is the name of the workflow file, e.g. 'ci.yml' or name if set.
|
||||
return process.env.GITHUB_WORKFLOW;
|
||||
}
|
||||
|
||||
private async addNewReportComment(prNodeId: string, report: string) {
|
||||
const reportUrl = process.env.HTML_REPORT_URL;
|
||||
const mergeWorkflowUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
|
||||
|
||||
const body = formatComment([
|
||||
magicComment,
|
||||
`### [Test results](${reportUrl}) for "${context.payload.workflow_run?.name}"`,
|
||||
this._magicComment(),
|
||||
`### ${reportUrl ? `[Test results](${reportUrl})` : 'Test results'} for "${this._workflowRunName()}"`,
|
||||
report,
|
||||
'',
|
||||
`Merge [workflow run](${mergeWorkflowUrl}).`
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "playwright-firefox",
|
||||
"version": "1.52.0-next",
|
||||
"version": "1.53.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.52.0-next"
|
||||
"playwright-core": "1.53.0-next"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@playwright/test",
|
||||
"version": "1.52.0-next",
|
||||
"version": "1.53.0-next",
|
||||
"description": "A high-level API to automate web browsers",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -30,6 +30,6 @@
|
|||
},
|
||||
"scripts": {},
|
||||
"dependencies": {
|
||||
"playwright": "1.52.0-next"
|
||||
"playwright": "1.53.0-next"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "playwright-webkit",
|
||||
"version": "1.52.0-next",
|
||||
"version": "1.53.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.52.0-next"
|
||||
"playwright-core": "1.53.0-next"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "playwright",
|
||||
"version": "1.52.0-next",
|
||||
"version": "1.53.0-next",
|
||||
"description": "A high-level API to automate web browsers",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -56,7 +56,7 @@
|
|||
},
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright-core": "1.52.0-next"
|
||||
"playwright-core": "1.53.0-next"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "2.3.2"
|
||||
|
|
|
@ -14,5 +14,5 @@ common/
|
|||
[internalsForTest.ts]
|
||||
**
|
||||
|
||||
[prompt.ts]
|
||||
[errorContext.ts]
|
||||
./transform/babelBundle.ts
|
||||
|
|
|
@ -36,6 +36,7 @@ export type FixturesWithLocation = {
|
|||
fixtures: Fixtures;
|
||||
location: Location;
|
||||
};
|
||||
export type Annotation = { type: string, description?: string };
|
||||
|
||||
export const defaultTimeout = 30000;
|
||||
|
||||
|
|
|
@ -17,10 +17,9 @@
|
|||
import { rootTestType } from './testType';
|
||||
import { computeTestCaseOutcome } from '../isomorphic/teleReceiver';
|
||||
|
||||
import type { FixturesWithLocation, FullProjectInternal } from './config';
|
||||
import type { Annotation, FixturesWithLocation, FullProjectInternal } from './config';
|
||||
import type { FixturePool } from './fixtures';
|
||||
import type { TestTypeImpl } from './testType';
|
||||
import type { TestAnnotation } from '../../types/test';
|
||||
import type * as reporterTypes from '../../types/testReporter';
|
||||
import type { FullProject, Location } from '../../types/testReporter';
|
||||
|
||||
|
@ -51,7 +50,7 @@ export class Suite extends Base {
|
|||
_timeout: number | undefined;
|
||||
_retries: number | undefined;
|
||||
// Annotations known statically before running the test, e.g. `test.describe.skip()` or `test.describe({ annotation }, body)`.
|
||||
_staticAnnotations: TestAnnotation[] = [];
|
||||
_staticAnnotations: Annotation[] = [];
|
||||
// Explicitly declared tags that are not a part of the title.
|
||||
_tags: string[] = [];
|
||||
_modifiers: Modifier[] = [];
|
||||
|
@ -253,7 +252,7 @@ export class TestCase extends Base implements reporterTypes.TestCase {
|
|||
|
||||
expectedStatus: reporterTypes.TestStatus = 'passed';
|
||||
timeout = 0;
|
||||
annotations: TestAnnotation[] = [];
|
||||
annotations: Annotation[] = [];
|
||||
retries = 0;
|
||||
repeatEachIndex = 0;
|
||||
|
||||
|
|
|
@ -25,8 +25,6 @@ import { wrapFunctionWithLocation } from '../transform/transform';
|
|||
import type { FixturesWithLocation } from './config';
|
||||
import type { Fixtures, TestDetails, TestStepInfo, TestType } from '../../types/test';
|
||||
import type { Location } from '../../types/testReporter';
|
||||
import type { TestInfoImpl, TestStepInternal } from '../worker/testInfo';
|
||||
|
||||
|
||||
const testTypeSymbol = Symbol('testType');
|
||||
|
||||
|
@ -104,7 +102,7 @@ export class TestTypeImpl {
|
|||
details = fnOrDetails;
|
||||
}
|
||||
|
||||
const validatedDetails = validateTestDetails(details, location);
|
||||
const validatedDetails = validateTestDetails(details);
|
||||
const test = new TestCase(title, body, this, location);
|
||||
test._requireFile = suite._requireFile;
|
||||
test.annotations.push(...validatedDetails.annotations);
|
||||
|
@ -114,9 +112,9 @@ export class TestTypeImpl {
|
|||
if (type === 'only' || type === 'fail.only')
|
||||
test._only = true;
|
||||
if (type === 'skip' || type === 'fixme' || type === 'fail')
|
||||
test.annotations.push({ type, location });
|
||||
test.annotations.push({ type });
|
||||
else if (type === 'fail.only')
|
||||
test.annotations.push({ type: 'fail', location });
|
||||
test.annotations.push({ type: 'fail' });
|
||||
}
|
||||
|
||||
private _describe(type: 'default' | 'only' | 'serial' | 'serial.only' | 'parallel' | 'parallel.only' | 'skip' | 'fixme', location: Location, titleOrFn: string | Function, fnOrDetails?: TestDetails | Function, fn?: Function) {
|
||||
|
@ -143,7 +141,7 @@ export class TestTypeImpl {
|
|||
body = fn!;
|
||||
}
|
||||
|
||||
const validatedDetails = validateTestDetails(details, location);
|
||||
const validatedDetails = validateTestDetails(details);
|
||||
const child = new Suite(title, 'describe');
|
||||
child._requireFile = suite._requireFile;
|
||||
child.location = location;
|
||||
|
@ -158,7 +156,7 @@ export class TestTypeImpl {
|
|||
if (type === 'parallel' || type === 'parallel.only')
|
||||
child._parallelMode = 'parallel';
|
||||
if (type === 'skip' || type === 'fixme')
|
||||
child._staticAnnotations.push({ type, location });
|
||||
child._staticAnnotations.push({ type });
|
||||
|
||||
for (let parent: Suite | undefined = suite; parent; parent = parent.parent) {
|
||||
if (parent._parallelMode === 'serial' && child._parallelMode === 'parallel')
|
||||
|
@ -229,7 +227,7 @@ export class TestTypeImpl {
|
|||
if (modifierArgs.length >= 1 && !modifierArgs[0])
|
||||
return;
|
||||
const description = modifierArgs[1];
|
||||
suite._staticAnnotations.push({ type, description, location });
|
||||
suite._staticAnnotations.push({ type, description });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
@ -239,7 +237,7 @@ export class TestTypeImpl {
|
|||
throw new Error(`test.${type}() can only be called inside test, describe block or fixture`);
|
||||
if (typeof modifierArgs[0] === 'function')
|
||||
throw new Error(`test.${type}() with a function can only be called inside describe block`);
|
||||
testInfo._modifier(type, location, modifierArgs as [any, any]);
|
||||
testInfo[type](...modifierArgs as [any, any]);
|
||||
}
|
||||
|
||||
private _setTimeout(location: Location, timeout: number) {
|
||||
|
@ -262,21 +260,17 @@ export class TestTypeImpl {
|
|||
suite._use.push({ fixtures, location });
|
||||
}
|
||||
|
||||
_step<T>(expectation: 'pass'|'skip', title: string, body: (step: TestStepInfo) => T | Promise<T>, options: {box?: boolean, location?: Location, timeout?: number } = {}): Promise<T> {
|
||||
async _step<T>(expectation: 'pass'|'skip', title: string, body: (step: TestStepInfo) => T | Promise<T>, options: {box?: boolean, location?: Location, timeout?: number } = {}): Promise<T> {
|
||||
const testInfo = currentTestInfo();
|
||||
if (!testInfo)
|
||||
throw new Error(`test.step() can only be called from a test`);
|
||||
const step = testInfo._addStep({ category: 'test.step', title, location: options.location, box: options.box });
|
||||
return testInfo._floatingPromiseScope.wrapPromiseAPIResult(this._stepInternal(expectation, testInfo, step, body, options), step.location);
|
||||
}
|
||||
|
||||
private async _stepInternal<T>(expectation: 'pass'|'skip', testInfo: TestInfoImpl, step: TestStepInternal, body: (step: TestStepInfo) => T | Promise<T>, options: {box?: boolean, location?: Location, timeout?: number } = {}): Promise<T> {
|
||||
return await currentZone().with('stepZone', step).run(async () => {
|
||||
try {
|
||||
let result: Awaited<ReturnType<typeof raceAgainstDeadline<T>>> | undefined = undefined;
|
||||
result = await raceAgainstDeadline(async () => {
|
||||
try {
|
||||
return await step.info._runStepBody(expectation === 'skip', body, step.location);
|
||||
return await step.info._runStepBody(expectation === 'skip', body);
|
||||
} catch (e) {
|
||||
// If the step timed out, the test fixtures will tear down, which in turn
|
||||
// will abort unfinished actions in the step body. Record such errors here.
|
||||
|
@ -315,9 +309,8 @@ function throwIfRunningInsideJest() {
|
|||
}
|
||||
}
|
||||
|
||||
function validateTestDetails(details: TestDetails, location: Location) {
|
||||
const originalAnnotations = Array.isArray(details.annotation) ? details.annotation : (details.annotation ? [details.annotation] : []);
|
||||
const annotations = originalAnnotations.map(annotation => ({ ...annotation, location }));
|
||||
function validateTestDetails(details: TestDetails) {
|
||||
const annotations = Array.isArray(details.annotation) ? details.annotation : (details.annotation ? [details.annotation] : []);
|
||||
const tags = Array.isArray(details.tag) ? details.tag : (details.tag ? [details.tag] : []);
|
||||
for (const tag of tags) {
|
||||
if (tag[0] !== '@')
|
||||
|
|
|
@ -22,13 +22,24 @@ import { parseErrorStack } from 'playwright-core/lib/utils';
|
|||
import { stripAnsiEscapes } from './util';
|
||||
import { codeFrameColumns } from './transform/babelBundle';
|
||||
|
||||
import type { TestInfo } from '../types/test';
|
||||
import type { MetadataWithCommitInfo } from './isomorphic/types';
|
||||
import type { TestInfoImpl } from './worker/testInfo';
|
||||
|
||||
export async function attachErrorPrompts(testInfo: TestInfo, sourceCache: Map<string, string>, ariaSnapshot: string | undefined) {
|
||||
if (process.env.PLAYWRIGHT_NO_COPY_PROMPT)
|
||||
export async function attachErrorContext(testInfo: TestInfoImpl, format: 'markdown' | 'json', sourceCache: Map<string, string>, ariaSnapshot: string | undefined) {
|
||||
if (format === 'json') {
|
||||
if (!ariaSnapshot)
|
||||
return;
|
||||
|
||||
testInfo._attach({
|
||||
name: `_error-context`,
|
||||
contentType: 'application/json',
|
||||
body: Buffer.from(JSON.stringify({
|
||||
pageSnapshot: ariaSnapshot,
|
||||
})),
|
||||
}, undefined);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const meaningfulSingleLineErrors = new Set(testInfo.errors.filter(e => e.message && !e.message.includes('\n')).map(e => e.message!));
|
||||
for (const error of testInfo.errors) {
|
||||
|
@ -51,16 +62,10 @@ export async function attachErrorPrompts(testInfo: TestInfo, sourceCache: Map<st
|
|||
|
||||
for (const [index, error] of errors) {
|
||||
const metadata = testInfo.config.metadata as MetadataWithCommitInfo;
|
||||
if (testInfo.attachments.find(a => a.name === `_prompt-${index}`))
|
||||
if (testInfo.attachments.find(a => a.name === `_error-context-${index}`))
|
||||
continue;
|
||||
|
||||
const promptParts = [
|
||||
`# Instructions`,
|
||||
'',
|
||||
`- Following Playwright test failed.`,
|
||||
`- Explain why, be concise, respect Playwright best practices.`,
|
||||
`- Provide a snippet of code with the fix, if possible.`,
|
||||
'',
|
||||
const lines = [
|
||||
`# Test info`,
|
||||
'',
|
||||
`- Name: ${testInfo.titlePath.slice(1).join(' >> ')}`,
|
||||
|
@ -74,7 +79,7 @@ export async function attachErrorPrompts(testInfo: TestInfo, sourceCache: Map<st
|
|||
];
|
||||
|
||||
if (ariaSnapshot) {
|
||||
promptParts.push(
|
||||
lines.push(
|
||||
'',
|
||||
'# Page snapshot',
|
||||
'',
|
||||
|
@ -103,7 +108,7 @@ export async function attachErrorPrompts(testInfo: TestInfo, sourceCache: Map<st
|
|||
message: inlineMessage || undefined,
|
||||
}
|
||||
);
|
||||
promptParts.push(
|
||||
lines.push(
|
||||
'',
|
||||
'# Test source',
|
||||
'',
|
||||
|
@ -113,7 +118,7 @@ export async function attachErrorPrompts(testInfo: TestInfo, sourceCache: Map<st
|
|||
);
|
||||
|
||||
if (metadata.gitDiff) {
|
||||
promptParts.push(
|
||||
lines.push(
|
||||
'',
|
||||
'# Local changes',
|
||||
'',
|
||||
|
@ -123,30 +128,17 @@ export async function attachErrorPrompts(testInfo: TestInfo, sourceCache: Map<st
|
|||
);
|
||||
}
|
||||
|
||||
const promptPath = testInfo.outputPath(errors.length === 1 ? `prompt.md` : `prompt-${index}.md`);
|
||||
await fs.writeFile(promptPath, promptParts.join('\n'), 'utf8');
|
||||
const filePath = testInfo.outputPath(errors.length === 1 ? `error-context.md` : `error-context-${index}.md`);
|
||||
await fs.writeFile(filePath, lines.join('\n'), 'utf8');
|
||||
|
||||
(testInfo as TestInfoImpl)._attach({
|
||||
name: `_prompt-${index}`,
|
||||
name: `_error-context-${index}`,
|
||||
contentType: 'text/markdown',
|
||||
path: promptPath,
|
||||
path: filePath,
|
||||
}, undefined);
|
||||
}
|
||||
}
|
||||
|
||||
export async function attachErrorContext(testInfo: TestInfo, ariaSnapshot: string | undefined) {
|
||||
if (!ariaSnapshot)
|
||||
return;
|
||||
|
||||
(testInfo as TestInfoImpl)._attach({
|
||||
name: `_error-context`,
|
||||
contentType: 'application/json',
|
||||
body: Buffer.from(JSON.stringify({
|
||||
pageSnapshot: ariaSnapshot,
|
||||
})),
|
||||
}, undefined);
|
||||
}
|
||||
|
||||
async function loadSource(file: string, sourceCache: Map<string, string>) {
|
||||
let source = sourceCache.get(file);
|
||||
if (!source) {
|
|
@ -22,7 +22,7 @@ import { setBoxedStackPrefixes, asLocator, createGuid, currentZone, debugMode, i
|
|||
|
||||
import { currentTestInfo } from './common/globals';
|
||||
import { rootTestType } from './common/testType';
|
||||
import { attachErrorContext, attachErrorPrompts } from './prompt';
|
||||
import { attachErrorContext } from './errorContext';
|
||||
|
||||
import type { Fixtures, PlaywrightTestArgs, PlaywrightTestOptions, PlaywrightWorkerArgs, PlaywrightWorkerOptions, ScreenshotMode, TestInfo, TestType, VideoMode } from '../types/test';
|
||||
import type { ContextReuseMode } from './common/config';
|
||||
|
@ -55,13 +55,15 @@ type TestFixtures = PlaywrightTestArgs & PlaywrightTestOptions & {
|
|||
_contextFactory: (options?: BrowserContextOptions) => Promise<BrowserContext>;
|
||||
};
|
||||
|
||||
type ErrorContextOption = { format: 'json' | 'markdown' } | undefined;
|
||||
|
||||
type WorkerFixtures = PlaywrightWorkerArgs & PlaywrightWorkerOptions & {
|
||||
playwright: PlaywrightImpl;
|
||||
_browserOptions: LaunchOptions;
|
||||
_optionContextReuseMode: ContextReuseMode,
|
||||
_optionConnectOptions: PlaywrightWorkerOptions['connectOptions'],
|
||||
_reuseContext: boolean,
|
||||
_optionAttachErrorContext: boolean,
|
||||
_optionErrorContext: ErrorContextOption,
|
||||
};
|
||||
|
||||
const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
|
||||
|
@ -245,13 +247,13 @@ const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
|
|||
playwright._defaultContextNavigationTimeout = undefined;
|
||||
}, { auto: 'all-hooks-included', title: 'context configuration', box: true } as any],
|
||||
|
||||
_setupArtifacts: [async ({ playwright, screenshot, _optionAttachErrorContext }, use, testInfo) => {
|
||||
_setupArtifacts: [async ({ playwright, screenshot, _optionErrorContext }, use, testInfo) => {
|
||||
// This fixture has a separate zero-timeout slot to ensure that artifact collection
|
||||
// happens even after some fixtures or hooks time out.
|
||||
// Now that default test timeout is known, we can replace zero with an actual value.
|
||||
testInfo.setTimeout(testInfo.project.timeout);
|
||||
|
||||
const artifactsRecorder = new ArtifactsRecorder(playwright, tracing().artifactsDir(), screenshot, _optionAttachErrorContext);
|
||||
const artifactsRecorder = new ArtifactsRecorder(playwright, tracing().artifactsDir(), screenshot, _optionErrorContext);
|
||||
await artifactsRecorder.willStartTest(testInfo as TestInfoImpl);
|
||||
|
||||
const tracingGroupSteps: TestStepInternal[] = [];
|
||||
|
@ -393,7 +395,7 @@ const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
|
|||
|
||||
_optionContextReuseMode: ['none', { scope: 'worker', option: true }],
|
||||
_optionConnectOptions: [undefined, { scope: 'worker', option: true }],
|
||||
_optionAttachErrorContext: [false, { scope: 'worker', option: true }],
|
||||
_optionErrorContext: [process.env.PLAYWRIGHT_NO_COPY_PROMPT ? undefined : { format: 'markdown' }, { scope: 'worker', option: true }],
|
||||
|
||||
_reuseContext: [async ({ video, _optionContextReuseMode }, use) => {
|
||||
let mode = _optionContextReuseMode;
|
||||
|
@ -622,12 +624,12 @@ class ArtifactsRecorder {
|
|||
private _screenshotRecorder: SnapshotRecorder;
|
||||
private _pageSnapshot: string | undefined;
|
||||
private _sourceCache: Map<string, string> = new Map();
|
||||
private _attachErrorContext: boolean;
|
||||
private _errorContext: ErrorContextOption;
|
||||
|
||||
constructor(playwright: PlaywrightImpl, artifactsDir: string, screenshot: ScreenshotOption, attachErrorContext: boolean) {
|
||||
constructor(playwright: PlaywrightImpl, artifactsDir: string, screenshot: ScreenshotOption, errorContext: ErrorContextOption) {
|
||||
this._playwright = playwright;
|
||||
this._artifactsDir = artifactsDir;
|
||||
this._attachErrorContext = attachErrorContext;
|
||||
this._errorContext = errorContext;
|
||||
const screenshotOptions = typeof screenshot === 'string' ? undefined : screenshot;
|
||||
this._startedCollectingArtifacts = Symbol('startedCollectingArtifacts');
|
||||
|
||||
|
@ -671,7 +673,7 @@ class ArtifactsRecorder {
|
|||
}
|
||||
|
||||
private async _takePageSnapshot(context: BrowserContext) {
|
||||
if (process.env.PLAYWRIGHT_NO_COPY_PROMPT)
|
||||
if (!this._errorContext)
|
||||
return;
|
||||
if (this._testInfo.errors.length === 0)
|
||||
return;
|
||||
|
@ -719,10 +721,8 @@ class ArtifactsRecorder {
|
|||
if (context)
|
||||
await this._takePageSnapshot(context);
|
||||
|
||||
if (this._attachErrorContext)
|
||||
await attachErrorContext(this._testInfo, this._pageSnapshot);
|
||||
else
|
||||
await attachErrorPrompts(this._testInfo, this._sourceCache, this._pageSnapshot);
|
||||
if (this._errorContext)
|
||||
await attachErrorContext(this._testInfo, this._errorContext.format, this._sourceCache, this._pageSnapshot);
|
||||
}
|
||||
|
||||
private async _startTraceChunkOnContextCreation(tracing: Tracing) {
|
||||
|
|
|
@ -14,8 +14,9 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { Metadata, TestAnnotation } from '../../types/test';
|
||||
import type { Metadata } from '../../types/test';
|
||||
import type * as reporterTypes from '../../types/testReporter';
|
||||
import type { Annotation } from '../common/config';
|
||||
import type { ReporterV2 } from '../reporters/reporterV2';
|
||||
|
||||
export type StringIntern = (s: string) => string;
|
||||
|
@ -67,7 +68,7 @@ export type JsonTestCase = {
|
|||
retries: number;
|
||||
tags?: string[];
|
||||
repeatEachIndex: number;
|
||||
annotations?: TestAnnotation[];
|
||||
annotations?: Annotation[];
|
||||
};
|
||||
|
||||
export type JsonTestEnd = {
|
||||
|
@ -86,15 +87,16 @@ export type JsonTestResultStart = {
|
|||
startTime: number;
|
||||
};
|
||||
|
||||
export type JsonAttachment = Omit<reporterTypes.TestResult['attachments'][0], 'body'> & { base64?: string };
|
||||
export type JsonAttachment = Omit<reporterTypes.TestResult['attachments'][0], 'body'> & { base64?: string; };
|
||||
|
||||
export type JsonTestResultEnd = {
|
||||
id: string;
|
||||
duration: number;
|
||||
status: reporterTypes.TestStatus;
|
||||
errors: reporterTypes.TestError[];
|
||||
attachments: JsonAttachment[];
|
||||
annotations?: TestAnnotation[];
|
||||
/** No longer emitted, but kept for backwards compatibility */
|
||||
attachments?: JsonAttachment[];
|
||||
annotations?: Annotation[];
|
||||
};
|
||||
|
||||
export type JsonTestStepStart = {
|
||||
|
@ -111,7 +113,13 @@ export type JsonTestStepEnd = {
|
|||
duration: number;
|
||||
error?: reporterTypes.TestError;
|
||||
attachments?: number[]; // index of JsonTestResultEnd.attachments
|
||||
annotations?: TestAnnotation[];
|
||||
annotations?: Annotation[];
|
||||
};
|
||||
|
||||
export type JsonTestResultOnAttach = {
|
||||
testId: string;
|
||||
resultId: string;
|
||||
attachments: JsonAttachment[];
|
||||
};
|
||||
|
||||
export type JsonFullResult = {
|
||||
|
@ -179,6 +187,10 @@ export class TeleReporterReceiver {
|
|||
this._onStepBegin(params.testId, params.resultId, params.step);
|
||||
return;
|
||||
}
|
||||
if (method === 'onAttach') {
|
||||
this._onAttach(params.testId, params.resultId, params.attachments);
|
||||
return;
|
||||
}
|
||||
if (method === 'onStepEnd') {
|
||||
this._onStepEnd(params.testId, params.resultId, params.step);
|
||||
return;
|
||||
|
@ -240,9 +252,11 @@ export class TeleReporterReceiver {
|
|||
result.status = payload.status;
|
||||
result.errors = payload.errors;
|
||||
result.error = result.errors?.[0];
|
||||
result.attachments = this._parseAttachments(payload.attachments);
|
||||
// Attachments are only present here from legacy blobs. These override all _onAttach events
|
||||
if (!!payload.attachments)
|
||||
result.attachments = this._parseAttachments(payload.attachments);
|
||||
if (payload.annotations) {
|
||||
result.annotations = this._absoluteAnnotationLocations(payload.annotations);
|
||||
result.annotations = payload.annotations;
|
||||
test.annotations = result.annotations;
|
||||
}
|
||||
this._reporter.onTestEnd?.(test, result);
|
||||
|
@ -275,6 +289,17 @@ export class TeleReporterReceiver {
|
|||
this._reporter.onStepEnd?.(test, result, step);
|
||||
}
|
||||
|
||||
private _onAttach(testId: string, resultId: string, attachments: JsonAttachment[]) {
|
||||
const test = this._tests.get(testId)!;
|
||||
const result = test.results.find(r => r._id === resultId)!;
|
||||
result.attachments.push(...attachments.map(a => ({
|
||||
name: a.name,
|
||||
contentType: a.contentType,
|
||||
path: a.path,
|
||||
body: a.base64 && (globalThis as any).Buffer ? Buffer.from(a.base64, 'base64') : undefined,
|
||||
})));
|
||||
}
|
||||
|
||||
private _onError(error: reporterTypes.TestError) {
|
||||
this._reporter.onError?.(error);
|
||||
}
|
||||
|
@ -374,18 +399,10 @@ export class TeleReporterReceiver {
|
|||
test.location = this._absoluteLocation(payload.location);
|
||||
test.retries = payload.retries;
|
||||
test.tags = payload.tags ?? [];
|
||||
test.annotations = this._absoluteAnnotationLocations(payload.annotations ?? []);
|
||||
test.annotations = payload.annotations ?? [];
|
||||
return test;
|
||||
}
|
||||
|
||||
private _absoluteAnnotationLocations(annotations: TestAnnotation[]): TestAnnotation[] {
|
||||
return annotations.map(annotation => {
|
||||
if (annotation.location)
|
||||
annotation.location = this._absoluteLocation(annotation.location);
|
||||
return annotation;
|
||||
});
|
||||
}
|
||||
|
||||
private _absoluteLocation(location: reporterTypes.Location): reporterTypes.Location;
|
||||
private _absoluteLocation(location?: reporterTypes.Location): reporterTypes.Location | undefined;
|
||||
private _absoluteLocation(location: reporterTypes.Location | undefined): reporterTypes.Location | undefined {
|
||||
|
@ -486,7 +503,7 @@ export class TeleTestCase implements reporterTypes.TestCase {
|
|||
|
||||
expectedStatus: reporterTypes.TestStatus = 'passed';
|
||||
timeout = 0;
|
||||
annotations: TestAnnotation[] = [];
|
||||
annotations: Annotation[] = [];
|
||||
retries = 0;
|
||||
tags: string[] = [];
|
||||
repeatEachIndex = 0;
|
||||
|
|
|
@ -102,7 +102,7 @@ export interface TestServerInterface {
|
|||
projects?: string[];
|
||||
reuseContext?: boolean;
|
||||
connectWsEndpoint?: string;
|
||||
attachErrorContext?: boolean;
|
||||
errorContext?: { format: 'json' | 'markdown' };
|
||||
}): Promise<{
|
||||
status: reporterTypes.FullResult['status'];
|
||||
}>;
|
||||
|
|
|
@ -385,10 +385,8 @@ class ExpectMetaInfoProxyHandler implements ProxyHandler<any> {
|
|||
setMatcherCallContext({ expectInfo: this._info, testInfo, step: step.info });
|
||||
const callback = () => matcher.call(target, ...args);
|
||||
const result = currentZone().with('stepZone', step).run(callback);
|
||||
if (result instanceof Promise) {
|
||||
const promise = result.then(finalizer).catch(reportStepError);
|
||||
return testInfo._floatingPromiseScope.wrapPromiseAPIResult(promise, stackFrames[0]);
|
||||
}
|
||||
if (result instanceof Promise)
|
||||
return result.then(finalizer).catch(reportStepError);
|
||||
finalizer();
|
||||
return result;
|
||||
} catch (e) {
|
||||
|
|
|
@ -26,7 +26,6 @@ import { getEastAsianWidth } from '../utilsBundle';
|
|||
import type { ReporterV2 } from './reporterV2';
|
||||
import type { FullConfig, FullResult, Location, Suite, TestCase, TestError, TestResult, TestStep } from '../../types/testReporter';
|
||||
import type { Colors } from '@isomorphic/colors';
|
||||
import type { TestAnnotation } from '../../types/test';
|
||||
|
||||
export type TestResultOutput = { chunk: string | Buffer, type: 'stdout' | 'stderr' };
|
||||
export const kOutputSymbol = Symbol('output');
|
||||
|
@ -320,7 +319,6 @@ export function formatFailure(screen: Screen, config: FullConfig, test: TestCase
|
|||
const header = formatTestHeader(screen, config, test, { indent: ' ', index, mode: 'error' });
|
||||
lines.push(screen.colors.red(header));
|
||||
for (const result of test.results) {
|
||||
const warnings = result.annotations.filter(a => a.type === 'warning');
|
||||
const resultLines: string[] = [];
|
||||
const errors = formatResultFailure(screen, test, result, ' ');
|
||||
if (!errors.length)
|
||||
|
@ -330,15 +328,11 @@ export function formatFailure(screen: Screen, config: FullConfig, test: TestCase
|
|||
resultLines.push(screen.colors.gray(separator(screen, ` Retry #${result.retry}`)));
|
||||
}
|
||||
resultLines.push(...errors.map(error => '\n' + error.message));
|
||||
if (warnings.length) {
|
||||
resultLines.push('');
|
||||
resultLines.push(...formatTestWarning(screen, config, warnings));
|
||||
}
|
||||
for (let i = 0; i < result.attachments.length; ++i) {
|
||||
const attachment = result.attachments[i];
|
||||
if (attachment.name.startsWith('_prompt') && attachment.path) {
|
||||
if (attachment.name.startsWith('_error-context') && attachment.path) {
|
||||
resultLines.push('');
|
||||
resultLines.push(screen.colors.dim(` Error Prompt: ${relativeFilePath(screen, config, attachment.path)}`));
|
||||
resultLines.push(screen.colors.dim(` Error Context: ${relativeFilePath(screen, config, attachment.path)}`));
|
||||
continue;
|
||||
}
|
||||
if (attachment.name.startsWith('_'))
|
||||
|
@ -376,26 +370,6 @@ export function formatFailure(screen: Screen, config: FullConfig, test: TestCase
|
|||
return lines.join('\n');
|
||||
}
|
||||
|
||||
function formatTestWarning(screen: Screen, config: FullConfig, warnings: TestAnnotation[]): string[] {
|
||||
warnings.sort((a, b) => {
|
||||
const aLocationKey = a.location ? `${a.location.file}:${a.location.line}:${a.location.column}` : undefined;
|
||||
const bLocationKey = b.location ? `${b.location.file}:${b.location.line}:${b.location.column}` : undefined;
|
||||
|
||||
if (!aLocationKey && !bLocationKey)
|
||||
return 0;
|
||||
if (!aLocationKey)
|
||||
return 1;
|
||||
if (!bLocationKey)
|
||||
return -1;
|
||||
return aLocationKey.localeCompare(bLocationKey);
|
||||
});
|
||||
|
||||
return warnings.filter(w => !!w.description).map(w => {
|
||||
const location = !!w.location ? `${relativeFilePath(screen, config, w.location.file)}:${w.location.line}:${w.location.column}: ` : '';
|
||||
return `${screen.colors.yellow(` Warning: ${location}${w.description}`)}`;
|
||||
});
|
||||
}
|
||||
|
||||
export function formatRetry(screen: Screen, result: TestResult) {
|
||||
const retryLines = [];
|
||||
if (result.retry) {
|
||||
|
|
|
@ -29,9 +29,9 @@ import { codeFrameColumns } from '../transform/babelBundle';
|
|||
import { resolveReporterOutputPath, stripAnsiEscapes } from '../util';
|
||||
|
||||
import type { ReporterV2 } from './reporterV2';
|
||||
import type { Metadata, TestAnnotation } from '../../types/test';
|
||||
import type { Metadata } from '../../types/test';
|
||||
import type * as api from '../../types/testReporter';
|
||||
import type { HTMLReport, Stats, TestAttachment, TestCase, TestCaseSummary, TestFile, TestFileSummary, TestResult, TestStep } from '@html-reporter/types';
|
||||
import type { HTMLReport, Stats, TestAttachment, TestCase, TestCaseSummary, TestFile, TestFileSummary, TestResult, TestStep, TestAnnotation } from '@html-reporter/types';
|
||||
import type { ZipFile } from 'playwright-core/lib/zipBundle';
|
||||
import type { TransformCallback } from 'stream';
|
||||
|
||||
|
@ -324,12 +324,15 @@ class HtmlBuilder {
|
|||
async function redirect() {
|
||||
const hmrURL = new URL('http://localhost:44224'); // dev server, port is harcoded in build.js
|
||||
const popup = window.open(hmrURL);
|
||||
window.addEventListener('message', evt => {
|
||||
const listener = (evt: MessageEvent) => {
|
||||
if (evt.source === popup && evt.data === 'ready') {
|
||||
popup!.postMessage((window as any).playwrightReportBase64, hmrURL.origin);
|
||||
window.removeEventListener('message', listener);
|
||||
// This is generally not allowed
|
||||
window.close();
|
||||
}
|
||||
}, { once: true });
|
||||
};
|
||||
window.addEventListener('message', listener);
|
||||
}
|
||||
|
||||
fs.appendFileSync(redirectFile, `<script>(${redirect.toString()})()</script>`);
|
||||
|
@ -503,7 +506,7 @@ class HtmlBuilder {
|
|||
|
||||
private _serializeAnnotations(annotations: api.TestCase['annotations']): TestAnnotation[] {
|
||||
// Annotations can be pushed directly, with a wrong type.
|
||||
return annotations.map(a => ({ type: a.type, description: a.description === undefined ? undefined : String(a.description), location: a.location }));
|
||||
return annotations.map(a => ({ type: a.type, description: a.description === undefined ? undefined : String(a.description) }));
|
||||
}
|
||||
|
||||
private _createTestResult(test: api.TestCase, result: api.TestResult): TestResult {
|
||||
|
|
|
@ -27,10 +27,10 @@ import { createReporters } from '../runner/reporters';
|
|||
import { relativeFilePath } from '../util';
|
||||
|
||||
import type { BlobReportMetadata } from './blob';
|
||||
import type { ReporterDescription, TestAnnotation } from '../../types/test';
|
||||
import type { ReporterDescription } from '../../types/test';
|
||||
import type { TestError } from '../../types/testReporter';
|
||||
import type { FullConfigInternal } from '../common/config';
|
||||
import type { JsonConfig, JsonEvent, JsonFullResult, JsonLocation, JsonProject, JsonSuite, JsonTestCase, JsonTestEnd, JsonTestResultEnd, JsonTestStepEnd, JsonTestStepStart } from '../isomorphic/teleReceiver';
|
||||
import type { JsonAttachment, JsonConfig, JsonEvent, JsonFullResult, JsonLocation, JsonProject, JsonSuite, JsonTestCase, JsonTestResultEnd, JsonTestResultOnAttach, JsonTestStepEnd, JsonTestStepStart } from '../isomorphic/teleReceiver';
|
||||
import type * as blobV1 from './versions/blobV1';
|
||||
|
||||
type StatusCallback = (message: string) => void;
|
||||
|
@ -390,6 +390,7 @@ class IdsPatcher {
|
|||
case 'onProject':
|
||||
this._onProject(params.project);
|
||||
return;
|
||||
case 'onAttach':
|
||||
case 'onTestBegin':
|
||||
case 'onStepBegin':
|
||||
case 'onStepEnd':
|
||||
|
@ -447,9 +448,14 @@ class AttachmentPathPatcher {
|
|||
}
|
||||
|
||||
patchEvent(event: JsonEvent) {
|
||||
if (event.method !== 'onTestEnd')
|
||||
return;
|
||||
for (const attachment of (event.params.result as JsonTestResultEnd).attachments) {
|
||||
if (event.method === 'onAttach')
|
||||
this._patchAttachments((event.params as JsonTestResultOnAttach).attachments);
|
||||
else if (event.method === 'onTestEnd')
|
||||
this._patchAttachments((event.params as JsonTestResultEnd).attachments ?? []);
|
||||
}
|
||||
|
||||
private _patchAttachments(attachments: JsonAttachment[]) {
|
||||
for (const attachment of attachments) {
|
||||
if (!attachment.path)
|
||||
continue;
|
||||
|
||||
|
@ -474,12 +480,9 @@ class PathSeparatorPatcher {
|
|||
return;
|
||||
}
|
||||
if (jsonEvent.method === 'onTestEnd') {
|
||||
const test = jsonEvent.params.test as JsonTestEnd;
|
||||
test.annotations?.forEach(annotation => this._updateAnnotationLocations(annotation));
|
||||
const testResult = jsonEvent.params.result as JsonTestResultEnd;
|
||||
testResult.annotations?.forEach(annotation => this._updateAnnotationLocations(annotation));
|
||||
testResult.errors.forEach(error => this._updateErrorLocations(error));
|
||||
testResult.attachments.forEach(attachment => {
|
||||
(testResult.attachments ?? []).forEach(attachment => {
|
||||
if (attachment.path)
|
||||
attachment.path = this._updatePath(attachment.path);
|
||||
});
|
||||
|
@ -493,7 +496,14 @@ class PathSeparatorPatcher {
|
|||
if (jsonEvent.method === 'onStepEnd') {
|
||||
const step = jsonEvent.params.step as JsonTestStepEnd;
|
||||
this._updateErrorLocations(step.error);
|
||||
step.annotations?.forEach(annotation => this._updateAnnotationLocations(annotation));
|
||||
return;
|
||||
}
|
||||
if (jsonEvent.method === 'onAttach') {
|
||||
const attach = jsonEvent.params as JsonTestResultOnAttach;
|
||||
attach.attachments.forEach(attachment => {
|
||||
if (attachment.path)
|
||||
attachment.path = this._updatePath(attachment.path);
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -510,12 +520,10 @@ class PathSeparatorPatcher {
|
|||
if (isFileSuite)
|
||||
suite.title = this._updatePath(suite.title);
|
||||
for (const entry of suite.entries) {
|
||||
if ('testId' in entry) {
|
||||
if ('testId' in entry)
|
||||
this._updateLocation(entry.location);
|
||||
entry.annotations?.forEach(annotation => this._updateAnnotationLocations(annotation));
|
||||
} else {
|
||||
else
|
||||
this._updateSuite(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -526,10 +534,6 @@ class PathSeparatorPatcher {
|
|||
}
|
||||
}
|
||||
|
||||
private _updateAnnotationLocations(annotation: TestAnnotation) {
|
||||
this._updateLocation(annotation.location);
|
||||
}
|
||||
|
||||
private _updateLocation(location?: JsonLocation) {
|
||||
if (location)
|
||||
location.file = this._updatePath(location.file);
|
||||
|
|
|
@ -22,7 +22,6 @@ import { serializeRegexPatterns } from '../isomorphic/teleReceiver';
|
|||
|
||||
import type { ReporterV2 } from './reporterV2';
|
||||
import type * as reporterTypes from '../../types/testReporter';
|
||||
import type { TestAnnotation } from '../../types/test';
|
||||
import type * as teleReceiver from '../isomorphic/teleReceiver';
|
||||
|
||||
export type TeleReporterEmitterOptions = {
|
||||
|
@ -34,6 +33,7 @@ export class TeleReporterEmitter implements ReporterV2 {
|
|||
private _messageSink: (message: teleReceiver.JsonEvent) => void;
|
||||
private _rootDir!: string;
|
||||
private _emitterOptions: TeleReporterEmitterOptions;
|
||||
private _resultKnownAttachmentCounts = new Map<string, number>();
|
||||
// In case there is blob reporter and UI mode, make sure one does override
|
||||
// the id assigned by the other.
|
||||
private readonly _idSymbol = Symbol('id');
|
||||
|
@ -77,6 +77,7 @@ export class TeleReporterEmitter implements ReporterV2 {
|
|||
timeout: test.timeout,
|
||||
annotations: []
|
||||
};
|
||||
this._sendNewAttachments(result, test.id);
|
||||
this._messageSink({
|
||||
method: 'onTestEnd',
|
||||
params: {
|
||||
|
@ -84,6 +85,8 @@ export class TeleReporterEmitter implements ReporterV2 {
|
|||
result: this._serializeResultEnd(result),
|
||||
}
|
||||
});
|
||||
|
||||
this._resultKnownAttachmentCounts.delete((result as any)[this._idSymbol]);
|
||||
}
|
||||
|
||||
onStepBegin(test: reporterTypes.TestCase, result: reporterTypes.TestResult, step: reporterTypes.TestStep): void {
|
||||
|
@ -99,11 +102,15 @@ export class TeleReporterEmitter implements ReporterV2 {
|
|||
}
|
||||
|
||||
onStepEnd(test: reporterTypes.TestCase, result: reporterTypes.TestResult, step: reporterTypes.TestStep): void {
|
||||
// Create synthetic onAttach event so we serialize the entire attachment along with the step
|
||||
const resultId = (result as any)[this._idSymbol] as string;
|
||||
this._sendNewAttachments(result, test.id);
|
||||
|
||||
this._messageSink({
|
||||
method: 'onStepEnd',
|
||||
params: {
|
||||
testId: test.id,
|
||||
resultId: (result as any)[this._idSymbol],
|
||||
resultId,
|
||||
step: this._serializeStepEnd(step, result)
|
||||
}
|
||||
});
|
||||
|
@ -217,7 +224,7 @@ export class TeleReporterEmitter implements ReporterV2 {
|
|||
retries: test.retries,
|
||||
tags: test.tags,
|
||||
repeatEachIndex: test.repeatEachIndex,
|
||||
annotations: this._relativeAnnotationLocations(test.annotations),
|
||||
annotations: test.annotations,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -237,17 +244,35 @@ export class TeleReporterEmitter implements ReporterV2 {
|
|||
duration: result.duration,
|
||||
status: result.status,
|
||||
errors: result.errors,
|
||||
attachments: this._serializeAttachments(result.attachments),
|
||||
annotations: result.annotations?.length ? this._relativeAnnotationLocations(result.annotations) : undefined,
|
||||
annotations: result.annotations?.length ? result.annotations : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
private _sendNewAttachments(result: reporterTypes.TestResult, testId: string) {
|
||||
const resultId = (result as any)[this._idSymbol] as string;
|
||||
// Track whether this step (or something else since the last step) has added attachments and send them
|
||||
const knownAttachmentCount = this._resultKnownAttachmentCounts.get(resultId) ?? 0;
|
||||
if (result.attachments.length > knownAttachmentCount) {
|
||||
this._messageSink({
|
||||
method: 'onAttach',
|
||||
params: {
|
||||
testId,
|
||||
resultId,
|
||||
attachments: this._serializeAttachments((result.attachments.slice(knownAttachmentCount))),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this._resultKnownAttachmentCounts.set(resultId, result.attachments.length);
|
||||
}
|
||||
|
||||
_serializeAttachments(attachments: reporterTypes.TestResult['attachments']): teleReceiver.JsonAttachment[] {
|
||||
return attachments.map(a => {
|
||||
const { body, ...rest } = a;
|
||||
return {
|
||||
...a,
|
||||
...rest,
|
||||
// There is no Buffer in the browser, so there is no point in sending the data there.
|
||||
base64: (a.body && !this._emitterOptions.omitBuffers) ? a.body.toString('base64') : undefined,
|
||||
base64: (body && !this._emitterOptions.omitBuffers) ? body.toString('base64') : undefined,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
@ -269,18 +294,10 @@ export class TeleReporterEmitter implements ReporterV2 {
|
|||
duration: step.duration,
|
||||
error: step.error,
|
||||
attachments: step.attachments.length ? step.attachments.map(a => result.attachments.indexOf(a)) : undefined,
|
||||
annotations: step.annotations.length ? this._relativeAnnotationLocations(step.annotations) : undefined,
|
||||
annotations: step.annotations.length ? step.annotations : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
private _relativeAnnotationLocations(annotations: TestAnnotation[]): TestAnnotation[] {
|
||||
return annotations.map(annotation => {
|
||||
if (annotation.location)
|
||||
annotation.location = this._relativeLocation(annotation.location);
|
||||
return annotation;
|
||||
});
|
||||
}
|
||||
|
||||
private _relativeLocation(location: reporterTypes.Location): reporterTypes.Location;
|
||||
private _relativeLocation(location?: reporterTypes.Location): reporterTypes.Location | undefined;
|
||||
private _relativeLocation(location: reporterTypes.Location | undefined): reporterTypes.Location | undefined {
|
||||
|
|
|
@ -314,7 +314,7 @@ export class TestServerDispatcher implements TestServerInterface {
|
|||
...(params.headed !== undefined ? { headless: !params.headed } : {}),
|
||||
_optionContextReuseMode: params.reuseContext ? 'when-possible' : undefined,
|
||||
_optionConnectOptions: params.connectWsEndpoint ? { wsEndpoint: params.connectWsEndpoint } : undefined,
|
||||
_optionAttachErrorContext: params.attachErrorContext,
|
||||
_optionErrorContext: params.errorContext,
|
||||
},
|
||||
...(params.updateSnapshots ? { updateSnapshots: params.updateSnapshots } : {}),
|
||||
...(params.updateSourceMethod ? { updateSourceMethod: params.updateSourceMethod } : {}),
|
||||
|
|
|
@ -1,62 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { Location } from '../../types/test';
|
||||
|
||||
export class FloatingPromiseScope {
|
||||
readonly _floatingCalls: Map<Promise<any>, Location | undefined> = new Map();
|
||||
|
||||
/**
|
||||
* Enables a promise API call to be tracked by the test, alerting if unawaited.
|
||||
*
|
||||
* **NOTE:** Returning from an async function wraps the result in a promise, regardless of whether the return value is a promise. This will automatically mark the promise as awaited. Avoid this.
|
||||
*/
|
||||
wrapPromiseAPIResult<T>(promise: Promise<T>, location: Location | undefined): Promise<T> {
|
||||
if (process.env.PW_DISABLE_FLOATING_PROMISES_WARNING)
|
||||
return promise;
|
||||
|
||||
const promiseProxy = new Proxy(promise, {
|
||||
get: (target, prop, receiver) => {
|
||||
if (prop === 'then') {
|
||||
return (...args: any[]) => {
|
||||
this._floatingCalls.delete(promise);
|
||||
|
||||
const originalThen = Reflect.get(target, prop, receiver) as Promise<T>['then'];
|
||||
return originalThen.call(target, ...args);
|
||||
};
|
||||
} else {
|
||||
return Reflect.get(target, prop, receiver);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this._floatingCalls.set(promise, location);
|
||||
|
||||
return promiseProxy;
|
||||
}
|
||||
|
||||
clear() {
|
||||
this._floatingCalls.clear();
|
||||
}
|
||||
|
||||
hasFloatingPromises(): boolean {
|
||||
return this._floatingCalls.size > 0;
|
||||
}
|
||||
|
||||
floatingPromises(): Array<{ location: Location | undefined, promise: Promise<any> }> {
|
||||
return Array.from(this._floatingCalls.entries()).map(([promise, location]) => ({ location, promise }));
|
||||
}
|
||||
}
|
|
@ -23,17 +23,16 @@ import { TimeoutManager, TimeoutManagerError, kMaxDeadline } from './timeoutMana
|
|||
import { filteredStackTrace, getContainedPath, normalizeAndSaveAttachment, trimLongString, windowsFilesystemFriendlyLength } from '../util';
|
||||
import { TestTracing } from './testTracing';
|
||||
import { testInfoError } from './util';
|
||||
import { FloatingPromiseScope } from './floatingPromiseScope';
|
||||
import { wrapFunctionWithLocation } from '../transform/transform';
|
||||
|
||||
import type { RunnableDescription } from './timeoutManager';
|
||||
import type { FullProject, TestAnnotation, TestInfo, TestStatus, TestStepInfo } from '../../types/test';
|
||||
import type { FullProject, TestInfo, TestStatus, TestStepInfo } from '../../types/test';
|
||||
import type { FullConfig, Location } from '../../types/testReporter';
|
||||
import type { FullConfigInternal, FullProjectInternal } from '../common/config';
|
||||
import type { Annotation, FullConfigInternal, FullProjectInternal } from '../common/config';
|
||||
import type { AttachmentPayload, StepBeginPayload, StepEndPayload, TestInfoErrorImpl, WorkerInitParams } from '../common/ipc';
|
||||
import type { TestCase } from '../common/test';
|
||||
import type { StackFrame } from '@protocol/channels';
|
||||
|
||||
|
||||
export interface TestStepInternal {
|
||||
complete(result: { error?: Error | unknown, suggestedRebaseline?: string }): void;
|
||||
info: TestStepInfoImpl
|
||||
|
@ -60,7 +59,6 @@ export class TestInfoImpl implements TestInfo {
|
|||
readonly _startTime: number;
|
||||
readonly _startWallTime: number;
|
||||
readonly _tracing: TestTracing;
|
||||
readonly _floatingPromiseScope: FloatingPromiseScope = new FloatingPromiseScope();
|
||||
readonly _uniqueSymbol;
|
||||
|
||||
_wasInterrupted = false;
|
||||
|
@ -75,12 +73,6 @@ export class TestInfoImpl implements TestInfo {
|
|||
_hasUnhandledError = false;
|
||||
_allowSkips = false;
|
||||
|
||||
// ------------ Main methods ------------
|
||||
skip: (arg?: any, description?: string) => void;
|
||||
fixme: (arg?: any, description?: string) => void;
|
||||
fail: (arg?: any, description?: string) => void;
|
||||
slow: (arg?: any, description?: string) => void;
|
||||
|
||||
// ------------ TestInfo fields ------------
|
||||
readonly testId: string;
|
||||
readonly repeatEachIndex: number;
|
||||
|
@ -98,7 +90,7 @@ export class TestInfoImpl implements TestInfo {
|
|||
readonly fn: Function;
|
||||
expectedStatus: TestStatus;
|
||||
duration: number = 0;
|
||||
readonly annotations: TestAnnotation[] = [];
|
||||
readonly annotations: Annotation[] = [];
|
||||
readonly attachments: TestInfo['attachments'] = [];
|
||||
status: TestStatus = 'passed';
|
||||
snapshotSuffix: string = '';
|
||||
|
@ -206,14 +198,9 @@ export class TestInfoImpl implements TestInfo {
|
|||
};
|
||||
|
||||
this._tracing = new TestTracing(this, workerParams.artifactsDir);
|
||||
|
||||
this.skip = wrapFunctionWithLocation((location, ...args) => this._modifier('skip', location, args));
|
||||
this.fixme = wrapFunctionWithLocation((location, ...args) => this._modifier('fixme', location, args));
|
||||
this.fail = wrapFunctionWithLocation((location, ...args) => this._modifier('fail', location, args));
|
||||
this.slow = wrapFunctionWithLocation((location, ...args) => this._modifier('slow', location, args));
|
||||
}
|
||||
|
||||
_modifier(type: 'skip' | 'fail' | 'fixme' | 'slow', location: Location, modifierArgs: [arg?: any, description?: string]) {
|
||||
private _modifier(type: 'skip' | 'fail' | 'fixme' | 'slow', modifierArgs: [arg?: any, description?: string]) {
|
||||
if (typeof modifierArgs[1] === 'function') {
|
||||
throw new Error([
|
||||
'It looks like you are calling test.skip() inside the test and pass a callback.',
|
||||
|
@ -228,7 +215,7 @@ export class TestInfoImpl implements TestInfo {
|
|||
return;
|
||||
|
||||
const description = modifierArgs[1];
|
||||
this.annotations.push({ type, description, location });
|
||||
this.annotations.push({ type, description });
|
||||
if (type === 'slow') {
|
||||
this._timeoutManager.slow();
|
||||
} else if (type === 'skip' || type === 'fixme') {
|
||||
|
@ -492,13 +479,29 @@ export class TestInfoImpl implements TestInfo {
|
|||
return this._resolveSnapshotPath(undefined, legacyTemplate, pathSegments);
|
||||
}
|
||||
|
||||
skip(...args: [arg?: any, description?: string]) {
|
||||
this._modifier('skip', args);
|
||||
}
|
||||
|
||||
fixme(...args: [arg?: any, description?: string]) {
|
||||
this._modifier('fixme', args);
|
||||
}
|
||||
|
||||
fail(...args: [arg?: any, description?: string]) {
|
||||
this._modifier('fail', args);
|
||||
}
|
||||
|
||||
slow(...args: [arg?: any, description?: string]) {
|
||||
this._modifier('slow', args);
|
||||
}
|
||||
|
||||
setTimeout(timeout: number) {
|
||||
this._timeoutManager.setTimeout(timeout);
|
||||
}
|
||||
}
|
||||
|
||||
export class TestStepInfoImpl implements TestStepInfo {
|
||||
annotations: TestAnnotation[] = [];
|
||||
annotations: Annotation[] = [];
|
||||
|
||||
private _testInfo: TestInfoImpl;
|
||||
private _stepId: string;
|
||||
|
@ -508,9 +511,9 @@ export class TestStepInfoImpl implements TestStepInfo {
|
|||
this._stepId = stepId;
|
||||
}
|
||||
|
||||
async _runStepBody<T>(skip: boolean, body: (step: TestStepInfo) => T | Promise<T>, location?: Location) {
|
||||
async _runStepBody<T>(skip: boolean, body: (step: TestStepInfo) => T | Promise<T>) {
|
||||
if (skip) {
|
||||
this.annotations.push({ type: 'skip', location });
|
||||
this.annotations.push({ type: 'skip' });
|
||||
return undefined as T;
|
||||
}
|
||||
try {
|
||||
|
@ -530,15 +533,15 @@ export class TestStepInfoImpl implements TestStepInfo {
|
|||
this._attachToStep(await normalizeAndSaveAttachment(this._testInfo.outputPath(), name, options));
|
||||
}
|
||||
|
||||
skip = wrapFunctionWithLocation((location: Location, ...args: unknown[]) => {
|
||||
skip(...args: unknown[]) {
|
||||
// skip();
|
||||
// skip(condition: boolean, description: string);
|
||||
if (args.length > 0 && !args[0])
|
||||
return;
|
||||
const description = args[1] as (string|undefined);
|
||||
this.annotations.push({ type: 'skip', description, location });
|
||||
this.annotations.push({ type: 'skip', description });
|
||||
throw new StepSkipError(description);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class TestSkipError extends Error {
|
||||
|
|
|
@ -32,10 +32,9 @@ import { loadTestFile } from '../common/testLoader';
|
|||
|
||||
import type { TimeSlot } from './timeoutManager';
|
||||
import type { Location } from '../../types/testReporter';
|
||||
import type { FullConfigInternal, FullProjectInternal } from '../common/config';
|
||||
import type { Annotation, FullConfigInternal, FullProjectInternal } from '../common/config';
|
||||
import type { DonePayload, RunPayload, TeardownErrorsPayload, TestBeginPayload, TestEndPayload, TestInfoErrorImpl, WorkerInitParams } from '../common/ipc';
|
||||
import type { Suite, TestCase } from '../common/test';
|
||||
import type { TestAnnotation } from '../../types/test';
|
||||
|
||||
export class WorkerMain extends ProcessRunner {
|
||||
private _params: WorkerInitParams;
|
||||
|
@ -61,7 +60,7 @@ export class WorkerMain extends ProcessRunner {
|
|||
// Suites that had their beforeAll hooks, but not afterAll hooks executed.
|
||||
// These suites still need afterAll hooks to be executed for the proper cleanup.
|
||||
// Contains dynamic annotations originated by modifiers with a callback, e.g. `test.skip(() => true)`.
|
||||
private _activeSuites = new Map<Suite, TestAnnotation[]>();
|
||||
private _activeSuites = new Map<Suite, Annotation[]>();
|
||||
|
||||
constructor(params: WorkerInitParams) {
|
||||
super();
|
||||
|
@ -265,7 +264,7 @@ export class WorkerMain extends ProcessRunner {
|
|||
stepEndPayload => this.dispatchEvent('stepEnd', stepEndPayload),
|
||||
attachment => this.dispatchEvent('attach', attachment));
|
||||
|
||||
const processAnnotation = (annotation: TestAnnotation) => {
|
||||
const processAnnotation = (annotation: Annotation) => {
|
||||
testInfo.annotations.push(annotation);
|
||||
switch (annotation.type) {
|
||||
case 'fixme':
|
||||
|
@ -428,25 +427,9 @@ export class WorkerMain extends ProcessRunner {
|
|||
throw firstAfterHooksError;
|
||||
}).catch(() => {}); // Ignore the top-level error, it is already inside TestInfo.errors.
|
||||
|
||||
if (testInfo._isFailure()) {
|
||||
if (testInfo._isFailure())
|
||||
this._isStopped = true;
|
||||
|
||||
// Only if failed, create warning if any of the async calls were not awaited in various stages.
|
||||
if (!process.env.PW_DISABLE_FLOATING_PROMISES_WARNING && testInfo._floatingPromiseScope.hasFloatingPromises()) {
|
||||
// Dedupe by location
|
||||
const annotationLocations = new Map<string | undefined, Location | undefined>(testInfo._floatingPromiseScope.floatingPromises().map(
|
||||
({ location }) => {
|
||||
const locationKey = location ? `${location.file}:${location.line}:${location.column}` : undefined;
|
||||
return [locationKey, location];
|
||||
}));
|
||||
|
||||
testInfo.annotations.push(...[...annotationLocations.values()].map(location => ({
|
||||
type: 'warning', description: `This async call was not awaited by the end of the test. This can cause flakiness. It is recommended to run ESLint with "@typescript-eslint/no-floating-promises" to verify.`, location
|
||||
})));
|
||||
testInfo._floatingPromiseScope.clear();
|
||||
}
|
||||
}
|
||||
|
||||
if (this._isStopped) {
|
||||
// Run all remaining "afterAll" hooks and teardown all fixtures when worker is shutting down.
|
||||
// Mark as "cleaned up" early to avoid running cleanup twice.
|
||||
|
@ -510,7 +493,7 @@ export class WorkerMain extends ProcessRunner {
|
|||
continue;
|
||||
const fn = async (fixtures: any) => {
|
||||
const result = await modifier.fn(fixtures);
|
||||
testInfo._modifier(modifier.type, modifier.location, [!!result, modifier.description]);
|
||||
testInfo[modifier.type](!!result, modifier.description);
|
||||
};
|
||||
inheritFixtureNames(modifier.fn, fn);
|
||||
runnables.push({
|
||||
|
@ -528,12 +511,12 @@ export class WorkerMain extends ProcessRunner {
|
|||
private async _runBeforeAllHooksForSuite(suite: Suite, testInfo: TestInfoImpl) {
|
||||
if (this._activeSuites.has(suite))
|
||||
return;
|
||||
const extraAnnotations: TestAnnotation[] = [];
|
||||
const extraAnnotations: Annotation[] = [];
|
||||
this._activeSuites.set(suite, extraAnnotations);
|
||||
await this._runAllHooksForSuite(suite, testInfo, 'beforeAll', extraAnnotations);
|
||||
}
|
||||
|
||||
private async _runAllHooksForSuite(suite: Suite, testInfo: TestInfoImpl, type: 'beforeAll' | 'afterAll', extraAnnotations?: TestAnnotation[]) {
|
||||
private async _runAllHooksForSuite(suite: Suite, testInfo: TestInfoImpl, type: 'beforeAll' | 'afterAll', extraAnnotations?: Annotation[]) {
|
||||
// Always run all the hooks, and capture the first error.
|
||||
let firstError: Error | undefined;
|
||||
for (const hook of this._collectHooksAndModifiers(suite, type, testInfo)) {
|
||||
|
|
|
@ -2049,10 +2049,6 @@ export type TestDetailsAnnotation = {
|
|||
description?: string;
|
||||
};
|
||||
|
||||
export type TestAnnotation = TestDetailsAnnotation & {
|
||||
location?: Location;
|
||||
};
|
||||
|
||||
export type TestDetails = {
|
||||
tag?: string | string[];
|
||||
annotation?: TestDetailsAnnotation | TestDetailsAnnotation[];
|
||||
|
@ -7751,9 +7747,9 @@ type AllMatchers<R, T> = PageAssertions & LocatorAssertions & APIResponseAsserti
|
|||
|
||||
type IfAny<T, Y, N> = 0 extends (1 & T) ? Y : N;
|
||||
type Awaited<T> = T extends PromiseLike<infer U> ? U : T;
|
||||
type ToUserMatcher<F> = F extends (first: any, ...args: infer Rest) => infer R ? (...args: Rest) => (R extends PromiseLike<infer U> ? Promise<void> : void) : never;
|
||||
type ToUserMatcherObject<T, ArgType> = {
|
||||
[K in keyof T as T[K] extends (arg: ArgType, ...rest: any[]) => any ? K : never]: ToUserMatcher<T[K]>;
|
||||
type ToUserMatcher<F, DefaultReturnType> = F extends (first: any, ...args: infer Rest) => infer R ? (...args: Rest) => (R extends PromiseLike<infer U> ? Promise<void> : DefaultReturnType) : never;
|
||||
type ToUserMatcherObject<T, DefaultReturnType, ArgType> = {
|
||||
[K in keyof T as T[K] extends (arg: ArgType, ...rest: any[]) => any ? K : never]: ToUserMatcher<T[K], DefaultReturnType>;
|
||||
};
|
||||
|
||||
type MatcherHintColor = (arg: string) => string;
|
||||
|
@ -7822,14 +7818,14 @@ type MakeMatchers<R, T, ExtendedMatchers> = {
|
|||
* If the promise is fulfilled the assertion fails.
|
||||
*/
|
||||
rejects: MakeMatchers<Promise<R>, any, ExtendedMatchers>;
|
||||
} & IfAny<T, AllMatchers<R, T>, SpecificMatchers<R, T> & ToUserMatcherObject<ExtendedMatchers, T>>;
|
||||
} & IfAny<T, AllMatchers<R, T>, SpecificMatchers<R, T> & ToUserMatcherObject<ExtendedMatchers, R, T>>;
|
||||
|
||||
type PollMatchers<R, T, ExtendedMatchers> = {
|
||||
/**
|
||||
* If you know how to test something, `.not` lets you test its opposite.
|
||||
*/
|
||||
not: PollMatchers<R, T, ExtendedMatchers>;
|
||||
} & BaseMatchers<R, T> & ToUserMatcherObject<ExtendedMatchers, T>;
|
||||
} & BaseMatchers<R, T> & ToUserMatcherObject<ExtendedMatchers, R, T>;
|
||||
|
||||
export type Expect<ExtendedMatchers = {}> = {
|
||||
<T = unknown>(actual: T, messageOrOptions?: string | { message?: string }): MakeMatchers<void, T, ExtendedMatchers>;
|
||||
|
@ -9442,11 +9438,6 @@ export interface TestInfo {
|
|||
* Optional description.
|
||||
*/
|
||||
description?: string;
|
||||
|
||||
/**
|
||||
* Optional location in the source where the annotation is added.
|
||||
*/
|
||||
location?: Location;
|
||||
}>;
|
||||
|
||||
/**
|
||||
|
|
|
@ -451,11 +451,6 @@ export interface TestCase {
|
|||
* Optional description.
|
||||
*/
|
||||
description?: string;
|
||||
|
||||
/**
|
||||
* Optional location in the source where the annotation is added.
|
||||
*/
|
||||
location?: Location;
|
||||
}>;
|
||||
|
||||
/**
|
||||
|
@ -612,11 +607,6 @@ export interface TestResult {
|
|||
* Optional description.
|
||||
*/
|
||||
description?: string;
|
||||
|
||||
/**
|
||||
* Optional location in the source where the annotation is added.
|
||||
*/
|
||||
location?: Location;
|
||||
}>;
|
||||
|
||||
/**
|
||||
|
@ -732,11 +722,6 @@ export interface TestStep {
|
|||
* Optional description.
|
||||
*/
|
||||
description?: string;
|
||||
|
||||
/**
|
||||
* Optional location in the source where the annotation is added.
|
||||
*/
|
||||
location?: Location;
|
||||
}>;
|
||||
|
||||
/**
|
||||
|
|
|
@ -2688,11 +2688,13 @@ export type FrameAddStyleTagResult = {
|
|||
export type FrameAriaSnapshotParams = {
|
||||
selector: string,
|
||||
ref?: boolean,
|
||||
emitGeneric?: boolean,
|
||||
mode?: 'raw' | 'regex',
|
||||
timeout?: number,
|
||||
};
|
||||
export type FrameAriaSnapshotOptions = {
|
||||
ref?: boolean,
|
||||
emitGeneric?: boolean,
|
||||
mode?: 'raw' | 'regex',
|
||||
timeout?: number,
|
||||
};
|
||||
|
|
|
@ -1975,6 +1975,7 @@ Frame:
|
|||
parameters:
|
||||
selector: string
|
||||
ref: boolean?
|
||||
emitGeneric: boolean?
|
||||
mode:
|
||||
type: enum?
|
||||
literals:
|
||||
|
|
|
@ -18,10 +18,11 @@ import * as React from 'react';
|
|||
import './annotationsTab.css';
|
||||
import { PlaceholderPanel } from './placeholderPanel';
|
||||
import { linkifyText } from '@web/renderUtils';
|
||||
import type { TestAnnotation } from '@playwright/test';
|
||||
|
||||
type Annotation = { type: string; description?: string; };
|
||||
|
||||
export const AnnotationsTab: React.FunctionComponent<{
|
||||
annotations: TestAnnotation[],
|
||||
annotations: Annotation[],
|
||||
}> = ({ annotations }) => {
|
||||
|
||||
if (!annotations.length)
|
||||
|
|
|
@ -26,6 +26,7 @@ import { ToolbarButton } from '@web/components/toolbarButton';
|
|||
import { useIsLLMAvailable, useLLMChat } from './llm';
|
||||
import { useAsyncMemo } from '@web/uiUtils';
|
||||
import { attachmentURL } from './attachmentsTab';
|
||||
import { fixTestInstructions } from '@web/prompts';
|
||||
|
||||
const CopyPromptButton: React.FC<{ prompt: string }> = ({ prompt }) => {
|
||||
return (
|
||||
|
@ -67,10 +68,10 @@ function Error({ message, error, errorId, sdkLanguage, revealInSource }: { messa
|
|||
}
|
||||
|
||||
const prompt = useAsyncMemo(async () => {
|
||||
if (!error.prompt)
|
||||
if (!error.context)
|
||||
return;
|
||||
const response = await fetch(attachmentURL(error.prompt));
|
||||
return await response.text();
|
||||
const response = await fetch(attachmentURL(error.context));
|
||||
return fixTestInstructions + await response.text();
|
||||
}, [error], undefined);
|
||||
|
||||
return <div style={{ display: 'flex', flexDirection: 'column', overflowX: 'clip' }}>
|
||||
|
|
|
@ -56,7 +56,7 @@ export type ErrorDescription = {
|
|||
action?: ActionTraceEventInContext;
|
||||
stack?: StackFrame[];
|
||||
message: string;
|
||||
prompt?: trace.AfterActionTraceEventAttachment & { traceUrl: string };
|
||||
context?: trace.AfterActionTraceEventAttachment & { traceUrl: string };
|
||||
};
|
||||
|
||||
export type Attachment = trace.AfterActionTraceEventAttachment & { traceUrl: string };
|
||||
|
@ -141,7 +141,7 @@ export class MultiTraceModel {
|
|||
return this.errors.filter(e => !!e.message).map((error, i) => ({
|
||||
stack: error.stack,
|
||||
message: error.message,
|
||||
prompt: this.attachments.find(a => a.name === `_prompt-${i}`),
|
||||
context: this.attachments.find(a => a.name === `_error-context-${i}`),
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,7 +42,6 @@ import { testStatusIcon, testStatusText } from './testUtils';
|
|||
import type { UITestStatus } from './testUtils';
|
||||
import type { AfterActionTraceEventAttachment } from '@trace/trace';
|
||||
import type { HighlightedElement } from './snapshotTab';
|
||||
import type { TestAnnotation } from '@playwright/test';
|
||||
|
||||
export const Workbench: React.FunctionComponent<{
|
||||
model?: modelUtil.MultiTraceModel,
|
||||
|
@ -52,7 +51,7 @@ export const Workbench: React.FunctionComponent<{
|
|||
isLive?: boolean,
|
||||
hideTimeline?: boolean,
|
||||
status?: UITestStatus,
|
||||
annotations?: TestAnnotation[];
|
||||
annotations?: { type: string; description?: string; }[];
|
||||
inert?: boolean,
|
||||
onOpenExternally?: (location: modelUtil.SourceLocation) => void,
|
||||
revealSource?: boolean,
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
/**
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export const fixTestInstructions = `
|
||||
# Instructions
|
||||
|
||||
- Following Playwright test failed.
|
||||
- Explain why, be concise, respect Playwright best practices.
|
||||
- Provide a snippet of code with the fix, if possible.
|
||||
`.trimStart();
|
|
@ -68,7 +68,6 @@ test.describe(() => {
|
|||
await recorder.trustedClick();
|
||||
await recorder.recorderPage.getByRole('tab', { name: 'Aria' }).click();
|
||||
await expect(recorder.recorderPage.locator('.tab-aria .CodeMirror')).toMatchAriaSnapshot(`
|
||||
- textbox
|
||||
- text: '- button "Submit"'
|
||||
`);
|
||||
});
|
||||
|
|
|
@ -730,3 +730,54 @@ it('ref mode can be used to stitch all frame snapshots', async ({ page, server }
|
|||
- text: Hi, I'm frame
|
||||
`.trim());
|
||||
});
|
||||
|
||||
it('should not include hidden input elements', async ({ page }) => {
|
||||
await page.setContent(`
|
||||
<button>One</button>
|
||||
<button style="width: 0; height: 0; appearance: none; border: 0; padding: 0;">Two</button>
|
||||
<button>Three</button>
|
||||
`);
|
||||
|
||||
const snapshot = await page.locator('body').ariaSnapshot();
|
||||
expect(snapshot).toContain(`- button \"One\"
|
||||
- button \"Three\"`);
|
||||
});
|
||||
|
||||
it('emit generic roles for nodes w/o roles', async ({ page }) => {
|
||||
await page.setContent(`
|
||||
<style>
|
||||
input {
|
||||
width: 0;
|
||||
height: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
<div>
|
||||
<label>
|
||||
<span>
|
||||
<input type="radio" value="Apple" checked="">
|
||||
</span>
|
||||
<span>Apple</span>
|
||||
</label>
|
||||
<label>
|
||||
<span>
|
||||
<input type="radio" value="Pear">
|
||||
</span>
|
||||
<span>Pear</span>
|
||||
</label>
|
||||
<label>
|
||||
<span>
|
||||
<input type="radio" value="Orange">
|
||||
</span>
|
||||
<span>Orange</span>
|
||||
</label>
|
||||
</div>
|
||||
`);
|
||||
|
||||
const snapshot = await page.locator('body').ariaSnapshot({ emitGeneric: true });
|
||||
|
||||
expect(snapshot).toContain(`- generic:
|
||||
- generic: Apple
|
||||
- generic: Pear
|
||||
- generic: Orange`);
|
||||
});
|
||||
|
|
|
@ -61,7 +61,7 @@ test('should access annotations in fixture', async ({ runInlineTest }) => {
|
|||
expect(exitCode).toBe(0);
|
||||
const test = report.suites[0].specs[0].tests[0];
|
||||
expect(test.annotations).toEqual([
|
||||
{ type: 'slow', description: 'just slow', location: { file: expect.any(String), line: 10, column: 14 } },
|
||||
{ type: 'slow', description: 'just slow' },
|
||||
{ type: 'myname', description: 'hello' }
|
||||
]);
|
||||
expect(test.results[0].stdout).toEqual([{ text: 'console.log\n' }]);
|
||||
|
|
|
@ -167,7 +167,7 @@ test('should print debug log when failed to connect', async ({ runInlineTest })
|
|||
expect(result.exitCode).toBe(1);
|
||||
expect(result.failed).toBe(1);
|
||||
expect(result.output).toContain('b-debug-log-string');
|
||||
expect(result.results[0].attachments).toEqual([expect.objectContaining({ name: '_prompt-0' })]);
|
||||
expect(result.results[0].attachments).toEqual([expect.objectContaining({ name: '_error-context-0' })]);
|
||||
});
|
||||
|
||||
test('should record trace', async ({ runInlineTest }) => {
|
||||
|
@ -223,7 +223,7 @@ test('should record trace', async ({ runInlineTest }) => {
|
|||
'After Hooks',
|
||||
'fixture: page',
|
||||
'fixture: context',
|
||||
'_attach "_prompt-0"',
|
||||
'_attach "_error-context-0"',
|
||||
'Worker Cleanup',
|
||||
'fixture: browser',
|
||||
]);
|
||||
|
|
|
@ -510,13 +510,13 @@ test('should work with video: on-first-retry', async ({ runInlineTest }) => {
|
|||
expect(fs.existsSync(dirPass)).toBeFalsy();
|
||||
|
||||
const dirFail = test.info().outputPath('test-results', 'a-fail-chromium');
|
||||
expect(fs.readdirSync(dirFail)).toEqual(['prompt.md']);
|
||||
expect(fs.readdirSync(dirFail)).toEqual(['error-context.md']);
|
||||
|
||||
const dirRetry = test.info().outputPath('test-results', 'a-fail-chromium-retry1');
|
||||
const videoFailRetry = fs.readdirSync(dirRetry).find(file => file.endsWith('webm'));
|
||||
expect(videoFailRetry).toBeTruthy();
|
||||
|
||||
const errorPrompt = expect.objectContaining({ name: '_prompt-0' });
|
||||
const errorPrompt = expect.objectContaining({ name: '_error-context-0' });
|
||||
expect(result.report.suites[0].specs[1].tests[0].results[0].attachments).toEqual([errorPrompt]);
|
||||
expect(result.report.suites[0].specs[1].tests[0].results[1].attachments).toEqual([{
|
||||
name: 'video',
|
||||
|
|
|
@ -486,72 +486,5 @@ for (const useIntermediateMergeReport of [false, true] as const) {
|
|||
expect(text).toContain('› passes @bar1 @bar2 (');
|
||||
expect(text).toContain('› passes @baz1 @baz2 (');
|
||||
});
|
||||
|
||||
test('should show warnings on failing tests', async ({ runInlineTest }) => {
|
||||
const result = await runInlineTest({
|
||||
'a.spec.ts': `
|
||||
import { test, expect } from '@playwright/test';
|
||||
test('fail', async ({ page }, testInfo) => {
|
||||
testInfo.annotations.push({ type: 'warning', description: 'foo' });
|
||||
expect(page.locator('div')).toHaveText('A', { timeout: 100 });
|
||||
throw new Error();
|
||||
});
|
||||
`,
|
||||
});
|
||||
expect(result.exitCode).toBe(1);
|
||||
expect(result.passed).toBe(0);
|
||||
expect(result.output).toContain('Warning: a.spec.ts:5:41: This async call was not awaited by the end of the test.');
|
||||
expect(result.output).toContain('Warning: foo');
|
||||
});
|
||||
|
||||
test('should not show warnings on passing tests', async ({ runInlineTest }) => {
|
||||
const result = await runInlineTest({
|
||||
'a.spec.ts': `
|
||||
import { test, expect } from '@playwright/test';
|
||||
test('success', async ({ page }, testInfo) => {
|
||||
testInfo.annotations.push({ type: 'warning', description: 'foo' });
|
||||
});
|
||||
`,
|
||||
});
|
||||
expect(result.exitCode).toBe(0);
|
||||
expect(result.passed).toBe(1);
|
||||
expect(result.output).not.toContain('Warning: foo');
|
||||
});
|
||||
|
||||
test('should properly sort warnings', async ({ runInlineTest }) => {
|
||||
const result = await runInlineTest({
|
||||
'external.js': `
|
||||
import { expect } from '@playwright/test';
|
||||
export const externalAsyncCall = (page) => {
|
||||
expect(page.locator('div')).toHaveText('A', { timeout: 100 });
|
||||
};
|
||||
`,
|
||||
'a.spec.ts': `
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { externalAsyncCall } from './external.js';
|
||||
test('fail a', async ({ page }, testInfo) => {
|
||||
testInfo.annotations.push({ type: 'warning', description: 'foo' });
|
||||
externalAsyncCall(page);
|
||||
expect(page.locator('div')).toHaveText('A', { timeout: 100 });
|
||||
testInfo.annotations.push({ type: 'warning', description: 'bar' });
|
||||
throw new Error();
|
||||
});
|
||||
`,
|
||||
});
|
||||
expect(result.exitCode).toBe(1);
|
||||
expect(result.passed).toBe(0);
|
||||
expect(result.output).toContain('Warning: a.spec.ts:7:41: This async call was not awaited by the end of the test.');
|
||||
expect(result.output).toContain('Warning: external.js:4:41: This async call was not awaited by the end of the test.');
|
||||
expect(result.output).toContain('Warning: foo');
|
||||
expect(result.output).toContain('Warning: bar');
|
||||
|
||||
const manualIndexFoo = result.output.indexOf('Warning: foo');
|
||||
const manualIndexBar = result.output.indexOf('Warning: bar');
|
||||
const externalIndex = result.output.indexOf('Warning: external.js:4:41');
|
||||
const specIndex = result.output.indexOf('Warning: a.spec.ts:7:41');
|
||||
expect(specIndex).toBeLessThan(externalIndex);
|
||||
expect(externalIndex).toBeLessThan(manualIndexFoo);
|
||||
expect(manualIndexFoo).toBeLessThan(manualIndexBar);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ import extractZip from '../../packages/playwright-core/bundles/zip/node_modules/
|
|||
import * as yazl from '../../packages/playwright-core/bundles/zip/node_modules/yazl';
|
||||
import { getUserAgent } from '../../packages/playwright-core/lib/server/utils/userAgent';
|
||||
import { Readable } from 'stream';
|
||||
import type { JSONReportTestResult } from '../../packages/playwright-test/reporter';
|
||||
|
||||
const DOES_NOT_SUPPORT_UTF8_IN_TERMINAL = process.platform === 'win32' && process.env.TERM_PROGRAM !== 'vscode' && !process.env.WT_SESSION;
|
||||
const POSITIVE_STATUS_MARK = DOES_NOT_SUPPORT_UTF8_IN_TERMINAL ? 'ok' : '✓ ';
|
||||
|
@ -1721,9 +1722,6 @@ test('merge reports with different rootDirs and path separators', async ({ runIn
|
|||
console.log('test:', test.location.file);
|
||||
console.log('test title:', test.titlePath()[2]);
|
||||
}
|
||||
onTestEnd(test) {
|
||||
console.log('annotations:', test.annotations.map(a => 'type: ' + a.type + ', description: ' + a.description + ', file: ' + a.location.file).join(','));
|
||||
}
|
||||
};
|
||||
`,
|
||||
'merge.config.ts': `module.exports = {
|
||||
|
@ -1735,7 +1733,7 @@ test('merge reports with different rootDirs and path separators', async ({ runIn
|
|||
};`,
|
||||
'dir1/tests1/a.test.js': `
|
||||
import { test, expect } from '@playwright/test';
|
||||
test('math 1', { annotation: { type: 'warning', description: 'Some warning' } }, async ({}) => { });
|
||||
test('math 1', async ({}) => { });
|
||||
`,
|
||||
};
|
||||
await runInlineTest(files1, { workers: 1 }, undefined, { additionalArgs: ['--config', test.info().outputPath('dir1/playwright.config.ts')] });
|
||||
|
@ -1746,7 +1744,7 @@ test('merge reports with different rootDirs and path separators', async ({ runIn
|
|||
};`,
|
||||
'dir2/tests2/b.test.js': `
|
||||
import { test, expect } from '@playwright/test';
|
||||
test('math 2', { annotation: { type: 'issue' } }, async ({}) => { });
|
||||
test('math 2', async ({}) => { });
|
||||
`,
|
||||
};
|
||||
await runInlineTest(files2, { workers: 1 }, undefined, { additionalArgs: ['--config', test.info().outputPath('dir2/playwright.config.ts')] });
|
||||
|
@ -1768,16 +1766,12 @@ test('merge reports with different rootDirs and path separators', async ({ runIn
|
|||
|
||||
{
|
||||
const { exitCode, output } = await mergeReports(allReportsDir, undefined, { additionalArgs: ['--config', 'merge.config.ts'] });
|
||||
const testPath1 = test.info().outputPath('mergeRoot', 'tests1', 'a.test.js');
|
||||
const testPath2 = test.info().outputPath('mergeRoot', 'tests2', 'b.test.js');
|
||||
expect(exitCode).toBe(0);
|
||||
expect(output).toContain(`rootDir: ${test.info().outputPath('mergeRoot')}`);
|
||||
expect(output).toContain(`test: ${testPath1}`);
|
||||
expect(output).toContain(`test: ${test.info().outputPath('mergeRoot', 'tests1', 'a.test.js')}`);
|
||||
expect(output).toContain(`test title: ${'tests1' + path.sep + 'a.test.js'}`);
|
||||
expect(output).toContain(`annotations: type: warning, description: Some warning, file: ${testPath1}`);
|
||||
expect(output).toContain(`test: ${testPath2}`);
|
||||
expect(output).toContain(`test: ${test.info().outputPath('mergeRoot', 'tests2', 'b.test.js')}`);
|
||||
expect(output).toContain(`test title: ${'tests2' + path.sep + 'b.test.js'}`);
|
||||
expect(output).toContain(`annotations: type: issue, description: undefined, file: ${testPath2}`);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -1793,9 +1787,6 @@ test('merge reports without --config preserves path separators', async ({ runInl
|
|||
console.log('test:', test.location.file);
|
||||
console.log('test title:', test.titlePath()[2]);
|
||||
}
|
||||
onTestEnd(test) {
|
||||
console.log('annotations:', test.annotations.map(a => 'type: ' + a.type + ', description: ' + a.description + ', file: ' + a.location.file).join(','));
|
||||
}
|
||||
};
|
||||
`,
|
||||
'dir1/playwright.config.ts': `module.exports = {
|
||||
|
@ -1803,11 +1794,11 @@ test('merge reports without --config preserves path separators', async ({ runInl
|
|||
};`,
|
||||
'dir1/tests1/a.test.js': `
|
||||
import { test, expect } from '@playwright/test';
|
||||
test('math 1', { annotation: { type: 'warning', description: 'Some warning' } }, async ({}) => { });
|
||||
test('math 1', async ({}) => { });
|
||||
`,
|
||||
'dir1/tests2/b.test.js': `
|
||||
import { test, expect } from '@playwright/test';
|
||||
test('math 2', { annotation: { type: 'issue' } }, async ({}) => { });
|
||||
test('math 2', async ({}) => { });
|
||||
`,
|
||||
};
|
||||
await runInlineTest(files1, { workers: 1 }, undefined, { additionalArgs: ['--config', test.info().outputPath('dir1/playwright.config.ts')] });
|
||||
|
@ -1827,15 +1818,97 @@ test('merge reports without --config preserves path separators', async ({ runInl
|
|||
const { exitCode, output } = await mergeReports(allReportsDir, undefined, { additionalArgs: ['--reporter', './echo-reporter.js'] });
|
||||
expect(exitCode).toBe(0);
|
||||
const otherSeparator = path.sep === '/' ? '\\' : '/';
|
||||
const testPath1 = test.info().outputPath('dir1', 'tests1', 'a.test.js').replaceAll(path.sep, otherSeparator);
|
||||
const testPath2 = test.info().outputPath('dir1', 'tests2', 'b.test.js').replaceAll(path.sep, otherSeparator);
|
||||
expect(output).toContain(`rootDir: ${test.info().outputPath('dir1').replaceAll(path.sep, otherSeparator)}`);
|
||||
expect(output).toContain(`test: ${testPath1}`);
|
||||
expect(output).toContain(`test: ${test.info().outputPath('dir1', 'tests1', 'a.test.js').replaceAll(path.sep, otherSeparator)}`);
|
||||
expect(output).toContain(`test title: ${'tests1' + otherSeparator + 'a.test.js'}`);
|
||||
expect(output).toContain(`annotations: type: warning, description: Some warning, file: ${testPath1}`);
|
||||
expect(output).toContain(`test: ${testPath2}`);
|
||||
expect(output).toContain(`test: ${test.info().outputPath('dir1', 'tests2', 'b.test.js').replaceAll(path.sep, otherSeparator)}`);
|
||||
expect(output).toContain(`test title: ${'tests2' + otherSeparator + 'b.test.js'}`);
|
||||
expect(output).toContain(`annotations: type: issue, description: undefined, file: ${testPath2}`);
|
||||
});
|
||||
|
||||
test('merge reports should preserve attachments', async ({ runInlineTest, mergeReports, showReport, page }) => {
|
||||
const reportDir = test.info().outputPath('blob-report-orig');
|
||||
const files = {
|
||||
'playwright.config.ts': `
|
||||
module.exports = {
|
||||
retries: 1,
|
||||
reporter: [['blob', { outputDir: '${reportDir.replace(/\\/g, '/')}' }]]
|
||||
};
|
||||
`,
|
||||
'a.test.js': `
|
||||
import { test, expect } from '@playwright/test';
|
||||
import * as fs from 'fs';
|
||||
test('attachment A', async ({}) => {
|
||||
const attachmentPath = test.info().outputPath('foo.txt');
|
||||
fs.writeFileSync(attachmentPath, 'hello!');
|
||||
await test.info().attach('file-attachment1', { path: attachmentPath });
|
||||
await test.info().attachments.push({ name: 'file-attachment2', path: attachmentPath, contentType: 'text/html' });
|
||||
await test.info().attach('file-attachment3', { path: attachmentPath });
|
||||
await test.info().attach('file-attachment4', { path: attachmentPath });
|
||||
await test.info().attachments.push({ name: 'file-attachment5', path: attachmentPath, contentType: 'text/html' });
|
||||
await test.info().attachments.push({ name: 'file-attachment6', path: attachmentPath, contentType: 'text/html' });
|
||||
await test.info().attach('file-attachment7', { path: attachmentPath });
|
||||
});
|
||||
`,
|
||||
'b.test.js': `
|
||||
import { test, expect } from '@playwright/test';
|
||||
import * as fs from 'fs';
|
||||
test('attachment B', async ({}) => {
|
||||
const attachmentPath = test.info().outputPath('bar.txt');
|
||||
fs.writeFileSync(attachmentPath, 'goodbye!');
|
||||
await test.info().attach('file-attachment8', { path: attachmentPath });
|
||||
await test.info().attachments.push({ name: 'file-attachment9', path: attachmentPath, contentType: 'application/json' });
|
||||
});
|
||||
`
|
||||
};
|
||||
await runInlineTest(files, { shard: `1/2` }, { PWTEST_BLOB_DO_NOT_REMOVE: '1' });
|
||||
await runInlineTest(files, { shard: `2/2` }, { PWTEST_BLOB_DO_NOT_REMOVE: '1' });
|
||||
|
||||
const reportFiles = await fs.promises.readdir(reportDir);
|
||||
reportFiles.sort();
|
||||
expect(reportFiles).toEqual(['report-1.zip', 'report-2.zip']);
|
||||
const { exitCode } = await mergeReports(reportDir, { 'PLAYWRIGHT_HTML_OPEN': 'never' }, { additionalArgs: ['--reporter', 'blob,html'] });
|
||||
expect(exitCode).toBe(0);
|
||||
|
||||
const reportZipFile = test.info().outputPath('blob-report', 'report.zip');
|
||||
const events = await extractReport(reportZipFile, test.info().outputPath('tmp'));
|
||||
|
||||
type Attachment = Omit<JSONReportTestResult['attachments'][number], 'path'> & {
|
||||
path: any
|
||||
};
|
||||
|
||||
const attachment1: Attachment = { name: 'file-attachment1', path: expect.stringContaining(''), contentType: 'text/plain' };
|
||||
const attachment2: Attachment = { name: 'file-attachment2', path: expect.stringContaining(''), contentType: 'text/html' };
|
||||
const attachment3: Attachment = { name: 'file-attachment3', path: expect.stringContaining(''), contentType: 'text/plain' };
|
||||
const attachment4: Attachment = { name: 'file-attachment4', path: expect.stringContaining(''), contentType: 'text/plain' };
|
||||
const attachment5: Attachment = { name: 'file-attachment5', path: expect.stringContaining(''), contentType: 'text/html' };
|
||||
const attachment6: Attachment = { name: 'file-attachment6', path: expect.stringContaining(''), contentType: 'text/html' };
|
||||
const attachment7: Attachment = { name: 'file-attachment7', path: expect.stringContaining(''), contentType: 'text/plain' };
|
||||
const attachment8: Attachment = { name: 'file-attachment8', path: expect.stringContaining(''), contentType: 'text/plain' };
|
||||
const attachment9: Attachment = { name: 'file-attachment9', path: expect.stringContaining(''), contentType: 'application/json' };
|
||||
|
||||
const aAttachments = [attachment1, attachment2, attachment3, attachment4, attachment5, attachment6, attachment7];
|
||||
const bAttachments = [attachment8, attachment9];
|
||||
|
||||
const allStepAttachments = events.flatMap(e => e.method === 'onStepEnd' ? e?.params?.step?.attachments ?? [] : []);
|
||||
expect(allStepAttachments).toEqual([0, 2, 3, 6, 0]);
|
||||
|
||||
const allTestAttachments = events.flatMap(e => e.method === 'onAttach' ? e?.params?.attachments ?? [] : []);
|
||||
expect(allTestAttachments).toEqual([...aAttachments, ...bAttachments]);
|
||||
|
||||
await showReport();
|
||||
|
||||
{
|
||||
await page.getByRole('link', { name: 'Attachment A' }).click();
|
||||
for (const attachment of aAttachments)
|
||||
await expect(page.getByRole('link', { name: attachment.name })).toBeVisible();
|
||||
await page.goBack();
|
||||
}
|
||||
{
|
||||
await page.getByRole('link', { name: 'Attachment B' }).click();
|
||||
for (const attachment of bAttachments)
|
||||
await expect(page.getByRole('link', { name: attachment.name })).toBeVisible();
|
||||
await page.goBack();
|
||||
}
|
||||
});
|
||||
|
||||
test('merge reports must not change test ids when there is no need to', async ({ runInlineTest, mergeReports }) => {
|
||||
|
|
|
@ -359,7 +359,7 @@ test('should report parallelIndex', async ({ runInlineTest }, testInfo) => {
|
|||
test('attaches error context', async ({ runInlineTest }) => {
|
||||
const result = await runInlineTest({
|
||||
'playwright.config.ts': `
|
||||
export default { use: { _optionAttachErrorContext: true } };
|
||||
export default { use: { _optionErrorContext: { format: 'json' } } };
|
||||
`,
|
||||
'a.test.js': `
|
||||
const { test, expect } = require('@playwright/test');
|
||||
|
|
|
@ -190,7 +190,7 @@ for (const useIntermediateMergeReport of [false, true] as const) {
|
|||
expect(result.exitCode).toBe(1);
|
||||
});
|
||||
|
||||
test('should show error prompt with relative path', async ({ runInlineTest, useIntermediateMergeReport }) => {
|
||||
test('should show error context with relative path', async ({ runInlineTest, useIntermediateMergeReport }) => {
|
||||
const result = await runInlineTest({
|
||||
'a.test.js': `
|
||||
const { test, expect } = require('@playwright/test');
|
||||
|
@ -201,9 +201,9 @@ for (const useIntermediateMergeReport of [false, true] as const) {
|
|||
}, { reporter: 'line' });
|
||||
const text = result.output;
|
||||
if (useIntermediateMergeReport)
|
||||
expect(text).toContain(`Error Prompt: ${path.join('blob-report', 'resources')}`);
|
||||
expect(text).toContain(`Error Context: ${path.join('blob-report', 'resources')}`);
|
||||
else
|
||||
expect(text).toContain(`Error Prompt: ${path.join('test-results', 'a-one', 'prompt.md')}`);
|
||||
expect(text).toContain(`Error Context: ${path.join('test-results', 'a-one', 'error-context.md')}`);
|
||||
expect(result.exitCode).toBe(1);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -584,9 +584,7 @@ test('should report annotations from test declaration', async ({ runInlineTest }
|
|||
const visit = suite => {
|
||||
for (const test of suite.tests || []) {
|
||||
const annotations = test.annotations.map(a => {
|
||||
const description = a.description ? a.type + '=' + a.description : a.type;
|
||||
const location = a.location ? '(' + a.location.line + ':' + a.location.column + ')' : '';
|
||||
return description + location;
|
||||
return a.description ? a.type + '=' + a.description : a.type;
|
||||
});
|
||||
console.log('\\n%%title=' + test.title + ', annotations=' + annotations.join(','));
|
||||
}
|
||||
|
@ -611,7 +609,7 @@ test('should report annotations from test declaration', async ({ runInlineTest }
|
|||
expect(test.info().annotations).toEqual([]);
|
||||
});
|
||||
test('foo', { annotation: { type: 'foo' } }, () => {
|
||||
expect(test.info().annotations).toEqual([{ type: 'foo', location: { file: expect.any(String), line: 6, column: 11 } }]);
|
||||
expect(test.info().annotations).toEqual([{ type: 'foo' }]);
|
||||
});
|
||||
test('foo-bar', {
|
||||
annotation: [
|
||||
|
@ -620,8 +618,8 @@ test('should report annotations from test declaration', async ({ runInlineTest }
|
|||
],
|
||||
}, () => {
|
||||
expect(test.info().annotations).toEqual([
|
||||
{ type: 'foo', description: 'desc', location: { file: expect.any(String), line: 9, column: 11 } },
|
||||
{ type: 'bar', location: { file: expect.any(String), line: 9, column: 11 } },
|
||||
{ type: 'foo', description: 'desc' },
|
||||
{ type: 'bar' },
|
||||
]);
|
||||
});
|
||||
test.skip('skip-foo', { annotation: { type: 'foo' } }, () => {
|
||||
|
@ -638,14 +636,11 @@ test('should report annotations from test declaration', async ({ runInlineTest }
|
|||
});
|
||||
test.describe('suite', { annotation: { type: 'foo' } }, () => {
|
||||
test('foo-suite', () => {
|
||||
expect(test.info().annotations).toEqual([{ type: 'foo', location: { file: expect.any(String), line: 32, column: 12 } }]);
|
||||
expect(test.info().annotations).toEqual([{ type: 'foo' }]);
|
||||
});
|
||||
test.describe('inner', { annotation: { type: 'bar' } }, () => {
|
||||
test('foo-bar-suite', () => {
|
||||
expect(test.info().annotations).toEqual([
|
||||
{ type: 'foo', location: { file: expect.any(String), line: 32, column: 12 } },
|
||||
{ type: 'bar', location: { file: expect.any(String), line: 36, column: 14 } }
|
||||
]);
|
||||
expect(test.info().annotations).toEqual([{ type: 'foo' }, { type: 'bar' }]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -662,15 +657,15 @@ test('should report annotations from test declaration', async ({ runInlineTest }
|
|||
expect(result.exitCode).toBe(0);
|
||||
expect(result.outputLines).toEqual([
|
||||
`title=none, annotations=`,
|
||||
`title=foo, annotations=foo(6:11)`,
|
||||
`title=foo-bar, annotations=foo=desc(9:11),bar(9:11)`,
|
||||
`title=skip-foo, annotations=foo(20:12),skip(20:12)`,
|
||||
`title=fixme-bar, annotations=bar(22:12),fixme(22:12)`,
|
||||
`title=fail-foo-bar, annotations=foo(24:12),bar=desc(24:12),fail(24:12)`,
|
||||
`title=foo-suite, annotations=foo(32:12)`,
|
||||
`title=foo-bar-suite, annotations=foo(32:12),bar(36:14)`,
|
||||
`title=skip-foo-suite, annotations=foo(45:21),skip(45:21)`,
|
||||
`title=fixme-bar-suite, annotations=bar(49:21),fixme(49:21)`,
|
||||
`title=foo, annotations=foo`,
|
||||
`title=foo-bar, annotations=foo=desc,bar`,
|
||||
`title=skip-foo, annotations=foo,skip`,
|
||||
`title=fixme-bar, annotations=bar,fixme`,
|
||||
`title=fail-foo-bar, annotations=foo,bar=desc,fail`,
|
||||
`title=foo-suite, annotations=foo`,
|
||||
`title=foo-bar-suite, annotations=foo,bar`,
|
||||
`title=skip-foo-suite, annotations=foo,skip`,
|
||||
`title=fixme-bar-suite, annotations=bar,fixme`,
|
||||
]);
|
||||
});
|
||||
|
||||
|
|
|
@ -260,5 +260,5 @@ test('failed and skipped on retry should be marked as flaky', async ({ runInline
|
|||
expect(result.failed).toBe(0);
|
||||
expect(result.flaky).toBe(1);
|
||||
expect(result.output).toContain('Failed on first run');
|
||||
expect(result.report.suites[0].specs[0].tests[0].annotations).toEqual([{ type: 'skip', description: 'Skipped on first retry', location: expect.anything() }]);
|
||||
expect(result.report.suites[0].specs[0].tests[0].annotations).toEqual([{ type: 'skip', description: 'Skipped on first retry' }]);
|
||||
});
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue