From b8f824ca6b31fb3a3a27e765ff8d92cfa185d17c Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Thu, 14 Nov 2024 16:12:07 +0100 Subject: [PATCH 01/38] chore: throw if protocol can't get generated when rolling (#33607) --- utils/roll_browser.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/roll_browser.js b/utils/roll_browser.js index 4d9167f0f7..ba2422d77e 100755 --- a/utils/roll_browser.js +++ b/utils/roll_browser.js @@ -109,7 +109,7 @@ Example: // 5. Generate types. console.log('\nGenerating protocol types...'); const executablePath = registry.findExecutable(browserName).executablePathOrDie(); - await protocolGenerator.generateProtocol(browserName, executablePath).catch(console.warn); + await protocolGenerator.generateProtocol(browserName, executablePath); // 6. Update docs. console.log('\nUpdating documentation...'); From 358fad45cd36f18d332bab3ac148cd73003e33bd Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Thu, 14 Nov 2024 16:19:42 +0100 Subject: [PATCH 02/38] chore: add ESRP CDN for browser downloads (#33585) --- .../src/server/registry/browserFetcher.ts | 2 +- .../playwright-core/src/server/registry/index.ts | 15 +++++++++++---- tests/installation/playwright-cdn.spec.ts | 10 ++++++---- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/packages/playwright-core/src/server/registry/browserFetcher.ts b/packages/playwright-core/src/server/registry/browserFetcher.ts index 6f8da6e825..3a6e36b42d 100644 --- a/packages/playwright-core/src/server/registry/browserFetcher.ts +++ b/packages/playwright-core/src/server/registry/browserFetcher.ts @@ -36,7 +36,7 @@ export async function downloadBrowserWithProgressBar(title: string, browserDirec const zipPath = path.join(os.tmpdir(), downloadFileName); try { - const retryCount = 3; + const retryCount = 5; for (let attempt = 1; attempt <= retryCount; ++attempt) { debugLogger.log('install', `downloading ${title} - attempt #${attempt}`); const url = downloadURLs[(attempt - 1) % downloadURLs.length]; diff --git a/packages/playwright-core/src/server/registry/index.ts b/packages/playwright-core/src/server/registry/index.ts index 709829621c..cd2f2f1d32 100644 --- a/packages/playwright-core/src/server/registry/index.ts +++ b/packages/playwright-core/src/server/registry/index.ts @@ -37,16 +37,23 @@ const PACKAGE_PATH = path.join(__dirname, '..', '..', '..'); const BIN_PATH = path.join(__dirname, '..', '..', '..', 'bin'); const PLAYWRIGHT_CDN_MIRRORS = [ + 'https://playwright.azureedge.net/dbazure/download/playwright', // ESRP CDN + 'https://playwright.download.prss.microsoft.com/dbazure/download/playwright', // Directly hit ESRP CDN + + // Old endpoints which hit the Storage Bucket directly: 'https://playwright.azureedge.net', - 'https://playwright-akamai.azureedge.net', - 'https://playwright-verizon.azureedge.net', + 'https://playwright-akamai.azureedge.net', // Actually Edgio which will be retired Q4 2025. + 'https://playwright-verizon.azureedge.net', // Actually Edgio which will be retired Q4 2025. ]; if (process.env.PW_TEST_CDN_THAT_SHOULD_WORK) { for (let i = 0; i < PLAYWRIGHT_CDN_MIRRORS.length; i++) { const cdn = PLAYWRIGHT_CDN_MIRRORS[i]; - if (cdn !== process.env.PW_TEST_CDN_THAT_SHOULD_WORK) - PLAYWRIGHT_CDN_MIRRORS[i] = cdn + '.does-not-resolve.playwright.dev'; + if (cdn !== process.env.PW_TEST_CDN_THAT_SHOULD_WORK) { + const parsedCDN = new URL(cdn); + parsedCDN.hostname = parsedCDN.hostname + '.does-not-resolve.playwright.dev'; + PLAYWRIGHT_CDN_MIRRORS[i] = parsedCDN.toString(); + } } } diff --git a/tests/installation/playwright-cdn.spec.ts b/tests/installation/playwright-cdn.spec.ts index 924dbb4c55..af0339f03b 100644 --- a/tests/installation/playwright-cdn.spec.ts +++ b/tests/installation/playwright-cdn.spec.ts @@ -19,6 +19,8 @@ import net from 'net'; import type { AddressInfo } from 'net'; const CDNS = [ + 'https://playwright.azureedge.net/dbazure/download/playwright', // ESRP + 'https://playwright.download.prss.microsoft.com/dbazure/download/playwright', // ESRP Fallback 'https://playwright.azureedge.net', 'https://playwright-akamai.azureedge.net', 'https://playwright-verizon.azureedge.net', @@ -90,8 +92,8 @@ test(`npx playwright install should not hang when CDN closes the connection`, as }, expectToExitWithError: true }); - expect(retryCount).toBe(3); - expect([...result.matchAll(/Download failed: server closed connection/g)]).toHaveLength(3); + expect(retryCount).toBe(5); + expect([...result.matchAll(/Download failed: server closed connection/g)]).toHaveLength(5); } finally { await new Promise(resolve => server.close(resolve)); } @@ -120,8 +122,8 @@ test(`npx playwright install should not hang when CDN TCP connection stalls`, as }, expectToExitWithError: true }); - expect(retryCount).toBe(3); - expect([...result.matchAll(/timed out after/g)]).toHaveLength(3); + expect(retryCount).toBe(5); + expect([...result.matchAll(/timed out after/g)]).toHaveLength(5); } finally { for (const socket of socketsToDestroy) socket.destroy(); From 5e579cc29c122a5d9a1f1e584be2d26ba0b7dfb3 Mon Sep 17 00:00:00 2001 From: Playwright Service <89237858+playwrightmachine@users.noreply.github.com> Date: Thu, 14 Nov 2024 07:38:56 -0800 Subject: [PATCH 03/38] feat(chromium-tip-of-tree): roll to r1278 (#33608) Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> --- packages/playwright-core/browsers.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/playwright-core/browsers.json b/packages/playwright-core/browsers.json index 81f1d3afd3..29092916b8 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -15,9 +15,9 @@ }, { "name": "chromium-tip-of-tree", - "revision": "1277", + "revision": "1278", "installByDefault": false, - "browserVersion": "132.0.6834.0" + "browserVersion": "133.0.6836.0" }, { "name": "firefox", From b61b3a5a135d54fb992e6f65142009d069a94e1a Mon Sep 17 00:00:00 2001 From: Playwright Service <89237858+playwrightmachine@users.noreply.github.com> Date: Thu, 14 Nov 2024 07:39:24 -0800 Subject: [PATCH 04/38] feat(chromium): roll to r1149 (#33606) --- README.md | 4 +- packages/playwright-core/browsers.json | 8 +- .../src/server/chromium/protocol.d.ts | 80 +++++++++++++--- .../src/server/deviceDescriptorsSource.json | 96 +++++++++---------- packages/playwright-core/types/protocol.d.ts | 80 +++++++++++++--- 5 files changed, 188 insertions(+), 80 deletions(-) diff --git a/README.md b/README.md index 3dcad58f49..2f04b28a4f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # 🎭 Playwright -[![npm version](https://img.shields.io/npm/v/playwright.svg)](https://www.npmjs.com/package/playwright) [![Chromium version](https://img.shields.io/badge/chromium-131.0.6778.33-blue.svg?logo=google-chrome)](https://www.chromium.org/Home) [![Firefox version](https://img.shields.io/badge/firefox-132.0-blue.svg?logo=firefoxbrowser)](https://www.mozilla.org/en-US/firefox/new/) [![WebKit version](https://img.shields.io/badge/webkit-18.2-blue.svg?logo=safari)](https://webkit.org/) [![Join Discord](https://img.shields.io/badge/join-discord-infomational)](https://aka.ms/playwright/discord) +[![npm version](https://img.shields.io/npm/v/playwright.svg)](https://www.npmjs.com/package/playwright) [![Chromium version](https://img.shields.io/badge/chromium-132.0.6834.6-blue.svg?logo=google-chrome)](https://www.chromium.org/Home) [![Firefox version](https://img.shields.io/badge/firefox-132.0-blue.svg?logo=firefoxbrowser)](https://www.mozilla.org/en-US/firefox/new/) [![WebKit version](https://img.shields.io/badge/webkit-18.2-blue.svg?logo=safari)](https://webkit.org/) [![Join Discord](https://img.shields.io/badge/join-discord-infomational)](https://aka.ms/playwright/discord) ## [Documentation](https://playwright.dev) | [API reference](https://playwright.dev/docs/api/class-playwright) @@ -8,7 +8,7 @@ Playwright is a framework for Web Testing and Automation. It allows testing [Chr | | Linux | macOS | Windows | | :--- | :---: | :---: | :---: | -| Chromium 131.0.6778.33 | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| Chromium 132.0.6834.6 | :white_check_mark: | :white_check_mark: | :white_check_mark: | | WebKit 18.2 | :white_check_mark: | :white_check_mark: | :white_check_mark: | | Firefox 132.0 | :white_check_mark: | :white_check_mark: | :white_check_mark: | diff --git a/packages/playwright-core/browsers.json b/packages/playwright-core/browsers.json index 29092916b8..8334940ec6 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -3,15 +3,15 @@ "browsers": [ { "name": "chromium", - "revision": "1148", + "revision": "1149", "installByDefault": true, - "browserVersion": "131.0.6778.33" + "browserVersion": "132.0.6834.6" }, { "name": "chromium-headless-shell", - "revision": "1148", + "revision": "1149", "installByDefault": true, - "browserVersion": "131.0.6778.33" + "browserVersion": "132.0.6834.6" }, { "name": "chromium-tip-of-tree", diff --git a/packages/playwright-core/src/server/chromium/protocol.d.ts b/packages/playwright-core/src/server/chromium/protocol.d.ts index 35aa6f2eb9..3a61fbca59 100644 --- a/packages/playwright-core/src/server/chromium/protocol.d.ts +++ b/packages/playwright-core/src/server/chromium/protocol.d.ts @@ -112,7 +112,7 @@ export module Protocol { - from 'checked' to 'selected': states which apply to widgets - from 'activedescendant' to 'owns' - relationships between elements other than parent/child/sibling. */ - export type AXPropertyName = "busy"|"disabled"|"editable"|"focusable"|"focused"|"hidden"|"hiddenRoot"|"invalid"|"keyshortcuts"|"settable"|"roledescription"|"live"|"atomic"|"relevant"|"root"|"autocomplete"|"hasPopup"|"level"|"multiselectable"|"orientation"|"multiline"|"readonly"|"required"|"valuemin"|"valuemax"|"valuetext"|"checked"|"expanded"|"modal"|"pressed"|"selected"|"activedescendant"|"controls"|"describedby"|"details"|"errormessage"|"flowto"|"labelledby"|"owns"|"url"; + export type AXPropertyName = "actions"|"busy"|"disabled"|"editable"|"focusable"|"focused"|"hidden"|"hiddenRoot"|"invalid"|"keyshortcuts"|"settable"|"roledescription"|"live"|"atomic"|"relevant"|"root"|"autocomplete"|"hasPopup"|"level"|"multiselectable"|"orientation"|"multiline"|"readonly"|"required"|"valuemin"|"valuemax"|"valuetext"|"checked"|"expanded"|"modal"|"pressed"|"selected"|"activedescendant"|"controls"|"describedby"|"details"|"errormessage"|"flowto"|"labelledby"|"owns"|"url"; /** * A node in the accessibility tree. */ @@ -694,7 +694,7 @@ percentage [0 - 100] for scroll driven animations export interface AffectedFrame { frameId: Page.FrameId; } - export type CookieExclusionReason = "ExcludeSameSiteUnspecifiedTreatedAsLax"|"ExcludeSameSiteNoneInsecure"|"ExcludeSameSiteLax"|"ExcludeSameSiteStrict"|"ExcludeInvalidSameParty"|"ExcludeSamePartyCrossPartyContext"|"ExcludeDomainNonASCII"|"ExcludeThirdPartyCookieBlockedInFirstPartySet"|"ExcludeThirdPartyPhaseout"; + export type CookieExclusionReason = "ExcludeSameSiteUnspecifiedTreatedAsLax"|"ExcludeSameSiteNoneInsecure"|"ExcludeSameSiteLax"|"ExcludeSameSiteStrict"|"ExcludeInvalidSameParty"|"ExcludeSamePartyCrossPartyContext"|"ExcludeDomainNonASCII"|"ExcludeThirdPartyCookieBlockedInFirstPartySet"|"ExcludeThirdPartyPhaseout"|"ExcludePortMismatch"|"ExcludeSchemeMismatch"; export type CookieWarningReason = "WarnSameSiteUnspecifiedCrossSiteContext"|"WarnSameSiteNoneInsecure"|"WarnSameSiteUnspecifiedLaxAllowUnsafe"|"WarnSameSiteStrictLaxDowngradeStrict"|"WarnSameSiteStrictCrossDowngradeStrict"|"WarnSameSiteStrictCrossDowngradeLax"|"WarnSameSiteLaxCrossDowngradeStrict"|"WarnSameSiteLaxCrossDowngradeLax"|"WarnAttributeValueExceedsMaxSize"|"WarnDomainNonASCII"|"WarnThirdPartyPhaseout"|"WarnCrossSiteRedirectDowngradeChangesInclusion"|"WarnDeprecationTrialMetadata"|"WarnThirdPartyCookieHeuristic"; export type CookieOperation = "SetCookie"|"ReadCookie"; /** @@ -2183,12 +2183,17 @@ The array enumerates @scope at-rules starting with the innermost one, going outw * The array keeps the types of ancestor CSSRules from the innermost going outwards. */ ruleTypes?: CSSRuleType[]; + /** + * @starting-style CSS at-rule array. +The array enumerates @starting-style at-rules starting with the innermost one, going outwards. + */ + startingStyles?: CSSStartingStyle[]; } /** * Enum indicating the type of a CSS rule, used to represent the order of a style rule's ancestors. This list only contains rule types that are collected during the ancestor rule collection. */ - export type CSSRuleType = "MediaRule"|"SupportsRule"|"ContainerRule"|"LayerRule"|"ScopeRule"|"StyleRule"; + export type CSSRuleType = "MediaRule"|"SupportsRule"|"ContainerRule"|"LayerRule"|"ScopeRule"|"StyleRule"|"StartingStyleRule"; /** * CSS coverage information. */ @@ -2424,6 +2429,10 @@ available). * Optional logical axes queried for the container. */ logicalAxes?: DOM.LogicalAxes; + /** + * true if the query contains scroll-state() queries. + */ + queriesScrollState?: boolean; } /** * CSS Supports at-rule descriptor. @@ -2475,6 +2484,20 @@ available). text: string; /** * The associated rule header range in the enclosing stylesheet (if +available). + */ + range?: SourceRange; + /** + * Identifier of the stylesheet containing this object (if exists). + */ + styleSheetId?: StyleSheetId; + } + /** + * CSS Starting Style at-rule descriptor. + */ + export interface CSSStartingStyle { + /** + * The associated rule header range in the enclosing stylesheet (if available). */ range?: SourceRange; @@ -2779,6 +2802,12 @@ resized.) The current implementation considers only viewport-dependent media fea */ styleSheetId: StyleSheetId; } + export type computedStyleUpdatedPayload = { + /** + * The node id that has updated computed styles. + */ + nodeId: DOM.NodeId; + } /** * Inserts a new rule with the given `ruleText` in a stylesheet with given `styleSheetId`, at the @@ -3039,6 +3068,19 @@ returns an array of locations of the CSS selector in the style sheet. export type getLocationForSelectorReturnValue = { ranges: SourceRange[]; } + /** + * Starts tracking the given node for the computed style updates +and whenever the computed style is updated for node, it queues +a `computedStyleUpdated` event with throttling. +There can only be 1 node tracked for computed style updates +so passing a new node id removes tracking from the previous node. +Pass `undefined` to disable tracking. + */ + export type trackComputedStyleUpdatesForNodeParameters = { + nodeId?: DOM.NodeId; + } + export type trackComputedStyleUpdatesForNodeReturnValue = { + } /** * Starts tracking the given computed styles for updates. The specified array of properties replaces the one previously specified. Pass empty array to disable tracking. @@ -3561,7 +3603,7 @@ front-end. /** * Pseudo element type. */ - export type PseudoType = "first-line"|"first-letter"|"before"|"after"|"marker"|"backdrop"|"column"|"selection"|"search-text"|"target-text"|"spelling-error"|"grammar-error"|"highlight"|"first-line-inherited"|"scroll-marker"|"scroll-marker-group"|"scroll-next-button"|"scroll-prev-button"|"scrollbar"|"scrollbar-thumb"|"scrollbar-button"|"scrollbar-track"|"scrollbar-track-piece"|"scrollbar-corner"|"resizer"|"input-list-button"|"view-transition"|"view-transition-group"|"view-transition-image-pair"|"view-transition-old"|"view-transition-new"|"placeholder"|"file-selector-button"|"details-content"|"select-fallback-button"|"select-fallback-button-text"|"picker"; + export type PseudoType = "first-line"|"first-letter"|"check"|"before"|"after"|"select-arrow"|"marker"|"backdrop"|"column"|"selection"|"search-text"|"target-text"|"spelling-error"|"grammar-error"|"highlight"|"first-line-inherited"|"scroll-marker"|"scroll-marker-group"|"scroll-next-button"|"scroll-prev-button"|"scrollbar"|"scrollbar-thumb"|"scrollbar-button"|"scrollbar-track"|"scrollbar-track-piece"|"scrollbar-corner"|"resizer"|"input-list-button"|"view-transition"|"view-transition-group"|"view-transition-image-pair"|"view-transition-old"|"view-transition-new"|"placeholder"|"file-selector-button"|"details-content"|"picker"; /** * Shadow root type. */ @@ -4876,15 +4918,17 @@ $x functions). } /** * Returns the query container of the given node based on container query -conditions: containerName, physical, and logical axes. If no axes are -provided, the style container is returned, which is the direct parent or the -closest element with a matching container-name. +conditions: containerName, physical and logical axes, and whether it queries +scroll-state. If no axes are provided and queriesScrollState is false, the +style container is returned, which is the direct parent or the closest +element with a matching container-name. */ export type getContainerForNodeParameters = { nodeId: NodeId; containerName?: string; physicalAxes?: PhysicalAxes; logicalAxes?: LogicalAxes; + queriesScrollState?: boolean; } export type getContainerForNodeReturnValue = { /** @@ -8255,7 +8299,9 @@ file, data and other requests and responses, their headers, bodies, timing, etc. */ export type LoaderId = string; /** - * Unique request identifier. + * Unique network request identifier. +Note that this does not identify individual HTTP requests that are part of +a network request. */ export type RequestId = string; /** @@ -8830,6 +8876,7 @@ If the opcode isn't 1, then payloadData is a base64 encoded string representing type: "parser"|"script"|"preload"|"SignedExchange"|"preflight"|"other"; /** * Initiator JavaScript stack trace, set for Script only. +Requires the Debugger domain to be enabled. */ stack?: Runtime.StackTrace; /** @@ -8944,7 +8991,7 @@ This is a temporary ability and it will be removed in the future. /** * Types of reasons why a cookie may not be sent with a request. */ - export type CookieBlockedReason = "SecureOnly"|"NotOnPath"|"DomainMismatch"|"SameSiteStrict"|"SameSiteLax"|"SameSiteUnspecifiedTreatedAsLax"|"SameSiteNoneInsecure"|"UserPreferences"|"ThirdPartyPhaseout"|"ThirdPartyBlockedInFirstPartySet"|"UnknownError"|"SchemefulSameSiteStrict"|"SchemefulSameSiteLax"|"SchemefulSameSiteUnspecifiedTreatedAsLax"|"SamePartyFromCrossPartyContext"|"NameValuePairExceedsMaxSize"; + export type CookieBlockedReason = "SecureOnly"|"NotOnPath"|"DomainMismatch"|"SameSiteStrict"|"SameSiteLax"|"SameSiteUnspecifiedTreatedAsLax"|"SameSiteNoneInsecure"|"UserPreferences"|"ThirdPartyPhaseout"|"ThirdPartyBlockedInFirstPartySet"|"UnknownError"|"SchemefulSameSiteStrict"|"SchemefulSameSiteLax"|"SchemefulSameSiteUnspecifiedTreatedAsLax"|"SamePartyFromCrossPartyContext"|"NameValuePairExceedsMaxSize"|"PortMismatch"|"SchemeMismatch"; /** * Types of reasons why a cookie should have been blocked by 3PCD but is exempted for the request. */ @@ -11498,7 +11545,7 @@ as an ad. * All Permissions Policy features. This enum should match the one defined in third_party/blink/renderer/core/permissions_policy/permissions_policy_features.json5. */ - export type PermissionsPolicyFeature = "accelerometer"|"all-screens-capture"|"ambient-light-sensor"|"attribution-reporting"|"autoplay"|"bluetooth"|"browsing-topics"|"camera"|"captured-surface-control"|"ch-dpr"|"ch-device-memory"|"ch-downlink"|"ch-ect"|"ch-prefers-color-scheme"|"ch-prefers-reduced-motion"|"ch-prefers-reduced-transparency"|"ch-rtt"|"ch-save-data"|"ch-ua"|"ch-ua-arch"|"ch-ua-bitness"|"ch-ua-platform"|"ch-ua-model"|"ch-ua-mobile"|"ch-ua-form-factors"|"ch-ua-full-version"|"ch-ua-full-version-list"|"ch-ua-platform-version"|"ch-ua-wow64"|"ch-viewport-height"|"ch-viewport-width"|"ch-width"|"clipboard-read"|"clipboard-write"|"compute-pressure"|"controlled-frame"|"cross-origin-isolated"|"deferred-fetch"|"digital-credentials-get"|"direct-sockets"|"direct-sockets-private"|"display-capture"|"document-domain"|"encrypted-media"|"execution-while-out-of-viewport"|"execution-while-not-rendered"|"focus-without-user-activation"|"fullscreen"|"frobulate"|"gamepad"|"geolocation"|"gyroscope"|"hid"|"identity-credentials-get"|"idle-detection"|"interest-cohort"|"join-ad-interest-group"|"keyboard-map"|"local-fonts"|"magnetometer"|"media-playback-while-not-visible"|"microphone"|"midi"|"otp-credentials"|"payment"|"picture-in-picture"|"popins"|"private-aggregation"|"private-state-token-issuance"|"private-state-token-redemption"|"publickey-credentials-create"|"publickey-credentials-get"|"run-ad-auction"|"screen-wake-lock"|"serial"|"shared-autofill"|"shared-storage"|"shared-storage-select-url"|"smart-card"|"speaker-selection"|"storage-access"|"sub-apps"|"sync-xhr"|"unload"|"usb"|"usb-unrestricted"|"vertical-scroll"|"web-app-installation"|"web-printing"|"web-share"|"window-management"|"xr-spatial-tracking"; + export type PermissionsPolicyFeature = "accelerometer"|"all-screens-capture"|"ambient-light-sensor"|"attribution-reporting"|"autoplay"|"bluetooth"|"browsing-topics"|"camera"|"captured-surface-control"|"ch-dpr"|"ch-device-memory"|"ch-downlink"|"ch-ect"|"ch-prefers-color-scheme"|"ch-prefers-reduced-motion"|"ch-prefers-reduced-transparency"|"ch-rtt"|"ch-save-data"|"ch-ua"|"ch-ua-arch"|"ch-ua-bitness"|"ch-ua-platform"|"ch-ua-model"|"ch-ua-mobile"|"ch-ua-form-factors"|"ch-ua-full-version"|"ch-ua-full-version-list"|"ch-ua-platform-version"|"ch-ua-wow64"|"ch-viewport-height"|"ch-viewport-width"|"ch-width"|"clipboard-read"|"clipboard-write"|"compute-pressure"|"controlled-frame"|"cross-origin-isolated"|"deferred-fetch"|"digital-credentials-get"|"direct-sockets"|"direct-sockets-private"|"display-capture"|"document-domain"|"encrypted-media"|"execution-while-out-of-viewport"|"execution-while-not-rendered"|"fenced-unpartitioned-storage-read"|"focus-without-user-activation"|"fullscreen"|"frobulate"|"gamepad"|"geolocation"|"gyroscope"|"hid"|"identity-credentials-get"|"idle-detection"|"interest-cohort"|"join-ad-interest-group"|"keyboard-map"|"local-fonts"|"magnetometer"|"media-playback-while-not-visible"|"microphone"|"midi"|"otp-credentials"|"payment"|"picture-in-picture"|"popins"|"private-aggregation"|"private-state-token-issuance"|"private-state-token-redemption"|"publickey-credentials-create"|"publickey-credentials-get"|"run-ad-auction"|"screen-wake-lock"|"serial"|"shared-autofill"|"shared-storage"|"shared-storage-select-url"|"smart-card"|"speaker-selection"|"storage-access"|"sub-apps"|"sync-xhr"|"unload"|"usb"|"usb-unrestricted"|"vertical-scroll"|"web-app-installation"|"web-printing"|"web-share"|"window-management"|"xr-spatial-tracking"; /** * Reason for a permissions policy feature to be disabled. */ @@ -12384,7 +12431,8 @@ the page execution. Execution can be resumed via calling Page.handleJavaScriptDi defaultPrompt?: string; } /** - * Fired for top level page lifecycle events such as navigation, load, paint, etc. + * Fired for lifecycle events (navigation, load, paint, etc) in the current +target (including local frames). */ export type lifecycleEventPayload = { /** @@ -14339,6 +14387,7 @@ int destinationLimitPriority: SignedInt64AsBase10; aggregatableDebugReportingConfig: AttributionReportingAggregatableDebugReportingConfig; scopesData?: AttributionScopesData; + maxEventLevelReports: number; } export type AttributionReportingSourceRegistrationResult = "success"|"internalError"|"insufficientSourceCapacity"|"insufficientUniqueDestinationCapacity"|"excessiveReportingOrigins"|"prohibitedByBrowserPolicy"|"successNoised"|"destinationReportingLimitReached"|"destinationGlobalLimitReached"|"destinationBothLimitsReached"|"reportingOriginsPerSiteLimitReached"|"exceedsMaxChannelCapacity"|"exceedsMaxScopesChannelCapacity"|"exceedsMaxTriggerStateCardinality"|"exceedsMaxEventStatesLimit"|"destinationPerDayReportingLimitReached"; export type AttributionReportingSourceRegistrationTimeConfig = "include"|"exclude"; @@ -14386,7 +14435,7 @@ int scopes: string[]; } export type AttributionReportingEventLevelResult = "success"|"successDroppedLowerPriority"|"internalError"|"noCapacityForAttributionDestination"|"noMatchingSources"|"deduplicated"|"excessiveAttributions"|"priorityTooLow"|"neverAttributedSource"|"excessiveReportingOrigins"|"noMatchingSourceFilterData"|"prohibitedByBrowserPolicy"|"noMatchingConfigurations"|"excessiveReports"|"falselyAttributedSource"|"reportWindowPassed"|"notRegistered"|"reportWindowNotStarted"|"noMatchingTriggerData"; - export type AttributionReportingAggregatableResult = "success"|"internalError"|"noCapacityForAttributionDestination"|"noMatchingSources"|"excessiveAttributions"|"excessiveReportingOrigins"|"noHistograms"|"insufficientBudget"|"noMatchingSourceFilterData"|"notRegistered"|"prohibitedByBrowserPolicy"|"deduplicated"|"reportWindowPassed"|"excessiveReports"; + export type AttributionReportingAggregatableResult = "success"|"internalError"|"noCapacityForAttributionDestination"|"noMatchingSources"|"excessiveAttributions"|"excessiveReportingOrigins"|"noHistograms"|"insufficientBudget"|"insufficientNamedBudget"|"noMatchingSourceFilterData"|"notRegistered"|"prohibitedByBrowserPolicy"|"deduplicated"|"reportWindowPassed"|"excessiveReports"; /** * A single Related Website Set object. */ @@ -15920,6 +15969,8 @@ are ignored. export module Fetch { /** * Unique request identifier. +Note that this does not identify individual HTTP requests that are part of +a network request. */ export type RequestId = string; /** @@ -16302,7 +16353,7 @@ https://webaudio.github.io/web-audio-api/ /** * Enum of AudioContextState from the spec */ - export type ContextState = "suspended"|"running"|"closed"; + export type ContextState = "suspended"|"running"|"closed"|"interrupted"; /** * Enum of AudioNode types */ @@ -20213,6 +20264,7 @@ Error was thrown. "CSS.styleSheetAdded": CSS.styleSheetAddedPayload; "CSS.styleSheetChanged": CSS.styleSheetChangedPayload; "CSS.styleSheetRemoved": CSS.styleSheetRemovedPayload; + "CSS.computedStyleUpdated": CSS.computedStyleUpdatedPayload; "Cast.sinksUpdated": Cast.sinksUpdatedPayload; "Cast.issueUpdated": Cast.issueUpdatedPayload; "DOM.attributeModified": DOM.attributeModifiedPayload; @@ -20464,6 +20516,7 @@ Error was thrown. "CSS.getStyleSheetText": CSS.getStyleSheetTextParameters; "CSS.getLayersForNode": CSS.getLayersForNodeParameters; "CSS.getLocationForSelector": CSS.getLocationForSelectorParameters; + "CSS.trackComputedStyleUpdatesForNode": CSS.trackComputedStyleUpdatesForNodeParameters; "CSS.trackComputedStyleUpdates": CSS.trackComputedStyleUpdatesParameters; "CSS.takeComputedStyleUpdates": CSS.takeComputedStyleUpdatesParameters; "CSS.setEffectivePropertyValueForNode": CSS.setEffectivePropertyValueForNodeParameters; @@ -21075,6 +21128,7 @@ Error was thrown. "CSS.getStyleSheetText": CSS.getStyleSheetTextReturnValue; "CSS.getLayersForNode": CSS.getLayersForNodeReturnValue; "CSS.getLocationForSelector": CSS.getLocationForSelectorReturnValue; + "CSS.trackComputedStyleUpdatesForNode": CSS.trackComputedStyleUpdatesForNodeReturnValue; "CSS.trackComputedStyleUpdates": CSS.trackComputedStyleUpdatesReturnValue; "CSS.takeComputedStyleUpdates": CSS.takeComputedStyleUpdatesReturnValue; "CSS.setEffectivePropertyValueForNode": CSS.setEffectivePropertyValueForNodeReturnValue; diff --git a/packages/playwright-core/src/server/deviceDescriptorsSource.json b/packages/playwright-core/src/server/deviceDescriptorsSource.json index 4132dceec9..e82744b73c 100644 --- a/packages/playwright-core/src/server/deviceDescriptorsSource.json +++ b/packages/playwright-core/src/server/deviceDescriptorsSource.json @@ -110,7 +110,7 @@ "defaultBrowserType": "webkit" }, "Galaxy S5": { - "userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.6 Mobile Safari/537.36", "viewport": { "width": 360, "height": 640 @@ -121,7 +121,7 @@ "defaultBrowserType": "chromium" }, "Galaxy S5 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.6 Mobile Safari/537.36", "viewport": { "width": 640, "height": 360 @@ -132,7 +132,7 @@ "defaultBrowserType": "chromium" }, "Galaxy S8": { - "userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.6 Mobile Safari/537.36", "viewport": { "width": 360, "height": 740 @@ -143,7 +143,7 @@ "defaultBrowserType": "chromium" }, "Galaxy S8 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.6 Mobile Safari/537.36", "viewport": { "width": 740, "height": 360 @@ -154,7 +154,7 @@ "defaultBrowserType": "chromium" }, "Galaxy S9+": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.6 Mobile Safari/537.36", "viewport": { "width": 320, "height": 658 @@ -165,7 +165,7 @@ "defaultBrowserType": "chromium" }, "Galaxy S9+ landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.6 Mobile Safari/537.36", "viewport": { "width": 658, "height": 320 @@ -176,7 +176,7 @@ "defaultBrowserType": "chromium" }, "Galaxy Tab S4": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.6 Safari/537.36", "viewport": { "width": 712, "height": 1138 @@ -187,7 +187,7 @@ "defaultBrowserType": "chromium" }, "Galaxy Tab S4 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.6 Safari/537.36", "viewport": { "width": 1138, "height": 712 @@ -1098,7 +1098,7 @@ "defaultBrowserType": "webkit" }, "LG Optimus L70": { - "userAgent": "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/131.0.6778.33 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/132.0.6834.6 Mobile Safari/537.36", "viewport": { "width": 384, "height": 640 @@ -1109,7 +1109,7 @@ "defaultBrowserType": "chromium" }, "LG Optimus L70 landscape": { - "userAgent": "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/131.0.6778.33 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/132.0.6834.6 Mobile Safari/537.36", "viewport": { "width": 640, "height": 384 @@ -1120,7 +1120,7 @@ "defaultBrowserType": "chromium" }, "Microsoft Lumia 550": { - "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Mobile Safari/537.36 Edge/14.14263", + "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.6 Mobile Safari/537.36 Edge/14.14263", "viewport": { "width": 640, "height": 360 @@ -1131,7 +1131,7 @@ "defaultBrowserType": "chromium" }, "Microsoft Lumia 550 landscape": { - "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Mobile Safari/537.36 Edge/14.14263", + "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.6 Mobile Safari/537.36 Edge/14.14263", "viewport": { "width": 360, "height": 640 @@ -1142,7 +1142,7 @@ "defaultBrowserType": "chromium" }, "Microsoft Lumia 950": { - "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Mobile Safari/537.36 Edge/14.14263", + "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.6 Mobile Safari/537.36 Edge/14.14263", "viewport": { "width": 360, "height": 640 @@ -1153,7 +1153,7 @@ "defaultBrowserType": "chromium" }, "Microsoft Lumia 950 landscape": { - "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Mobile Safari/537.36 Edge/14.14263", + "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.6 Mobile Safari/537.36 Edge/14.14263", "viewport": { "width": 640, "height": 360 @@ -1164,7 +1164,7 @@ "defaultBrowserType": "chromium" }, "Nexus 10": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.6 Safari/537.36", "viewport": { "width": 800, "height": 1280 @@ -1175,7 +1175,7 @@ "defaultBrowserType": "chromium" }, "Nexus 10 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.6 Safari/537.36", "viewport": { "width": 1280, "height": 800 @@ -1186,7 +1186,7 @@ "defaultBrowserType": "chromium" }, "Nexus 4": { - "userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.6 Mobile Safari/537.36", "viewport": { "width": 384, "height": 640 @@ -1197,7 +1197,7 @@ "defaultBrowserType": "chromium" }, "Nexus 4 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.6 Mobile Safari/537.36", "viewport": { "width": 640, "height": 384 @@ -1208,7 +1208,7 @@ "defaultBrowserType": "chromium" }, "Nexus 5": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.6 Mobile Safari/537.36", "viewport": { "width": 360, "height": 640 @@ -1219,7 +1219,7 @@ "defaultBrowserType": "chromium" }, "Nexus 5 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.6 Mobile Safari/537.36", "viewport": { "width": 640, "height": 360 @@ -1230,7 +1230,7 @@ "defaultBrowserType": "chromium" }, "Nexus 5X": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.6 Mobile Safari/537.36", "viewport": { "width": 412, "height": 732 @@ -1241,7 +1241,7 @@ "defaultBrowserType": "chromium" }, "Nexus 5X landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.6 Mobile Safari/537.36", "viewport": { "width": 732, "height": 412 @@ -1252,7 +1252,7 @@ "defaultBrowserType": "chromium" }, "Nexus 6": { - "userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.6 Mobile Safari/537.36", "viewport": { "width": 412, "height": 732 @@ -1263,7 +1263,7 @@ "defaultBrowserType": "chromium" }, "Nexus 6 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.6 Mobile Safari/537.36", "viewport": { "width": 732, "height": 412 @@ -1274,7 +1274,7 @@ "defaultBrowserType": "chromium" }, "Nexus 6P": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.6 Mobile Safari/537.36", "viewport": { "width": 412, "height": 732 @@ -1285,7 +1285,7 @@ "defaultBrowserType": "chromium" }, "Nexus 6P landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.6 Mobile Safari/537.36", "viewport": { "width": 732, "height": 412 @@ -1296,7 +1296,7 @@ "defaultBrowserType": "chromium" }, "Nexus 7": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.6 Safari/537.36", "viewport": { "width": 600, "height": 960 @@ -1307,7 +1307,7 @@ "defaultBrowserType": "chromium" }, "Nexus 7 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.6 Safari/537.36", "viewport": { "width": 960, "height": 600 @@ -1362,7 +1362,7 @@ "defaultBrowserType": "webkit" }, "Pixel 2": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.6 Mobile Safari/537.36", "viewport": { "width": 411, "height": 731 @@ -1373,7 +1373,7 @@ "defaultBrowserType": "chromium" }, "Pixel 2 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.6 Mobile Safari/537.36", "viewport": { "width": 731, "height": 411 @@ -1384,7 +1384,7 @@ "defaultBrowserType": "chromium" }, "Pixel 2 XL": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.6 Mobile Safari/537.36", "viewport": { "width": 411, "height": 823 @@ -1395,7 +1395,7 @@ "defaultBrowserType": "chromium" }, "Pixel 2 XL landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.6 Mobile Safari/537.36", "viewport": { "width": 823, "height": 411 @@ -1406,7 +1406,7 @@ "defaultBrowserType": "chromium" }, "Pixel 3": { - "userAgent": "Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.6 Mobile Safari/537.36", "viewport": { "width": 393, "height": 786 @@ -1417,7 +1417,7 @@ "defaultBrowserType": "chromium" }, "Pixel 3 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.6 Mobile Safari/537.36", "viewport": { "width": 786, "height": 393 @@ -1428,7 +1428,7 @@ "defaultBrowserType": "chromium" }, "Pixel 4": { - "userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.6 Mobile Safari/537.36", "viewport": { "width": 353, "height": 745 @@ -1439,7 +1439,7 @@ "defaultBrowserType": "chromium" }, "Pixel 4 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.6 Mobile Safari/537.36", "viewport": { "width": 745, "height": 353 @@ -1450,7 +1450,7 @@ "defaultBrowserType": "chromium" }, "Pixel 4a (5G)": { - "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.6 Mobile Safari/537.36", "screen": { "width": 412, "height": 892 @@ -1465,7 +1465,7 @@ "defaultBrowserType": "chromium" }, "Pixel 4a (5G) landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.6 Mobile Safari/537.36", "screen": { "height": 892, "width": 412 @@ -1480,7 +1480,7 @@ "defaultBrowserType": "chromium" }, "Pixel 5": { - "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.6 Mobile Safari/537.36", "screen": { "width": 393, "height": 851 @@ -1495,7 +1495,7 @@ "defaultBrowserType": "chromium" }, "Pixel 5 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.6 Mobile Safari/537.36", "screen": { "width": 851, "height": 393 @@ -1510,7 +1510,7 @@ "defaultBrowserType": "chromium" }, "Pixel 7": { - "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.6 Mobile Safari/537.36", "screen": { "width": 412, "height": 915 @@ -1525,7 +1525,7 @@ "defaultBrowserType": "chromium" }, "Pixel 7 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.6 Mobile Safari/537.36", "screen": { "width": 915, "height": 412 @@ -1540,7 +1540,7 @@ "defaultBrowserType": "chromium" }, "Moto G4": { - "userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.6 Mobile Safari/537.36", "viewport": { "width": 360, "height": 640 @@ -1551,7 +1551,7 @@ "defaultBrowserType": "chromium" }, "Moto G4 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.6 Mobile Safari/537.36", "viewport": { "width": 640, "height": 360 @@ -1562,7 +1562,7 @@ "defaultBrowserType": "chromium" }, "Desktop Chrome HiDPI": { - "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Safari/537.36", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.6 Safari/537.36", "screen": { "width": 1792, "height": 1120 @@ -1577,7 +1577,7 @@ "defaultBrowserType": "chromium" }, "Desktop Edge HiDPI": { - "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Safari/537.36 Edg/131.0.6778.33", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.6 Safari/537.36 Edg/132.0.6834.6", "screen": { "width": 1792, "height": 1120 @@ -1622,7 +1622,7 @@ "defaultBrowserType": "webkit" }, "Desktop Chrome": { - "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Safari/537.36", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.6 Safari/537.36", "screen": { "width": 1920, "height": 1080 @@ -1637,7 +1637,7 @@ "defaultBrowserType": "chromium" }, "Desktop Edge": { - "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.33 Safari/537.36 Edg/131.0.6778.33", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.6 Safari/537.36 Edg/132.0.6834.6", "screen": { "width": 1920, "height": 1080 diff --git a/packages/playwright-core/types/protocol.d.ts b/packages/playwright-core/types/protocol.d.ts index 35aa6f2eb9..3a61fbca59 100644 --- a/packages/playwright-core/types/protocol.d.ts +++ b/packages/playwright-core/types/protocol.d.ts @@ -112,7 +112,7 @@ export module Protocol { - from 'checked' to 'selected': states which apply to widgets - from 'activedescendant' to 'owns' - relationships between elements other than parent/child/sibling. */ - export type AXPropertyName = "busy"|"disabled"|"editable"|"focusable"|"focused"|"hidden"|"hiddenRoot"|"invalid"|"keyshortcuts"|"settable"|"roledescription"|"live"|"atomic"|"relevant"|"root"|"autocomplete"|"hasPopup"|"level"|"multiselectable"|"orientation"|"multiline"|"readonly"|"required"|"valuemin"|"valuemax"|"valuetext"|"checked"|"expanded"|"modal"|"pressed"|"selected"|"activedescendant"|"controls"|"describedby"|"details"|"errormessage"|"flowto"|"labelledby"|"owns"|"url"; + export type AXPropertyName = "actions"|"busy"|"disabled"|"editable"|"focusable"|"focused"|"hidden"|"hiddenRoot"|"invalid"|"keyshortcuts"|"settable"|"roledescription"|"live"|"atomic"|"relevant"|"root"|"autocomplete"|"hasPopup"|"level"|"multiselectable"|"orientation"|"multiline"|"readonly"|"required"|"valuemin"|"valuemax"|"valuetext"|"checked"|"expanded"|"modal"|"pressed"|"selected"|"activedescendant"|"controls"|"describedby"|"details"|"errormessage"|"flowto"|"labelledby"|"owns"|"url"; /** * A node in the accessibility tree. */ @@ -694,7 +694,7 @@ percentage [0 - 100] for scroll driven animations export interface AffectedFrame { frameId: Page.FrameId; } - export type CookieExclusionReason = "ExcludeSameSiteUnspecifiedTreatedAsLax"|"ExcludeSameSiteNoneInsecure"|"ExcludeSameSiteLax"|"ExcludeSameSiteStrict"|"ExcludeInvalidSameParty"|"ExcludeSamePartyCrossPartyContext"|"ExcludeDomainNonASCII"|"ExcludeThirdPartyCookieBlockedInFirstPartySet"|"ExcludeThirdPartyPhaseout"; + export type CookieExclusionReason = "ExcludeSameSiteUnspecifiedTreatedAsLax"|"ExcludeSameSiteNoneInsecure"|"ExcludeSameSiteLax"|"ExcludeSameSiteStrict"|"ExcludeInvalidSameParty"|"ExcludeSamePartyCrossPartyContext"|"ExcludeDomainNonASCII"|"ExcludeThirdPartyCookieBlockedInFirstPartySet"|"ExcludeThirdPartyPhaseout"|"ExcludePortMismatch"|"ExcludeSchemeMismatch"; export type CookieWarningReason = "WarnSameSiteUnspecifiedCrossSiteContext"|"WarnSameSiteNoneInsecure"|"WarnSameSiteUnspecifiedLaxAllowUnsafe"|"WarnSameSiteStrictLaxDowngradeStrict"|"WarnSameSiteStrictCrossDowngradeStrict"|"WarnSameSiteStrictCrossDowngradeLax"|"WarnSameSiteLaxCrossDowngradeStrict"|"WarnSameSiteLaxCrossDowngradeLax"|"WarnAttributeValueExceedsMaxSize"|"WarnDomainNonASCII"|"WarnThirdPartyPhaseout"|"WarnCrossSiteRedirectDowngradeChangesInclusion"|"WarnDeprecationTrialMetadata"|"WarnThirdPartyCookieHeuristic"; export type CookieOperation = "SetCookie"|"ReadCookie"; /** @@ -2183,12 +2183,17 @@ The array enumerates @scope at-rules starting with the innermost one, going outw * The array keeps the types of ancestor CSSRules from the innermost going outwards. */ ruleTypes?: CSSRuleType[]; + /** + * @starting-style CSS at-rule array. +The array enumerates @starting-style at-rules starting with the innermost one, going outwards. + */ + startingStyles?: CSSStartingStyle[]; } /** * Enum indicating the type of a CSS rule, used to represent the order of a style rule's ancestors. This list only contains rule types that are collected during the ancestor rule collection. */ - export type CSSRuleType = "MediaRule"|"SupportsRule"|"ContainerRule"|"LayerRule"|"ScopeRule"|"StyleRule"; + export type CSSRuleType = "MediaRule"|"SupportsRule"|"ContainerRule"|"LayerRule"|"ScopeRule"|"StyleRule"|"StartingStyleRule"; /** * CSS coverage information. */ @@ -2424,6 +2429,10 @@ available). * Optional logical axes queried for the container. */ logicalAxes?: DOM.LogicalAxes; + /** + * true if the query contains scroll-state() queries. + */ + queriesScrollState?: boolean; } /** * CSS Supports at-rule descriptor. @@ -2475,6 +2484,20 @@ available). text: string; /** * The associated rule header range in the enclosing stylesheet (if +available). + */ + range?: SourceRange; + /** + * Identifier of the stylesheet containing this object (if exists). + */ + styleSheetId?: StyleSheetId; + } + /** + * CSS Starting Style at-rule descriptor. + */ + export interface CSSStartingStyle { + /** + * The associated rule header range in the enclosing stylesheet (if available). */ range?: SourceRange; @@ -2779,6 +2802,12 @@ resized.) The current implementation considers only viewport-dependent media fea */ styleSheetId: StyleSheetId; } + export type computedStyleUpdatedPayload = { + /** + * The node id that has updated computed styles. + */ + nodeId: DOM.NodeId; + } /** * Inserts a new rule with the given `ruleText` in a stylesheet with given `styleSheetId`, at the @@ -3039,6 +3068,19 @@ returns an array of locations of the CSS selector in the style sheet. export type getLocationForSelectorReturnValue = { ranges: SourceRange[]; } + /** + * Starts tracking the given node for the computed style updates +and whenever the computed style is updated for node, it queues +a `computedStyleUpdated` event with throttling. +There can only be 1 node tracked for computed style updates +so passing a new node id removes tracking from the previous node. +Pass `undefined` to disable tracking. + */ + export type trackComputedStyleUpdatesForNodeParameters = { + nodeId?: DOM.NodeId; + } + export type trackComputedStyleUpdatesForNodeReturnValue = { + } /** * Starts tracking the given computed styles for updates. The specified array of properties replaces the one previously specified. Pass empty array to disable tracking. @@ -3561,7 +3603,7 @@ front-end. /** * Pseudo element type. */ - export type PseudoType = "first-line"|"first-letter"|"before"|"after"|"marker"|"backdrop"|"column"|"selection"|"search-text"|"target-text"|"spelling-error"|"grammar-error"|"highlight"|"first-line-inherited"|"scroll-marker"|"scroll-marker-group"|"scroll-next-button"|"scroll-prev-button"|"scrollbar"|"scrollbar-thumb"|"scrollbar-button"|"scrollbar-track"|"scrollbar-track-piece"|"scrollbar-corner"|"resizer"|"input-list-button"|"view-transition"|"view-transition-group"|"view-transition-image-pair"|"view-transition-old"|"view-transition-new"|"placeholder"|"file-selector-button"|"details-content"|"select-fallback-button"|"select-fallback-button-text"|"picker"; + export type PseudoType = "first-line"|"first-letter"|"check"|"before"|"after"|"select-arrow"|"marker"|"backdrop"|"column"|"selection"|"search-text"|"target-text"|"spelling-error"|"grammar-error"|"highlight"|"first-line-inherited"|"scroll-marker"|"scroll-marker-group"|"scroll-next-button"|"scroll-prev-button"|"scrollbar"|"scrollbar-thumb"|"scrollbar-button"|"scrollbar-track"|"scrollbar-track-piece"|"scrollbar-corner"|"resizer"|"input-list-button"|"view-transition"|"view-transition-group"|"view-transition-image-pair"|"view-transition-old"|"view-transition-new"|"placeholder"|"file-selector-button"|"details-content"|"picker"; /** * Shadow root type. */ @@ -4876,15 +4918,17 @@ $x functions). } /** * Returns the query container of the given node based on container query -conditions: containerName, physical, and logical axes. If no axes are -provided, the style container is returned, which is the direct parent or the -closest element with a matching container-name. +conditions: containerName, physical and logical axes, and whether it queries +scroll-state. If no axes are provided and queriesScrollState is false, the +style container is returned, which is the direct parent or the closest +element with a matching container-name. */ export type getContainerForNodeParameters = { nodeId: NodeId; containerName?: string; physicalAxes?: PhysicalAxes; logicalAxes?: LogicalAxes; + queriesScrollState?: boolean; } export type getContainerForNodeReturnValue = { /** @@ -8255,7 +8299,9 @@ file, data and other requests and responses, their headers, bodies, timing, etc. */ export type LoaderId = string; /** - * Unique request identifier. + * Unique network request identifier. +Note that this does not identify individual HTTP requests that are part of +a network request. */ export type RequestId = string; /** @@ -8830,6 +8876,7 @@ If the opcode isn't 1, then payloadData is a base64 encoded string representing type: "parser"|"script"|"preload"|"SignedExchange"|"preflight"|"other"; /** * Initiator JavaScript stack trace, set for Script only. +Requires the Debugger domain to be enabled. */ stack?: Runtime.StackTrace; /** @@ -8944,7 +8991,7 @@ This is a temporary ability and it will be removed in the future. /** * Types of reasons why a cookie may not be sent with a request. */ - export type CookieBlockedReason = "SecureOnly"|"NotOnPath"|"DomainMismatch"|"SameSiteStrict"|"SameSiteLax"|"SameSiteUnspecifiedTreatedAsLax"|"SameSiteNoneInsecure"|"UserPreferences"|"ThirdPartyPhaseout"|"ThirdPartyBlockedInFirstPartySet"|"UnknownError"|"SchemefulSameSiteStrict"|"SchemefulSameSiteLax"|"SchemefulSameSiteUnspecifiedTreatedAsLax"|"SamePartyFromCrossPartyContext"|"NameValuePairExceedsMaxSize"; + export type CookieBlockedReason = "SecureOnly"|"NotOnPath"|"DomainMismatch"|"SameSiteStrict"|"SameSiteLax"|"SameSiteUnspecifiedTreatedAsLax"|"SameSiteNoneInsecure"|"UserPreferences"|"ThirdPartyPhaseout"|"ThirdPartyBlockedInFirstPartySet"|"UnknownError"|"SchemefulSameSiteStrict"|"SchemefulSameSiteLax"|"SchemefulSameSiteUnspecifiedTreatedAsLax"|"SamePartyFromCrossPartyContext"|"NameValuePairExceedsMaxSize"|"PortMismatch"|"SchemeMismatch"; /** * Types of reasons why a cookie should have been blocked by 3PCD but is exempted for the request. */ @@ -11498,7 +11545,7 @@ as an ad. * All Permissions Policy features. This enum should match the one defined in third_party/blink/renderer/core/permissions_policy/permissions_policy_features.json5. */ - export type PermissionsPolicyFeature = "accelerometer"|"all-screens-capture"|"ambient-light-sensor"|"attribution-reporting"|"autoplay"|"bluetooth"|"browsing-topics"|"camera"|"captured-surface-control"|"ch-dpr"|"ch-device-memory"|"ch-downlink"|"ch-ect"|"ch-prefers-color-scheme"|"ch-prefers-reduced-motion"|"ch-prefers-reduced-transparency"|"ch-rtt"|"ch-save-data"|"ch-ua"|"ch-ua-arch"|"ch-ua-bitness"|"ch-ua-platform"|"ch-ua-model"|"ch-ua-mobile"|"ch-ua-form-factors"|"ch-ua-full-version"|"ch-ua-full-version-list"|"ch-ua-platform-version"|"ch-ua-wow64"|"ch-viewport-height"|"ch-viewport-width"|"ch-width"|"clipboard-read"|"clipboard-write"|"compute-pressure"|"controlled-frame"|"cross-origin-isolated"|"deferred-fetch"|"digital-credentials-get"|"direct-sockets"|"direct-sockets-private"|"display-capture"|"document-domain"|"encrypted-media"|"execution-while-out-of-viewport"|"execution-while-not-rendered"|"focus-without-user-activation"|"fullscreen"|"frobulate"|"gamepad"|"geolocation"|"gyroscope"|"hid"|"identity-credentials-get"|"idle-detection"|"interest-cohort"|"join-ad-interest-group"|"keyboard-map"|"local-fonts"|"magnetometer"|"media-playback-while-not-visible"|"microphone"|"midi"|"otp-credentials"|"payment"|"picture-in-picture"|"popins"|"private-aggregation"|"private-state-token-issuance"|"private-state-token-redemption"|"publickey-credentials-create"|"publickey-credentials-get"|"run-ad-auction"|"screen-wake-lock"|"serial"|"shared-autofill"|"shared-storage"|"shared-storage-select-url"|"smart-card"|"speaker-selection"|"storage-access"|"sub-apps"|"sync-xhr"|"unload"|"usb"|"usb-unrestricted"|"vertical-scroll"|"web-app-installation"|"web-printing"|"web-share"|"window-management"|"xr-spatial-tracking"; + export type PermissionsPolicyFeature = "accelerometer"|"all-screens-capture"|"ambient-light-sensor"|"attribution-reporting"|"autoplay"|"bluetooth"|"browsing-topics"|"camera"|"captured-surface-control"|"ch-dpr"|"ch-device-memory"|"ch-downlink"|"ch-ect"|"ch-prefers-color-scheme"|"ch-prefers-reduced-motion"|"ch-prefers-reduced-transparency"|"ch-rtt"|"ch-save-data"|"ch-ua"|"ch-ua-arch"|"ch-ua-bitness"|"ch-ua-platform"|"ch-ua-model"|"ch-ua-mobile"|"ch-ua-form-factors"|"ch-ua-full-version"|"ch-ua-full-version-list"|"ch-ua-platform-version"|"ch-ua-wow64"|"ch-viewport-height"|"ch-viewport-width"|"ch-width"|"clipboard-read"|"clipboard-write"|"compute-pressure"|"controlled-frame"|"cross-origin-isolated"|"deferred-fetch"|"digital-credentials-get"|"direct-sockets"|"direct-sockets-private"|"display-capture"|"document-domain"|"encrypted-media"|"execution-while-out-of-viewport"|"execution-while-not-rendered"|"fenced-unpartitioned-storage-read"|"focus-without-user-activation"|"fullscreen"|"frobulate"|"gamepad"|"geolocation"|"gyroscope"|"hid"|"identity-credentials-get"|"idle-detection"|"interest-cohort"|"join-ad-interest-group"|"keyboard-map"|"local-fonts"|"magnetometer"|"media-playback-while-not-visible"|"microphone"|"midi"|"otp-credentials"|"payment"|"picture-in-picture"|"popins"|"private-aggregation"|"private-state-token-issuance"|"private-state-token-redemption"|"publickey-credentials-create"|"publickey-credentials-get"|"run-ad-auction"|"screen-wake-lock"|"serial"|"shared-autofill"|"shared-storage"|"shared-storage-select-url"|"smart-card"|"speaker-selection"|"storage-access"|"sub-apps"|"sync-xhr"|"unload"|"usb"|"usb-unrestricted"|"vertical-scroll"|"web-app-installation"|"web-printing"|"web-share"|"window-management"|"xr-spatial-tracking"; /** * Reason for a permissions policy feature to be disabled. */ @@ -12384,7 +12431,8 @@ the page execution. Execution can be resumed via calling Page.handleJavaScriptDi defaultPrompt?: string; } /** - * Fired for top level page lifecycle events such as navigation, load, paint, etc. + * Fired for lifecycle events (navigation, load, paint, etc) in the current +target (including local frames). */ export type lifecycleEventPayload = { /** @@ -14339,6 +14387,7 @@ int destinationLimitPriority: SignedInt64AsBase10; aggregatableDebugReportingConfig: AttributionReportingAggregatableDebugReportingConfig; scopesData?: AttributionScopesData; + maxEventLevelReports: number; } export type AttributionReportingSourceRegistrationResult = "success"|"internalError"|"insufficientSourceCapacity"|"insufficientUniqueDestinationCapacity"|"excessiveReportingOrigins"|"prohibitedByBrowserPolicy"|"successNoised"|"destinationReportingLimitReached"|"destinationGlobalLimitReached"|"destinationBothLimitsReached"|"reportingOriginsPerSiteLimitReached"|"exceedsMaxChannelCapacity"|"exceedsMaxScopesChannelCapacity"|"exceedsMaxTriggerStateCardinality"|"exceedsMaxEventStatesLimit"|"destinationPerDayReportingLimitReached"; export type AttributionReportingSourceRegistrationTimeConfig = "include"|"exclude"; @@ -14386,7 +14435,7 @@ int scopes: string[]; } export type AttributionReportingEventLevelResult = "success"|"successDroppedLowerPriority"|"internalError"|"noCapacityForAttributionDestination"|"noMatchingSources"|"deduplicated"|"excessiveAttributions"|"priorityTooLow"|"neverAttributedSource"|"excessiveReportingOrigins"|"noMatchingSourceFilterData"|"prohibitedByBrowserPolicy"|"noMatchingConfigurations"|"excessiveReports"|"falselyAttributedSource"|"reportWindowPassed"|"notRegistered"|"reportWindowNotStarted"|"noMatchingTriggerData"; - export type AttributionReportingAggregatableResult = "success"|"internalError"|"noCapacityForAttributionDestination"|"noMatchingSources"|"excessiveAttributions"|"excessiveReportingOrigins"|"noHistograms"|"insufficientBudget"|"noMatchingSourceFilterData"|"notRegistered"|"prohibitedByBrowserPolicy"|"deduplicated"|"reportWindowPassed"|"excessiveReports"; + export type AttributionReportingAggregatableResult = "success"|"internalError"|"noCapacityForAttributionDestination"|"noMatchingSources"|"excessiveAttributions"|"excessiveReportingOrigins"|"noHistograms"|"insufficientBudget"|"insufficientNamedBudget"|"noMatchingSourceFilterData"|"notRegistered"|"prohibitedByBrowserPolicy"|"deduplicated"|"reportWindowPassed"|"excessiveReports"; /** * A single Related Website Set object. */ @@ -15920,6 +15969,8 @@ are ignored. export module Fetch { /** * Unique request identifier. +Note that this does not identify individual HTTP requests that are part of +a network request. */ export type RequestId = string; /** @@ -16302,7 +16353,7 @@ https://webaudio.github.io/web-audio-api/ /** * Enum of AudioContextState from the spec */ - export type ContextState = "suspended"|"running"|"closed"; + export type ContextState = "suspended"|"running"|"closed"|"interrupted"; /** * Enum of AudioNode types */ @@ -20213,6 +20264,7 @@ Error was thrown. "CSS.styleSheetAdded": CSS.styleSheetAddedPayload; "CSS.styleSheetChanged": CSS.styleSheetChangedPayload; "CSS.styleSheetRemoved": CSS.styleSheetRemovedPayload; + "CSS.computedStyleUpdated": CSS.computedStyleUpdatedPayload; "Cast.sinksUpdated": Cast.sinksUpdatedPayload; "Cast.issueUpdated": Cast.issueUpdatedPayload; "DOM.attributeModified": DOM.attributeModifiedPayload; @@ -20464,6 +20516,7 @@ Error was thrown. "CSS.getStyleSheetText": CSS.getStyleSheetTextParameters; "CSS.getLayersForNode": CSS.getLayersForNodeParameters; "CSS.getLocationForSelector": CSS.getLocationForSelectorParameters; + "CSS.trackComputedStyleUpdatesForNode": CSS.trackComputedStyleUpdatesForNodeParameters; "CSS.trackComputedStyleUpdates": CSS.trackComputedStyleUpdatesParameters; "CSS.takeComputedStyleUpdates": CSS.takeComputedStyleUpdatesParameters; "CSS.setEffectivePropertyValueForNode": CSS.setEffectivePropertyValueForNodeParameters; @@ -21075,6 +21128,7 @@ Error was thrown. "CSS.getStyleSheetText": CSS.getStyleSheetTextReturnValue; "CSS.getLayersForNode": CSS.getLayersForNodeReturnValue; "CSS.getLocationForSelector": CSS.getLocationForSelectorReturnValue; + "CSS.trackComputedStyleUpdatesForNode": CSS.trackComputedStyleUpdatesForNodeReturnValue; "CSS.trackComputedStyleUpdates": CSS.trackComputedStyleUpdatesReturnValue; "CSS.takeComputedStyleUpdates": CSS.takeComputedStyleUpdatesReturnValue; "CSS.setEffectivePropertyValueForNode": CSS.setEffectivePropertyValueForNodeReturnValue; From b3e5daff2b73d1285e9bd20af228bb9a2e7b5332 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Thu, 14 Nov 2024 19:40:46 +0100 Subject: [PATCH 05/38] cherry-pick(#33606): feat(chromium): roll to r1149 (#33612) From f5477d90512f3fb325bae773a5a2ad436b28ff04 Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Thu, 14 Nov 2024 12:23:42 -0800 Subject: [PATCH 06/38] docs: add ariaSnapshot.timeout for language ports (#33614) --- docs/src/api/class-locator.md | 3 +++ docs/src/api/class-locatorassertions.md | 3 +++ 2 files changed, 6 insertions(+) diff --git a/docs/src/api/class-locator.md b/docs/src/api/class-locator.md index f4a2fe6d7c..64c5873710 100644 --- a/docs/src/api/class-locator.md +++ b/docs/src/api/class-locator.md @@ -206,6 +206,9 @@ Below is the HTML markup and the respective ARIA snapshot: - link "About" ``` +### option: Locator.ariaSnapshot.timeout = %%-input-timeout-%% +* since: v1.49 + ### option: Locator.ariaSnapshot.timeout = %%-input-timeout-js-%% * since: v1.49 diff --git a/docs/src/api/class-locatorassertions.md b/docs/src/api/class-locatorassertions.md index 5d06824b47..369dd995e0 100644 --- a/docs/src/api/class-locatorassertions.md +++ b/docs/src/api/class-locatorassertions.md @@ -2159,3 +2159,6 @@ assertThat(page.locator("body")).matchesAriaSnapshot(""" ### option: LocatorAssertions.toMatchAriaSnapshot.timeout = %%-js-assertions-timeout-%% * since: v1.49 + +### option: LocatorAssertions.toMatchAriaSnapshot.timeout = %%-csharp-java-python-assertions-timeout-%% +* since: v1.49 From eaf3536014b22bbf6ff3c52ef3820e0209341e0e Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Thu, 14 Nov 2024 21:24:02 +0000 Subject: [PATCH 07/38] fix(trace): afterAll hook should not break tracing with reused context (#33616) --- packages/playwright/src/index.ts | 3 ++- tests/playwright-test/playwright.reuse.spec.ts | 12 ++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/playwright/src/index.ts b/packages/playwright/src/index.ts index a08f3790d9..27f1e9320b 100644 --- a/packages/playwright/src/index.ts +++ b/packages/playwright/src/index.ts @@ -618,9 +618,10 @@ class ArtifactsRecorder { if (captureScreenshots) await this._screenshotOnTestFailure(); - const leftoverContexts: BrowserContext[] = []; + let leftoverContexts: BrowserContext[] = []; for (const browserType of [this._playwright.chromium, this._playwright.firefox, this._playwright.webkit]) leftoverContexts.push(...(browserType as any)._contexts); + leftoverContexts = leftoverContexts.filter(context => !this._reusedContexts.has(context)); const leftoverApiRequests: APIRequestContext[] = Array.from((this._playwright.request as any)._contexts as Set); // Collect traces/screenshots for remaining contexts. diff --git a/tests/playwright-test/playwright.reuse.spec.ts b/tests/playwright-test/playwright.reuse.spec.ts index 0620195f65..9732750474 100644 --- a/tests/playwright-test/playwright.reuse.spec.ts +++ b/tests/playwright-test/playwright.reuse.spec.ts @@ -96,6 +96,14 @@ test('should reuse context with trace if mode=when-possible', async ({ runInline import { test, expect } from '@playwright/test'; let lastContextGuid; + test.beforeAll(async () => { + console.log('fromBeforeAll'); + }); + + test.afterAll(async () => { + console.log('fromAfterAll'); + }); + test('one', async ({ context, page }) => { lastContextGuid = context._guid; await page.setContent(''); @@ -113,10 +121,13 @@ test('should reuse context with trace if mode=when-possible', async ({ runInline expect(result.exitCode).toBe(0); expect(result.passed).toBe(2); + expect(result.output).toContain('fromBeforeAll'); + expect(result.output).toContain('fromAfterAll'); const trace1 = await parseTrace(testInfo.outputPath('test-results', 'reuse-one', 'trace.zip')); expect(trace1.actionTree).toEqual([ 'Before Hooks', + ' beforeAll hook', ' fixture: browser', ' browserType.launch', ' fixture: context', @@ -143,6 +154,7 @@ test('should reuse context with trace if mode=when-possible', async ({ runInline 'After Hooks', ' fixture: page', ' fixture: context', + ' afterAll hook', ]); expect(trace2.traceModel.storage().snapshotsForTest().length).toBeGreaterThan(0); }); From 2aa9e11a7ff274da7a4126b8e25ec8ee432ec85c Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Fri, 15 Nov 2024 10:49:03 +0000 Subject: [PATCH 08/38] fix(aria): normalize whitespace in toMatchAccessible{Name,Description} (#33619) --- packages/playwright/src/matchers/matchers.ts | 4 ++-- tests/page/expect-misc.spec.ts | 9 +++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/packages/playwright/src/matchers/matchers.ts b/packages/playwright/src/matchers/matchers.ts index f7c68b4544..dd159a7f5e 100644 --- a/packages/playwright/src/matchers/matchers.ts +++ b/packages/playwright/src/matchers/matchers.ts @@ -181,7 +181,7 @@ export function toHaveAccessibleDescription( options?: { timeout?: number, ignoreCase?: boolean }, ) { return toMatchText.call(this, 'toHaveAccessibleDescription', locator, 'Locator', async (isNot, timeout) => { - const expectedText = serializeExpectedTextValues([expected], { ignoreCase: options?.ignoreCase }); + const expectedText = serializeExpectedTextValues([expected], { ignoreCase: options?.ignoreCase, normalizeWhiteSpace: true }); return await locator._expect('to.have.accessible.description', { expectedText, isNot, timeout }); }, expected, options); } @@ -193,7 +193,7 @@ export function toHaveAccessibleName( options?: { timeout?: number, ignoreCase?: boolean }, ) { return toMatchText.call(this, 'toHaveAccessibleName', locator, 'Locator', async (isNot, timeout) => { - const expectedText = serializeExpectedTextValues([expected], { ignoreCase: options?.ignoreCase }); + const expectedText = serializeExpectedTextValues([expected], { ignoreCase: options?.ignoreCase, normalizeWhiteSpace: true }); return await locator._expect('to.have.accessible.name', { expectedText, isNot, timeout }); }, expected, options); } diff --git a/tests/page/expect-misc.spec.ts b/tests/page/expect-misc.spec.ts index 2242a55378..f99ac12376 100644 --- a/tests/page/expect-misc.spec.ts +++ b/tests/page/expect-misc.spec.ts @@ -431,6 +431,9 @@ test('toHaveAccessibleName', async ({ page }) => { await expect(page.locator('div')).toHaveAccessibleName(/ell\w/); await expect(page.locator('div')).not.toHaveAccessibleName(/hello/); await expect(page.locator('div')).toHaveAccessibleName(/hello/, { ignoreCase: true }); + + await page.setContent(``); + await expect(page.locator('button')).toHaveAccessibleName('foo bar baz'); }); test('toHaveAccessibleDescription', async ({ page }) => { @@ -443,6 +446,12 @@ test('toHaveAccessibleDescription', async ({ page }) => { await expect(page.locator('div')).toHaveAccessibleDescription(/ell\w/); await expect(page.locator('div')).not.toHaveAccessibleDescription(/hello/); await expect(page.locator('div')).toHaveAccessibleDescription(/hello/, { ignoreCase: true }); + + await page.setContent(` +
+ foo bar\nbaz + `); + await expect(page.locator('div')).toHaveAccessibleDescription('foo bar baz'); }); test('toHaveRole', async ({ page }) => { From e61cea597a6a8b39599b271a05b3c780566b026d Mon Sep 17 00:00:00 2001 From: Amaechi Hope <51549388+Honyii@users.noreply.github.com> Date: Fri, 15 Nov 2024 15:02:40 +0100 Subject: [PATCH 09/38] docs(dotnet): fix assertion snippets (#33622) --- docs/src/api/class-locatorassertions.md | 6 +++--- docs/src/clock.md | 4 ++-- docs/src/release-notes-csharp.md | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/src/api/class-locatorassertions.md b/docs/src/api/class-locatorassertions.md index 369dd995e0..c252b1348b 100644 --- a/docs/src/api/class-locatorassertions.md +++ b/docs/src/api/class-locatorassertions.md @@ -701,7 +701,7 @@ expect(locator).to_be_enabled() ```csharp var locator = Page.Locator("button.submit"); -await Expect(locator).toBeEnabledAsync(); +await Expect(locator).ToBeEnabledAsync(); ``` ### option: LocatorAssertions.toBeEnabled.enabled @@ -1181,7 +1181,7 @@ expect(locator).to_have_accessible_description("Save results to disk") ```csharp var locator = Page.GetByTestId("save-button"); -await Expect(locator).toHaveAccessibleDescriptionAsync("Save results to disk"); +await Expect(locator).ToHaveAccessibleDescriptionAsync("Save results to disk"); ``` ### param: LocatorAssertions.toHaveAccessibleDescription.description @@ -1231,7 +1231,7 @@ expect(locator).to_have_accessible_name("Save to disk") ```csharp var locator = Page.GetByTestId("save-button"); -await Expect(locator).toHaveAccessibleNameAsync("Save to disk"); +await Expect(locator).ToHaveAccessibleNameAsync("Save to disk"); ``` ### param: LocatorAssertions.toHaveAccessibleName.name diff --git a/docs/src/clock.md b/docs/src/clock.md index ea14941d82..2f846e4fd3 100644 --- a/docs/src/clock.md +++ b/docs/src/clock.md @@ -164,11 +164,11 @@ await Page.GotoAsync("http://localhost:3333"); await Page.Clock.PauseAtAsync(new DateTime(2024, 2, 2, 10, 0, 0)); // Assert the page state. -await Expect(Page.GetByTestId("current-time")).ToHaveText("2/2/2024, 10:00:00 AM"); +await Expect(Page.GetByTestId("current-time")).ToHaveTextAsync("2/2/2024, 10:00:00 AM"); // Close the laptop lid again and open it at 10:30am. await Page.Clock.FastForwardAsync("30:00"); -await Expect(Page.GetByTestId("current-time")).ToHaveText("2/2/2024, 10:30:00 AM"); +await Expect(Page.GetByTestId("current-time")).ToHaveTextAsync("2/2/2024, 10:30:00 AM"); ``` ## Test inactivity monitoring diff --git a/docs/src/release-notes-csharp.md b/docs/src/release-notes-csharp.md index 130b9d4a71..c39e454ebc 100644 --- a/docs/src/release-notes-csharp.md +++ b/docs/src/release-notes-csharp.md @@ -788,7 +788,7 @@ All the same methods are also available on [Locator], [FrameLocator] and [Frame] - [`method: LocatorAssertions.toHaveAttribute`] with an empty value does not match missing attribute anymore. For example, the following snippet will succeed when `button` **does not** have a `disabled` attribute. ```csharp - await Expect(Page.GetByRole(AriaRole.Button)).ToHaveAttribute("disabled", ""); + await Expect(Page.GetByRole(AriaRole.Button)).ToHaveAttributeAsync("disabled", ""); ``` ### Browser Versions From c81504c5d6cd488701e2be2d093becf49a2fc342 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Fri, 15 Nov 2024 17:14:49 +0100 Subject: [PATCH 10/38] fix(codegen): document.documentElement is null on early navigation (#33627) --- packages/playwright-core/src/server/injected/highlight.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/playwright-core/src/server/injected/highlight.ts b/packages/playwright-core/src/server/injected/highlight.ts index c06e58f529..5720ffc539 100644 --- a/packages/playwright-core/src/server/injected/highlight.ts +++ b/packages/playwright-core/src/server/injected/highlight.ts @@ -90,7 +90,8 @@ export class Highlight { } install() { - if (!this._injectedScript.document.documentElement.contains(this._glassPaneElement)) + // NOTE: document.documentElement can be null: https://github.com/microsoft/TypeScript/issues/50078 + if (this._injectedScript.document.documentElement && !this._injectedScript.document.documentElement.contains(this._glassPaneElement)) this._injectedScript.document.documentElement.appendChild(this._glassPaneElement); } From 77dee44984af1cd5b7c1ec4f6ed6fe022c7c55e6 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Fri, 15 Nov 2024 17:08:31 +0000 Subject: [PATCH 11/38] fix(rebase): do not apply multiple rebaselines to the same assertion (#33629) --- packages/playwright/src/runner/rebase.ts | 4 ++++ tests/playwright-test/update-aria-snapshot.spec.ts | 12 ++++++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/packages/playwright/src/runner/rebase.ts b/packages/playwright/src/runner/rebase.ts index 24f37ca7f7..bc59e8374a 100644 --- a/packages/playwright/src/runner/rebase.ts +++ b/packages/playwright/src/runner/rebase.ts @@ -83,6 +83,10 @@ export async function applySuggestedRebaselines(config: FullConfigInternal, repo const indent = lines[matcher.loc!.start.line - 1].match(/^\s*/)![0]; const newText = replacement.code.replace(/\{indent\}/g, indent); ranges.push({ start: matcher.start!, end: node.end!, oldText: source.substring(matcher.start!, node.end!), newText }); + // We can have multiple, hopefully equal, replacements for the same location, + // for example when a single test runs multiple times because of projects or retries. + // Do not apply multiple replacements for the same assertion. + break; } } }); diff --git a/tests/playwright-test/update-aria-snapshot.spec.ts b/tests/playwright-test/update-aria-snapshot.spec.ts index 57424b7bc6..63769f8408 100644 --- a/tests/playwright-test/update-aria-snapshot.spec.ts +++ b/tests/playwright-test/update-aria-snapshot.spec.ts @@ -24,12 +24,15 @@ function trimPatch(patch: string) { return patch.split('\n').map(line => line.trimEnd()).join('\n'); } -test('should update snapshot with the update-snapshots flag', async ({ runInlineTest }, testInfo) => { +test('should update snapshot with the update-snapshots flag with multiple projects', async ({ runInlineTest }, testInfo) => { const result = await runInlineTest({ + 'playwright.config.ts': ` + export default { projects: [{ name: 'p1' }, { name: 'p2' }] }; + `, 'a.spec.ts': ` import { test, expect } from '@playwright/test'; test('test', async ({ page }) => { - await page.setContent(\`

hello

\`); + await page.setContent(\`

hello

bye

\`); await expect(page.locator('body')).toMatchAriaSnapshot(\` - heading "world" \`); @@ -43,12 +46,13 @@ test('should update snapshot with the update-snapshots flag', async ({ runInline expect(trimPatch(data)).toBe(`diff --git a/a.spec.ts b/a.spec.ts --- a/a.spec.ts +++ b/a.spec.ts -@@ -3,7 +3,7 @@ +@@ -3,7 +3,8 @@ test('test', async ({ page }) => { - await page.setContent(\`

hello

\`); + await page.setContent(\`

hello

bye

\`); await expect(page.locator('body')).toMatchAriaSnapshot(\` - - heading "world" + - heading "hello" [level=1] ++ - heading "bye" [level=2] \`); }); From e24780f825c1f18140384b95b563958d2f956e0a Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Fri, 15 Nov 2024 12:47:25 -0800 Subject: [PATCH 12/38] chore: unshift shortest whitespace prefix only (#33618) --- .../playwright/src/matchers/toMatchAriaSnapshot.ts | 1 - tests/page/page-aria-snapshot.spec.ts | 1 - tests/page/to-match-aria-snapshot.spec.ts | 14 ++++++++++++++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/packages/playwright/src/matchers/toMatchAriaSnapshot.ts b/packages/playwright/src/matchers/toMatchAriaSnapshot.ts index f3e7d47a6e..0bb600f7b0 100644 --- a/packages/playwright/src/matchers/toMatchAriaSnapshot.ts +++ b/packages/playwright/src/matchers/toMatchAriaSnapshot.ts @@ -126,7 +126,6 @@ function unshift(snapshot: string): string { const match = line.match(/^(\s*)/); if (match && match[1].length < whitespacePrefixLength) whitespacePrefixLength = match[1].length; - break; } return lines.filter(t => t.trim()).map(line => line.substring(whitespacePrefixLength)).join('\n'); } diff --git a/tests/page/page-aria-snapshot.spec.ts b/tests/page/page-aria-snapshot.spec.ts index a7937e9be5..fe0de8cb9e 100644 --- a/tests/page/page-aria-snapshot.spec.ts +++ b/tests/page/page-aria-snapshot.spec.ts @@ -27,7 +27,6 @@ function unshift(snapshot: string): string { const match = line.match(/^(\s*)/); if (match && match[1].length < whitespacePrefixLength) whitespacePrefixLength = match[1].length; - break; } return lines.filter(t => t.trim()).map(line => line.substring(whitespacePrefixLength)).join('\n'); } diff --git a/tests/page/to-match-aria-snapshot.spec.ts b/tests/page/to-match-aria-snapshot.spec.ts index 05a02f369b..3961d6ad0a 100644 --- a/tests/page/to-match-aria-snapshot.spec.ts +++ b/tests/page/to-match-aria-snapshot.spec.ts @@ -659,3 +659,17 @@ test('should parse attributes', async ({ page }) => { `); } }); + +test('should not unshift actual template text', async ({ page }) => { + await page.setContent(` +

title

+

title 2

+ `); + const error = await expect(page.locator('body')).toMatchAriaSnapshot(` + - heading "title" [level=1] + - heading "title 2" [level=1] + `, { timeout: 1000 }).catch(e => e); + expect(stripAnsi(error.message)).toContain(` + - heading "title" [level=1] +- heading "title 2" [level=1]`); +}); From b91f609b14bd18c01e8a81d1daadf1423d6ecfa3 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Fri, 15 Nov 2024 22:25:26 +0100 Subject: [PATCH 13/38] chore: fix react attribute casing in TestErrorView (#33633) --- packages/html-reporter/src/testErrorView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/html-reporter/src/testErrorView.tsx b/packages/html-reporter/src/testErrorView.tsx index d63f5d7945..ea402106a8 100644 --- a/packages/html-reporter/src/testErrorView.tsx +++ b/packages/html-reporter/src/testErrorView.tsx @@ -25,7 +25,7 @@ export const TestErrorView: React.FC<{ testId?: string; }> = ({ error, testId }) => { const html = React.useMemo(() => ansiErrorToHtml(error), [error]); - return
; + return
; }; export const TestScreenshotErrorView: React.FC<{ From 44cd1d03cc9faa31eba58111297fc1cf7828808e Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Fri, 15 Nov 2024 13:43:00 -0800 Subject: [PATCH 14/38] chore: highlight edited locator while recording (#33632) --- .../src/server/injected/recorder/recorder.ts | 29 ++++---- packages/recorder/src/recorder.tsx | 8 +-- tests/library/debug-controller.spec.ts | 5 +- .../inspector/cli-codegen-aria.spec.ts | 9 +-- .../cli-codegen-pick-locator.spec.ts | 69 +++++++++++++++++++ 5 files changed, 93 insertions(+), 27 deletions(-) create mode 100644 tests/library/inspector/cli-codegen-pick-locator.spec.ts diff --git a/packages/playwright-core/src/server/injected/recorder/recorder.ts b/packages/playwright-core/src/server/injected/recorder/recorder.ts index 1828af3bd0..b905ba8548 100644 --- a/packages/playwright-core/src/server/injected/recorder/recorder.ts +++ b/packages/playwright-core/src/server/injected/recorder/recorder.ts @@ -1018,7 +1018,7 @@ export class Recorder { private _listeners: (() => void)[] = []; private _currentTool: RecorderTool; private _tools: Record; - private _actionSelectorModel: HighlightModel | null = null; + private _lastHighlightedSelector: string | undefined = undefined; private _lastHighlightedAriaTemplateJSON: string = 'undefined'; readonly highlight: Highlight; readonly overlay: Overlay | undefined; @@ -1129,12 +1129,12 @@ export class Recorder { this._switchCurrentTool(); this.overlay?.setUIState(state); - // Race or scroll. - if (this._actionSelectorModel?.selector && !this._actionSelectorModel?.elements.length && !this._lastHighlightedAriaTemplateJSON) - this._actionSelectorModel = null; - - if (state.actionSelector && state.actionSelector !== this._actionSelectorModel?.selector) - this._actionSelectorModel = querySelector(this.injectedScript, state.actionSelector, this.document); + let highlight: HighlightModel | 'clear' | 'noop' = 'noop'; + if (state.actionSelector !== this._lastHighlightedSelector) { + this._lastHighlightedSelector = state.actionSelector; + const model = state.actionSelector ? querySelector(this.injectedScript, state.actionSelector, this.document) : null; + highlight = model?.elements.length ? model : 'clear'; + } const ariaTemplateJSON = JSON.stringify(state.ariaTemplate); if (this._lastHighlightedAriaTemplateJSON !== ariaTemplateJSON) { @@ -1142,16 +1142,15 @@ export class Recorder { const template = state.ariaTemplate ? this.injectedScript.utils.parseYamlTemplate(state.ariaTemplate) : undefined; const elements = template ? this.injectedScript.getAllByAria(this.document, template) : []; if (elements.length) - this._actionSelectorModel = { elements }; + highlight = { elements }; else - this._actionSelectorModel = null; + highlight = 'clear'; } - if (!state.actionSelector && !state.ariaTemplate) - this._actionSelectorModel = null; - - if (this.state.mode === 'none' || this.state.mode === 'standby') - this.updateHighlight(this._actionSelectorModel, false); + if (highlight === 'clear') + this.clearHighlight(); + else if (highlight !== 'noop') + this.updateHighlight(highlight, false); } clearHighlight() { @@ -1266,6 +1265,8 @@ export class Recorder { private _onScroll(event: Event) { if (!event.isTrusted) return; + this._lastHighlightedSelector = undefined; + this._lastHighlightedAriaTemplateJSON = 'undefined'; this.highlight.hideActionPoint(); this._currentTool.onScroll?.(event); } diff --git a/packages/recorder/src/recorder.tsx b/packages/recorder/src/recorder.tsx index 50fc5174ec..f98682af89 100644 --- a/packages/recorder/src/recorder.tsx +++ b/packages/recorder/src/recorder.tsx @@ -67,6 +67,7 @@ export const Recorder: React.FC = ({ const language = source.language; setLocator(asLocator(language, elementInfo.selector)); setAriaSnapshot(elementInfo.ariaSnapshot); + setAriaSnapshotErrors([]); if (userGesture && selectedTab !== 'locator' && selectedTab !== 'aria') setSelectedTab('locator'); @@ -122,9 +123,6 @@ export const Recorder: React.FC = ({ if (!errors.length) window.dispatch({ event: 'highlightRequested', params: { ariaTemplate: fragment } }); }, [mode]); - const isRecording = mode === 'recording' || mode === 'recording-inspecting'; - const locatorPlaceholder = isRecording ? '// Unavailable while recording' : (locator ? undefined : '// Pick element or type locator'); - const ariaPlaceholder = isRecording ? '# Unavailable while recording' : (ariaSnapshot ? undefined : '# Pick element or type snapshot'); return
@@ -191,7 +189,7 @@ export const Recorder: React.FC = ({ { id: 'locator', title: 'Locator', - render: () => + render: () => }, { id: 'log', @@ -201,7 +199,7 @@ export const Recorder: React.FC = ({ { id: 'aria', title: 'Aria snapshot', - render: () => + render: () => }, ]} selectedTab={selectedTab} diff --git a/tests/library/debug-controller.spec.ts b/tests/library/debug-controller.spec.ts index 528b4fcec8..6050d4e645 100644 --- a/tests/library/debug-controller.spec.ts +++ b/tests/library/debug-controller.spec.ts @@ -31,8 +31,9 @@ type Fixtures = { }; const test = baseTest.extend({ - wsEndpoint: async ({ }, use) => { - process.env.PW_DEBUG_CONTROLLER_HEADLESS = '1'; + wsEndpoint: async ({ headless }, use) => { + if (headless) + process.env.PW_DEBUG_CONTROLLER_HEADLESS = '1'; const server = new PlaywrightServer({ mode: 'extension', path: '/' + createGuid(), maxConnections: Number.MAX_VALUE, enableSocksProxy: false }); const wsEndpoint = await server.listen(); await use(wsEndpoint); diff --git a/tests/library/inspector/cli-codegen-aria.spec.ts b/tests/library/inspector/cli-codegen-aria.spec.ts index 6fe75209eb..820dab7c14 100644 --- a/tests/library/inspector/cli-codegen-aria.spec.ts +++ b/tests/library/inspector/cli-codegen-aria.spec.ts @@ -64,11 +64,10 @@ test.describe(() => { test('should inspect aria snapshot', async ({ openRecorder }) => { const { recorder } = await openRecorder(); await recorder.setContentAndWait(`
`); - await recorder.recorderPage.getByRole('button', { name: 'Record' }).click(); await recorder.page.click('x-pw-tool-item.pick-locator'); await recorder.page.hover('button'); await recorder.trustedClick(); - await recorder.recorderPage.getByRole('tab', { name: 'Aria snapshot ' }).click(); + await recorder.recorderPage.getByRole('tab', { name: 'Aria snapshot' }).click(); await expect(recorder.recorderPage.locator('.tab-aria .CodeMirror')).toMatchAriaSnapshot(` - textbox - text: '- button "Submit"' @@ -85,12 +84,11 @@ test.describe(() => { const submitButton = recorder.page.getByRole('button', { name: 'Submit' }); const cancelButton = recorder.page.getByRole('button', { name: 'Cancel' }); - await recorder.recorderPage.getByRole('button', { name: 'Record' }).click(); await recorder.page.click('x-pw-tool-item.pick-locator'); await submitButton.hover(); await recorder.trustedClick(); - await recorder.recorderPage.getByRole('tab', { name: 'Aria snapshot ' }).click(); + await recorder.recorderPage.getByRole('tab', { name: 'Aria snapshot' }).click(); await expect(recorder.recorderPage.locator('.tab-aria .CodeMirror')).toMatchAriaSnapshot(` - text: '- button "Submit"' `); @@ -128,13 +126,12 @@ test.describe(() => { `); const submitButton = recorder.page.getByRole('button', { name: 'Submit' }); - await recorder.recorderPage.getByRole('button', { name: 'Record' }).click(); await recorder.page.click('x-pw-tool-item.pick-locator'); await submitButton.hover(); await recorder.trustedClick(); - await recorder.recorderPage.getByRole('tab', { name: 'Aria snapshot ' }).click(); + await recorder.recorderPage.getByRole('tab', { name: 'Aria snapshot' }).click(); await expect(recorder.recorderPage.locator('.tab-aria .CodeMirror')).toMatchAriaSnapshot(` - text: '- button "Submit"' `); diff --git a/tests/library/inspector/cli-codegen-pick-locator.spec.ts b/tests/library/inspector/cli-codegen-pick-locator.spec.ts new file mode 100644 index 0000000000..53d106b4c9 --- /dev/null +++ b/tests/library/inspector/cli-codegen-pick-locator.spec.ts @@ -0,0 +1,69 @@ +/** + * 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 { test, expect } from './inspectorTest'; +import { roundBox } from '../../page/pageTest'; + +test.describe(() => { + test.skip(({ mode }) => mode !== 'default'); + test.skip(({ trace, codegenMode }) => trace === 'on' && codegenMode === 'trace-events'); + + test('should inspect locator', async ({ openRecorder }) => { + const { recorder } = await openRecorder(); + await recorder.setContentAndWait(`
`); + await recorder.page.click('x-pw-tool-item.pick-locator'); + await recorder.page.hover('button'); + await recorder.trustedClick(); + await recorder.recorderPage.getByRole('tab', { name: 'Locator' }).click(); + await expect(recorder.recorderPage.locator('.tab-locator .CodeMirror')).toMatchAriaSnapshot(` + - text: "getByRole('button', { name: 'Submit' })" + `); + }); + + test('should update locator highlight', async ({ openRecorder }) => { + const { recorder } = await openRecorder(); + await recorder.setContentAndWait(`
+ + +
`); + + const submitButton = recorder.page.getByRole('button', { name: 'Submit' }); + const cancelButton = recorder.page.getByRole('button', { name: 'Cancel' }); + + await recorder.recorderPage.getByRole('button', { name: 'Record' }).click(); + + await recorder.page.click('x-pw-tool-item.pick-locator'); + await submitButton.hover(); + await recorder.trustedClick(); + await recorder.recorderPage.getByRole('tab', { name: 'Locator' }).click(); + await expect(recorder.recorderPage.locator('.tab-locator .CodeMirror')).toMatchAriaSnapshot(` + - text: "getByRole('button', { name: 'Submit' })" + `); + + await recorder.recorderPage.locator('.tab-locator .CodeMirror').click(); + for (let i = 0; i < `Submit' })`.length; i++) + await recorder.recorderPage.keyboard.press('Backspace'); + + { + // Different button. + await recorder.recorderPage.locator('.tab-locator .CodeMirror').pressSequentially(`Cancel' })`); + await expect(recorder.page.locator('x-pw-highlight')).toBeVisible(); + const box1 = roundBox(await cancelButton.boundingBox()); + const box2 = roundBox(await recorder.page.locator('x-pw-highlight').boundingBox()); + expect(box1).toEqual(box2); + } + }); +}); From d1272558811949b495ca834eba728accfc3bb5f2 Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Fri, 15 Nov 2024 13:48:43 -0800 Subject: [PATCH 15/38] chore: add AriaSnapshot internal type (#33631) --- .../src/server/injected/ariaSnapshot.ts | 48 +++-- .../src/server/injected/injectedScript.ts | 24 ++- .../playwright-core/src/server/recorder.ts | 4 + .../src/server/recorder/chat.ts | 184 ++++++++++++++++++ .../src/server/recorder/contextRecorder.ts | 4 + .../playwright-core/src/utils/debugLogger.ts | 3 +- 6 files changed, 250 insertions(+), 17 deletions(-) create mode 100644 packages/playwright-core/src/server/recorder/chat.ts diff --git a/packages/playwright-core/src/server/injected/ariaSnapshot.ts b/packages/playwright-core/src/server/injected/ariaSnapshot.ts index b2352a1b9c..d541646f0e 100644 --- a/packages/playwright-core/src/server/injected/ariaSnapshot.ts +++ b/packages/playwright-core/src/server/injected/ariaSnapshot.ts @@ -20,15 +20,36 @@ import { escapeRegExp, longestCommonSubstring } from '@isomorphic/stringUtils'; import { yamlEscapeKeyIfNeeded, yamlEscapeValueIfNeeded } from './yaml'; import type { AriaProps, AriaRole, AriaTemplateNode, AriaTemplateRoleNode, AriaTemplateTextNode } from '@isomorphic/ariaSnapshot'; -type AriaNode = AriaProps & { +export type AriaNode = AriaProps & { role: AriaRole | 'fragment'; name: string; children: (AriaNode | string)[]; element: Element; }; -export function generateAriaTree(rootElement: Element): AriaNode { +export type AriaSnapshot = { + root: AriaNode; + elements: Map; + ids: Map; +}; + +export function generateAriaTree(rootElement: Element): AriaSnapshot { const visited = new Set(); + + const snapshot: AriaSnapshot = { + root: { role: 'fragment', name: '', children: [], element: rootElement }, + elements: new Map(), + ids: new Map(), + }; + + const addElement = (element: Element) => { + const id = snapshot.elements.size + 1; + snapshot.elements.set(id, element); + snapshot.ids.set(element, id); + }; + + addElement(rootElement); + const visit = (ariaNode: AriaNode, node: Node) => { if (visited.has(node)) return; @@ -58,6 +79,7 @@ export function generateAriaTree(rootElement: Element): AriaNode { } } + addElement(element); const childAriaNode = toAriaNode(element); if (childAriaNode) ariaNode.children.push(childAriaNode); @@ -100,15 +122,14 @@ export function generateAriaTree(rootElement: Element): AriaNode { } roleUtils.beginAriaCaches(); - const ariaRoot: AriaNode = { role: 'fragment', name: '', children: [], element: rootElement }; try { - visit(ariaRoot, rootElement); + visit(snapshot.root, rootElement); } finally { roleUtils.endAriaCaches(); } - normalizeStringChildren(ariaRoot); - return ariaRoot; + normalizeStringChildren(snapshot.root); + return snapshot; } function toAriaNode(element: Element): AriaNode | null { @@ -143,10 +164,6 @@ function toAriaNode(element: Element): AriaNode | null { return result; } -export function renderedAriaTree(rootElement: Element, options?: { mode?: 'raw' | 'regex' }): string { - return renderAriaTree(generateAriaTree(rootElement), options); -} - function normalizeStringChildren(rootA11yNode: AriaNode) { const flushChildren = (buffer: string[], normalizedChildren: (AriaNode | string)[]) => { if (!buffer.length) @@ -203,7 +220,7 @@ export type MatcherReceived = { }; export function matchesAriaTree(rootElement: Element, template: AriaTemplateNode): { matches: AriaNode[], received: MatcherReceived } { - const root = generateAriaTree(rootElement); + const root = generateAriaTree(rootElement).root; const matches = matchesNodeDeep(root, template, false); return { matches, @@ -215,7 +232,7 @@ export function matchesAriaTree(rootElement: Element, template: AriaTemplateNode } export function getAllByAria(rootElement: Element, template: AriaTemplateNode): Element[] { - const root = generateAriaTree(rootElement); + const root = generateAriaTree(rootElement).root; const matches = matchesNodeDeep(root, template, true); return matches.map(n => n.element); } @@ -285,7 +302,7 @@ function matchesNodeDeep(root: AriaNode, template: AriaTemplateNode, collectAll: return results; } -export function renderAriaTree(ariaNode: AriaNode, options?: { mode?: 'raw' | 'regex' }): string { +export function renderAriaTree(ariaNode: AriaNode, options?: { mode?: 'raw' | 'regex', ids?: Map }): string { const lines: string[] = []; const includeText = options?.mode === 'regex' ? textContributesInfo : () => true; const renderString = options?.mode === 'regex' ? convertToBestGuessRegex : (str: string) => str; @@ -324,6 +341,11 @@ export function renderAriaTree(ariaNode: AriaNode, options?: { mode?: 'raw' | 'r key += ` [pressed]`; if (ariaNode.selected === true) key += ` [selected]`; + if (options?.ids) { + const id = options?.ids.get(ariaNode.element); + if (id) + key += ` [id=${id}]`; + } const escapedKey = indent + '- ' + yamlEscapeKeyIfNeeded(key); if (!ariaNode.children.length) { diff --git a/packages/playwright-core/src/server/injected/injectedScript.ts b/packages/playwright-core/src/server/injected/injectedScript.ts index d74a1c1482..7c235b700f 100644 --- a/packages/playwright-core/src/server/injected/injectedScript.ts +++ b/packages/playwright-core/src/server/injected/injectedScript.ts @@ -34,7 +34,8 @@ import { kLayoutSelectorNames, type LayoutSelectorName, layoutSelectorScore } fr import { asLocator } from '../../utils/isomorphic/locatorGenerators'; import type { Language } from '../../utils/isomorphic/locatorGenerators'; import { cacheNormalizedWhitespaces, normalizeWhiteSpace, trimStringWithEllipsis } from '../../utils/isomorphic/stringUtils'; -import { matchesAriaTree, renderedAriaTree, getAllByAria } from './ariaSnapshot'; +import { matchesAriaTree, getAllByAria, generateAriaTree, renderAriaTree } from './ariaSnapshot'; +import type { AriaNode, AriaSnapshot } from './ariaSnapshot'; import type { AriaTemplateNode } from '@isomorphic/ariaSnapshot'; import { parseYamlTemplate } from '@isomorphic/ariaSnapshot'; @@ -215,10 +216,27 @@ export class InjectedScript { return new Set(result.map(r => r.element)); } - ariaSnapshot(node: Node, options?: { mode?: 'raw' | 'regex' }): string { + ariaSnapshot(node: Node, options?: { mode?: 'raw' | 'regex', id?: boolean }): string { if (node.nodeType !== Node.ELEMENT_NODE) throw this.createStacklessError('Can only capture aria snapshot of Element nodes.'); - return renderedAriaTree(node as Element, options); + const ariaSnapshot = generateAriaTree(node as Element); + return renderAriaTree(ariaSnapshot.root, options); + } + + ariaSnapshotAsObject(node: Node): AriaSnapshot { + return generateAriaTree(node as Element); + } + + ariaSnapshotElement(snapshot: AriaSnapshot, elementId: number): Element | null { + return snapshot.elements.get(elementId) || null; + } + + renderAriaTree(ariaNode: AriaNode, options?: { mode?: 'raw' | 'regex', id?: boolean}): string { + return renderAriaTree(ariaNode, options); + } + + renderAriaSnapshotWithIds(ariaSnapshot: AriaSnapshot): string { + return renderAriaTree(ariaSnapshot.root, { ids: ariaSnapshot.ids }); } getAllByAria(document: Document, template: AriaTemplateNode): Element[] { diff --git a/packages/playwright-core/src/server/recorder.ts b/packages/playwright-core/src/server/recorder.ts index 13dd3829b1..16f9d791e1 100644 --- a/packages/playwright-core/src/server/recorder.ts +++ b/packages/playwright-core/src/server/recorder.ts @@ -132,6 +132,10 @@ export class Recorder implements InstrumentationListener, IRecorder { this._contextRecorder.clearScript(); return; } + if (data.event === 'runTask') { + this._contextRecorder.runTask(data.params.task); + return; + } }); await Promise.all([ diff --git a/packages/playwright-core/src/server/recorder/chat.ts b/packages/playwright-core/src/server/recorder/chat.ts new file mode 100644 index 0000000000..5b3917c735 --- /dev/null +++ b/packages/playwright-core/src/server/recorder/chat.ts @@ -0,0 +1,184 @@ +/** + * 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 { WebSocketTransport } from '../transport'; +import type { ConnectionTransport, ProtocolResponse } from '../transport'; + +export type ChatMessage = { + content: string; + user: 'user' | 'assistant'; +}; + +export class Chat { + private _history: ChatMessage[] = []; + private _connectionPromise: Promise | undefined; + private _chatSinks = new Map void>(); + private _wsEndpoint: string; + + constructor(wsEndpoint: string) { + this._wsEndpoint = wsEndpoint; + } + + clearHistory() { + this._history = []; + } + + async post(prompt: string): Promise { + await this._append('user', prompt); + let text = await asString(await this._post()); + if (text.startsWith('```json') && text.endsWith('```')) + text = text.substring('```json'.length, text.length - '```'.length); + for (let i = 0; i < 3; ++i) { + try { + return JSON.parse(text); + } catch (e) { + await this._append('user', String(e)); + } + } + throw new Error('Failed to parse response: ' + text); + } + + private async _append(user: ChatMessage['user'], content: string) { + this._history.push({ user, content }); + } + + private async _connection(): Promise { + if (!this._connectionPromise) { + this._connectionPromise = WebSocketTransport.connect(undefined, this._wsEndpoint).then(transport => { + return new Connection(transport, (method, params) => this._dispatchEvent(method, params), () => {}); + }); + } + return this._connectionPromise; + } + + private _dispatchEvent(method: string, params: any) { + if (method === 'chatChunk') { + const { chatId, chunk } = params; + const chunkSink = this._chatSinks.get(chatId)!; + chunkSink(chunk); + if (!chunk) + this._chatSinks.delete(chatId); + } + } + + private async _post(): Promise> { + const connection = await this._connection(); + const result = await connection.send('chat', { history: this._history }); + const { chatId } = result; + const { iterable, addChunk } = iterablePump(); + this._chatSinks.set(chatId, addChunk); + return iterable; + } +} + +export async function asString(stream: AsyncIterable): Promise { + let result = ''; + for await (const chunk of stream) + result += chunk; + return result; +} + +type ChunkIterator = { + iterable: AsyncIterable; + addChunk: (chunk: string) => void; +}; + +function iterablePump(): ChunkIterator { + let controller: ReadableStreamDefaultController; + const stream = new ReadableStream({ start: c => controller = c }); + + const iterable = (async function* () { + const reader = stream.getReader(); + while (true) { + const { done, value } = await reader.read(); + if (done) + break; + yield value!; + } + })(); + + return { + iterable, + addChunk: chunk => { + if (chunk) + controller.enqueue(chunk); + else + controller.close(); + } + }; +} + +class Connection { + private readonly _transport: ConnectionTransport; + private _lastId = 0; + private _closed = false; + private _pending = new Map void; reject: (error: any) => void; }>(); + private _onEvent: (method: string, params: any) => void; + private _onClose: () => void; + + constructor(transport: ConnectionTransport, onEvent: (method: string, params: any) => void, onClose: () => void) { + this._transport = transport; + this._onEvent = onEvent; + this._onClose = onClose; + this._transport.onmessage = this._dispatchMessage.bind(this); + this._transport.onclose = this._close.bind(this); + } + + send(method: string, params: any): Promise { + const id = this._lastId++; + const message = { id, method, params }; + this._transport.send(message); + return new Promise((resolve, reject) => { + this._pending.set(id, { resolve, reject }); + }); + } + + private _dispatchMessage(message: ProtocolResponse) { + if (message.id === undefined) { + this._onEvent(message.method!, message.params); + return; + } + + const callback = this._pending.get(message.id); + this._pending.delete(message.id); + if (!callback) + return; + + if (message.error) { + callback.reject(new Error(message.error.message)); + return; + } + callback.resolve(message.result); + } + + _close() { + this._closed = true; + this._transport.onmessage = undefined; + this._transport.onclose = undefined; + for (const { reject } of this._pending.values()) + reject(new Error('Connection closed')); + this._onClose(); + } + + isClosed() { + return this._closed; + } + + close() { + if (!this._closed) + this._transport.close(); + } +} diff --git a/packages/playwright-core/src/server/recorder/contextRecorder.ts b/packages/playwright-core/src/server/recorder/contextRecorder.ts index 933a036233..d7a3c908e8 100644 --- a/packages/playwright-core/src/server/recorder/contextRecorder.ts +++ b/packages/playwright-core/src/server/recorder/contextRecorder.ts @@ -208,6 +208,10 @@ export class ContextRecorder extends EventEmitter { } } + runTask(task: string): void { + // TODO: implement + } + private _describeMainFrame(page: Page): actions.FrameDescription { return { pageAlias: this._pageAliases.get(page)!, diff --git a/packages/playwright-core/src/utils/debugLogger.ts b/packages/playwright-core/src/utils/debugLogger.ts index a5196da896..d50180a2ed 100644 --- a/packages/playwright-core/src/utils/debugLogger.ts +++ b/packages/playwright-core/src/utils/debugLogger.ts @@ -29,7 +29,8 @@ const debugLoggerColorMap = { 'channel': 33, // blue 'server': 45, // cyan 'server:channel': 34, // green - 'server:metadata': 33, // blue + 'server:metadata': 33, // blue, + 'recorder': 45, // cyan }; export type LogName = keyof typeof debugLoggerColorMap; From c36b5a6059ee46a8c0e59b85d42fd99f6b85ec8a Mon Sep 17 00:00:00 2001 From: Rui Figueira Date: Fri, 15 Nov 2024 22:44:27 +0000 Subject: [PATCH 16/38] =?UTF-8?q?fix:=20ensure=20toMatchAriaSnapshot=20is?= =?UTF-8?q?=20properly=20commented=20in=20javascript=20c=E2=80=A6=20(#3359?= =?UTF-8?q?3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/server/codegen/javascript.ts | 6 ++-- .../inspector/cli-codegen-aria.spec.ts | 31 +++++++++++++++++++ 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/packages/playwright-core/src/server/codegen/javascript.ts b/packages/playwright-core/src/server/codegen/javascript.ts index 428ca493f8..e5f72ce122 100644 --- a/packages/playwright-core/src/server/codegen/javascript.ts +++ b/packages/playwright-core/src/server/codegen/javascript.ts @@ -117,8 +117,10 @@ export class JavaScriptLanguageGenerator implements LanguageGenerator { const assertion = action.value ? `toHaveValue(${quote(action.value)})` : `toBeEmpty()`; return `${this._isTest ? '' : '// '}await expect(${subject}.${this._asLocator(action.selector)}).${assertion};`; } - case 'assertSnapshot': - return `${this._isTest ? '' : '// '}await expect(${subject}.${this._asLocator(action.selector)}).toMatchAriaSnapshot(${quoteMultiline(action.snapshot)});`; + case 'assertSnapshot': { + const commentIfNeeded = this._isTest ? '' : '// '; + return `${commentIfNeeded}await expect(${subject}.${this._asLocator(action.selector)}).toMatchAriaSnapshot(${quoteMultiline(action.snapshot, `${commentIfNeeded} `)});`; + } } } diff --git a/tests/library/inspector/cli-codegen-aria.spec.ts b/tests/library/inspector/cli-codegen-aria.spec.ts index 820dab7c14..093ab0bbe5 100644 --- a/tests/library/inspector/cli-codegen-aria.spec.ts +++ b/tests/library/inspector/cli-codegen-aria.spec.ts @@ -141,4 +141,35 @@ test.describe(() => { // 3 highlighted tokens. await expect(recorder.recorderPage.locator('.source-line-error-underline')).toHaveCount(3); }); + + test('should generate valid javascript with multiline snapshot assertion', async ({ openRecorder }) => { + const { recorder } = await openRecorder(); + // set width and height to 100% to ensure click is outside of the list + await recorder.setContentAndWait(`
  • item 1
  • item 2
`); + + await recorder.page.click('x-pw-tool-item.snapshot'); + await recorder.page.hover('body'); + await recorder.trustedClick(); + + // playwright tests assertions are uncommented + await expect.poll(() => + recorder.text('Playwright Test')).toContain([ + ` await expect(page.locator('body')).toMatchAriaSnapshot(\``, + ` - list:`, + ` - listitem: item 1`, + ` - listitem: item 2`, + ` \`);`, + ].join('\n')); + + // non-test javascript has commented assertions + await expect.poll(() => + recorder.text('JavaScript')).toContain([ + ` // await expect(page.locator('body')).toMatchAriaSnapshot(\``, + ` // - list:`, + ` // - listitem: item 1`, + ` // - listitem: item 2`, + ` // \`);`, + ].join('\n')); + + }); }); From 37ce53945ebfab6f808308f46aa0ad8563cb2983 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Fri, 15 Nov 2024 23:45:25 +0100 Subject: [PATCH 17/38] fix(ui-mode): fix issue when updating state while rendering (#33634) --- .../trace-viewer/src/ui/uiModeTestListView.tsx | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/packages/trace-viewer/src/ui/uiModeTestListView.tsx b/packages/trace-viewer/src/ui/uiModeTestListView.tsx index a6cb82fb8a..1d6ddd850c 100644 --- a/packages/trace-viewer/src/ui/uiModeTestListView.tsx +++ b/packages/trace-viewer/src/ui/uiModeTestListView.tsx @@ -99,11 +99,17 @@ export const TestListView: React.FC<{ setSelectedTreeItemId(selectedTreeItem.id); }, [runningState, setSelectedTreeItemId, testTree, collapseAllCount, setCollapseAllCount, requestedCollapseAllCount, expandAllCount, setExpandAllCount, requestedExpandAllCount, treeState, setTreeState]); - // Compute selected item. - const { selectedTreeItem } = React.useMemo(() => { + // Compute selected item + const selectedTreeItem = React.useMemo(() => { + if (!selectedTreeItemId) + return undefined; + return testTree.treeItemById(selectedTreeItemId); + }, [selectedTreeItemId, testTree]); + + // Handle selection effects separately + React.useEffect(() => { if (!testModel) - return { selectedTreeItem: undefined }; - const selectedTreeItem = selectedTreeItemId ? testTree.treeItemById(selectedTreeItemId) : undefined; + return; const testFile = itemLocation(selectedTreeItem, testModel); let selectedTest: reporterTypes.TestCase | undefined; if (selectedTreeItem?.kind === 'test') @@ -111,8 +117,7 @@ export const TestListView: React.FC<{ else if (selectedTreeItem?.kind === 'case' && selectedTreeItem.tests.length === 1) selectedTest = selectedTreeItem.tests[0]; onItemSelected({ treeItem: selectedTreeItem, testCase: selectedTest, testFile }); - return { selectedTreeItem }; - }, [onItemSelected, selectedTreeItemId, testModel, testTree]); + }, [testModel, selectedTreeItem, onItemSelected]); // Update watch all. React.useEffect(() => { From 508021362dd3d9e42f3122c3120eecc1608d56f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Greffier?= Date: Fri, 15 Nov 2024 23:45:54 +0100 Subject: [PATCH 18/38] fix minor typos in "Getting Started" (#33613) --- docs/src/ci-intro.md | 2 +- docs/src/intro-js.md | 2 +- docs/src/running-tests-js.md | 6 +++--- docs/src/trace-viewer-intro-js.md | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/src/ci-intro.md b/docs/src/ci-intro.md index 9e0ee0eb4f..ed7e2208fc 100644 --- a/docs/src/ci-intro.md +++ b/docs/src/ci-intro.md @@ -21,7 +21,7 @@ Playwright tests can be run on any CI provider. This guide covers one way of run ## Introduction * langs: python, java, csharp -Playwright tests can be ran on any CI provider. In this section we will cover running tests on GitHub using GitHub actions. If you would like to see how to configure other CI providers check out our detailed doc on Continuous Integration. +Playwright tests can be run on any CI provider. In this section we will cover running tests on GitHub using GitHub actions. If you would like to see how to configure other CI providers check out our detailed doc on Continuous Integration. #### You will learn * langs: python, java, csharp diff --git a/docs/src/intro-js.md b/docs/src/intro-js.md index 8c29641bd9..321629662b 100644 --- a/docs/src/intro-js.md +++ b/docs/src/intro-js.md @@ -80,7 +80,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 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. Date: Fri, 15 Nov 2024 16:19:35 -0800 Subject: [PATCH 19/38] chore: add cm placeholder text (#33635) --- packages/recorder/src/recorder.tsx | 10 +++++----- packages/web/src/components/codeMirrorModule.tsx | 1 + packages/web/src/components/codeMirrorWrapper.css | 4 ++++ packages/web/src/components/codeMirrorWrapper.tsx | 8 ++++++-- tests/library/inspector/cli-codegen-aria.spec.ts | 6 +++--- 5 files changed, 19 insertions(+), 10 deletions(-) diff --git a/packages/recorder/src/recorder.tsx b/packages/recorder/src/recorder.tsx index f98682af89..a34131a2c8 100644 --- a/packages/recorder/src/recorder.tsx +++ b/packages/recorder/src/recorder.tsx @@ -27,7 +27,7 @@ import { CallLogView } from './callLog'; import './recorder.css'; import { asLocator } from '@isomorphic/locatorGenerators'; import { toggleTheme } from '@web/theme'; -import { copy } from '@web/uiUtils'; +import { copy, useSetting } from '@web/uiUtils'; import yaml from 'yaml'; import { parseAriaKey } from '@isomorphic/ariaSnapshot'; import type { AriaKeyError, ParsedYaml } from '@isomorphic/ariaSnapshot'; @@ -47,7 +47,7 @@ export const Recorder: React.FC = ({ }) => { const [selectedFileId, setSelectedFileId] = React.useState(); const [runningFileId, setRunningFileId] = React.useState(); - const [selectedTab, setSelectedTab] = React.useState('log'); + const [selectedTab, setSelectedTab] = useSetting('recorderPropertiesTab', 'log'); const [ariaSnapshot, setAriaSnapshot] = React.useState(); const [ariaSnapshotErrors, setAriaSnapshotErrors] = React.useState(); @@ -189,7 +189,7 @@ export const Recorder: React.FC = ({ { id: 'locator', title: 'Locator', - render: () => + render: () => }, { id: 'log', @@ -198,8 +198,8 @@ export const Recorder: React.FC = ({ }, { id: 'aria', - title: 'Aria snapshot', - render: () => + title: 'Aria', + render: () => }, ]} selectedTab={selectedTab} diff --git a/packages/web/src/components/codeMirrorModule.tsx b/packages/web/src/components/codeMirrorModule.tsx index e0a32772ff..f398032f02 100644 --- a/packages/web/src/components/codeMirrorModule.tsx +++ b/packages/web/src/components/codeMirrorModule.tsx @@ -24,6 +24,7 @@ import 'codemirror/mode/javascript/javascript'; import 'codemirror/mode/python/python'; import 'codemirror/mode/clike/clike'; import 'codemirror/mode/markdown/markdown'; +import 'codemirror/addon/display/placeholder'; import 'codemirror/addon/mode/simple'; import 'codemirror/mode/yaml/yaml'; diff --git a/packages/web/src/components/codeMirrorWrapper.css b/packages/web/src/components/codeMirrorWrapper.css index 8851b5445a..59a519005e 100644 --- a/packages/web/src/components/codeMirrorWrapper.css +++ b/packages/web/src/components/codeMirrorWrapper.css @@ -181,3 +181,7 @@ body.dark-mode .CodeMirror span.cm-type { text-decoration-color: var(--vscode-errorForeground); text-decoration-style: wavy; } + +.CodeMirror-placeholder { + color: var(--vscode-input-placeholderForeground) !important; +} diff --git a/packages/web/src/components/codeMirrorWrapper.tsx b/packages/web/src/components/codeMirrorWrapper.tsx index 9957559679..68f07704b1 100644 --- a/packages/web/src/components/codeMirrorWrapper.tsx +++ b/packages/web/src/components/codeMirrorWrapper.tsx @@ -46,6 +46,7 @@ export interface SourceProps { wrapLines?: boolean; onChange?: (text: string) => void; dataTestId?: string; + placeholder?: string; } export const CodeMirrorWrapper: React.FC = ({ @@ -62,6 +63,7 @@ export const CodeMirrorWrapper: React.FC = ({ wrapLines, onChange, dataTestId, + placeholder, }) => { const [measure, codemirrorElement] = useMeasure(); const [modulePromise] = React.useState>(import('./codeMirrorModule').then(m => m.default)); @@ -89,7 +91,8 @@ export const CodeMirrorWrapper: React.FC = ({ && mode === codemirrorRef.current.cm.getOption('mode') && !!readOnly === codemirrorRef.current.cm.getOption('readOnly') && lineNumbers === codemirrorRef.current.cm.getOption('lineNumbers') - && wrapLines === codemirrorRef.current.cm.getOption('lineWrapping')) { + && wrapLines === codemirrorRef.current.cm.getOption('lineWrapping') + && placeholder === codemirrorRef.current.cm.getOption('placeholder')) { // No need to re-create codemirror. return; } @@ -102,6 +105,7 @@ export const CodeMirrorWrapper: React.FC = ({ readOnly: !!readOnly, lineNumbers, lineWrapping: wrapLines, + placeholder, }); codemirrorRef.current = { cm }; if (isFocused) @@ -109,7 +113,7 @@ export const CodeMirrorWrapper: React.FC = ({ setCodemirror(cm); return cm; })(); - }, [modulePromise, codemirror, codemirrorElement, language, mimeType, linkify, lineNumbers, wrapLines, readOnly, isFocused]); + }, [modulePromise, codemirror, codemirrorElement, language, mimeType, linkify, lineNumbers, wrapLines, readOnly, isFocused, placeholder]); React.useEffect(() => { if (codemirrorRef.current) diff --git a/tests/library/inspector/cli-codegen-aria.spec.ts b/tests/library/inspector/cli-codegen-aria.spec.ts index 093ab0bbe5..354ca6495a 100644 --- a/tests/library/inspector/cli-codegen-aria.spec.ts +++ b/tests/library/inspector/cli-codegen-aria.spec.ts @@ -67,7 +67,7 @@ test.describe(() => { await recorder.page.click('x-pw-tool-item.pick-locator'); await recorder.page.hover('button'); await recorder.trustedClick(); - await recorder.recorderPage.getByRole('tab', { name: 'Aria snapshot' }).click(); + await recorder.recorderPage.getByRole('tab', { name: 'Aria' }).click(); await expect(recorder.recorderPage.locator('.tab-aria .CodeMirror')).toMatchAriaSnapshot(` - textbox - text: '- button "Submit"' @@ -88,7 +88,7 @@ test.describe(() => { await recorder.page.click('x-pw-tool-item.pick-locator'); await submitButton.hover(); await recorder.trustedClick(); - await recorder.recorderPage.getByRole('tab', { name: 'Aria snapshot' }).click(); + await recorder.recorderPage.getByRole('tab', { name: 'Aria' }).click(); await expect(recorder.recorderPage.locator('.tab-aria .CodeMirror')).toMatchAriaSnapshot(` - text: '- button "Submit"' `); @@ -131,7 +131,7 @@ test.describe(() => { await submitButton.hover(); await recorder.trustedClick(); - await recorder.recorderPage.getByRole('tab', { name: 'Aria snapshot' }).click(); + await recorder.recorderPage.getByRole('tab', { name: 'Aria' }).click(); await expect(recorder.recorderPage.locator('.tab-aria .CodeMirror')).toMatchAriaSnapshot(` - text: '- button "Submit"' `); From 46321e5bf25225762a375514063c55bda130ec9e Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Sat, 16 Nov 2024 07:56:33 -0800 Subject: [PATCH 20/38] chore: clear highlight when performing action (#33638) --- .../src/server/injected/recorder/recorder.ts | 7 +++++-- tests/library/inspector/cli-codegen-2.spec.ts | 11 ----------- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/packages/playwright-core/src/server/injected/recorder/recorder.ts b/packages/playwright-core/src/server/injected/recorder/recorder.ts index b905ba8548..ddfb0386d9 100644 --- a/packages/playwright-core/src/server/injected/recorder/recorder.ts +++ b/packages/playwright-core/src/server/injected/recorder/recorder.ts @@ -492,9 +492,10 @@ class RecordActionTool implements RecorderTool { return; const result = activeElement ? this._recorder.injectedScript.generateSelector(activeElement, { testIdAttributeName: this._recorder.state.testIdAttributeName }) : null; this._activeModel = result && result.selector ? result : null; - if (userGesture) + if (userGesture) { this._hoveredElement = activeElement as HTMLElement | null; - this._updateModelForHoveredElement(); + this._updateModelForHoveredElement(); + } } private _shouldIgnoreMouseEvent(event: MouseEvent): boolean { @@ -589,6 +590,8 @@ class RecordActionTool implements RecorderTool { } private _updateModelForHoveredElement() { + if (this._performingActions.size) + return; if (!this._hoveredElement || !this._hoveredElement.isConnected) { this._hoveredModel = null; this._hoveredElement = null; diff --git a/tests/library/inspector/cli-codegen-2.spec.ts b/tests/library/inspector/cli-codegen-2.spec.ts index e4aa0a0786..47afcc2fab 100644 --- a/tests/library/inspector/cli-codegen-2.spec.ts +++ b/tests/library/inspector/cli-codegen-2.spec.ts @@ -402,17 +402,6 @@ await page1.GotoAsync("about:blank?foo");`); await expect.poll(() => messages).toEqual(['mousedown', 'mouseup', 'click']); }); - test('should update hover model on action', async ({ openRecorder }) => { - const { page, recorder } = await openRecorder(); - - await recorder.setContentAndWait(``); - const [models] = await Promise.all([ - recorder.waitForActionPerformed(), - page.click('input') - ]); - expect(models.hovered).toBe('#checkbox'); - }); - test('should reset hover model on action when element detaches', async ({ openRecorder }) => { const { page, recorder } = await openRecorder(); From 82c77a5e9e14a119e2089e5ce849e980fcf1009e Mon Sep 17 00:00:00 2001 From: Rui Figueira Date: Mon, 18 Nov 2024 09:03:21 +0000 Subject: [PATCH 21/38] fix(ui-mode): prevent websocket connection leaks on reload (#33643) --- packages/trace-viewer/src/ui/uiModeView.tsx | 7 ++++-- .../playwright-test/ui-mode-test-run.spec.ts | 23 +++++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/packages/trace-viewer/src/ui/uiModeView.tsx b/packages/trace-viewer/src/ui/uiModeView.tsx index 69a5988641..23138b7f0b 100644 --- a/packages/trace-viewer/src/ui/uiModeView.tsx +++ b/packages/trace-viewer/src/ui/uiModeView.tsx @@ -109,7 +109,10 @@ export const UIModeView: React.FC<{}> = ({ const inputRef = React.useRef(null); const reloadTests = React.useCallback(() => { - setTestServerConnection(new TestServerConnection(new WebSocketTestServerTransport(wsURL))); + setTestServerConnection(prevConnection => { + prevConnection?.close(); + return new TestServerConnection(new WebSocketTestServerTransport(wsURL)); + }); }, []); // Load tests on startup. @@ -224,7 +227,7 @@ export const UIModeView: React.FC<{}> = ({ newFilter.set(projectSuite.title, !!selectedProjects?.includes(projectSuite.title)); } if (!selectedProjects && newFilter.size && ![...newFilter.values()].includes(true)) - newFilter.set(newFilter.entries().next().value[0], true); + newFilter.set(newFilter.entries().next().value![0], true); if (projectFilters.size !== newFilter.size || [...projectFilters].some(([k, v]) => newFilter.get(k) !== v)) setProjectFilters(newFilter); }, [projectFilters, testModel]); diff --git a/tests/playwright-test/ui-mode-test-run.spec.ts b/tests/playwright-test/ui-mode-test-run.spec.ts index e47bd8ff9b..3b2351083a 100644 --- a/tests/playwright-test/ui-mode-test-run.spec.ts +++ b/tests/playwright-test/ui-mode-test-run.spec.ts @@ -775,3 +775,26 @@ test('should respect --ignore-snapshots option', { - treeitem ${/\[icon-check\] snapshot/} `); }); + +test('should not leak websocket connections', { + annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/33641' } +}, async ({ runUITest }) => { + const { page } = await runUITest({ + 'a.test.ts': ` + import { test, expect } from '@playwright/test'; + test('test', async () => {}); + `, + }); + + const [ws1] = await Promise.all([ + page.waitForEvent('websocket'), + page.getByTitle('Reload').click(), + ]); + + await Promise.all([ + page.waitForEvent('websocket'), + page.getByTitle('Reload').click(), + ]); + + await expect.poll(() => ws1.isClosed()).toBe(true); +}); From 6fce5620e0c178b04c8bdd660275e39e0fc74d89 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Mon, 18 Nov 2024 10:21:28 +0100 Subject: [PATCH 22/38] fix(ui-mode): use onChange instead of onClick for { return
; diff --git a/packages/trace-viewer/src/ui/uiModeFiltersView.tsx b/packages/trace-viewer/src/ui/uiModeFiltersView.tsx index 314fce3a96..8ed0f47982 100644 --- a/packages/trace-viewer/src/ui/uiModeFiltersView.tsx +++ b/packages/trace-viewer/src/ui/uiModeFiltersView.tsx @@ -62,7 +62,7 @@ export const FiltersView: React.FC<{ {[...statusFilters.entries()].map(([status, value]) => { return