From ffd2e02aa3ba2cbaee8e9de01540b6ab66f1ce3b Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Sat, 4 Nov 2023 21:18:27 -0700 Subject: [PATCH 001/491] feat(recorder): various UX fixes (#27967) --- .../src/server/injected/recorder.ts | 245 ++++++++++-------- .../playwright-core/src/server/recorder.ts | 17 ++ .../src/server/recorder/recorderApp.ts | 22 +- packages/recorder/src/recorder.css | 6 +- packages/recorder/src/recorder.tsx | 29 ++- packages/recorder/src/recorderTypes.ts | 3 +- packages/trace-viewer/src/ui/snapshotTab.tsx | 1 + packages/web/src/components/toolbarButton.css | 9 + packages/web/src/components/toolbarButton.tsx | 6 + 9 files changed, 206 insertions(+), 132 deletions(-) diff --git a/packages/playwright-core/src/server/injected/recorder.ts b/packages/playwright-core/src/server/injected/recorder.ts index ac28dbe0be..334e8569a5 100644 --- a/packages/playwright-core/src/server/injected/recorder.ts +++ b/packages/playwright-core/src/server/injected/recorder.ts @@ -29,6 +29,7 @@ interface RecorderDelegate { recordAction?(action: actions.Action): Promise; setSelector?(selector: string): Promise; setMode?(mode: Mode): Promise; + setOverlayPosition?(position: { x: number, y: number }): Promise; highlightUpdated?(): void; } @@ -441,7 +442,7 @@ class RecordActionTool implements RecorderTool { class TextAssertionTool implements RecorderTool { private _selectionHighlight: HighlightModel | null = null; - private _inputIsFocused = false; + private _inputHighlight: HighlightModel | null = null; constructor(private _recorder: Recorder) { } @@ -457,36 +458,15 @@ class TextAssertionTool implements RecorderTool { disable() { this._recorder.injectedScript.document.designMode = 'off'; this._selectionHighlight = null; - this._inputIsFocused = false; + this._inputHighlight = null; } onClick(event: MouseEvent) { consumeEvent(event); - const target = this._recorder.deepEventTarget(event); - if (event.detail === 1 && ['INPUT', 'TEXTAREA'].includes(target.nodeName)) { - const highlight = generateSelector(this._recorder.injectedScript, target, { testIdAttributeName: this._recorder.state.testIdAttributeName }); - if (target.nodeName === 'INPUT' && ['checkbox', 'radio'].includes((target as HTMLInputElement).type.toLowerCase())) { - this._recorder.delegate.recordAction?.({ - name: 'assertChecked', - selector: highlight.selector, - signals: [], - // Interestingly, inputElement.checked is reversed inside this event handler. - checked: !(target as HTMLInputElement).checked, - }); - } else { - this._recorder.delegate.recordAction?.({ - name: 'assertValue', - selector: highlight.selector, - signals: [], - value: target.isContentEditable ? target.innerText : (target as HTMLInputElement).value, - }); - } - return; - } - const selection = this._recorder.document.getSelection(); - if (event.detail === 1 && selection && !selection.toString()) { + if (event.detail === 1 && selection && !selection.toString() && !this._inputHighlight) { + const target = this._recorder.deepEventTarget(event); selection.selectAllChildren(target); this._updateSelectionHighlight(); } @@ -496,14 +476,13 @@ class TextAssertionTool implements RecorderTool { const target = this._recorder.deepEventTarget(event); if (['INPUT', 'TEXTAREA'].includes(target.nodeName)) { this._recorder.injectedScript.window.getSelection()?.empty(); - this._selectionHighlight = generateSelector(this._recorder.injectedScript, target, { testIdAttributeName: this._recorder.state.testIdAttributeName }); - this._inputIsFocused = true; - this._recorder.updateHighlight(this._selectionHighlight, true, '#6fdcbd38'); + this._inputHighlight = generateSelector(this._recorder.injectedScript, target, { testIdAttributeName: this._recorder.state.testIdAttributeName }); + this._recorder.updateHighlight(this._inputHighlight, true, '#6fdcbd38'); consumeEvent(event); return; } - this._inputIsFocused = false; + this._inputHighlight = null; this._updateSelectionHighlight(); } @@ -522,13 +501,35 @@ class TextAssertionTool implements RecorderTool { onKeyDown(event: KeyboardEvent) { if (event.key === 'Escape') { this._resetSelectionAndHighlight(); + this._recorder.delegate.setMode?.('recording'); consumeEvent(event); return; } if (event.key === 'Enter') { const selection = this._recorder.document.getSelection(); - if (selection && this._selectionHighlight) { + + if (this._inputHighlight) { + const target = this._inputHighlight.elements[0] as HTMLInputElement; + if (target.nodeName === 'INPUT' && ['checkbox', 'radio'].includes(target.type.toLowerCase())) { + this._recorder.delegate.recordAction?.({ + name: 'assertChecked', + selector: this._inputHighlight.selector, + signals: [], + // Interestingly, inputElement.checked is reversed inside this event handler. + checked: !(target as HTMLInputElement).checked, + }); + this._recorder.delegate.setMode?.('recording'); + } else { + this._recorder.delegate.recordAction?.({ + name: 'assertValue', + selector: this._inputHighlight.selector, + signals: [], + value: target.value, + }); + this._recorder.delegate.setMode?.('recording'); + } + } else if (selection && this._selectionHighlight) { const selectedText = normalizeWhiteSpace(selection.toString()); const fullText = normalizeWhiteSpace(elementText(new Map(), this._selectionHighlight.elements[0]).full); this._recorder.delegate.recordAction?.({ @@ -538,6 +539,7 @@ class TextAssertionTool implements RecorderTool { text: selectedText, substring: fullText !== selectedText, }); + this._recorder.delegate.setMode?.('recording'); this._resetSelectionAndHighlight(); } consumeEvent(event); @@ -561,12 +563,13 @@ class TextAssertionTool implements RecorderTool { private _resetSelectionAndHighlight() { this._selectionHighlight = null; + this._inputHighlight = null; this._recorder.injectedScript.window.getSelection()?.empty(); this._recorder.updateHighlight(null, false); } private _updateSelectionHighlight() { - if (this._inputIsFocused) + if (this._inputHighlight) return; const selection = this._recorder.document.getSelection(); let highlight: HighlightModel | null = null; @@ -586,7 +589,9 @@ class TextAssertionTool implements RecorderTool { class Overlay { private _overlayElement: HTMLElement; - private _tools: Record; + private _recordToggle: HTMLElement; + private _pickLocatorToggle: HTMLElement; + private _assertToggle: HTMLElement; private _position: { x: number, y: number } = { x: 0, y: 0 }; private _dragState: { position: { x: number, y: number }, dragStart: { x: number, y: number } } | undefined; private _measure: { width: number, height: number } = { width: 0, height: 0 }; @@ -603,78 +608,77 @@ class Overlay { max-width: min-content; z-index: 2147483647; background: transparent; - cursor: grab; } x-pw-tools-list { - box-shadow: rgba(0, 0, 0, 0.1) 0px 0.25em 0.5em; + box-shadow: rgba(0, 0, 0, 0.1) 0px 5px 5px; backdrop-filter: blur(5px); background-color: hsla(0 0% 100% / .9); - font-family: 'Dank Mono', 'Operator Mono', Inconsolata, 'Fira Mono', - 'SF Mono', Monaco, 'Droid Sans Mono', 'Source Code Pro', monospace; + font-family: 'Dank Mono', 'Operator Mono', Inconsolata, 'Fira Mono', 'SF Mono', Monaco, 'Droid Sans Mono', 'Source Code Pro', monospace; display: flex; flex-direction: column; - margin: 1em; - padding: 0px; - border-radius: 2em; + margin: 10px; + padding: 3px 0; + border-radius: 17px; + } + + x-pw-drag-handle { + cursor: grab; + height: 2px; + margin: 5px 9px; + border-top: 1px solid rgb(86 86 86 / 90%); + border-bottom: 1px solid rgb(86 86 86 / 90%); + } + x-pw-drag-handle:active { + cursor: grabbing; } x-pw-tool-item { cursor: pointer; - height: 2.25em; - width: 2.25em; - margin: 0.05em 0.25em; - display: inline-flex; - align-items: center; - justify-content: center; - position: relative; + height: 28px; + width: 28px; + margin: 2px 4px; border-radius: 50%; } - x-pw-tool-item:first-child { - margin-top: 0.25em; - } - x-pw-tool-item:last-child { - margin-bottom: 0.25em; - } - x-pw-tool-item:hover { - background-color: hsl(0, 0%, 95%); - } - x-pw-tool-item.active { - background-color: hsl(0, 0%, 100%); + x-pw-tool-item:not(.disabled):hover { + background-color: hsl(0, 0%, 86%); } x-pw-tool-item > div { width: 100%; height: 100%; - background-color: black; -webkit-mask-repeat: no-repeat; -webkit-mask-position: center; -webkit-mask-size: 20px; mask-repeat: no-repeat; mask-position: center; - mask-size: 20px; + mask-size: 16px; + background-color: #3a3a3a; + } + x-pw-tool-item.disabled > div { + background-color: rgba(97, 97, 97, 0.5); + cursor: default; } x-pw-tool-item.active > div { - background-color: #ff4ca5; + background-color: #006ab1; } - x-pw-tool-item.none > div { - /* codicon: close */ - -webkit-mask-image: url("data:image/svg+xml;utf8,"); - mask-image: url("data:image/svg+xml;utf8,"); + x-pw-tool-item.record.active > div { + background-color: #a1260d; } - x-pw-tool-item.inspecting > div { - /* codicon: target */ - -webkit-mask-image: url("data:image/svg+xml;utf8,"); - mask-image: url("data:image/svg+xml;utf8,"); + + x-pw-tool-item.record > div { + /* codicon: circle-large-filled */ + -webkit-mask-image: url("data:image/svg+xml;utf8,"); + mask-image: url("data:image/svg+xml;utf8,"); } - x-pw-tool-item.recording > div { - /* codicon: record */ - -webkit-mask-image: url("data:image/svg+xml;utf8,"); - mask-image: url("data:image/svg+xml;utf8,"); + x-pw-tool-item.pick-locator > div { + /* codicon: inspect */ + -webkit-mask-image: url("data:image/svg+xml;utf8,"); + mask-image: url("data:image/svg+xml;utf8,"); } - x-pw-tool-item.assertingText > div { - /* codicon: text-size */ - -webkit-mask-image: url("data:image/svg+xml;utf8,"); - mask-image: url("data:image/svg+xml;utf8,"); + x-pw-tool-item.assert > div { + /* codicon: check-all */ + -webkit-mask-image: url("data:image/svg+xml;utf8,"); + mask-image: url("data:image/svg+xml;utf8,"); } `; shadow.appendChild(styleElement); @@ -682,34 +686,50 @@ class Overlay { const toolsListElement = document.createElement('x-pw-tools-list'); shadow.appendChild(toolsListElement); - this._tools = { - none: this._createToolElement(toolsListElement, 'none', 'Disable'), - inspecting: this._createToolElement(toolsListElement, 'inspecting', 'Pick locator'), - recording: this._createToolElement(toolsListElement, 'recording', 'Record actions'), - assertingText: this._createToolElement(toolsListElement, 'assertingText', 'Assert text and values'), - }; + this._recordToggle = this._recorder.injectedScript.document.createElement('x-pw-tool-item'); + this._recordToggle.title = 'Record'; + this._recordToggle.classList.add('record'); + this._recordToggle.appendChild(this._recorder.injectedScript.document.createElement('div')); + this._recordToggle.addEventListener('click', () => { + this._recorder.delegate.setMode?.(this._recorder.state.mode === 'none' || this._recorder.state.mode === 'inspecting' ? 'recording' : 'none'); + }); + toolsListElement.appendChild(this._recordToggle); - this._overlayElement.addEventListener('mousedown', event => { + const dragHandle = document.createElement('x-pw-drag-handle'); + dragHandle.addEventListener('mousedown', event => { this._dragState = { position: this._position, dragStart: { x: event.clientX, y: event.clientY } }; }); + toolsListElement.appendChild(dragHandle); + + this._pickLocatorToggle = this._recorder.injectedScript.document.createElement('x-pw-tool-item'); + this._pickLocatorToggle.title = 'Pick locator'; + this._pickLocatorToggle.classList.add('pick-locator'); + this._pickLocatorToggle.appendChild(this._recorder.injectedScript.document.createElement('div')); + this._pickLocatorToggle.addEventListener('click', () => { + const newMode: Record = { + 'inspecting': 'none', + 'none': 'inspecting', + 'recording': 'recording-inspecting', + 'recording-inspecting': 'recording', + 'assertingText': 'recording-inspecting', + }; + this._recorder.delegate.setMode?.(newMode[this._recorder.state.mode]); + }); + toolsListElement.appendChild(this._pickLocatorToggle); + + this._assertToggle = this._recorder.injectedScript.document.createElement('x-pw-tool-item'); + this._assertToggle.title = 'Assert text and values'; + this._assertToggle.classList.add('assert'); + this._assertToggle.appendChild(this._recorder.injectedScript.document.createElement('div')); + this._assertToggle.addEventListener('click', () => { + if (!this._assertToggle.classList.contains('disabled')) + this._recorder.delegate.setMode?.(this._recorder.state.mode === 'assertingText' ? 'recording' : 'assertingText'); + }); + toolsListElement.appendChild(this._assertToggle); - if (this._recorder.injectedScript.isUnderTest) { - // Most of our tests put elements at the top left, so get out of the way. - this._position = { x: 350, y: 350 }; - } this._updateVisualPosition(); } - private _createToolElement(parent: Element, mode: Mode, title: string) { - const element = this._recorder.injectedScript.document.createElement('x-pw-tool-item'); - element.title = title; - element.classList.add(mode); - element.appendChild(this._recorder.injectedScript.document.createElement('div')); - element.addEventListener('click', () => this._recorder.delegate.setMode?.(mode)); - parent.appendChild(element); - return element; - } - install() { this._recorder.injectedScript.document.documentElement.appendChild(this._overlayElement); this._measure = this._overlayElement.getBoundingClientRect(); @@ -720,8 +740,14 @@ class Overlay { } setUIState(state: UIState) { - for (const [mode, tool] of Object.entries(this._tools)) - tool.classList.toggle('active', state.mode === mode); + this._recordToggle.classList.toggle('active', state.mode === 'recording' || state.mode === 'assertingText' || state.mode === 'recording-inspecting'); + this._pickLocatorToggle.classList.toggle('active', state.mode === 'inspecting' || state.mode === 'recording-inspecting'); + this._assertToggle.classList.toggle('active', state.mode === 'assertingText'); + this._assertToggle.classList.toggle('disabled', state.mode === 'none' || state.mode === 'inspecting'); + if (this._position.x !== state.overlayPosition.x || this._position.y !== state.overlayPosition.y) { + this._position = state.overlayPosition; + this._updateVisualPosition(); + } } private _updateVisualPosition() { @@ -742,6 +768,7 @@ class Overlay { this._position.x = Math.max(0, Math.min(this._recorder.injectedScript.window.innerWidth - this._measure.width, this._position.x)); this._position.y = Math.max(0, Math.min(this._recorder.injectedScript.window.innerHeight - this._measure.height, this._position.y)); this._updateVisualPosition(); + this._recorder.delegate.setOverlayPosition?.(this._position); consumeEvent(event); return true; } @@ -767,7 +794,7 @@ export class Recorder { private _highlight: Highlight; private _overlay: Overlay | undefined; private _styleElement: HTMLStyleElement; - state: UIState = { mode: 'none', testIdAttributeName: 'data-testid', language: 'javascript' }; + state: UIState = { mode: 'none', testIdAttributeName: 'data-testid', language: 'javascript', overlayPosition: { x: 0, y: 0 } }; readonly document: Document; delegate: RecorderDelegate = {}; @@ -776,10 +803,11 @@ export class Recorder { this.injectedScript = injectedScript; this._highlight = new Highlight(injectedScript); this._tools = { - none: new NoneTool(), - inspecting: new InspectTool(this), - recording: new RecordActionTool(this), - assertingText: new TextAssertionTool(this), + 'none': new NoneTool(), + 'inspecting': new InspectTool(this), + 'recording': new RecordActionTool(this), + 'recording-inspecting': new InspectTool(this), + 'assertingText': new TextAssertionTool(this), }; this._currentTool = this._tools.none; if (injectedScript.window.top === injectedScript.window) { @@ -1074,6 +1102,7 @@ interface Embedder { __pw_recorderState(): Promise; __pw_recorderSetSelector(selector: string): Promise; __pw_recorderSetMode(mode: Mode): Promise; + __pw_recorderSetOverlayPosition(position: { x: number, y: number }): Promise; __pw_refreshOverlay(): void; } @@ -1122,10 +1151,6 @@ export class PollingRecorder implements RecorderDelegate { await this._embedder.__pw_recorderRecordAction(action); } - async __pw_recorderState(): Promise { - return await this._embedder.__pw_recorderState(); - } - async setSelector(selector: string): Promise { await this._embedder.__pw_recorderSetSelector(selector); } @@ -1133,6 +1158,10 @@ export class PollingRecorder implements RecorderDelegate { async setMode(mode: Mode): Promise { await this._embedder.__pw_recorderSetMode(mode); } + + async setOverlayPosition(position: { x: number, y: number }): Promise { + await this._embedder.__pw_recorderSetOverlayPosition(position); + } } export default PollingRecorder; diff --git a/packages/playwright-core/src/server/recorder.ts b/packages/playwright-core/src/server/recorder.ts index f90645e778..8f46e2995b 100644 --- a/packages/playwright-core/src/server/recorder.ts +++ b/packages/playwright-core/src/server/recorder.ts @@ -55,6 +55,7 @@ export class Recorder implements InstrumentationListener { private _context: BrowserContext; private _mode: Mode; private _highlightedSelector = ''; + private _overlayPosition: Point = { x: 0, y: 0 }; private _recorderApp: IRecorderApp | null = null; private _currentCallsMetadata = new Map(); private _recorderSources: Source[] = []; @@ -97,6 +98,11 @@ export class Recorder implements InstrumentationListener { this._handleSIGINT = params.handleSIGINT; context.instrumentation.addListener(this, context); this._currentLanguage = this._contextRecorder.languageName(); + + if (isUnderTest()) { + // Most of our tests put elements at the top left, so get out of the way. + this._overlayPosition = { x: 350, y: 350 }; + } } private static async defaultRecorderAppFactory(recorder: Recorder) { @@ -180,6 +186,7 @@ export class Recorder implements InstrumentationListener { actionSelector, language: this._currentLanguage, testIdAttributeName: this._contextRecorder.testIdAttributeName(), + overlayPosition: this._overlayPosition, }; return uiState; }); @@ -202,6 +209,12 @@ export class Recorder implements InstrumentationListener { this.setMode(mode); }); + await this._context.exposeBinding('__pw_recorderSetOverlayPosition', false, async ({ frame }, position: Point) => { + if (frame.parentFrame()) + return; + this._overlayPosition = position; + }); + await this._context.exposeBinding('__pw_resume', false, () => { this._debugger.resume(false); }); @@ -244,6 +257,10 @@ export class Recorder implements InstrumentationListener { this._debugger.resume(false); } + mode() { + return this._mode; + } + setHighlightedSelector(language: Language, selector: string) { this._highlightedSelector = locatorOrSelectorAsSelector(language, selector, this._context.selectors().testIdAttributeName()); this._refreshOverlay(); diff --git a/packages/playwright-core/src/server/recorder/recorderApp.ts b/packages/playwright-core/src/server/recorder/recorderApp.ts index aef4b999ee..3fb72d9f9e 100644 --- a/packages/playwright-core/src/server/recorder/recorderApp.ts +++ b/packages/playwright-core/src/server/recorder/recorderApp.ts @@ -46,7 +46,7 @@ export interface IRecorderApp extends EventEmitter { setPaused(paused: boolean): Promise; setMode(mode: Mode): Promise; setFileIfNeeded(file: string): Promise; - setSelector(selector: string, focus?: boolean): Promise; + setSelector(selector: string, userGesture?: boolean): Promise; updateCallLogs(callLogs: CallLog[]): Promise; setSources(sources: Source[]): Promise; } @@ -56,7 +56,7 @@ export class EmptyRecorderApp extends EventEmitter implements IRecorderApp { async setPaused(paused: boolean): Promise {} async setMode(mode: Mode): Promise {} async setFileIfNeeded(file: string): Promise {} - async setSelector(selector: string, focus?: boolean): Promise {} + async setSelector(selector: string, userGesture?: boolean): Promise {} async updateCallLogs(callLogs: CallLog[]): Promise {} async setSources(sources: Source[]): Promise {} } @@ -166,14 +166,18 @@ export class RecorderApp extends EventEmitter implements IRecorderApp { (process as any)._didSetSourcesForTest(sources[0].text); } - async setSelector(selector: string, focus?: boolean): Promise { - if (focus) { - this._recorder.setMode('none'); - this._page.bringToFront(); + async setSelector(selector: string, userGesture?: boolean): Promise { + if (userGesture) { + if (this._recorder.mode() === 'inspecting') { + this._recorder.setMode('none'); + this._page.bringToFront(); + } else { + this._recorder.setMode('recording'); + } } - await this._page.mainFrame().evaluateExpression(((arg: any) => { - window.playwrightSetSelector(arg.selector, arg.focus); - }).toString(), { isFunction: true }, { selector, focus }).catch(() => {}); + await this._page.mainFrame().evaluateExpression(((selector: string) => { + window.playwrightSetSelector(selector); + }).toString(), { isFunction: true }, selector).catch(() => {}); } async updateCallLogs(callLogs: CallLog[]): Promise { diff --git a/packages/recorder/src/recorder.css b/packages/recorder/src/recorder.css index b91eba9ff1..93eae2ba21 100644 --- a/packages/recorder/src/recorder.css +++ b/packages/recorder/src/recorder.css @@ -28,13 +28,11 @@ min-width: 100px; } -.recorder .toolbar-button.toggled.record, -.recorder .toolbar-button.toggled.text-size { +.recorder .toolbar-button.toggled.circle-large-filled { color: #a1260d; } -body.dark-mode .recorder .toolbar-button.toggled.record, -body.dark-mode .recorder .toolbar-button.toggled.text-size { +body.dark-mode .recorder .toolbar-button.toggled.circle-large-filled { color: #f48771; } diff --git a/packages/recorder/src/recorder.tsx b/packages/recorder/src/recorder.tsx index a11cdc2b87..6a1c6eb325 100644 --- a/packages/recorder/src/recorder.tsx +++ b/packages/recorder/src/recorder.tsx @@ -19,7 +19,7 @@ import { CodeMirrorWrapper } from '@web/components/codeMirrorWrapper'; import { SplitView } from '@web/components/splitView'; import { TabbedPane } from '@web/components/tabbedPane'; import { Toolbar } from '@web/components/toolbar'; -import { ToolbarButton } from '@web/components/toolbarButton'; +import { ToolbarButton, ToolbarSeparator } from '@web/components/toolbarButton'; import * as React from 'react'; import { CallLogView } from './callLog'; import './recorder.css'; @@ -66,7 +66,7 @@ export const Recorder: React.FC = ({ }; const [locator, setLocator] = React.useState(''); - window.playwrightSetSelector = (selector: string, focus?: boolean) => { + window.playwrightSetSelector = (selector: string) => { const language = source.language; setLocator(asLocator(language, selector)); }; @@ -113,12 +113,25 @@ export const Recorder: React.FC = ({ return
- { - window.dispatch({ event: 'setMode', params: { mode: mode === 'recording' ? 'none' : 'recording' } }); + { + window.dispatch({ event: 'setMode', params: { mode: mode === 'none' || mode === 'inspecting' ? 'recording' : 'none' } }); }}>Record - { - window.dispatch({ event: 'setMode', params: { mode: mode === 'assertingText' ? 'none' : 'assertingText' } }); + + { + const newMode = { + 'inspecting': 'none', + 'none': 'inspecting', + 'recording': 'recording-inspecting', + 'recording-inspecting': 'recording', + 'assertingText': 'recording-inspecting', + }[mode]; + window.dispatch({ event: 'setMode', params: { mode: newMode } }).catch(() => { }); + setSelectedTab('locator'); + }}>Pick locator + { + window.dispatch({ event: 'setMode', params: { mode: mode === 'assertingText' ? 'recording' : 'assertingText' } }); }}>Assert + { copy(source.text); }}> @@ -145,10 +158,6 @@ export const Recorder: React.FC = ({ { - window.dispatch({ event: 'setMode', params: { mode: mode === 'inspecting' ? 'none' : 'inspecting' } }).catch(() => { }); - setSelectedTab('locator'); - }} />]} rightToolbar={selectedTab === 'locator' ? [ copy(locator)} />] : []} tabs={[ { diff --git a/packages/recorder/src/recorderTypes.ts b/packages/recorder/src/recorderTypes.ts index fc1c3b9777..5855aa0f63 100644 --- a/packages/recorder/src/recorderTypes.ts +++ b/packages/recorder/src/recorderTypes.ts @@ -18,7 +18,7 @@ import type { Language } from '../../playwright-core/src/utils/isomorphic/locato export type Point = { x: number, y: number }; -export type Mode = 'inspecting' | 'recording' | 'none' | 'assertingText'; +export type Mode = 'inspecting' | 'recording' | 'none' | 'assertingText' | 'recording-inspecting'; export type EventData = { event: 'clear' | 'resume' | 'step' | 'pause' | 'setMode' | 'setRecordingTool' | 'selectorUpdated' | 'fileChanged'; @@ -31,6 +31,7 @@ export type UIState = { actionSelector?: string; language: 'javascript' | 'python' | 'java' | 'csharp' | 'jsonl'; testIdAttributeName: string; + overlayPosition: Point; }; export type CallLogStatus = 'in-progress' | 'done' | 'error' | 'paused'; diff --git a/packages/trace-viewer/src/ui/snapshotTab.tsx b/packages/trace-viewer/src/ui/snapshotTab.tsx index b128827b9b..c29846ebb4 100644 --- a/packages/trace-viewer/src/ui/snapshotTab.tsx +++ b/packages/trace-viewer/src/ui/snapshotTab.tsx @@ -242,6 +242,7 @@ export const InspectModeController: React.FunctionComponent<{ actionSelector: actionSelector.startsWith(frameSelector) ? actionSelector.substring(frameSelector.length).trim() : undefined, language: sdkLanguage, testIdAttributeName, + overlayPosition: { x: 0, y: 0 }, }, { async setSelector(selector: string) { setHighlightedLocator(asLocator(sdkLanguage, frameSelector + selector, false /* isFrameLocator */, true /* playSafe */)); diff --git a/packages/web/src/components/toolbarButton.css b/packages/web/src/components/toolbarButton.css index 32f3f72f31..bdf6db9ad9 100644 --- a/packages/web/src/components/toolbarButton.css +++ b/packages/web/src/components/toolbarButton.css @@ -46,3 +46,12 @@ .toolbar-button.toggled .codicon { font-weight: bold; } + +.toolbar-separator { + flex: none; + background-color: var(--vscode-menu-separatorBackground); + width: 1px; + padding: 0; + margin: 5px 4px; + height: 16px; +} diff --git a/packages/web/src/components/toolbarButton.tsx b/packages/web/src/components/toolbarButton.tsx index 7725531a3f..ac255dd66f 100644 --- a/packages/web/src/components/toolbarButton.tsx +++ b/packages/web/src/components/toolbarButton.tsx @@ -53,6 +53,12 @@ export const ToolbarButton: React.FC ; }; +export const ToolbarSeparator: React.FC<{ style?: React.CSSProperties }> = ({ + style, +}) => { + return
; +}; + const preventDefault = (e: any) => { e.stopPropagation(); e.preventDefault(); From 87787dcc7deb5678e9ffe96b34305e2392cd3a63 Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Mon, 6 Nov 2023 15:13:41 -0800 Subject: [PATCH 002/491] chore: allow click close the page w/o errors (#27994) --- .../src/server/dispatchers/artifactDispatcher.ts | 2 +- .../server/dispatchers/browserContextDispatcher.ts | 2 +- .../src/server/dispatchers/browserDispatcher.ts | 4 ++-- .../src/server/dispatchers/cdpSessionDispatcher.ts | 2 +- .../src/server/dispatchers/dispatcher.ts | 2 +- .../src/server/dispatchers/frameDispatcher.ts | 3 ++- .../src/server/dispatchers/jsHandleDispatcher.ts | 2 +- .../src/server/dispatchers/networkDispatchers.ts | 2 +- .../src/server/dispatchers/pageDispatcher.ts | 2 +- packages/protocol/src/callMetadata.ts | 2 +- tests/library/popup.spec.ts | 13 +++++++++++++ 11 files changed, 25 insertions(+), 11 deletions(-) diff --git a/packages/playwright-core/src/server/dispatchers/artifactDispatcher.ts b/packages/playwright-core/src/server/dispatchers/artifactDispatcher.ts index ee549222f1..1996f11776 100644 --- a/packages/playwright-core/src/server/dispatchers/artifactDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/artifactDispatcher.ts @@ -107,7 +107,7 @@ export class ArtifactDispatcher extends Dispatcher { - metadata.closesScope = true; + metadata.potentiallyClosesScope = true; await this._object.delete(); this._dispose(); } diff --git a/packages/playwright-core/src/server/dispatchers/browserContextDispatcher.ts b/packages/playwright-core/src/server/dispatchers/browserContextDispatcher.ts index 8bfc2ade0b..1805f0db64 100644 --- a/packages/playwright-core/src/server/dispatchers/browserContextDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/browserContextDispatcher.ts @@ -273,7 +273,7 @@ export class BrowserContextDispatcher extends Dispatcher { - metadata.closesScope = true; + metadata.potentiallyClosesScope = true; await this._context.close(params); } diff --git a/packages/playwright-core/src/server/dispatchers/browserDispatcher.ts b/packages/playwright-core/src/server/dispatchers/browserDispatcher.ts index 7a24f55ab8..99ce5f961f 100644 --- a/packages/playwright-core/src/server/dispatchers/browserDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/browserDispatcher.ts @@ -56,12 +56,12 @@ export class BrowserDispatcher extends Dispatcher { - metadata.closesScope = true; + metadata.potentiallyClosesScope = true; await this._object.close(params); } async killForTests(_: any, metadata: CallMetadata): Promise { - metadata.closesScope = true; + metadata.potentiallyClosesScope = true; await this._object.killForTests(); } diff --git a/packages/playwright-core/src/server/dispatchers/cdpSessionDispatcher.ts b/packages/playwright-core/src/server/dispatchers/cdpSessionDispatcher.ts index bf9dc61d76..33fa19f5e7 100644 --- a/packages/playwright-core/src/server/dispatchers/cdpSessionDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/cdpSessionDispatcher.ts @@ -35,7 +35,7 @@ export class CDPSessionDispatcher extends Dispatcher { - metadata.closesScope = true; + metadata.potentiallyClosesScope = true; await this._object.detach(); } } diff --git a/packages/playwright-core/src/server/dispatchers/dispatcher.ts b/packages/playwright-core/src/server/dispatchers/dispatcher.ts index c55558efd4..31931d1d31 100644 --- a/packages/playwright-core/src/server/dispatchers/dispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/dispatcher.ts @@ -99,7 +99,7 @@ export class Dispatcher { + metadata.potentiallyClosesScope = true; return await this._frame.click(metadata, params.selector, params); } @@ -265,7 +266,7 @@ export class FrameDispatcher extends Dispatcher { - metadata.closesScope = true; + metadata.potentiallyClosesScope = true; const expectedValue = params.expectedValue ? parseArgument(params.expectedValue) : undefined; const result = await this._frame.expect(metadata, params.selector, { ...params, expectedValue }); if (result.received !== undefined) diff --git a/packages/playwright-core/src/server/dispatchers/jsHandleDispatcher.ts b/packages/playwright-core/src/server/dispatchers/jsHandleDispatcher.ts index 8fa5093560..547c499746 100644 --- a/packages/playwright-core/src/server/dispatchers/jsHandleDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/jsHandleDispatcher.ts @@ -68,7 +68,7 @@ export class JSHandleDispatcher extends Dispatcher { - metadata.closesScope = true; + metadata.potentiallyClosesScope = true; await this._object.dispose(); this._dispose(); } diff --git a/packages/playwright-core/src/server/dispatchers/pageDispatcher.ts b/packages/playwright-core/src/server/dispatchers/pageDispatcher.ts index 814538dbee..60e53a7ea9 100644 --- a/packages/playwright-core/src/server/dispatchers/pageDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/pageDispatcher.ts @@ -201,7 +201,7 @@ export class PageDispatcher extends Dispatcher { if (!params.runBeforeUnload) - metadata.closesScope = true; + metadata.potentiallyClosesScope = true; await this._page.close(metadata, params); } diff --git a/packages/protocol/src/callMetadata.ts b/packages/protocol/src/callMetadata.ts index 3f5fc78764..b011e9bf61 100644 --- a/packages/protocol/src/callMetadata.ts +++ b/packages/protocol/src/callMetadata.ts @@ -42,5 +42,5 @@ export type CallMetadata = { objectId?: string; pageId?: string; frameId?: string; - closesScope?: boolean; + potentiallyClosesScope?: boolean; }; diff --git a/tests/library/popup.spec.ts b/tests/library/popup.spec.ts index 8100fd28da..52e13c5175 100644 --- a/tests/library/popup.spec.ts +++ b/tests/library/popup.spec.ts @@ -261,6 +261,19 @@ it('should not throttle rAF in the opener page', async ({ page, server }) => { ]); }); +it('should not throw when click closes popup', async ({ browserName, page, server }) => { + it.fixme(browserName === 'firefox'); + await page.goto(server.EMPTY_PAGE); + const [popup] = await Promise.all([ + page.waitForEvent('popup'), + page.evaluate(() => { + const w = window.open('about:blank'); + w.document.body.innerHTML = ``; + }), + ]); + await popup.getByRole('button').click(); +}); + async function waitForRafs(page: Page, count: number): Promise { await page.evaluate(count => new Promise(resolve => { const onRaf = () => { From 810382c074fb94ba2ac3f2b399a463d96dc3ef1f Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Mon, 6 Nov 2023 16:40:33 -0800 Subject: [PATCH 003/491] chore(recorder): more UX fixes for text assertions (#27995) --- .../src/server/injected/highlight.ts | 33 ++- .../src/server/injected/recorder.ts | 219 +++++++++++++----- .../playwright-core/src/server/recorder.ts | 19 +- .../src/server/recorder/recorderApp.ts | 9 + packages/recorder/src/main.tsx | 4 +- packages/recorder/src/recorder.css | 4 + packages/recorder/src/recorder.tsx | 5 + packages/recorder/src/recorderTypes.ts | 10 +- packages/trace-viewer/src/ui/snapshotTab.tsx | 2 +- 9 files changed, 222 insertions(+), 83 deletions(-) diff --git a/packages/playwright-core/src/server/injected/highlight.ts b/packages/playwright-core/src/server/injected/highlight.ts index 75c878cc97..16dd044949 100644 --- a/packages/playwright-core/src/server/injected/highlight.ts +++ b/packages/playwright-core/src/server/injected/highlight.ts @@ -30,6 +30,13 @@ type HighlightEntry = { tooltipText?: string, }; +export type HighlightOptions = { + tooltipText?: string; + color?: string; + anchorGetter?: (element: Element) => DOMRect; + decorateTooltip?: (tooltip: Element) => void; +}; + export class Highlight { private _glassPaneElement: HTMLElement; private _glassPaneShadow: ShadowRoot; @@ -112,7 +119,7 @@ export class Highlight { runHighlightOnRaf(selector: ParsedSelector) { if (this._rafRequest) cancelAnimationFrame(this._rafRequest); - this.updateHighlight(this._injectedScript.querySelectorAll(selector, this._injectedScript.document.documentElement), stringifySelector(selector)); + this.updateHighlight(this._injectedScript.querySelectorAll(selector, this._injectedScript.document.documentElement), { tooltipText: asLocator(this._language, stringifySelector(selector)) }); this._rafRequest = requestAnimationFrame(() => this.runHighlightOnRaf(selector)); } @@ -144,17 +151,19 @@ export class Highlight { this._highlightEntries = []; } - updateHighlight(elements: Element[], selector: string, color?: string) { - if (!color) - color = elements.length > 1 ? '#f6b26b7f' : '#6fa8dc7f'; - this._innerUpdateHighlight(elements, { color, tooltipText: selector ? asLocator(this._language, selector) : '' }); + updateHighlight(elements: Element[], options: HighlightOptions) { + this._innerUpdateHighlight(elements, options); } maskElements(elements: Element[], color?: string) { this._innerUpdateHighlight(elements, { color: color ? color : '#F0F' }); } - private _innerUpdateHighlight(elements: Element[], options: { color: string, tooltipText?: string }) { + private _innerUpdateHighlight(elements: Element[], options: HighlightOptions) { + let color = options.color; + if (!color) + color = elements.length > 1 ? '#f6b26b7f' : '#6fa8dc7f'; + // Code below should trigger one layout and leave with the // destroyed layout. @@ -177,6 +186,7 @@ export class Highlight { tooltipElement.style.top = '0'; tooltipElement.style.left = '0'; tooltipElement.style.display = 'flex'; + options.decorateTooltip?.(tooltipElement); } this._highlightEntries.push({ targetElement: elements[i], tooltipElement, highlightElement, tooltipText: options.tooltipText }); } @@ -193,14 +203,15 @@ export class Highlight { const totalWidth = this._glassPaneElement.offsetWidth; const totalHeight = this._glassPaneElement.offsetHeight; - let anchorLeft = entry.box.left; + const anchorBox = options.anchorGetter ? options.anchorGetter(entry.targetElement) : entry.box; + let anchorLeft = anchorBox.left; if (anchorLeft + tooltipWidth > totalWidth - 5) anchorLeft = totalWidth - tooltipWidth - 5; - let anchorTop = entry.box.bottom + 5; + let anchorTop = anchorBox.bottom + 5; if (anchorTop + tooltipHeight > totalHeight - 5) { // If can't fit below, either position above... - if (entry.box.top > tooltipHeight + 5) { - anchorTop = entry.box.top - tooltipHeight - 5; + if (anchorBox.top > tooltipHeight + 5) { + anchorTop = anchorBox.top - tooltipHeight - 5; } else { // Or on top in case of large element anchorTop = totalHeight - 5 - tooltipHeight; @@ -219,7 +230,7 @@ export class Highlight { entry.tooltipElement.style.left = entry.tooltipLeft + 'px'; } const box = entry.box!; - entry.highlightElement.style.backgroundColor = options.color; + entry.highlightElement.style.backgroundColor = color; entry.highlightElement.style.left = box.x + 'px'; entry.highlightElement.style.top = box.y + 'px'; entry.highlightElement.style.width = box.width + 'px'; diff --git a/packages/playwright-core/src/server/injected/recorder.ts b/packages/playwright-core/src/server/injected/recorder.ts index 334e8569a5..d78b92d938 100644 --- a/packages/playwright-core/src/server/injected/recorder.ts +++ b/packages/playwright-core/src/server/injected/recorder.ts @@ -18,18 +18,19 @@ import type * as actions from '../recorder/recorderActions'; import type { InjectedScript } from '../injected/injectedScript'; import { generateSelector } from '../injected/selectorGenerator'; import type { Point } from '../../common/types'; -import type { Mode, UIState } from '@recorder/recorderTypes'; -import { Highlight } from '../injected/highlight'; +import type { Mode, OverlayState, UIState } from '@recorder/recorderTypes'; +import { Highlight, type HighlightOptions } from '../injected/highlight'; import { enclosingElement, isInsideScope, parentElementOrShadowHost } from './domUtils'; import { elementText } from './selectorUtils'; -import { normalizeWhiteSpace } from '@isomorphic/stringUtils'; +import { escapeWithQuotes, normalizeWhiteSpace } from '../../utils/isomorphic/stringUtils'; +import { asLocator } from '../../utils/isomorphic/locatorGenerators'; interface RecorderDelegate { performAction?(action: actions.Action): Promise; recordAction?(action: actions.Action): Promise; setSelector?(selector: string): Promise; setMode?(mode: Mode): Promise; - setOverlayPosition?(position: { x: number, y: number }): Promise; + setOverlayState?(state: OverlayState): Promise; highlightUpdated?(): void; } @@ -436,15 +437,23 @@ class RecordActionTool implements RecorderTool { if (this._hoveredModel && this._hoveredModel.selector === selector) return; this._hoveredModel = selector ? { selector, elements } : null; - this._recorder.updateHighlight(this._hoveredModel, true, '#dc6f6f7f'); + this._recorder.updateHighlight(this._hoveredModel, true, { color: '#dc6f6f7f' }); } } class TextAssertionTool implements RecorderTool { + private _hoverHighlight: HighlightModel | null = null; private _selectionHighlight: HighlightModel | null = null; + private _selectionText: { selectedText: string, fullText: string } | null = null; private _inputHighlight: HighlightModel | null = null; + private _acceptButton: HTMLElement; constructor(private _recorder: Recorder) { + this._acceptButton = this._recorder.document.createElement('button'); + this._acceptButton.textContent = 'Accept'; + this._acceptButton.style.cursor = 'pointer'; + this._acceptButton.style.pointerEvents = 'auto'; + this._acceptButton.addEventListener('click', () => this._commitAction()); } cursor() { @@ -457,13 +466,19 @@ class TextAssertionTool implements RecorderTool { disable() { this._recorder.injectedScript.document.designMode = 'off'; + this._hoverHighlight = null; this._selectionHighlight = null; + this._selectionText = null; this._inputHighlight = null; } onClick(event: MouseEvent) { - consumeEvent(event); + // Hack: work around highlight's glass pane having a closed shadow root. + const box = this._acceptButton.getBoundingClientRect(); + if (box.left <= event.clientX && event.clientX <= box.right && box.top <= event.clientY && event.clientY <= box.bottom) + return; + consumeEvent(event); const selection = this._recorder.document.getSelection(); if (event.detail === 1 && selection && !selection.toString() && !this._inputHighlight) { const target = this._recorder.deepEventTarget(event); @@ -477,12 +492,13 @@ class TextAssertionTool implements RecorderTool { if (['INPUT', 'TEXTAREA'].includes(target.nodeName)) { this._recorder.injectedScript.window.getSelection()?.empty(); this._inputHighlight = generateSelector(this._recorder.injectedScript, target, { testIdAttributeName: this._recorder.state.testIdAttributeName }); - this._recorder.updateHighlight(this._inputHighlight, true, '#6fdcbd38'); + this._showHighlight(true); consumeEvent(event); return; } this._inputHighlight = null; + this._hoverHighlight = null; this._updateSelectionHighlight(); } @@ -491,7 +507,18 @@ class TextAssertionTool implements RecorderTool { } onMouseMove(event: MouseEvent) { - this._updateSelectionHighlight(); + const selection = this._recorder.document.getSelection(); + if (selection && selection.toString()) { + this._updateSelectionHighlight(); + return; + } + if (this._inputHighlight || event.buttons) + return; + const target = this._recorder.deepEventTarget(event); + if (this._hoverHighlight?.elements[0] === target) + return; + this._hoverHighlight = elementText(new Map(), target).full ? { elements: [target], selector: '' } : null; + this._recorder.updateHighlight(this._hoverHighlight, true, { color: '#8acae480' }); } onDragStart(event: DragEvent) { @@ -500,48 +527,17 @@ class TextAssertionTool implements RecorderTool { onKeyDown(event: KeyboardEvent) { if (event.key === 'Escape') { - this._resetSelectionAndHighlight(); - this._recorder.delegate.setMode?.('recording'); + const selection = this._recorder.document.getSelection(); + if (selection && selection.toString()) + this._resetSelectionAndHighlight(); + else + this._recorder.delegate.setMode?.('recording'); consumeEvent(event); return; } if (event.key === 'Enter') { - const selection = this._recorder.document.getSelection(); - - if (this._inputHighlight) { - const target = this._inputHighlight.elements[0] as HTMLInputElement; - if (target.nodeName === 'INPUT' && ['checkbox', 'radio'].includes(target.type.toLowerCase())) { - this._recorder.delegate.recordAction?.({ - name: 'assertChecked', - selector: this._inputHighlight.selector, - signals: [], - // Interestingly, inputElement.checked is reversed inside this event handler. - checked: !(target as HTMLInputElement).checked, - }); - this._recorder.delegate.setMode?.('recording'); - } else { - this._recorder.delegate.recordAction?.({ - name: 'assertValue', - selector: this._inputHighlight.selector, - signals: [], - value: target.value, - }); - this._recorder.delegate.setMode?.('recording'); - } - } else if (selection && this._selectionHighlight) { - const selectedText = normalizeWhiteSpace(selection.toString()); - const fullText = normalizeWhiteSpace(elementText(new Map(), this._selectionHighlight.elements[0]).full); - this._recorder.delegate.recordAction?.({ - name: 'assertText', - selector: this._selectionHighlight.selector, - signals: [], - text: selectedText, - substring: fullText !== selectedText, - }); - this._recorder.delegate.setMode?.('recording'); - this._resetSelectionAndHighlight(); - } + this._commitAction(); consumeEvent(event); return; } @@ -558,11 +554,67 @@ class TextAssertionTool implements RecorderTool { } onScroll(event: Event) { - this._recorder.updateHighlight(this._selectionHighlight, false, '#6fdcbd38'); + this._hoverHighlight = null; + this._showHighlight(false); + } + + private _generateAction(): actions.Action | null { + if (this._inputHighlight) { + const target = this._inputHighlight.elements[0] as HTMLInputElement; + if (target.nodeName === 'INPUT' && ['checkbox', 'radio'].includes(target.type.toLowerCase())) { + return { + name: 'assertChecked', + selector: this._inputHighlight.selector, + signals: [], + // Interestingly, inputElement.checked is reversed inside this event handler. + checked: !(target as HTMLInputElement).checked, + }; + } else { + return { + name: 'assertValue', + selector: this._inputHighlight.selector, + signals: [], + value: target.value, + }; + } + } else if (this._selectionText && this._selectionHighlight) { + return { + name: 'assertText', + selector: this._selectionHighlight.selector, + signals: [], + text: this._selectionText.selectedText, + substring: this._selectionText.fullText !== this._selectionText.selectedText, + }; + } + return null; + } + + private _generateActionPreview() { + const action = this._generateAction(); + // TODO: support other languages, maybe unify with code generator? + if (action?.name === 'assertText') + return `expect(${asLocator(this._recorder.state.language, action.selector)}).${action.substring ? 'toContainText' : 'toHaveText'}(${escapeWithQuotes(action.text)})`; + if (action?.name === 'assertChecked') + return `expect(${asLocator(this._recorder.state.language, action.selector)})${action.checked ? '' : '.not'}.toBeChecked()`; + if (action?.name === 'assertValue') { + const assertion = action.value ? `toHaveValue(${escapeWithQuotes(action.value)})` : `toBeEmpty()`; + return `expect(${asLocator(this._recorder.state.language, action.selector)}).${assertion}`; + } + return ''; + } + + private _commitAction() { + const action = this._generateAction(); + if (action) { + this._resetSelectionAndHighlight(); + this._recorder.delegate.recordAction?.(action); + this._recorder.delegate.setMode?.('recording'); + } } private _resetSelectionAndHighlight() { this._selectionHighlight = null; + this._selectionText = null; this._inputHighlight = null; this._recorder.injectedScript.window.getSelection()?.empty(); this._recorder.updateHighlight(null, false); @@ -572,18 +624,32 @@ class TextAssertionTool implements RecorderTool { if (this._inputHighlight) return; const selection = this._recorder.document.getSelection(); + const selectedText = normalizeWhiteSpace(selection?.toString() || ''); let highlight: HighlightModel | null = null; - if (selection && selection.focusNode && selection.anchorNode && selection.toString()) { + if (selection && selection.focusNode && selection.anchorNode && selectedText) { const focusElement = enclosingElement(selection.focusNode); let lcaElement = focusElement ? enclosingElement(selection.anchorNode) : undefined; while (lcaElement && !isInsideScope(lcaElement, focusElement)) lcaElement = parentElementOrShadowHost(lcaElement); highlight = lcaElement ? generateSelector(this._recorder.injectedScript, lcaElement, { testIdAttributeName: this._recorder.state.testIdAttributeName, forTextExpect: true }) : null; } - if (highlight?.selector === this._selectionHighlight?.selector) + const fullText = highlight ? normalizeWhiteSpace(elementText(new Map(), highlight.elements[0]).full) : ''; + const selectionText = highlight ? { selectedText, fullText } : null; + if (highlight?.selector === this._selectionHighlight?.selector && this._selectionText?.fullText === selectionText?.fullText && this._selectionText?.selectedText === selectionText?.selectedText) return; this._selectionHighlight = highlight; - this._recorder.updateHighlight(highlight, true, '#6fdcbd38'); + this._selectionText = selectionText; + this._showHighlight(true); + } + + private _showHighlight(userGesture: boolean) { + const options: HighlightOptions = { color: '#6fdcbd38', tooltipText: this._generateActionPreview(), decorateTooltip: tooltip => tooltip.appendChild(this._acceptButton) }; + if (this._inputHighlight) { + this._recorder.updateHighlight(this._inputHighlight, userGesture, options); + } else { + options.anchorGetter = (e: Element) => this._recorder.document.getSelection()?.getRangeAt(0)?.getBoundingClientRect() || e.getBoundingClientRect(); + this._recorder.updateHighlight(this._selectionHighlight, userGesture, options); + } } } @@ -624,15 +690,23 @@ class Overlay { x-pw-drag-handle { cursor: grab; - height: 2px; - margin: 5px 9px; - border-top: 1px solid rgb(86 86 86 / 90%); - border-bottom: 1px solid rgb(86 86 86 / 90%); + padding: 6px 9px; + } + x-pw-drag-handle > div { + height: 1px; + margin-top: 2px; + background: rgb(148 148 148 / 90%); } x-pw-drag-handle:active { cursor: grabbing; } + x-pw-separator { + height: 1px; + margin: 6px 9px; + background: rgb(148 148 148 / 90%); + } + x-pw-tool-item { cursor: pointer; height: 28px; @@ -680,6 +754,11 @@ class Overlay { -webkit-mask-image: url("data:image/svg+xml;utf8,"); mask-image: url("data:image/svg+xml;utf8,"); } + x-pw-tool-item.close > div { + /* codicon: close */ + -webkit-mask-image: url("data:image/svg+xml;utf8,"); + mask-image: url("data:image/svg+xml;utf8,"); + } `; shadow.appendChild(styleElement); @@ -699,6 +778,9 @@ class Overlay { dragHandle.addEventListener('mousedown', event => { this._dragState = { position: this._position, dragStart: { x: event.clientX, y: event.clientY } }; }); + dragHandle.append(document.createElement('div')); + dragHandle.append(document.createElement('div')); + dragHandle.append(document.createElement('div')); toolsListElement.appendChild(dragHandle); this._pickLocatorToggle = this._recorder.injectedScript.document.createElement('x-pw-tool-item'); @@ -727,6 +809,16 @@ class Overlay { }); toolsListElement.appendChild(this._assertToggle); + const closeButton = this._recorder.injectedScript.document.createElement('x-pw-tool-item'); + closeButton.title = 'Hide this overlay'; + closeButton.classList.add('close'); + closeButton.appendChild(this._recorder.injectedScript.document.createElement('div')); + closeButton.addEventListener('click', () => { + this._overlayElement.style.display = 'none'; + this._recorder.delegate.setOverlayState?.({ position: this._position, visible: false }); + }); + toolsListElement.appendChild(closeButton); + this._updateVisualPosition(); } @@ -744,10 +836,11 @@ class Overlay { this._pickLocatorToggle.classList.toggle('active', state.mode === 'inspecting' || state.mode === 'recording-inspecting'); this._assertToggle.classList.toggle('active', state.mode === 'assertingText'); this._assertToggle.classList.toggle('disabled', state.mode === 'none' || state.mode === 'inspecting'); - if (this._position.x !== state.overlayPosition.x || this._position.y !== state.overlayPosition.y) { - this._position = state.overlayPosition; + if (this._position.x !== state.overlay.position.x || this._position.y !== state.overlay.position.y) { + this._position = state.overlay.position; this._updateVisualPosition(); } + this._overlayElement.style.display = state.overlay.visible ? 'block' : 'none'; } private _updateVisualPosition() { @@ -768,7 +861,7 @@ class Overlay { this._position.x = Math.max(0, Math.min(this._recorder.injectedScript.window.innerWidth - this._measure.width, this._position.x)); this._position.y = Math.max(0, Math.min(this._recorder.injectedScript.window.innerHeight - this._measure.height, this._position.y)); this._updateVisualPosition(); - this._recorder.delegate.setOverlayPosition?.(this._position); + this._recorder.delegate.setOverlayState?.({ position: this._position, visible: true }); consumeEvent(event); return true; } @@ -794,7 +887,7 @@ export class Recorder { private _highlight: Highlight; private _overlay: Overlay | undefined; private _styleElement: HTMLStyleElement; - state: UIState = { mode: 'none', testIdAttributeName: 'data-testid', language: 'javascript', overlayPosition: { x: 0, y: 0 } }; + state: UIState = { mode: 'none', testIdAttributeName: 'data-testid', language: 'javascript', overlay: { position: { x: 0, y: 0 }, visible: true } }; readonly document: Document; delegate: RecorderDelegate = {}; @@ -1000,8 +1093,10 @@ export class Recorder { this._currentTool.onKeyUp?.(event); } - updateHighlight(model: HighlightModel | null, userGesture: boolean, color?: string) { - this._highlight.updateHighlight(model?.elements || [], model?.selector || '', color); + updateHighlight(model: HighlightModel | null, userGesture: boolean, options: HighlightOptions = {}) { + if (options.tooltipText === undefined && model?.selector) + options.tooltipText = asLocator(this.state.language, model.selector); + this._highlight.updateHighlight(model?.elements || [], options); if (userGesture) this.delegate.highlightUpdated?.(); } @@ -1102,7 +1197,7 @@ interface Embedder { __pw_recorderState(): Promise; __pw_recorderSetSelector(selector: string): Promise; __pw_recorderSetMode(mode: Mode): Promise; - __pw_recorderSetOverlayPosition(position: { x: number, y: number }): Promise; + __pw_recorderSetOverlayState(state: OverlayState): Promise; __pw_refreshOverlay(): void; } @@ -1159,8 +1254,8 @@ export class PollingRecorder implements RecorderDelegate { await this._embedder.__pw_recorderSetMode(mode); } - async setOverlayPosition(position: { x: number, y: number }): Promise { - await this._embedder.__pw_recorderSetOverlayPosition(position); + async setOverlayState(state: OverlayState): Promise { + await this._embedder.__pw_recorderSetOverlayState(state); } } diff --git a/packages/playwright-core/src/server/recorder.ts b/packages/playwright-core/src/server/recorder.ts index 8f46e2995b..553d12fcc7 100644 --- a/packages/playwright-core/src/server/recorder.ts +++ b/packages/playwright-core/src/server/recorder.ts @@ -35,7 +35,7 @@ import type { IRecorderApp } from './recorder/recorderApp'; import { RecorderApp } from './recorder/recorderApp'; import type { CallMetadata, InstrumentationListener, SdkObject } from './instrumentation'; import type { Point } from '../common/types'; -import type { CallLog, CallLogStatus, EventData, Mode, Source, UIState } from '@recorder/recorderTypes'; +import type { CallLog, CallLogStatus, EventData, Mode, OverlayState, Source, UIState } from '@recorder/recorderTypes'; import { createGuid, isUnderTest, monotonicTime } from '../utils'; import { metadataToCallLog } from './recorder/recorderUtils'; import { Debugger } from './debugger'; @@ -55,7 +55,7 @@ export class Recorder implements InstrumentationListener { private _context: BrowserContext; private _mode: Mode; private _highlightedSelector = ''; - private _overlayPosition: Point = { x: 0, y: 0 }; + private _overlayState: OverlayState = { position: { x: 0, y: 0 }, visible: true }; private _recorderApp: IRecorderApp | null = null; private _currentCallsMetadata = new Map(); private _recorderSources: Source[] = []; @@ -101,7 +101,7 @@ export class Recorder implements InstrumentationListener { if (isUnderTest()) { // Most of our tests put elements at the top left, so get out of the way. - this._overlayPosition = { x: 350, y: 350 }; + this._overlayState.position = { x: 350, y: 350 }; } } @@ -123,6 +123,12 @@ export class Recorder implements InstrumentationListener { this.setMode(data.params.mode); return; } + if (data.event === 'setOverlayVisible') { + this._overlayState.visible = data.params.visible; + this._recorderApp?.setOverlayVisible(this._overlayState.visible); + this._refreshOverlay(); + return; + } if (data.event === 'selectorUpdated') { this.setHighlightedSelector(this._currentLanguage, data.params.selector); return; @@ -186,7 +192,7 @@ export class Recorder implements InstrumentationListener { actionSelector, language: this._currentLanguage, testIdAttributeName: this._contextRecorder.testIdAttributeName(), - overlayPosition: this._overlayPosition, + overlay: this._overlayState, }; return uiState; }); @@ -209,10 +215,11 @@ export class Recorder implements InstrumentationListener { this.setMode(mode); }); - await this._context.exposeBinding('__pw_recorderSetOverlayPosition', false, async ({ frame }, position: Point) => { + await this._context.exposeBinding('__pw_recorderSetOverlayState', false, async ({ frame }, state: OverlayState) => { if (frame.parentFrame()) return; - this._overlayPosition = position; + this._overlayState = state; + this._recorderApp?.setOverlayVisible(state.visible); }); await this._context.exposeBinding('__pw_resume', false, () => { diff --git a/packages/playwright-core/src/server/recorder/recorderApp.ts b/packages/playwright-core/src/server/recorder/recorderApp.ts index 3fb72d9f9e..de108c8fc4 100644 --- a/packages/playwright-core/src/server/recorder/recorderApp.ts +++ b/packages/playwright-core/src/server/recorder/recorderApp.ts @@ -34,6 +34,7 @@ declare global { playwrightSetMode: (mode: Mode) => void; playwrightSetPaused: (paused: boolean) => void; playwrightSetSources: (sources: Source[]) => void; + playwrightSetOverlayVisible: (visible: boolean) => void; playwrightSetSelector: (selector: string, focus?: boolean) => void; playwrightUpdateLogs: (callLogs: CallLog[]) => void; dispatch(data: EventData): Promise; @@ -45,6 +46,7 @@ export interface IRecorderApp extends EventEmitter { close(): Promise; setPaused(paused: boolean): Promise; setMode(mode: Mode): Promise; + setOverlayVisible(visible: boolean): Promise; setFileIfNeeded(file: string): Promise; setSelector(selector: string, userGesture?: boolean): Promise; updateCallLogs(callLogs: CallLog[]): Promise; @@ -55,6 +57,7 @@ export class EmptyRecorderApp extends EventEmitter implements IRecorderApp { async close(): Promise {} async setPaused(paused: boolean): Promise {} async setMode(mode: Mode): Promise {} + async setOverlayVisible(visible: boolean): Promise {} async setFileIfNeeded(file: string): Promise {} async setSelector(selector: string, userGesture?: boolean): Promise {} async updateCallLogs(callLogs: CallLog[]): Promise {} @@ -144,6 +147,12 @@ export class RecorderApp extends EventEmitter implements IRecorderApp { }).toString(), { isFunction: true }, mode).catch(() => {}); } + async setOverlayVisible(visible: boolean): Promise { + await this._page.mainFrame().evaluateExpression(((visible: boolean) => { + window.playwrightSetOverlayVisible(visible); + }).toString(), { isFunction: true }, visible).catch(() => {}); + } + async setFileIfNeeded(file: string): Promise { await this._page.mainFrame().evaluateExpression(((file: string) => { window.playwrightSetFileIfNeeded(file); diff --git a/packages/recorder/src/main.tsx b/packages/recorder/src/main.tsx index ddd9d90acd..be5791eb15 100644 --- a/packages/recorder/src/main.tsx +++ b/packages/recorder/src/main.tsx @@ -25,10 +25,12 @@ export const Main: React.FC = ({ const [paused, setPaused] = React.useState(false); const [log, setLog] = React.useState(new Map()); const [mode, setMode] = React.useState('none'); + const [overlayVisible, setOverlayVisible] = React.useState(true); window.playwrightSetMode = setMode; window.playwrightSetSources = setSources; window.playwrightSetPaused = setPaused; + window.playwrightSetOverlayVisible = setOverlayVisible; window.playwrightUpdateLogs = callLogs => { const newLog = new Map(log); for (const callLog of callLogs) { @@ -39,5 +41,5 @@ export const Main: React.FC = ({ }; window.playwrightSourcesEchoForTest = sources; - return ; + return ; }; diff --git a/packages/recorder/src/recorder.css b/packages/recorder/src/recorder.css index 93eae2ba21..8219457295 100644 --- a/packages/recorder/src/recorder.css +++ b/packages/recorder/src/recorder.css @@ -28,6 +28,10 @@ min-width: 100px; } +.recorder .codicon { + font-size: 15px; +} + .recorder .toolbar-button.toggled.circle-large-filled { color: #a1260d; } diff --git a/packages/recorder/src/recorder.tsx b/packages/recorder/src/recorder.tsx index 6a1c6eb325..65d5628b54 100644 --- a/packages/recorder/src/recorder.tsx +++ b/packages/recorder/src/recorder.tsx @@ -40,6 +40,7 @@ export interface RecorderProps { paused: boolean, log: Map, mode: Mode, + overlayVisible: boolean, } export const Recorder: React.FC = ({ @@ -47,6 +48,7 @@ export const Recorder: React.FC = ({ paused, log, mode, + overlayVisible, }) => { const [fileId, setFileId] = React.useState(); const [selectedTab, setSelectedTab] = React.useState('log'); @@ -154,6 +156,9 @@ export const Recorder: React.FC = ({ window.dispatch({ event: 'clear' }); }}>
toggleTheme()}> + { + window.dispatch({ event: 'setOverlayVisible', params: { visible: !overlayVisible } }); + }}>
diff --git a/packages/recorder/src/recorderTypes.ts b/packages/recorder/src/recorderTypes.ts index 5855aa0f63..d8ac13240e 100644 --- a/packages/recorder/src/recorderTypes.ts +++ b/packages/recorder/src/recorderTypes.ts @@ -21,17 +21,22 @@ export type Point = { x: number, y: number }; export type Mode = 'inspecting' | 'recording' | 'none' | 'assertingText' | 'recording-inspecting'; export type EventData = { - event: 'clear' | 'resume' | 'step' | 'pause' | 'setMode' | 'setRecordingTool' | 'selectorUpdated' | 'fileChanged'; + event: 'clear' | 'resume' | 'step' | 'pause' | 'setMode' | 'selectorUpdated' | 'fileChanged' | 'setOverlayVisible'; params: any; }; +export type OverlayState = { + position: Point; + visible: boolean; +}; + export type UIState = { mode: Mode; actionPoint?: Point; actionSelector?: string; language: 'javascript' | 'python' | 'java' | 'csharp' | 'jsonl'; testIdAttributeName: string; - overlayPosition: Point; + overlay: OverlayState; }; export type CallLogStatus = 'in-progress' | 'done' | 'error' | 'paused'; @@ -75,6 +80,7 @@ declare global { playwrightSetMode: (mode: Mode) => void; playwrightSetPaused: (paused: boolean) => void; playwrightSetSources: (sources: Source[]) => void; + playwrightSetOverlayVisible: (visible: boolean) => void; playwrightUpdateLogs: (callLogs: CallLog[]) => void; playwrightSetFileIfNeeded: (file: string) => void; playwrightSetSelector: (selector: string, focus?: boolean) => void; diff --git a/packages/trace-viewer/src/ui/snapshotTab.tsx b/packages/trace-viewer/src/ui/snapshotTab.tsx index c29846ebb4..793fb93908 100644 --- a/packages/trace-viewer/src/ui/snapshotTab.tsx +++ b/packages/trace-viewer/src/ui/snapshotTab.tsx @@ -242,7 +242,7 @@ export const InspectModeController: React.FunctionComponent<{ actionSelector: actionSelector.startsWith(frameSelector) ? actionSelector.substring(frameSelector.length).trim() : undefined, language: sdkLanguage, testIdAttributeName, - overlayPosition: { x: 0, y: 0 }, + overlay: { position: { x: 0, y: 0 }, visible: false }, }, { async setSelector(selector: string) { setHighlightedLocator(asLocator(sdkLanguage, frameSelector + selector, false /* isFrameLocator */, true /* playSafe */)); From 19b0f5ccb38fb0dee6e04b44448a8a2a212ee393 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Tue, 7 Nov 2023 09:28:18 +0100 Subject: [PATCH 004/491] docs(dotnet): page -> Page (#27988) Fixes https://github.com/microsoft/playwright-dotnet/issues/2738 Fixes https://github.com/microsoft/playwright/pull/27946 --- docs/src/api/class-pageassertions.md | 6 +++--- docs/src/api/class-playwrightassertions.md | 2 +- docs/src/api/params.md | 4 ++-- docs/src/locators.md | 24 +++++++++++----------- docs/src/other-locators.md | 2 +- docs/src/release-notes-csharp.md | 4 ++-- docs/src/writing-tests-csharp.md | 2 +- 7 files changed, 22 insertions(+), 22 deletions(-) diff --git a/docs/src/api/class-pageassertions.md b/docs/src/api/class-pageassertions.md index 9bea84e384..fcb4f38652 100644 --- a/docs/src/api/class-pageassertions.md +++ b/docs/src/api/class-pageassertions.md @@ -85,7 +85,7 @@ assertThat(page).not().hasURL("error"); ``` ```csharp -await Expect(page).Not.ToHaveURL("error"); +await Expect(Page).Not.ToHaveURL("error"); ``` ## async method: PageAssertions.NotToHaveTitle @@ -261,7 +261,7 @@ expect(page).to_have_title(re.compile(r".*checkout")) ``` ```csharp -await Expect(page).ToHaveTitle("Playwright"); +await Expect(Page).ToHaveTitle("Playwright"); ``` ### param: PageAssertions.toHaveTitle.titleOrRegExp @@ -310,7 +310,7 @@ expect(page).to_have_url(re.compile(".*checkout")) ``` ```csharp -await Expect(page).ToHaveURL(new Regex(".*checkout")); +await Expect(Page).ToHaveURL(new Regex(".*checkout")); ``` ### param: PageAssertions.toHaveURL.urlOrRegExp diff --git a/docs/src/api/class-playwrightassertions.md b/docs/src/api/class-playwrightassertions.md index bb5bb305a9..0d8a2c9ce8 100644 --- a/docs/src/api/class-playwrightassertions.md +++ b/docs/src/api/class-playwrightassertions.md @@ -157,7 +157,7 @@ PlaywrightAssertions.assertThat(page).hasTitle("News"); ``` ```csharp -await Expect(page).ToHaveTitleAsync("News"); +await Expect(Page).ToHaveTitleAsync("News"); ``` ### param: PlaywrightAssertions.expectPage.page diff --git a/docs/src/api/params.md b/docs/src/api/params.md index 81fa010aee..293b415c44 100644 --- a/docs/src/api/params.md +++ b/docs/src/api/params.md @@ -1589,7 +1589,7 @@ page.getByRole(AriaRole.BUTTON, ``` ```csharp -await Expect(page +await Expect(Page .GetByRole(AriaRole.Heading, new() { Name = "Sign up" })) .ToBeVisibleAsync(); @@ -1641,7 +1641,7 @@ expect(page.get_by_title("Issues count")).to_have_text("25 issues") ``` ```csharp -await Expect(page.GetByTitle("Issues count")).toHaveText("25 issues"); +await Expect(Page.GetByTitle("Issues count")).toHaveText("25 issues"); ``` ## test-config-snapshot-path-template diff --git a/docs/src/locators.md b/docs/src/locators.md index bbecedfd1f..ee036dbf79 100644 --- a/docs/src/locators.md +++ b/docs/src/locators.md @@ -68,7 +68,7 @@ await page.GetByLabel("Password").FillAsync("secret-password"); await page.GetByRole(AriaRole.Button, new() { Name = "Sign in" }).ClickAsync(); -await Expect(page.GetByText("Welcome, John!")).ToBeVisibleAsync(); +await Expect(Page.GetByText("Welcome, John!")).ToBeVisibleAsync(); ``` ## Locating elements @@ -245,7 +245,7 @@ page.getByRole(AriaRole.BUTTON, ``` ```csharp -await Expect(page +await Expect(Page .GetByRole(AriaRole.Heading, new() { Name = "Sign up" })) .ToBeVisibleAsync(); @@ -373,7 +373,7 @@ expect(page.get_by_text("Welcome, John")).to_be_visible() ``` ```csharp -await Expect(page.GetByText("Welcome, John")).ToBeVisibleAsync(); +await Expect(Page.GetByText("Welcome, John")).ToBeVisibleAsync(); ``` Set an exact match: @@ -396,7 +396,7 @@ expect(page.get_by_text("Welcome, John", exact=True)).to_be_visible() ``` ```csharp -await Expect(page +await Expect(Page .GetByText("Welcome, John", new() { Exact = true })) .ToBeVisibleAsync(); ``` @@ -424,7 +424,7 @@ expect(page.get_by_text(re.compile("welcome, john", re.IGNORECASE))).to_be_visib ``` ```csharp -await Expect(page +await Expect(Page .GetByText(new Regex("welcome, john", RegexOptions.IgnoreCase))) .ToBeVisibleAsync(); ``` @@ -504,7 +504,7 @@ expect(page.get_by_title("Issues count")).to_have_text("25 issues") ``` ```csharp -await Expect(page.GetByTitle("Issues count")).toHaveText("25 issues"); +await Expect(Page.GetByTitle("Issues count")).toHaveText("25 issues"); ``` :::note When to use title locators @@ -784,7 +784,7 @@ await expect(page.locator("x-details")).to_contain_text("Details") expect(page.locator("x-details")).to_contain_text("Details") ``` ```csharp -await Expect(page.Locator("x-details")).ToContainTextAsync("Details"); +await Expect(Page.Locator("x-details")).ToContainTextAsync("Details"); ``` ## Filtering Locators @@ -910,7 +910,7 @@ expect(page.get_by_role("listitem").filter(has_not_text="Out of stock")).to_have ```csharp // 5 in-stock items -await Expect(page.getByRole(AriaRole.Listitem).Filter(new() { HasNotText = "Out of stock" })) +await Expect(Page.getByRole(AriaRole.Listitem).Filter(new() { HasNotText = "Out of stock" })) .ToHaveCountAsync(5); ``` @@ -1006,7 +1006,7 @@ expect( ``` ```csharp -await Expect(page +await Expect(Page .GetByRole(AriaRole.Listitem) .Filter(new() { Has = page.GetByRole(AriaRole.Heading, new() { Name = "Product 2" }) @@ -1049,7 +1049,7 @@ expect( ``` ```csharp -await Expect(page +await Expect(Page .GetByRole(AriaRole.Listitem) .Filter(new() { HasNot = page.GetByRole(AriaRole.Heading, new() { Name = "Product 2" }) @@ -1309,7 +1309,7 @@ assertThat(page.getByRole(AriaRole.LISTITEM).hasCount(3); ``` ```csharp -await Expect(page.GetByRole(AriaRole.Listitem)).ToHaveCountAsync(3); +await Expect(Page.GetByRole(AriaRole.Listitem)).ToHaveCountAsync(3); ``` ### Assert all text in a list @@ -1349,7 +1349,7 @@ assertThat(page ``` ```csharp -await Expect(page +await Expect(Page .GetByRole(AriaRole.Listitem)) .ToHaveTextAsync(new string[] {"apple", "banana", "orange"}); ``` diff --git a/docs/src/other-locators.md b/docs/src/other-locators.md index 665dcce2c5..2950d937b8 100644 --- a/docs/src/other-locators.md +++ b/docs/src/other-locators.md @@ -726,7 +726,7 @@ expect(page.locator("label")).to_have_text("Password") ```csharp // Fill the input by targeting the label. -await Expect(page.Locator("label")).ToHaveTextAsync("Password"); +await Expect(Page.Locator("label")).ToHaveTextAsync("Password"); ``` ## Legacy text locator diff --git a/docs/src/release-notes-csharp.md b/docs/src/release-notes-csharp.md index 440981298a..1383b0a0b3 100644 --- a/docs/src/release-notes-csharp.md +++ b/docs/src/release-notes-csharp.md @@ -373,7 +373,7 @@ await page.GetByLabel("Password").FillAsync("secret-password"); await page.GetByRole(AriaRole.Button, new() { NameString = "Sign in" }).ClickAsync(); -await Expect(page.GetByText("Welcome, John!")).ToBeVisibleAsync(); +await Expect(Page.GetByText("Welcome, John!")).ToBeVisibleAsync(); ``` All the same methods are also available on [Locator], [FrameLocator] and [Frame] classes. @@ -387,7 +387,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)).ToHaveAttribute("disabled", ""); ``` ### Browser Versions diff --git a/docs/src/writing-tests-csharp.md b/docs/src/writing-tests-csharp.md index ca3a1b41ab..1aea228c68 100644 --- a/docs/src/writing-tests-csharp.md +++ b/docs/src/writing-tests-csharp.md @@ -50,7 +50,7 @@ public class Tests : PageTest await getStarted.ClickAsync(); // Expects page to have a heading with the name of Installation. - await Expect(page + await Expect(Page .GetByRole(AriaRole.Heading, new() { Name = "Installation" })) .ToBeVisibleAsync(); } From 07d5093583f8b8916893f038c8b84a55f0539794 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Tue, 7 Nov 2023 17:47:15 +0100 Subject: [PATCH 005/491] test: test gardening (#28001) --- tests/page/page-set-input-files.spec.ts | 9 ++++++--- tests/page/selectors-css.spec.ts | 3 ++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/tests/page/page-set-input-files.spec.ts b/tests/page/page-set-input-files.spec.ts index 93d223a88c..19869e9139 100644 --- a/tests/page/page-set-input-files.spec.ts +++ b/tests/page/page-set-input-files.spec.ts @@ -37,9 +37,10 @@ it('should upload the file', async ({ page, server, asset }) => { }, input)).toBe('contents of the file'); }); -it('should upload large file', async ({ page, server, browserName, isMac, isAndroid, mode }, testInfo) => { +it('should upload large file', async ({ page, server, browserName, isMac, isAndroid, isWebView2, mode }, testInfo) => { it.skip(browserName === 'webkit' && isMac && parseInt(os.release(), 10) < 20, 'WebKit for macOS 10.15 is frozen and does not have corresponding protocol features.'); it.skip(isAndroid); + it.skip(isWebView2); it.skip(mode.startsWith('service')); it.slow(); @@ -87,9 +88,10 @@ it('should upload large file', async ({ page, server, browserName, isMac, isAndr await Promise.all([uploadFile, file1.filepath].map(fs.promises.unlink)); }); -it('should upload multiple large files', async ({ page, server, browserName, isMac, isAndroid, mode }, testInfo) => { +it('should upload multiple large files', async ({ page, server, browserName, isMac, isAndroid, isWebView2, mode }, testInfo) => { it.skip(browserName === 'webkit' && isMac && parseInt(os.release(), 10) < 20, 'WebKit for macOS 10.15 is frozen and does not have corresponding protocol features.'); it.skip(isAndroid); + it.skip(isWebView2); it.skip(mode.startsWith('service')); it.slow(); @@ -127,9 +129,10 @@ it('should upload multiple large files', async ({ page, server, browserName, isM await Promise.all(uploadFiles.map(path => fs.promises.unlink(path))); }); -it('should upload large file with relative path', async ({ page, server, browserName, isMac, isAndroid, mode }, testInfo) => { +it('should upload large file with relative path', async ({ page, server, browserName, isMac, isAndroid, isWebView2, mode }, testInfo) => { it.skip(browserName === 'webkit' && isMac && parseInt(os.release(), 10) < 20, 'WebKit for macOS 10.15 is frozen and does not have corresponding protocol features.'); it.skip(isAndroid); + it.skip(isWebView2); it.skip(mode.startsWith('service')); it.slow(); diff --git a/tests/page/selectors-css.spec.ts b/tests/page/selectors-css.spec.ts index 5ce11f11ba..172493dbea 100644 --- a/tests/page/selectors-css.spec.ts +++ b/tests/page/selectors-css.spec.ts @@ -274,8 +274,9 @@ it('should work with :nth-child', async ({ page, server }) => { expect(await page.$$eval(`css=span:nth-child(23n+2)`, els => els.length)).toBe(1); }); -it('should work with :nth-child(of) notation with nested functions', async ({ page, browserName }) => { +it('should work with :nth-child(of) notation with nested functions', async ({ page, browserName, browserMajorVersion }) => { it.fixme(browserName === 'firefox', 'Should enable once Firefox supports this syntax'); + it.skip(browserName === 'chromium' && browserMajorVersion < 111, 'https://caniuse.com/css-nth-child-of'); await page.setContent(`
From a9c4406439a1428b0f01f2559061994c342c923d Mon Sep 17 00:00:00 2001 From: Playwright Service <89237858+playwrightmachine@users.noreply.github.com> Date: Tue, 7 Nov 2023 10:27:33 -0800 Subject: [PATCH 006/491] feat(chromium-tip-of-tree): roll to r1166 (#28009) 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 a5350fd5a4..81bd7c3a6f 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -15,9 +15,9 @@ }, { "name": "chromium-tip-of-tree", - "revision": "1165", + "revision": "1166", "installByDefault": false, - "browserVersion": "121.0.6102.0" + "browserVersion": "121.0.6113.0" }, { "name": "firefox", From eeda25c47f1b5eb2c2a4e2e6354a9c1bc73d16dc Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Tue, 7 Nov 2023 12:58:41 -0800 Subject: [PATCH 007/491] chore(recorder): glue the overlay to the top (#28021) --- .../src/server/injected/recorder.ts | 111 +++++++++--------- .../playwright-core/src/server/recorder.ts | 11 +- .../src/server/recorder/recorderApp.ts | 8 -- packages/recorder/src/main.tsx | 4 +- packages/recorder/src/recorder.tsx | 5 - packages/recorder/src/recorderTypes.ts | 5 +- packages/trace-viewer/src/ui/snapshotTab.tsx | 2 +- 7 files changed, 59 insertions(+), 87 deletions(-) diff --git a/packages/playwright-core/src/server/injected/recorder.ts b/packages/playwright-core/src/server/injected/recorder.ts index d78b92d938..f262fa19bc 100644 --- a/packages/playwright-core/src/server/injected/recorder.ts +++ b/packages/playwright-core/src/server/injected/recorder.ts @@ -658,13 +658,15 @@ class Overlay { private _recordToggle: HTMLElement; private _pickLocatorToggle: HTMLElement; private _assertToggle: HTMLElement; - private _position: { x: number, y: number } = { x: 0, y: 0 }; - private _dragState: { position: { x: number, y: number }, dragStart: { x: number, y: number } } | undefined; + private _offsetX = 0; + private _dragState: { offsetX: number, dragStart: { x: number, y: number } } | undefined; private _measure: { width: number, height: number } = { width: 0, height: 0 }; constructor(private _recorder: Recorder) { const document = this._recorder.injectedScript.document; this._overlayElement = document.createElement('x-pw-overlay'); + this._overlayElement.style.top = '0'; + this._overlayElement.style.position = 'absolute'; const shadow = this._overlayElement.attachShadow({ mode: 'closed' }); const styleElement = document.createElement('style'); @@ -682,23 +684,7 @@ class Overlay { background-color: hsla(0 0% 100% / .9); font-family: 'Dank Mono', 'Operator Mono', Inconsolata, 'Fira Mono', 'SF Mono', Monaco, 'Droid Sans Mono', 'Source Code Pro', monospace; display: flex; - flex-direction: column; - margin: 10px; - padding: 3px 0; - border-radius: 17px; - } - - x-pw-drag-handle { - cursor: grab; - padding: 6px 9px; - } - x-pw-drag-handle > div { - height: 1px; - margin-top: 2px; - background: rgb(148 148 148 / 90%); - } - x-pw-drag-handle:active { - cursor: grabbing; + border-radius: 3px; } x-pw-separator { @@ -712,7 +698,7 @@ class Overlay { height: 28px; width: 28px; margin: 2px 4px; - border-radius: 50%; + border-radius: 3px; } x-pw-tool-item:not(.disabled):hover { background-color: hsl(0, 0%, 86%); @@ -738,7 +724,28 @@ class Overlay { x-pw-tool-item.record.active > div { background-color: #a1260d; } - + x-pw-tool-gripper { + height: 28px; + width: 24px; + margin: 2px 0; + cursor: grab; + } + x-pw-tool-gripper:active { + cursor: grabbing; + } + x-pw-tool-gripper > div { + width: 100%; + height: 100%; + -webkit-mask-repeat: no-repeat; + -webkit-mask-position: center; + -webkit-mask-size: 20px; + mask-repeat: no-repeat; + mask-position: center; + mask-size: 16px; + -webkit-mask-image: url("data:image/svg+xml;utf8,"); + mask-image: url("data:image/svg+xml;utf8,"); + background-color: #555555; + } x-pw-tool-item.record > div { /* codicon: circle-large-filled */ -webkit-mask-image: url("data:image/svg+xml;utf8,"); @@ -754,17 +761,19 @@ class Overlay { -webkit-mask-image: url("data:image/svg+xml;utf8,"); mask-image: url("data:image/svg+xml;utf8,"); } - x-pw-tool-item.close > div { - /* codicon: close */ - -webkit-mask-image: url("data:image/svg+xml;utf8,"); - mask-image: url("data:image/svg+xml;utf8,"); - } `; shadow.appendChild(styleElement); const toolsListElement = document.createElement('x-pw-tools-list'); shadow.appendChild(toolsListElement); + const dragHandle = document.createElement('x-pw-tool-gripper'); + dragHandle.addEventListener('mousedown', event => { + this._dragState = { offsetX: this._offsetX, dragStart: { x: event.clientX, y: 0 } }; + }); + dragHandle.appendChild(document.createElement('div')); + toolsListElement.appendChild(dragHandle); + this._recordToggle = this._recorder.injectedScript.document.createElement('x-pw-tool-item'); this._recordToggle.title = 'Record'; this._recordToggle.classList.add('record'); @@ -774,15 +783,6 @@ class Overlay { }); toolsListElement.appendChild(this._recordToggle); - const dragHandle = document.createElement('x-pw-drag-handle'); - dragHandle.addEventListener('mousedown', event => { - this._dragState = { position: this._position, dragStart: { x: event.clientX, y: event.clientY } }; - }); - dragHandle.append(document.createElement('div')); - dragHandle.append(document.createElement('div')); - dragHandle.append(document.createElement('div')); - toolsListElement.appendChild(dragHandle); - this._pickLocatorToggle = this._recorder.injectedScript.document.createElement('x-pw-tool-item'); this._pickLocatorToggle.title = 'Pick locator'; this._pickLocatorToggle.classList.add('pick-locator'); @@ -809,16 +809,6 @@ class Overlay { }); toolsListElement.appendChild(this._assertToggle); - const closeButton = this._recorder.injectedScript.document.createElement('x-pw-tool-item'); - closeButton.title = 'Hide this overlay'; - closeButton.classList.add('close'); - closeButton.appendChild(this._recorder.injectedScript.document.createElement('div')); - closeButton.addEventListener('click', () => { - this._overlayElement.style.display = 'none'; - this._recorder.delegate.setOverlayState?.({ position: this._position, visible: false }); - }); - toolsListElement.appendChild(closeButton); - this._updateVisualPosition(); } @@ -836,16 +826,14 @@ class Overlay { this._pickLocatorToggle.classList.toggle('active', state.mode === 'inspecting' || state.mode === 'recording-inspecting'); this._assertToggle.classList.toggle('active', state.mode === 'assertingText'); this._assertToggle.classList.toggle('disabled', state.mode === 'none' || state.mode === 'inspecting'); - if (this._position.x !== state.overlay.position.x || this._position.y !== state.overlay.position.y) { - this._position = state.overlay.position; + if (this._offsetX !== state.overlay.offsetX) { + this._offsetX = state.overlay.offsetX; this._updateVisualPosition(); } - this._overlayElement.style.display = state.overlay.visible ? 'block' : 'none'; } private _updateVisualPosition() { - this._overlayElement.style.left = this._position.x + 'px'; - this._overlayElement.style.top = this._position.y + 'px'; + this._overlayElement.style.left = (this._recorder.injectedScript.window.innerWidth / 2 + this._offsetX) + 'px'; } onMouseMove(event: MouseEvent) { @@ -854,14 +842,11 @@ class Overlay { return false; } if (this._dragState) { - this._position = { - x: this._dragState.position.x + event.clientX - this._dragState.dragStart.x, - y: this._dragState.position.y + event.clientY - this._dragState.dragStart.y, - }; - this._position.x = Math.max(0, Math.min(this._recorder.injectedScript.window.innerWidth - this._measure.width, this._position.x)); - this._position.y = Math.max(0, Math.min(this._recorder.injectedScript.window.innerHeight - this._measure.height, this._position.y)); + this._offsetX = this._dragState.offsetX + event.clientX - this._dragState.dragStart.x; + this._offsetX = Math.min(this._recorder.injectedScript.window.innerWidth / 2 - 10 - this._measure.width, this._offsetX); + this._offsetX = Math.max(10 - this._recorder.injectedScript.window.innerWidth / 2, this._offsetX); this._updateVisualPosition(); - this._recorder.delegate.setOverlayState?.({ position: this._position, visible: true }); + this._recorder.delegate.setOverlayState?.({ offsetX: this._offsetX }); consumeEvent(event); return true; } @@ -869,6 +854,14 @@ class Overlay { } onMouseUp(event: MouseEvent) { + if (this._dragState) { + consumeEvent(event); + return true; + } + return false; + } + + onClick(event: MouseEvent) { if (this._dragState) { this._dragState = undefined; consumeEvent(event); @@ -887,7 +880,7 @@ export class Recorder { private _highlight: Highlight; private _overlay: Overlay | undefined; private _styleElement: HTMLStyleElement; - state: UIState = { mode: 'none', testIdAttributeName: 'data-testid', language: 'javascript', overlay: { position: { x: 0, y: 0 }, visible: true } }; + state: UIState = { mode: 'none', testIdAttributeName: 'data-testid', language: 'javascript', overlay: { offsetX: 0 } }; readonly document: Document; delegate: RecorderDelegate = {}; @@ -991,6 +984,8 @@ export class Recorder { private _onClick(event: MouseEvent) { if (!event.isTrusted) return; + if (this._overlay?.onClick(event)) + return; if (this._ignoreOverlayEvent(event)) return; this._currentTool.onClick?.(event); diff --git a/packages/playwright-core/src/server/recorder.ts b/packages/playwright-core/src/server/recorder.ts index 553d12fcc7..f5329072f9 100644 --- a/packages/playwright-core/src/server/recorder.ts +++ b/packages/playwright-core/src/server/recorder.ts @@ -55,7 +55,7 @@ export class Recorder implements InstrumentationListener { private _context: BrowserContext; private _mode: Mode; private _highlightedSelector = ''; - private _overlayState: OverlayState = { position: { x: 0, y: 0 }, visible: true }; + private _overlayState: OverlayState = { offsetX: 0 }; private _recorderApp: IRecorderApp | null = null; private _currentCallsMetadata = new Map(); private _recorderSources: Source[] = []; @@ -101,7 +101,7 @@ export class Recorder implements InstrumentationListener { if (isUnderTest()) { // Most of our tests put elements at the top left, so get out of the way. - this._overlayState.position = { x: 350, y: 350 }; + this._overlayState.offsetX = 200; } } @@ -123,12 +123,6 @@ export class Recorder implements InstrumentationListener { this.setMode(data.params.mode); return; } - if (data.event === 'setOverlayVisible') { - this._overlayState.visible = data.params.visible; - this._recorderApp?.setOverlayVisible(this._overlayState.visible); - this._refreshOverlay(); - return; - } if (data.event === 'selectorUpdated') { this.setHighlightedSelector(this._currentLanguage, data.params.selector); return; @@ -219,7 +213,6 @@ export class Recorder implements InstrumentationListener { if (frame.parentFrame()) return; this._overlayState = state; - this._recorderApp?.setOverlayVisible(state.visible); }); await this._context.exposeBinding('__pw_resume', false, () => { diff --git a/packages/playwright-core/src/server/recorder/recorderApp.ts b/packages/playwright-core/src/server/recorder/recorderApp.ts index de108c8fc4..6f56db67b7 100644 --- a/packages/playwright-core/src/server/recorder/recorderApp.ts +++ b/packages/playwright-core/src/server/recorder/recorderApp.ts @@ -46,7 +46,6 @@ export interface IRecorderApp extends EventEmitter { close(): Promise; setPaused(paused: boolean): Promise; setMode(mode: Mode): Promise; - setOverlayVisible(visible: boolean): Promise; setFileIfNeeded(file: string): Promise; setSelector(selector: string, userGesture?: boolean): Promise; updateCallLogs(callLogs: CallLog[]): Promise; @@ -57,7 +56,6 @@ export class EmptyRecorderApp extends EventEmitter implements IRecorderApp { async close(): Promise {} async setPaused(paused: boolean): Promise {} async setMode(mode: Mode): Promise {} - async setOverlayVisible(visible: boolean): Promise {} async setFileIfNeeded(file: string): Promise {} async setSelector(selector: string, userGesture?: boolean): Promise {} async updateCallLogs(callLogs: CallLog[]): Promise {} @@ -147,12 +145,6 @@ export class RecorderApp extends EventEmitter implements IRecorderApp { }).toString(), { isFunction: true }, mode).catch(() => {}); } - async setOverlayVisible(visible: boolean): Promise { - await this._page.mainFrame().evaluateExpression(((visible: boolean) => { - window.playwrightSetOverlayVisible(visible); - }).toString(), { isFunction: true }, visible).catch(() => {}); - } - async setFileIfNeeded(file: string): Promise { await this._page.mainFrame().evaluateExpression(((file: string) => { window.playwrightSetFileIfNeeded(file); diff --git a/packages/recorder/src/main.tsx b/packages/recorder/src/main.tsx index be5791eb15..5980716d1e 100644 --- a/packages/recorder/src/main.tsx +++ b/packages/recorder/src/main.tsx @@ -25,12 +25,10 @@ export const Main: React.FC = ({ const [paused, setPaused] = React.useState(false); const [log, setLog] = React.useState(new Map()); const [mode, setMode] = React.useState('none'); - const [overlayVisible, setOverlayVisible] = React.useState(true); window.playwrightSetMode = setMode; window.playwrightSetSources = setSources; window.playwrightSetPaused = setPaused; - window.playwrightSetOverlayVisible = setOverlayVisible; window.playwrightUpdateLogs = callLogs => { const newLog = new Map(log); for (const callLog of callLogs) { @@ -41,5 +39,5 @@ export const Main: React.FC = ({ }; window.playwrightSourcesEchoForTest = sources; - return ; + return ; }; diff --git a/packages/recorder/src/recorder.tsx b/packages/recorder/src/recorder.tsx index 65d5628b54..6a1c6eb325 100644 --- a/packages/recorder/src/recorder.tsx +++ b/packages/recorder/src/recorder.tsx @@ -40,7 +40,6 @@ export interface RecorderProps { paused: boolean, log: Map, mode: Mode, - overlayVisible: boolean, } export const Recorder: React.FC = ({ @@ -48,7 +47,6 @@ export const Recorder: React.FC = ({ paused, log, mode, - overlayVisible, }) => { const [fileId, setFileId] = React.useState(); const [selectedTab, setSelectedTab] = React.useState('log'); @@ -156,9 +154,6 @@ export const Recorder: React.FC = ({ window.dispatch({ event: 'clear' }); }}> toggleTheme()}> - { - window.dispatch({ event: 'setOverlayVisible', params: { visible: !overlayVisible } }); - }}> diff --git a/packages/recorder/src/recorderTypes.ts b/packages/recorder/src/recorderTypes.ts index d8ac13240e..0d4523d09f 100644 --- a/packages/recorder/src/recorderTypes.ts +++ b/packages/recorder/src/recorderTypes.ts @@ -21,13 +21,12 @@ export type Point = { x: number, y: number }; export type Mode = 'inspecting' | 'recording' | 'none' | 'assertingText' | 'recording-inspecting'; export type EventData = { - event: 'clear' | 'resume' | 'step' | 'pause' | 'setMode' | 'selectorUpdated' | 'fileChanged' | 'setOverlayVisible'; + event: 'clear' | 'resume' | 'step' | 'pause' | 'setMode' | 'selectorUpdated' | 'fileChanged'; params: any; }; export type OverlayState = { - position: Point; - visible: boolean; + offsetX: number; }; export type UIState = { diff --git a/packages/trace-viewer/src/ui/snapshotTab.tsx b/packages/trace-viewer/src/ui/snapshotTab.tsx index 793fb93908..a292fd6785 100644 --- a/packages/trace-viewer/src/ui/snapshotTab.tsx +++ b/packages/trace-viewer/src/ui/snapshotTab.tsx @@ -242,7 +242,7 @@ export const InspectModeController: React.FunctionComponent<{ actionSelector: actionSelector.startsWith(frameSelector) ? actionSelector.substring(frameSelector.length).trim() : undefined, language: sdkLanguage, testIdAttributeName, - overlay: { position: { x: 0, y: 0 }, visible: false }, + overlay: { offsetX: 0 }, }, { async setSelector(selector: string) { setHighlightedLocator(asLocator(sdkLanguage, frameSelector + selector, false /* isFrameLocator */, true /* playSafe */)); From b9aaa38d3bf6c86029042e3d3ec0470650f5c611 Mon Sep 17 00:00:00 2001 From: Playwright Service <89237858+playwrightmachine@users.noreply.github.com> Date: Tue, 7 Nov 2023 13:20:55 -0800 Subject: [PATCH 008/491] feat(firefox): roll to r1429 (#28020) Fixes https://github.com/microsoft/playwright/issues/27682 Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> --- README.md | 4 ++-- packages/playwright-core/browsers.json | 8 ++++---- .../src/server/deviceDescriptorsSource.json | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 52eae54863..50b0de8f45 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-120.0.6099.5-blue.svg?logo=google-chrome)](https://www.chromium.org/Home) [![Firefox version](https://img.shields.io/badge/firefox-118.0.1-blue.svg?logo=firefoxbrowser)](https://www.mozilla.org/en-US/firefox/new/) [![WebKit version](https://img.shields.io/badge/webkit-17.4-blue.svg?logo=safari)](https://webkit.org/) +[![npm version](https://img.shields.io/npm/v/playwright.svg)](https://www.npmjs.com/package/playwright) [![Chromium version](https://img.shields.io/badge/chromium-120.0.6099.5-blue.svg?logo=google-chrome)](https://www.chromium.org/Home) [![Firefox version](https://img.shields.io/badge/firefox-119.0-blue.svg?logo=firefoxbrowser)](https://www.mozilla.org/en-US/firefox/new/) [![WebKit version](https://img.shields.io/badge/webkit-17.4-blue.svg?logo=safari)](https://webkit.org/) ## [Documentation](https://playwright.dev) | [API reference](https://playwright.dev/docs/api/class-playwright) @@ -10,7 +10,7 @@ Playwright is a framework for Web Testing and Automation. It allows testing [Chr | :--- | :---: | :---: | :---: | | Chromium 120.0.6099.5 | :white_check_mark: | :white_check_mark: | :white_check_mark: | | WebKit 17.4 | :white_check_mark: | :white_check_mark: | :white_check_mark: | -| Firefox 118.0.1 | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| Firefox 119.0 | :white_check_mark: | :white_check_mark: | :white_check_mark: | Headless execution is supported for all browsers on all platforms. Check out [system requirements](https://playwright.dev/docs/intro#system-requirements) for details. diff --git a/packages/playwright-core/browsers.json b/packages/playwright-core/browsers.json index 81bd7c3a6f..68170e4307 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -21,15 +21,15 @@ }, { "name": "firefox", - "revision": "1428", + "revision": "1429", "installByDefault": true, - "browserVersion": "118.0.1" + "browserVersion": "119.0" }, { "name": "firefox-asan", - "revision": "1428", + "revision": "1429", "installByDefault": false, - "browserVersion": "118.0.1" + "browserVersion": "119.0" }, { "name": "firefox-beta", diff --git a/packages/playwright-core/src/server/deviceDescriptorsSource.json b/packages/playwright-core/src/server/deviceDescriptorsSource.json index 29f965c95b..bfc39f856c 100644 --- a/packages/playwright-core/src/server/deviceDescriptorsSource.json +++ b/packages/playwright-core/src/server/deviceDescriptorsSource.json @@ -1472,7 +1472,7 @@ "defaultBrowserType": "chromium" }, "Desktop Firefox HiDPI": { - "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:118.0.1) Gecko/20100101 Firefox/118.0.1", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:119.0) Gecko/20100101 Firefox/119.0", "screen": { "width": 1792, "height": 1120 @@ -1532,7 +1532,7 @@ "defaultBrowserType": "chromium" }, "Desktop Firefox": { - "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:118.0.1) Gecko/20100101 Firefox/118.0.1", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:119.0) Gecko/20100101 Firefox/119.0", "screen": { "width": 1920, "height": 1080 From cb14de7a5b2716be7e225be1fca707ff97d7d77b Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Tue, 7 Nov 2023 14:09:40 -0800 Subject: [PATCH 009/491] chore: do not use ansi outsite of TTY (#27979) Fixes https://github.com/microsoft/playwright/issues/27891 --- packages/playwright/src/reporters/base.ts | 33 ++++++++---- packages/playwright/src/reporters/dot.ts | 3 +- packages/playwright/src/reporters/html.ts | 4 +- .../src/reporters/internalReporter.ts | 3 +- packages/playwright/src/reporters/line.ts | 3 +- packages/playwright/src/reporters/list.ts | 53 ++++++++++--------- 6 files changed, 57 insertions(+), 42 deletions(-) diff --git a/packages/playwright/src/reporters/base.ts b/packages/playwright/src/reporters/base.ts index 15bb85159f..7e94cb2f6a 100644 --- a/packages/playwright/src/reporters/base.ts +++ b/packages/playwright/src/reporters/base.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { colors, ms as milliseconds, parseStackTraceLine } from 'playwright-core/lib/utilsBundle'; +import { colors as realColors, ms as milliseconds, parseStackTraceLine } from 'playwright-core/lib/utilsBundle'; import path from 'path'; import type { FullConfig, TestCase, Suite, TestResult, TestError, FullResult, TestStep, Location } from '../../types/testReporter'; import type { SuitePrivate } from '../../types/reporterPrivate'; @@ -45,6 +45,28 @@ type TestSummary = { fatalErrors: TestError[]; }; +export const isTTY = !!process.env.PWTEST_TTY_WIDTH || process.stdout.isTTY; +export const ttyWidth = process.env.PWTEST_TTY_WIDTH ? parseInt(process.env.PWTEST_TTY_WIDTH, 10) : process.stdout.columns || 0; +let useColors = isTTY; +if (process.env.DEBUG_COLORS === '0' + || process.env.DEBUG_COLORS === 'false' + || process.env.FORCE_COLOR === '0' + || process.env.FORCE_COLOR === 'false') + useColors = false; +else if (process.env.DEBUG_COLORS || process.env.FORCE_COLOR) + useColors = true; + +export const colors = useColors ? realColors : { + bold: (t: string) => t, + cyan: (t: string) => t, + dim: (t: string) => t, + gray: (t: string) => t, + green: (t: string) => t, + red: (t: string) => t, + yellow: (t: string) => t, + enabled: false, +}; + export class BaseReporter implements ReporterV2 { config!: FullConfig; suite!: Suite; @@ -52,13 +74,11 @@ export class BaseReporter implements ReporterV2 { result!: FullResult; private fileDurations = new Map(); private _omitFailures: boolean; - private readonly _ttyWidthForTest: number; private _fatalErrors: TestError[] = []; private _failureCount: number = 0; constructor(options: { omitFailures?: boolean } = {}) { this._omitFailures = options.omitFailures || false; - this._ttyWidthForTest = parseInt(process.env.PWTEST_TTY_WIDTH || '', 10); } version(): 'v2' { @@ -128,12 +148,7 @@ export class BaseReporter implements ReporterV2 { return true; } - protected ttyWidth() { - return this._ttyWidthForTest || process.stdout.columns || 0; - } - protected fitToScreen(line: string, prefix?: string): string { - const ttyWidth = this.ttyWidth(); if (!ttyWidth) { // Guard against the case where we cannot determine available width. return line; @@ -473,7 +488,7 @@ export function formatError(error: TestError, highlightCode: boolean): ErrorDeta export function separator(text: string = ''): string { if (text) text += ' '; - const columns = Math.min(100, process.stdout?.columns || 100); + const columns = Math.min(100, ttyWidth || 100); return text + colors.dim('─'.repeat(Math.max(0, columns - text.length))); } diff --git a/packages/playwright/src/reporters/dot.ts b/packages/playwright/src/reporters/dot.ts index 71599ff352..05d2da1e4e 100644 --- a/packages/playwright/src/reporters/dot.ts +++ b/packages/playwright/src/reporters/dot.ts @@ -14,8 +14,7 @@ * limitations under the License. */ -import { colors } from 'playwright-core/lib/utilsBundle'; -import { BaseReporter, formatError } from './base'; +import { colors, BaseReporter, formatError } from './base'; import type { FullResult, TestCase, TestResult, Suite, TestError } from '../../types/testReporter'; class DotReporter extends BaseReporter { diff --git a/packages/playwright/src/reporters/html.ts b/packages/playwright/src/reporters/html.ts index 731ed0be3c..ddac659269 100644 --- a/packages/playwright/src/reporters/html.ts +++ b/packages/playwright/src/reporters/html.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { colors, open } from 'playwright-core/lib/utilsBundle'; +import { open } from 'playwright-core/lib/utilsBundle'; import { MultiMap, getPackageManagerExecCommand } from 'playwright-core/lib/utils'; import fs from 'fs'; import path from 'path'; @@ -25,7 +25,7 @@ import { codeFrameColumns } from '../transform/babelBundle'; import type { FullResult, FullConfig, Location, Suite, TestCase as TestCasePublic, TestResult as TestResultPublic, TestStep as TestStepPublic, TestError } from '../../types/testReporter'; import type { SuitePrivate } from '../../types/reporterPrivate'; import { HttpServer, assert, calculateSha1, copyFileAndMakeWritable, gracefullyProcessExitDoNotHang, removeFolders, sanitizeForFilePath } from 'playwright-core/lib/utils'; -import { formatError, formatResultFailure, stripAnsiEscapes } from './base'; +import { colors, formatError, formatResultFailure, stripAnsiEscapes } from './base'; import { resolveReporterOutputPath } from '../util'; import type { Metadata } from '../../types/test'; import type { ZipFile } from 'playwright-core/lib/zipBundle'; diff --git a/packages/playwright/src/reporters/internalReporter.ts b/packages/playwright/src/reporters/internalReporter.ts index fefb089b0b..02959ab11b 100644 --- a/packages/playwright/src/reporters/internalReporter.ts +++ b/packages/playwright/src/reporters/internalReporter.ts @@ -15,11 +15,10 @@ */ import fs from 'fs'; -import { colors } from 'playwright-core/lib/utilsBundle'; import { codeFrameColumns } from '../transform/babelBundle'; import type { FullConfig, TestCase, TestError, TestResult, FullResult, TestStep } from '../../types/testReporter'; import { Suite } from '../common/test'; -import { prepareErrorStack, relativeFilePath } from './base'; +import { colors, prepareErrorStack, relativeFilePath } from './base'; import type { ReporterV2 } from './reporterV2'; import { monotonicTime } from 'playwright-core/lib/utils'; diff --git a/packages/playwright/src/reporters/line.ts b/packages/playwright/src/reporters/line.ts index 66345aa879..a44f29d02e 100644 --- a/packages/playwright/src/reporters/line.ts +++ b/packages/playwright/src/reporters/line.ts @@ -14,8 +14,7 @@ * limitations under the License. */ -import { colors } from 'playwright-core/lib/utilsBundle'; -import { BaseReporter, formatError, formatFailure, formatTestTitle } from './base'; +import { colors, BaseReporter, formatError, formatFailure, formatTestTitle } from './base'; import type { TestCase, Suite, TestResult, FullResult, TestStep, TestError } from '../../types/testReporter'; class LineReporter extends BaseReporter { diff --git a/packages/playwright/src/reporters/list.ts b/packages/playwright/src/reporters/list.ts index 71998c49d9..11f6a8b755 100644 --- a/packages/playwright/src/reporters/list.ts +++ b/packages/playwright/src/reporters/list.ts @@ -14,8 +14,8 @@ * limitations under the License. */ -import { colors, ms as milliseconds } from 'playwright-core/lib/utilsBundle'; -import { BaseReporter, formatError, formatTestTitle, stepSuffix, stripAnsiEscapes } from './base'; +import { ms as milliseconds } from 'playwright-core/lib/utilsBundle'; +import { colors, BaseReporter, formatError, formatTestTitle, isTTY, stepSuffix, stripAnsiEscapes, ttyWidth } from './base'; import type { FullResult, Suite, TestCase, TestError, TestResult, TestStep } from '../../types/testReporter'; // Allow it in the Visual Studio Code Terminal and the new Windows Terminal @@ -31,13 +31,11 @@ class ListReporter extends BaseReporter { private _resultIndex = new Map(); private _stepIndex = new Map(); private _needNewLine = false; - private readonly _liveTerminal: string | boolean | undefined; private _printSteps: boolean; constructor(options: { omitFailures?: boolean, printSteps?: boolean } = {}) { super(options); - this._printSteps = options.printSteps || !!process.env.PW_TEST_DEBUG_REPORTERS_PRINT_STEPS; - this._liveTerminal = process.stdout.isTTY || !!process.env.PWTEST_TTY_WIDTH; + this._printSteps = isTTY && (options.printSteps || !!process.env.PW_TEST_DEBUG_REPORTERS_PRINT_STEPS); } override printsToStdio() { @@ -55,16 +53,15 @@ class ListReporter extends BaseReporter { override onTestBegin(test: TestCase, result: TestResult) { super.onTestBegin(test, result); - if (this._liveTerminal) - this._maybeWriteNewLine(); - this._resultIndex.set(result, String(this._resultIndex.size + 1)); - if (this._liveTerminal) { - this._testRows.set(test, this._lastRow); - const index = this._resultIndex.get(result)!; - const prefix = this._testPrefix(index, ''); - const line = colors.dim(formatTestTitle(this.config, test)) + this._retrySuffix(result); - this._appendLine(line, prefix); - } + if (!isTTY) + return; + this._maybeWriteNewLine(); + const index = String(this._resultIndex.size + 1); + this._resultIndex.set(result, index); + this._testRows.set(test, this._lastRow); + const prefix = this._testPrefix(index, ''); + const line = colors.dim(formatTestTitle(this.config, test)) + this._retrySuffix(result); + this._appendLine(line, prefix); } override onStdOut(chunk: string | Buffer, test?: TestCase, result?: TestResult) { @@ -81,9 +78,9 @@ class ListReporter extends BaseReporter { super.onStepBegin(test, result, step); if (step.category !== 'test.step') return; - const testIndex = this._resultIndex.get(result)!; + const testIndex = this._resultIndex.get(result) || ''; if (!this._printSteps) { - if (this._liveTerminal) + if (isTTY) this._updateLine(this._testRows.get(test)!, colors.dim(formatTestTitle(this.config, test, step)) + this._retrySuffix(result), this._testPrefix(testIndex, '')); return; } @@ -93,9 +90,8 @@ class ListReporter extends BaseReporter { const stepIndex = `${testIndex}.${ordinal}`; this._stepIndex.set(step, stepIndex); - if (this._liveTerminal) + if (isTTY) { this._maybeWriteNewLine(); - if (this._liveTerminal) { this._stepRows.set(step, this._lastRow); const prefix = this._testPrefix(stepIndex, ''); const line = test.title + colors.dim(stepSuffix(step)); @@ -108,9 +104,9 @@ class ListReporter extends BaseReporter { if (step.category !== 'test.step') return; - const testIndex = this._resultIndex.get(result)!; + const testIndex = this._resultIndex.get(result) || ''; if (!this._printSteps) { - if (this._liveTerminal) + if (isTTY) this._updateLine(this._testRows.get(test)!, colors.dim(formatTestTitle(this.config, test, step.parent)) + this._retrySuffix(result), this._testPrefix(testIndex, '')); return; } @@ -137,8 +133,7 @@ class ListReporter extends BaseReporter { private _updateLineCountAndNewLineFlagForOutput(text: string) { this._needNewLine = text[text.length - 1] !== '\n'; - const ttyWidth = this.ttyWidth(); - if (!this._liveTerminal || ttyWidth === 0) + if (!ttyWidth) return; for (const ch of text) { if (ch === '\n') { @@ -168,7 +163,15 @@ class ListReporter extends BaseReporter { const title = formatTestTitle(this.config, test); let prefix = ''; let text = ''; - const index = this._resultIndex.get(result)!; + + // In TTY mode test index is incremented in onTestStart + // and in non-TTY mode it is incremented onTestEnd. + let index = this._resultIndex.get(result); + if (!index) { + index = String(this._resultIndex.size + 1); + this._resultIndex.set(result, index); + } + if (result.status === 'skipped') { prefix = this._testPrefix(index, colors.green('-')); // Do not show duration for skipped. @@ -189,7 +192,7 @@ class ListReporter extends BaseReporter { } private _updateOrAppendLine(row: number, text: string, prefix: string) { - if (this._liveTerminal) { + if (isTTY) { this._updateLine(row, text, prefix); } else { this._maybeWriteNewLine(); From e788c711c6b482a4b38daf0298a110d9d02cddb7 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Tue, 7 Nov 2023 23:42:17 +0100 Subject: [PATCH 010/491] fix: electron video tests (#28004) --- packages/playwright-core/src/server/chromium/crBrowser.ts | 6 +++++- packages/playwright-core/src/server/electron/electron.ts | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/playwright-core/src/server/chromium/crBrowser.ts b/packages/playwright-core/src/server/chromium/crBrowser.ts index 016663707e..0217005427 100644 --- a/packages/playwright-core/src/server/chromium/crBrowser.ts +++ b/packages/playwright-core/src/server/chromium/crBrowser.ts @@ -525,7 +525,7 @@ export class CRBrowserContext extends BrowserContext { await Promise.all(openedBeforeUnloadDialogs.map(dialog => dialog.dismiss())); if (!this._browserContextId) { - await Promise.all(this._crPages().map(crPage => crPage._mainFrameSession._stopVideoRecording())); + await this.stopVideoRecording(); // Closing persistent context should close the browser. await this._browser.close({ reason }); return; @@ -545,6 +545,10 @@ export class CRBrowserContext extends BrowserContext { } } + async stopVideoRecording() { + await Promise.all(this._crPages().map(crPage => crPage._mainFrameSession._stopVideoRecording())); + } + onClosePersistent() { // When persistent context is closed, we do not necessary get Target.detachedFromTarget // for all the background pages. diff --git a/packages/playwright-core/src/server/electron/electron.ts b/packages/playwright-core/src/server/electron/electron.ts index 677446695b..eeb27552a8 100644 --- a/packages/playwright-core/src/server/electron/electron.ts +++ b/packages/playwright-core/src/server/electron/electron.ts @@ -78,6 +78,7 @@ export class ElectronApplication extends SdkObject { }); }); this._browserContext.setCustomCloseHandler(async () => { + await this._browserContext.stopVideoRecording(); const electronHandle = await this._nodeElectronHandlePromise; await electronHandle.evaluate(({ app }) => app.quit()).catch(() => {}); }); From 1a8b61199ff65eef6c3b4ddd53446c98871f3a3b Mon Sep 17 00:00:00 2001 From: Playwright Service <89237858+playwrightmachine@users.noreply.github.com> Date: Tue, 7 Nov 2023 19:22:34 -0800 Subject: [PATCH 011/491] feat(firefox-beta): roll to r1429 (#28024) 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 68170e4307..2978c5c966 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -33,9 +33,9 @@ }, { "name": "firefox-beta", - "revision": "1428", + "revision": "1429", "installByDefault": false, - "browserVersion": "118.0b5" + "browserVersion": "120.0b8" }, { "name": "webkit", From 061ded19b674d956fb1ac3e9e1a2a17543f86764 Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Tue, 7 Nov 2023 19:36:12 -0800 Subject: [PATCH 012/491] chore: place overlay inside the glass pane (#28026) --- .../src/server/injected/highlight.css.ts | 172 +++++++++++++++++ .../src/server/injected/highlight.ts | 77 +++----- .../src/server/injected/recorder.ts | 179 ++++-------------- .../src/server/recorder/recorderApp.ts | 6 +- packages/recorder/src/recorder.tsx | 9 +- .../web/src/components/codeMirrorWrapper.css | 4 + .../web/src/components/codeMirrorWrapper.tsx | 1 - packages/web/src/components/toolbarButton.css | 4 - 8 files changed, 252 insertions(+), 200 deletions(-) create mode 100644 packages/playwright-core/src/server/injected/highlight.css.ts diff --git a/packages/playwright-core/src/server/injected/highlight.css.ts b/packages/playwright-core/src/server/injected/highlight.css.ts new file mode 100644 index 0000000000..18955755ba --- /dev/null +++ b/packages/playwright-core/src/server/injected/highlight.css.ts @@ -0,0 +1,172 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export const highlightCSS = ` +x-pw-tooltip { + backdrop-filter: blur(5px); + background-color: white; + color: #222; + border-radius: 6px; + box-shadow: 0 0.5rem 1.2rem rgba(0,0,0,.3); + display: none; + font-family: 'Dank Mono', 'Operator Mono', Inconsolata, 'Fira Mono', + 'SF Mono', Monaco, 'Droid Sans Mono', 'Source Code Pro', monospace; + font-size: 12.8px; + font-weight: normal; + left: 0; + line-height: 1.5; + max-width: 600px; + position: absolute; + top: 0; +} +x-pw-tooltip-body { + align-items: center; + padding: 3.2px 5.12px 3.2px; +} +x-pw-highlight { + position: absolute; + top: 0; + left: 0; + width: 0; + height: 0; +} +x-pw-action-point { + position: absolute; + width: 20px; + height: 20px; + background: red; + border-radius: 10px; + margin: -10px 0 0 -10px; + z-index: 2; +} +x-pw-separator { + height: 1px; + margin: 6px 9px; + background: rgb(148 148 148 / 90%); +} +x-pw-tool-gripper { + height: 28px; + width: 24px; + margin: 2px 0; + cursor: grab; +} +x-pw-tool-gripper:active { + cursor: grabbing; +} +x-pw-tool-gripper > x-div { + width: 100%; + height: 100%; + -webkit-mask-repeat: no-repeat; + -webkit-mask-position: center; + -webkit-mask-size: 20px; + mask-repeat: no-repeat; + mask-position: center; + mask-size: 16px; + -webkit-mask-image: url("data:image/svg+xml;utf8,"); + mask-image: url("data:image/svg+xml;utf8,"); + background-color: #555555; +} +x-pw-tools-list { + display: flex; + width: 100%; + border-bottom: 1px solid #dddddd; +} +x-pw-tool-item { + pointer-events: auto; + cursor: pointer; + height: 28px; + width: 28px; + border-radius: 3px; +} +x-pw-tool-item:not(.disabled):hover { + background-color: hsl(0, 0%, 86%); +} +x-pw-tool-item > x-div { + width: 100%; + height: 100%; + -webkit-mask-repeat: no-repeat; + -webkit-mask-position: center; + -webkit-mask-size: 20px; + mask-repeat: no-repeat; + mask-position: center; + mask-size: 16px; + background-color: #3a3a3a; +} +x-pw-tool-item.disabled > x-div { + background-color: rgba(97, 97, 97, 0.5); + cursor: default; +} +x-pw-tool-item.active > x-div { + background-color: #006ab1; +} +x-pw-tool-item.record.active > x-div { + background-color: #a1260d; +} +x-pw-tool-item.accept > x-div { + background-color: #388a34; +} +x-pw-tool-item.cancel > x-div { + background-color: #e51400; +} +x-pw-tool-item.record > x-div { + /* codicon: circle-large-filled */ + -webkit-mask-image: url("data:image/svg+xml;utf8,"); + mask-image: url("data:image/svg+xml;utf8,"); +} +x-pw-tool-item.pick-locator > x-div { + /* codicon: inspect */ + -webkit-mask-image: url("data:image/svg+xml;utf8,"); + mask-image: url("data:image/svg+xml;utf8,"); +} +x-pw-tool-item.assert > x-div { + /* codicon: check-all */ + -webkit-mask-image: url("data:image/svg+xml;utf8,"); + mask-image: url("data:image/svg+xml;utf8,"); +} +x-pw-tool-item.accept > x-div { + -webkit-mask-image: url("data:image/svg+xml;utf8,"); + mask-image: url("data:image/svg+xml;utf8,"); +} +x-pw-tool-item.cancel > x-div { + -webkit-mask-image: url("data:image/svg+xml;utf8,"); + mask-image: url("data:image/svg+xml;utf8,"); +} +x-pw-overlay { + position: absolute; + top: 0; + max-width: min-content; + z-index: 2147483647; + background: transparent; + pointer-events: auto; +} +x-pw-overlay x-pw-tools-list { + background-color: #ffffffdd; + box-shadow: rgba(0, 0, 0, 0.1) 0px 5px 5px; + border-radius: 3px; + border-bottom: none; +} +x-pw-overlay x-pw-tool-item { + margin: 2px; +} +x-div { + display: block; +} +* { + box-sizing: border-box; +} +*[hidden] { + display: none !important; +}`; diff --git a/packages/playwright-core/src/server/injected/highlight.ts b/packages/playwright-core/src/server/injected/highlight.ts index 16dd044949..d6253d3292 100644 --- a/packages/playwright-core/src/server/injected/highlight.ts +++ b/packages/playwright-core/src/server/injected/highlight.ts @@ -19,6 +19,7 @@ import type { ParsedSelector } from '../../utils/isomorphic/selectorParser'; import type { InjectedScript } from './injectedScript'; import { asLocator } from '../../utils/isomorphic/locatorGenerators'; import type { Language } from '../../utils/isomorphic/locatorGenerators'; +import { highlightCSS } from './highlight.css'; type HighlightEntry = { targetElement: Element, @@ -34,7 +35,8 @@ export type HighlightOptions = { tooltipText?: string; color?: string; anchorGetter?: (element: Element) => DOMRect; - decorateTooltip?: (tooltip: Element) => void; + toolbar?: Element[]; + interactive?: boolean; }; export class Highlight { @@ -67,44 +69,7 @@ export class Highlight { this._glassPaneShadow = this._glassPaneElement.attachShadow({ mode: this._isUnderTest ? 'open' : 'closed' }); this._glassPaneShadow.appendChild(this._actionPointElement); const styleElement = document.createElement('style'); - styleElement.textContent = ` - x-pw-tooltip { - align-items: center; - backdrop-filter: blur(5px); - background-color: rgba(0, 0, 0, 0.7); - border-radius: 2px; - box-shadow: rgba(0, 0, 0, 0.1) 0px 3.6px 3.7px, - rgba(0, 0, 0, 0.15) 0px 12.1px 12.3px, - rgba(0, 0, 0, 0.1) 0px -2px 4px, - rgba(0, 0, 0, 0.15) 0px -12.1px 24px, - rgba(0, 0, 0, 0.25) 0px 54px 55px; - color: rgb(204, 204, 204); - display: none; - font-family: 'Dank Mono', 'Operator Mono', Inconsolata, 'Fira Mono', - 'SF Mono', Monaco, 'Droid Sans Mono', 'Source Code Pro', monospace; - font-size: 12.8px; - font-weight: normal; - left: 0; - line-height: 1.5; - max-width: 600px; - padding: 3.2px 5.12px 3.2px; - position: absolute; - top: 0; - } - x-pw-action-point { - position: absolute; - width: 20px; - height: 20px; - background: red; - border-radius: 10px; - pointer-events: none; - margin: -10px 0 0 -10px; - z-index: 2; - } - *[hidden] { - display: none !important; - } - `; + styleElement.textContent = highlightCSS; this._glassPaneShadow.appendChild(styleElement); } @@ -180,13 +145,26 @@ export class Highlight { let tooltipElement; if (options.tooltipText) { tooltipElement = this._injectedScript.document.createElement('x-pw-tooltip'); - this._glassPaneShadow.appendChild(tooltipElement); - const suffix = elements.length > 1 ? ` [${i + 1} of ${elements.length}]` : ''; - tooltipElement.textContent = options.tooltipText + suffix; tooltipElement.style.top = '0'; tooltipElement.style.left = '0'; tooltipElement.style.display = 'flex'; - options.decorateTooltip?.(tooltipElement); + tooltipElement.style.flexDirection = 'column'; + tooltipElement.style.alignItems = 'start'; + if (options.interactive) + tooltipElement.style.pointerEvents = 'auto'; + + if (options.toolbar) { + const toolbar = this._injectedScript.document.createElement('x-pw-tools-list'); + tooltipElement.appendChild(toolbar); + for (const toolbarElement of options.toolbar) + toolbar.appendChild(toolbarElement); + } + const bodyElement = this._injectedScript.document.createElement('x-pw-tooltip-body'); + tooltipElement.appendChild(bodyElement); + + this._glassPaneShadow.appendChild(tooltipElement); + const suffix = elements.length > 1 ? ` [${i + 1} of ${elements.length}]` : ''; + bodyElement.textContent = options.tooltipText + suffix; } this._highlightEntries.push({ targetElement: elements[i], tooltipElement, highlightElement, tooltipText: options.tooltipText }); } @@ -260,13 +238,10 @@ export class Highlight { } private _createHighlightElement(): HTMLElement { - const highlightElement = this._injectedScript.document.createElement('x-pw-highlight'); - highlightElement.style.position = 'absolute'; - highlightElement.style.top = '0'; - highlightElement.style.left = '0'; - highlightElement.style.width = '0'; - highlightElement.style.height = '0'; - highlightElement.style.boxSizing = 'border-box'; - return highlightElement; + return this._injectedScript.document.createElement('x-pw-highlight'); + } + + appendChild(element: HTMLElement) { + this._glassPaneShadow.appendChild(element); } } diff --git a/packages/playwright-core/src/server/injected/recorder.ts b/packages/playwright-core/src/server/injected/recorder.ts index f262fa19bc..19545c15f5 100644 --- a/packages/playwright-core/src/server/injected/recorder.ts +++ b/packages/playwright-core/src/server/injected/recorder.ts @@ -36,8 +36,7 @@ interface RecorderDelegate { interface RecorderTool { cursor(): string; - enable?(): void; - disable?(): void; + cleanup?(): void; onClick?(event: MouseEvent): void; onDragStart?(event: DragEvent): void; onInput?(event: Event): void; @@ -70,7 +69,7 @@ class InspectTool implements RecorderTool { return 'pointer'; } - disable() { + cleanup() { this._hoveredModel = null; this._hoveredElement = null; } @@ -151,7 +150,7 @@ class RecordActionTool implements RecorderTool { return 'pointer'; } - disable() { + cleanup() { this._hoveredModel = null; this._hoveredElement = null; this._activeModel = null; @@ -447,25 +446,25 @@ class TextAssertionTool implements RecorderTool { private _selectionText: { selectedText: string, fullText: string } | null = null; private _inputHighlight: HighlightModel | null = null; private _acceptButton: HTMLElement; + private _cancelButton: HTMLElement; constructor(private _recorder: Recorder) { - this._acceptButton = this._recorder.document.createElement('button'); - this._acceptButton.textContent = 'Accept'; - this._acceptButton.style.cursor = 'pointer'; - this._acceptButton.style.pointerEvents = 'auto'; + this._acceptButton = this._recorder.document.createElement('x-pw-tool-item'); + this._acceptButton.classList.add('accept'); + this._acceptButton.appendChild(this._recorder.document.createElement('x-div')); this._acceptButton.addEventListener('click', () => this._commitAction()); + + this._cancelButton = this._recorder.document.createElement('x-pw-tool-item'); + this._cancelButton.classList.add('cancel'); + this._cancelButton.appendChild(this._recorder.document.createElement('x-div')); + this._cancelButton.addEventListener('click', () => this._cancelAction()); } cursor() { return 'text'; } - enable() { - this._recorder.injectedScript.document.designMode = 'on'; - } - - disable() { - this._recorder.injectedScript.document.designMode = 'off'; + cleanup() { this._hoverHighlight = null; this._selectionHighlight = null; this._selectionText = null; @@ -473,11 +472,6 @@ class TextAssertionTool implements RecorderTool { } onClick(event: MouseEvent) { - // Hack: work around highlight's glass pane having a closed shadow root. - const box = this._acceptButton.getBoundingClientRect(); - if (box.left <= event.clientX && event.clientX <= box.right && box.top <= event.clientY && event.clientY <= box.bottom) - return; - consumeEvent(event); const selection = this._recorder.document.getSelection(); if (event.detail === 1 && selection && !selection.toString() && !this._inputHighlight) { @@ -612,6 +606,10 @@ class TextAssertionTool implements RecorderTool { } } + private _cancelAction() { + this._resetSelectionAndHighlight(); + } + private _resetSelectionAndHighlight() { this._selectionHighlight = null; this._selectionText = null; @@ -643,7 +641,12 @@ class TextAssertionTool implements RecorderTool { } private _showHighlight(userGesture: boolean) { - const options: HighlightOptions = { color: '#6fdcbd38', tooltipText: this._generateActionPreview(), decorateTooltip: tooltip => tooltip.appendChild(this._acceptButton) }; + const options: HighlightOptions = { + color: '#6fdcbd38', + tooltipText: this._generateActionPreview(), + toolbar: [this._acceptButton, this._cancelButton], + interactive: true, + }; if (this._inputHighlight) { this._recorder.updateHighlight(this._inputHighlight, userGesture, options); } else { @@ -665,119 +668,21 @@ class Overlay { constructor(private _recorder: Recorder) { const document = this._recorder.injectedScript.document; this._overlayElement = document.createElement('x-pw-overlay'); - this._overlayElement.style.top = '0'; - this._overlayElement.style.position = 'absolute'; - - const shadow = this._overlayElement.attachShadow({ mode: 'closed' }); - const styleElement = document.createElement('style'); - styleElement.textContent = ` - :host { - position: fixed; - max-width: min-content; - z-index: 2147483647; - background: transparent; - } - - x-pw-tools-list { - box-shadow: rgba(0, 0, 0, 0.1) 0px 5px 5px; - backdrop-filter: blur(5px); - background-color: hsla(0 0% 100% / .9); - font-family: 'Dank Mono', 'Operator Mono', Inconsolata, 'Fira Mono', 'SF Mono', Monaco, 'Droid Sans Mono', 'Source Code Pro', monospace; - display: flex; - border-radius: 3px; - } - - x-pw-separator { - height: 1px; - margin: 6px 9px; - background: rgb(148 148 148 / 90%); - } - - x-pw-tool-item { - cursor: pointer; - height: 28px; - width: 28px; - margin: 2px 4px; - border-radius: 3px; - } - x-pw-tool-item:not(.disabled):hover { - background-color: hsl(0, 0%, 86%); - } - x-pw-tool-item > div { - width: 100%; - height: 100%; - -webkit-mask-repeat: no-repeat; - -webkit-mask-position: center; - -webkit-mask-size: 20px; - mask-repeat: no-repeat; - mask-position: center; - mask-size: 16px; - background-color: #3a3a3a; - } - x-pw-tool-item.disabled > div { - background-color: rgba(97, 97, 97, 0.5); - cursor: default; - } - x-pw-tool-item.active > div { - background-color: #006ab1; - } - x-pw-tool-item.record.active > div { - background-color: #a1260d; - } - x-pw-tool-gripper { - height: 28px; - width: 24px; - margin: 2px 0; - cursor: grab; - } - x-pw-tool-gripper:active { - cursor: grabbing; - } - x-pw-tool-gripper > div { - width: 100%; - height: 100%; - -webkit-mask-repeat: no-repeat; - -webkit-mask-position: center; - -webkit-mask-size: 20px; - mask-repeat: no-repeat; - mask-position: center; - mask-size: 16px; - -webkit-mask-image: url("data:image/svg+xml;utf8,"); - mask-image: url("data:image/svg+xml;utf8,"); - background-color: #555555; - } - x-pw-tool-item.record > div { - /* codicon: circle-large-filled */ - -webkit-mask-image: url("data:image/svg+xml;utf8,"); - mask-image: url("data:image/svg+xml;utf8,"); - } - x-pw-tool-item.pick-locator > div { - /* codicon: inspect */ - -webkit-mask-image: url("data:image/svg+xml;utf8,"); - mask-image: url("data:image/svg+xml;utf8,"); - } - x-pw-tool-item.assert > div { - /* codicon: check-all */ - -webkit-mask-image: url("data:image/svg+xml;utf8,"); - mask-image: url("data:image/svg+xml;utf8,"); - } - `; - shadow.appendChild(styleElement); const toolsListElement = document.createElement('x-pw-tools-list'); - shadow.appendChild(toolsListElement); + this._overlayElement.appendChild(toolsListElement); const dragHandle = document.createElement('x-pw-tool-gripper'); dragHandle.addEventListener('mousedown', event => { this._dragState = { offsetX: this._offsetX, dragStart: { x: event.clientX, y: 0 } }; }); - dragHandle.appendChild(document.createElement('div')); + dragHandle.appendChild(document.createElement('x-div')); toolsListElement.appendChild(dragHandle); this._recordToggle = this._recorder.injectedScript.document.createElement('x-pw-tool-item'); this._recordToggle.title = 'Record'; this._recordToggle.classList.add('record'); - this._recordToggle.appendChild(this._recorder.injectedScript.document.createElement('div')); + this._recordToggle.appendChild(this._recorder.injectedScript.document.createElement('x-div')); this._recordToggle.addEventListener('click', () => { this._recorder.delegate.setMode?.(this._recorder.state.mode === 'none' || this._recorder.state.mode === 'inspecting' ? 'recording' : 'none'); }); @@ -786,7 +691,7 @@ class Overlay { this._pickLocatorToggle = this._recorder.injectedScript.document.createElement('x-pw-tool-item'); this._pickLocatorToggle.title = 'Pick locator'; this._pickLocatorToggle.classList.add('pick-locator'); - this._pickLocatorToggle.appendChild(this._recorder.injectedScript.document.createElement('div')); + this._pickLocatorToggle.appendChild(this._recorder.injectedScript.document.createElement('x-div')); this._pickLocatorToggle.addEventListener('click', () => { const newMode: Record = { 'inspecting': 'none', @@ -802,7 +707,7 @@ class Overlay { this._assertToggle = this._recorder.injectedScript.document.createElement('x-pw-tool-item'); this._assertToggle.title = 'Assert text and values'; this._assertToggle.classList.add('assert'); - this._assertToggle.appendChild(this._recorder.injectedScript.document.createElement('div')); + this._assertToggle.appendChild(this._recorder.injectedScript.document.createElement('x-div')); this._assertToggle.addEventListener('click', () => { if (!this._assertToggle.classList.contains('disabled')) this._recorder.delegate.setMode?.(this._recorder.state.mode === 'assertingText' ? 'recording' : 'assertingText'); @@ -813,7 +718,7 @@ class Overlay { } install() { - this._recorder.injectedScript.document.documentElement.appendChild(this._overlayElement); + this._recorder.highlight.appendChild(this._overlayElement); this._measure = this._overlayElement.getBoundingClientRect(); } @@ -877,7 +782,7 @@ export class Recorder { private _currentTool: RecorderTool; private _tools: Record; private _actionSelectorModel: HighlightModel | null = null; - private _highlight: Highlight; + readonly highlight: Highlight; private _overlay: Overlay | undefined; private _styleElement: HTMLStyleElement; state: UIState = { mode: 'none', testIdAttributeName: 'data-testid', language: 'javascript', overlay: { offsetX: 0 } }; @@ -887,7 +792,7 @@ export class Recorder { constructor(injectedScript: InjectedScript) { this.document = injectedScript.document; this.injectedScript = injectedScript; - this._highlight = new Highlight(injectedScript); + this.highlight = new Highlight(injectedScript); this._tools = { 'none': new NoneTool(), 'inspecting': new InspectTool(this), @@ -913,7 +818,7 @@ export class Recorder { installListeners() { // Ensure we are attached to the current document, and we are on top (last element); - if (this._highlight.isInstalled()) + if (this.highlight.isInstalled()) return; removeEventListeners(this._listeners); this._listeners = [ @@ -932,7 +837,7 @@ export class Recorder { addEventListener(this.document, 'focus', event => this._onFocus(event), true), addEventListener(this.document, 'scroll', event => this._onScroll(event), true), ]; - this._highlight.install(); + this.highlight.install(); this._overlay?.install(); this.injectedScript.document.head.appendChild(this._styleElement); } @@ -941,10 +846,9 @@ export class Recorder { const newTool = this._tools[this.state.mode]; if (newTool === this._currentTool) return; - this._currentTool.disable?.(); + this._currentTool.cleanup?.(); this.clearHighlight(); this._currentTool = newTool; - this._currentTool.enable?.(); this.injectedScript.document.body?.setAttribute('data-pw-cursor', newTool.cursor()); } @@ -957,13 +861,13 @@ export class Recorder { // All good. } else { if (state.actionPoint) - this._highlight.showActionPoint(state.actionPoint.x, state.actionPoint.y); + this.highlight.showActionPoint(state.actionPoint.x, state.actionPoint.y); else - this._highlight.hideActionPoint(); + this.highlight.hideActionPoint(); } this.state = state; - this._highlight.setLanguage(state.language); + this.highlight.setLanguage(state.language); this._switchCurrentTool(); this._overlay?.setUIState(state); @@ -977,7 +881,7 @@ export class Recorder { } clearHighlight() { - this._currentTool.disable?.(); + this._currentTool.cleanup?.(); this.updateHighlight(null, false); } @@ -1062,7 +966,7 @@ export class Recorder { private _onScroll(event: Event) { if (!event.isTrusted) return; - this._highlight.hideActionPoint(); + this.highlight.hideActionPoint(); this._currentTool.onScroll?.(event); } @@ -1091,13 +995,14 @@ export class Recorder { updateHighlight(model: HighlightModel | null, userGesture: boolean, options: HighlightOptions = {}) { if (options.tooltipText === undefined && model?.selector) options.tooltipText = asLocator(this.state.language, model.selector); - this._highlight.updateHighlight(model?.elements || [], options); + this.highlight.updateHighlight(model?.elements || [], options); if (userGesture) this.delegate.highlightUpdated?.(); } private _ignoreOverlayEvent(event: Event) { - return this._overlay?.contains(event.composedPath()[0] as Element); + const target = event.composedPath()[0] as Element; + return target.nodeName.toLowerCase() === 'x-pw-glass'; } deepEventTarget(event: Event): HTMLElement { diff --git a/packages/playwright-core/src/server/recorder/recorderApp.ts b/packages/playwright-core/src/server/recorder/recorderApp.ts index 6f56db67b7..37ff723dff 100644 --- a/packages/playwright-core/src/server/recorder/recorderApp.ts +++ b/packages/playwright-core/src/server/recorder/recorderApp.ts @@ -176,9 +176,9 @@ export class RecorderApp extends EventEmitter implements IRecorderApp { this._recorder.setMode('recording'); } } - await this._page.mainFrame().evaluateExpression(((selector: string) => { - window.playwrightSetSelector(selector); - }).toString(), { isFunction: true }, selector).catch(() => {}); + await this._page.mainFrame().evaluateExpression(((data: { selector: string, userGesture?: boolean }) => { + window.playwrightSetSelector(data.selector, data.userGesture); + }).toString(), { isFunction: true }, { selector, userGesture }).catch(() => {}); } async updateCallLogs(callLogs: CallLog[]): Promise { diff --git a/packages/recorder/src/recorder.tsx b/packages/recorder/src/recorder.tsx index 6a1c6eb325..7892791183 100644 --- a/packages/recorder/src/recorder.tsx +++ b/packages/recorder/src/recorder.tsx @@ -66,8 +66,10 @@ export const Recorder: React.FC = ({ }; const [locator, setLocator] = React.useState(''); - window.playwrightSetSelector = (selector: string) => { + window.playwrightSetSelector = (selector: string, focus?: boolean) => { const language = source.language; + if (focus) + setSelectedTab('locator'); setLocator(asLocator(language, selector)); }; @@ -126,7 +128,6 @@ export const Recorder: React.FC = ({ 'assertingText': 'recording-inspecting', }[mode]; window.dispatch({ event: 'setMode', params: { mode: newMode } }).catch(() => { }); - setSelectedTab('locator'); }}>Pick locator { window.dispatch({ event: 'setMode', params: { mode: mode === 'assertingText' ? 'recording' : 'assertingText' } }); @@ -155,7 +156,7 @@ export const Recorder: React.FC = ({ }}> toggleTheme()}> - + copy(locator)} />] : []} @@ -163,7 +164,7 @@ export const Recorder: React.FC = ({ { id: 'locator', title: 'Locator', - render: () => + render: () => }, { id: 'log', diff --git a/packages/web/src/components/codeMirrorWrapper.css b/packages/web/src/components/codeMirrorWrapper.css index 5abb4cc722..1e79c18274 100644 --- a/packages/web/src/components/codeMirrorWrapper.css +++ b/packages/web/src/components/codeMirrorWrapper.css @@ -16,6 +16,10 @@ @import '../third_party/vscode/colors.css'; +.cm-wrapper { + line-height: 18px; +} + .cm-wrapper, .cm-wrapper > div { width: 100%; height: 100%; diff --git a/packages/web/src/components/codeMirrorWrapper.tsx b/packages/web/src/components/codeMirrorWrapper.tsx index 3f64a1d944..738f0cfff1 100644 --- a/packages/web/src/components/codeMirrorWrapper.tsx +++ b/packages/web/src/components/codeMirrorWrapper.tsx @@ -93,7 +93,6 @@ export const CodeMirrorWrapper: React.FC = ({ // Either configuration is different or we don't have a codemirror yet. codemirrorRef.current?.cm?.getWrapperElement().remove(); - const cm = CodeMirror(element, { value: '', mode, diff --git a/packages/web/src/components/toolbarButton.css b/packages/web/src/components/toolbarButton.css index bdf6db9ad9..813d9e4b91 100644 --- a/packages/web/src/components/toolbarButton.css +++ b/packages/web/src/components/toolbarButton.css @@ -43,10 +43,6 @@ color: var(--vscode-notificationLink-foreground); } -.toolbar-button.toggled .codicon { - font-weight: bold; -} - .toolbar-separator { flex: none; background-color: var(--vscode-menu-separatorBackground); From 2afd857642c26980e56f269d05df72d4d69f57e7 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Tue, 7 Nov 2023 20:49:03 -0800 Subject: [PATCH 013/491] chore: prepare to use codemirror in recorder (#28025) --- utils/generate_injected.js | 38 +++++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/utils/generate_injected.js b/utils/generate_injected.js index 652f59d0a3..9fefed0d4d 100644 --- a/utils/generate_injected.js +++ b/utils/generate_injected.js @@ -30,12 +30,30 @@ const injectedScripts = [ ]; const modulePrefix = ` +var __commonJS = obj => { + let required = false; + let result; + return function __require() { + if (!required) { + required = true; + let fn; + for (const name in obj) { fn = obj[name]; break; } + const module = { exports: {} }; + fn(module.exports, module); + result = module.exports; + } + return result; + } +}; var __export = (target, all) => {for (var name in all) target[name] = all[name];}; +var __toESM = mod => ({ ...mod, 'default': mod }); var __toCommonJS = mod => ({ ...mod, __esModule: true }); `; async function replaceEsbuildHeader(content, outFileJs) { - const sourcesStart = content.indexOf('// packages/playwright-core/src/server'); + let sourcesStart = content.indexOf('__toCommonJS'); + if (sourcesStart !== -1) + sourcesStart = content.indexOf('\n', sourcesStart); if (sourcesStart === -1) throw new Error(`Did not find start of bundled code in ${outFileJs}`); @@ -49,19 +67,33 @@ async function replaceEsbuildHeader(content, outFileJs) { return content; } +const inlineCSSPlugin = { + name: 'inlineCSSPlugin', + setup(build) { + build.onLoad({ filter: /\.css$/ }, async (args) => { + const f = await fs.promises.readFile(args.path) + const css = await esbuild.transform(f, { loader: 'css', minify: true }); + return { loader: 'text', contents: css.code }; + }); + }, +}; + (async () => { const generatedFolder = path.join(ROOT, 'packages', 'playwright-core', 'src', 'generated'); await fs.promises.mkdir(generatedFolder, { recursive: true }); for (const injected of injectedScripts) { const outdir = path.join(ROOT, 'packages', 'playwright-core', 'lib', 'server', 'injected', 'packed'); - await esbuild.build({ + const buildOutput = await esbuild.build({ entryPoints: [injected], bundle: true, outdir, format: 'cjs', platform: 'browser', - target: 'ES2019' + target: 'ES2019', + plugins: [inlineCSSPlugin], }); + for (const message of [...buildOutput.errors, ...buildOutput.warnings]) + console.log(message.text); const baseName = path.basename(injected); const outFileJs = path.join(outdir, baseName.replace('.ts', '.js')); let content = await fs.promises.readFile(outFileJs, 'utf-8'); From c759e6a6f6e577208e6bb499964eec10d0f71e32 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Wed, 8 Nov 2023 09:29:36 -0800 Subject: [PATCH 014/491] docs: move expect.extend docs from "test configuration" to "assertions" (#28039) --- docs/src/release-notes-js.md | 2 +- docs/src/test-assertions-js.md | 89 +++++++++++++++++++++++++++++-- docs/src/test-configuration-js.md | 83 ---------------------------- 3 files changed, 87 insertions(+), 87 deletions(-) diff --git a/docs/src/release-notes-js.md b/docs/src/release-notes-js.md index 7aca02a33c..164108ad1c 100644 --- a/docs/src/release-notes-js.md +++ b/docs/src/release-notes-js.md @@ -30,7 +30,7 @@ test('pass', async ({ page }) => { }); ``` -See the documentation [for a full example](./test-configuration.md#add-custom-matchers-using-expectextend). +See the documentation [for a full example](./test-assertions.md#add-custom-matchers-using-expectextend). ### Merge test fixtures diff --git a/docs/src/test-assertions-js.md b/docs/src/test-assertions-js.md index cb357ac056..d05b7a750a 100644 --- a/docs/src/test-assertions-js.md +++ b/docs/src/test-assertions-js.md @@ -94,7 +94,7 @@ Prefer [auto-retrying](#auto-retrying-assertions) assertions whenever possible. | [`method: GenericAssertions.stringContaining`] | String contains a substring | | [`method: GenericAssertions.stringMatching`] | String matches a regular expression | -## Negating Matchers +## Negating matchers In general, we can expect the opposite to be true by adding a `.not` to the front of the matchers: @@ -104,7 +104,7 @@ expect(value).not.toEqual(0); await expect(locator).not.toContainText('some text'); ``` -## Soft Assertions +## Soft assertions By default, failed assertion will terminate test execution. Playwright also supports *soft assertions*: failed soft assertions **do not** terminate test execution, @@ -134,7 +134,7 @@ expect(test.info().errors).toHaveLength(0); Note that soft assertions only work with Playwright test runner. -## Custom Expect Message +## Custom expect message You can specify a custom error message as a second argument to the `expect` function, for example: @@ -236,3 +236,86 @@ await expect(async () => { timeout: 60_000 }); ``` + +## Add custom matchers using expect.extend + +You can extend Playwright assertions by providing custom matchers. These matchers will be available on the `expect` object. + +In this example we add a custom `toHaveAmount` function. Custom matcher should return a `message` callback and a `pass` flag indicating whether the assertion passed. + +```js title="fixtures.ts" +import { expect as baseExpect } from '@playwright/test'; +import type { Page, Locator } from '@playwright/test'; + +export { test } from '@playwright/test'; + +export const expect = baseExpect.extend({ + async toHaveAmount(locator: Locator, expected: number, options?: { timeout?: number }) { + const assertionName = 'toHaveAmount'; + let pass: boolean; + let matcherResult: any; + try { + await baseExpect(locator).toHaveAttribute('data-amount', String(expected), options); + pass = true; + } catch (e: any) { + matcherResult = e.matcherResult; + pass = false; + } + + const message = pass + ? () => this.utils.matcherHint(assertionName, undefined, undefined, { isNot: this.isNot }) + + '\n\n' + + `Locator: ${locator}\n` + + `Expected: ${this.isNot ? 'not' : ''}${this.utils.printExpected(expected)}\n` + + (matcherResult ? `Received: ${this.utils.printReceived(matcherResult.actual)}` : '') + : () => this.utils.matcherHint(assertionName, undefined, undefined, { isNot: this.isNot }) + + '\n\n' + + `Locator: ${locator}\n` + + `Expected: ${this.utils.printExpected(expected)}\n` + + (matcherResult ? `Received: ${this.utils.printReceived(matcherResult.actual)}` : ''); + + return { + message, + pass, + name: assertionName, + expected, + actual: matcherResult?.actual, + }; + }, +}); +``` + +Now we can use `toHaveAmount` in the test. + +```js title="example.spec.ts" +import { test, expect } from './fixtures'; + +test('amount', async () => { + await expect(page.locator('.cart')).toHaveAmount(4); +}); +``` + +:::note +Do not confuse Playwright's `expect` with the [`expect` library](https://jestjs.io/docs/expect). The latter is not fully integrated with Playwright test runner, so make sure to use Playwright's own `expect`. +::: + +### Combine custom matchers from multiple modules + +You can combine custom matchers from multiple files or modules. + +```js title="fixtures.ts" +import { mergeTests, mergeExpects } from '@playwright/test'; +import { test as dbTest, expect as dbExpect } from 'database-test-utils'; +import { test as a11yTest, expect as a11yExpect } from 'a11y-test-utils'; + +export const expect = mergeExpects(dbExpect, a11yExpect); +export const test = mergeTests(dbTest, a11yTest); +``` + +```js title="test.spec.ts" +import { test, expect } from './fixtures'; + +test('passes', async ({ database }) => { + await expect(database).toHaveDatabaseUser('admin'); +}); +``` diff --git a/docs/src/test-configuration-js.md b/docs/src/test-configuration-js.md index 6ebd29db08..ea598c8c8a 100644 --- a/docs/src/test-configuration-js.md +++ b/docs/src/test-configuration-js.md @@ -150,86 +150,3 @@ export default defineConfig({ | [`method: PageAssertions.toHaveScreenshot#1`] | Configuration for the `expect(locator).toHaveScreeshot()` method. | | [`method: SnapshotAssertions.toMatchSnapshot#1`]| Configuration for the `expect(locator).toMatchSnapshot()` method.| - -### Add custom matchers using expect.extend - -You can extend Playwright assertions by providing custom matchers. These matchers will be available on the `expect` object. - -In this example we add a custom `toHaveAmount` function. Custom matcher should return a `message` callback and a `pass` flag indicating whether the assertion passed. - -```js title="fixtures.ts" -import { expect as baseExpect } from '@playwright/test'; -import type { Page, Locator } from '@playwright/test'; - -export { test } from '@playwright/test'; - -export const expect = baseExpect.extend({ - async toHaveAmount(locator: Locator, expected: number, options?: { timeout?: number }) { - const assertionName = 'toHaveAmount'; - let pass: boolean; - let matcherResult: any; - try { - await baseExpect(locator).toHaveAttribute('data-amount', String(expected), options); - pass = true; - } catch (e: any) { - matcherResult = e.matcherResult; - pass = false; - } - - const message = pass - ? () => this.utils.matcherHint(assertionName, undefined, undefined, { isNot: this.isNot }) + - '\n\n' + - `Locator: ${locator}\n` + - `Expected: ${this.isNot ? 'not' : ''}${this.utils.printExpected(expected)}\n` + - (matcherResult ? `Received: ${this.utils.printReceived(matcherResult.actual)}` : '') - : () => this.utils.matcherHint(assertionName, undefined, undefined, { isNot: this.isNot }) + - '\n\n' + - `Locator: ${locator}\n` + - `Expected: ${this.utils.printExpected(expected)}\n` + - (matcherResult ? `Received: ${this.utils.printReceived(matcherResult.actual)}` : ''); - - return { - message, - pass, - name: assertionName, - expected, - actual: matcherResult?.actual, - }; - }, -}); -``` - -Now we can use `toHaveAmount` in the test. - -```js title="example.spec.ts" -import { test, expect } from './fixtures'; - -test('amount', async () => { - await expect(page.locator('.cart')).toHaveAmount(4); -}); -``` - -:::note -Do not confuse Playwright's `expect` with the [`expect` library](https://jestjs.io/docs/expect). The latter is not fully integrated with Playwright test runner, so make sure to use Playwright's own `expect`. -::: - -### Combine custom matchers from multiple modules - -You can combine custom matchers from multiple files or modules. - -```js title="fixtures.ts" -import { mergeTests, mergeExpects } from '@playwright/test'; -import { test as dbTest, expect as dbExpect } from 'database-test-utils'; -import { test as a11yTest, expect as a11yExpect } from 'a11y-test-utils'; - -export const expect = mergeExpects(dbExpect, a11yExpect); -export const test = mergeTests(dbTest, a11yTest); -``` - -```js title="test.spec.ts" -import { test, expect } from './fixtures'; - -test('passes', async ({ database }) => { - await expect(database).toHaveDatabaseUser('admin'); -}); -``` From 5a9fa69c6dcbdbaee98252e8ff690bbd1a6738d3 Mon Sep 17 00:00:00 2001 From: Mattias Wallander Date: Wed, 8 Nov 2023 18:50:25 +0100 Subject: [PATCH 015/491] feat: Add support for dispatching device orientation events (#27960) Fixes #27887 --- .../src/server/injected/injectedScript.ts | 18 +++++++++++- tests/assets/device-orientation.html | 29 +++++++++++++++++++ tests/page/page-dispatchevent.spec.ts | 20 +++++++++++++ 3 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 tests/assets/device-orientation.html diff --git a/packages/playwright-core/src/server/injected/injectedScript.ts b/packages/playwright-core/src/server/injected/injectedScript.ts index d19603b827..48a9a388dd 100644 --- a/packages/playwright-core/src/server/injected/injectedScript.ts +++ b/packages/playwright-core/src/server/injected/injectedScript.ts @@ -67,6 +67,10 @@ export type HitTargetInterceptionResult = { stop: () => 'done' | { hitTargetDescription: string }; }; +interface WebKitLegacyDeviceOrientationEvent extends DeviceOrientationEvent { + readonly initDeviceOrientationEvent: (type: string, bubbles: boolean, cancelable: boolean, alpha: number, beta: number, gamma: number, absolute: boolean) => void; +} + export class InjectedScript { private _engines: Map; _evaluator: SelectorEvaluatorImpl; @@ -1036,6 +1040,15 @@ export class InjectedScript { case 'focus': event = new FocusEvent(type, eventInit); break; case 'drag': event = new DragEvent(type, eventInit); break; case 'wheel': event = new WheelEvent(type, eventInit); break; + case 'deviceorientation': + try { + event = new DeviceOrientationEvent(type, eventInit); + } catch { + const { bubbles, cancelable, alpha, beta, gamma, absolute } = eventInit as {bubbles: boolean, cancelable: boolean, alpha: number, beta: number, gamma: number, absolute: boolean}; + event = this.document.createEvent('DeviceOrientationEvent') as WebKitLegacyDeviceOrientationEvent; + event.initDeviceOrientationEvent(type, bubbles, cancelable, alpha, beta, gamma, absolute); + } + break; default: event = new Event(type, eventInit); break; } node.dispatchEvent(event); @@ -1371,7 +1384,7 @@ function oneLine(s: string): string { return s.replace(/\n/g, '↵').replace(/\t/g, '⇆'); } -const eventType = new Map([ +const eventType = new Map([ ['auxclick', 'mouse'], ['click', 'mouse'], ['dblclick', 'mouse'], @@ -1419,6 +1432,9 @@ const eventType = new Map + + + + Device orientation test + + + + + + + diff --git a/tests/page/page-dispatchevent.spec.ts b/tests/page/page-dispatchevent.spec.ts index 85b928862b..840c525788 100644 --- a/tests/page/page-dispatchevent.spec.ts +++ b/tests/page/page-dispatchevent.spec.ts @@ -171,3 +171,23 @@ it('should dispatch wheel event', async ({ page, server }) => { expect(await eventsHandle.evaluate(e => e[0] instanceof WheelEvent)).toBeTruthy(); expect(await eventsHandle.evaluate(e => ({ deltaX: e[0].deltaX, deltaY: e[0].deltaY }))).toEqual({ deltaX: 100, deltaY: 200 }); }); + +it('should dispatch device orientation event', async ({ page, server }) => { + await page.goto(server.PREFIX + '/device-orientation.html'); + await page.locator('html').dispatchEvent('deviceorientation', { alpha: 10, beta: 20, gamma: 30 }); + expect(await page.evaluate('result')).toBe('Oriented'); + expect(await page.evaluate('alpha')).toBe(10); + expect(await page.evaluate('beta')).toBe(20); + expect(await page.evaluate('gamma')).toBe(30); + expect(await page.evaluate('absolute')).toBeFalsy(); +}); + +it('should dispatch absolute device orientation event', async ({ page, server }) => { + await page.goto(server.PREFIX + '/device-orientation.html'); + await page.locator('html').dispatchEvent('deviceorientationabsolute', { alpha: 10, beta: 20, gamma: 30, absolute: true }); + expect(await page.evaluate('result')).toBe('Oriented'); + expect(await page.evaluate('alpha')).toBe(10); + expect(await page.evaluate('beta')).toBe(20); + expect(await page.evaluate('gamma')).toBe(30); + expect(await page.evaluate('absolute')).toBeTruthy(); +}); From 5f527fedb1f6893219b69d735b1a9cdd81ad1466 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Thu, 9 Nov 2023 00:11:01 +0100 Subject: [PATCH 016/491] fix: JSHandle preview text for non-ascii attributes/children (#28038) This surfaced in .NET that the string in the driver got incorrectly cut, then transferred to .NET as an invalid UTF8 character [`\ud835`](https://charbase.com/d835-unicode-invalid-character) which .NET wasn't able to parse and threw an error. Drive-by: Move similar function from `packages/playwright-core/src/client/page.ts` into isomorphic `stringUtils`. https://github.com/microsoft/playwright-dotnet/issues/2748 --- packages/playwright-core/src/client/page.ts | 11 +++-------- .../src/server/injected/injectedScript.ts | 12 ++++-------- .../src/server/injected/selectorGenerator.ts | 4 ++-- .../src/utils/isomorphic/stringUtils.ts | 13 +++++++++++++ tests/page/elementhandle-convenience.spec.ts | 7 +++++++ tests/page/page-wait-for-request.spec.ts | 2 +- 6 files changed, 30 insertions(+), 19 deletions(-) diff --git a/packages/playwright-core/src/client/page.ts b/packages/playwright-core/src/client/page.ts index 0e8701f905..0430e3c0bc 100644 --- a/packages/playwright-core/src/client/page.ts +++ b/packages/playwright-core/src/client/page.ts @@ -42,6 +42,7 @@ import { Keyboard, Mouse, Touchscreen } from './input'; import { assertMaxArguments, JSHandle, parseResult, serializeArgument } from './jsHandle'; import type { FrameLocator, Locator, LocatorOptions } from './locator'; import type { ByRoleOptions } from '../utils/isomorphic/locatorUtils'; +import { trimStringWithEllipsis } from '../utils/isomorphic/stringUtils'; import { type RouteHandlerCallback, type Request, Response, Route, RouteHandler, validateHeaders, WebSocket } from './network'; import type { FilePayload, Headers, LifecycleEvent, SelectOption, SelectOptionOptions, Size, URLMatch, WaitForEventOptions, WaitForFunctionOptions } from './types'; import { Video } from './video'; @@ -751,15 +752,9 @@ export class BindingCall extends ChannelOwner { } } -function trimEnd(s: string): string { - if (s.length > 50) - s = s.substring(0, 50) + '\u2026'; - return s; -} - function trimUrl(param: any): string | undefined { if (isRegExp(param)) - return `/${trimEnd(param.source)}/${param.flags}`; + return `/${trimStringWithEllipsis(param.source, 50)}/${param.flags}`; if (isString(param)) - return `"${trimEnd(param)}"`; + return `"${trimStringWithEllipsis(param, 50)}"`; } diff --git a/packages/playwright-core/src/server/injected/injectedScript.ts b/packages/playwright-core/src/server/injected/injectedScript.ts index 48a9a388dd..13a35cdb53 100644 --- a/packages/playwright-core/src/server/injected/injectedScript.ts +++ b/packages/playwright-core/src/server/injected/injectedScript.ts @@ -33,7 +33,7 @@ import { getChecked, getAriaDisabled, getAriaRole, getElementAccessibleName } fr import { kLayoutSelectorNames, type LayoutSelectorName, layoutSelectorScore } from './layoutSelectorUtils'; import { asLocator } from '../../utils/isomorphic/locatorGenerators'; import type { Language } from '../../utils/isomorphic/locatorGenerators'; -import { normalizeWhiteSpace } from '../../utils/isomorphic/stringUtils'; +import { normalizeWhiteSpace, trimStringWithEllipsis } from '../../utils/isomorphic/stringUtils'; type Predicate = (progress: InjectedScriptProgress) => T | symbol; @@ -1072,9 +1072,7 @@ export class InjectedScript { attrs.push(` ${name}="${value}"`); } attrs.sort((a, b) => a.length - b.length); - let attrText = attrs.join(''); - if (attrText.length > 50) - attrText = attrText.substring(0, 49) + '\u2026'; + const attrText = trimStringWithEllipsis(attrs.join(''), 50); if (autoClosingTags.has(element.nodeName)) return oneLine(`<${element.nodeName.toLowerCase()}${attrText}/>`); @@ -1085,10 +1083,8 @@ export class InjectedScript { for (let i = 0; i < children.length; i++) onlyText = onlyText && children[i].nodeType === Node.TEXT_NODE; } - let text = onlyText ? (element.textContent || '') : (children.length ? '\u2026' : ''); - if (text.length > 50) - text = text.substring(0, 49) + '\u2026'; - return oneLine(`<${element.nodeName.toLowerCase()}${attrText}>${text}`); + const text = onlyText ? (element.textContent || '') : (children.length ? '\u2026' : ''); + return oneLine(`<${element.nodeName.toLowerCase()}${attrText}>${trimStringWithEllipsis(text, 50)}`); } strictModeViolationError(selector: ParsedSelector, matches: Element[]): Error { diff --git a/packages/playwright-core/src/server/injected/selectorGenerator.ts b/packages/playwright-core/src/server/injected/selectorGenerator.ts index 51ff0c2560..e353ad1556 100644 --- a/packages/playwright-core/src/server/injected/selectorGenerator.ts +++ b/packages/playwright-core/src/server/injected/selectorGenerator.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { cssEscape, escapeForAttributeSelector, escapeForTextSelector, normalizeWhiteSpace, quoteCSSAttributeValue } from '../../utils/isomorphic/stringUtils'; +import { cssEscape, escapeForAttributeSelector, escapeForTextSelector, normalizeWhiteSpace, quoteCSSAttributeValue, trimString } from '../../utils/isomorphic/stringUtils'; import { closestCrossShadow, isInsideScope, parentElementOrShadowHost } from './domUtils'; import type { InjectedScript } from './injectedScript'; import { getAriaRole, getElementAccessibleName, beginAriaCaches, endAriaCaches } from './roleUtils'; @@ -276,7 +276,7 @@ function buildTextCandidates(injectedScript: InjectedScript, element: Element, i } const fullText = normalizeWhiteSpace(elementText(injectedScript._evaluator._cacheText, element).full); - const text = fullText.substring(0, 80); + const text = trimString(fullText, 80); if (text) { const escaped = escapeForTextSelector(text, false); if (isTargetNode) { diff --git a/packages/playwright-core/src/utils/isomorphic/stringUtils.ts b/packages/playwright-core/src/utils/isomorphic/stringUtils.ts index ee2258641a..d51ecf8d41 100644 --- a/packages/playwright-core/src/utils/isomorphic/stringUtils.ts +++ b/packages/playwright-core/src/utils/isomorphic/stringUtils.ts @@ -103,3 +103,16 @@ export function escapeForAttributeSelector(value: string | RegExp, exact: boolea // so we escape them differently. return `"${value.replace(/\\/g, '\\\\').replace(/["]/g, '\\"')}"${exact ? 's' : 'i'}`; } + +export function trimString(input: string, cap: number, suffix: string = ''): string { + if (input.length <= cap) + return input; + const chars = [...input]; + if (chars.length > cap) + return chars.slice(0, cap - suffix.length).join('') + suffix; + return chars.join(''); +} + +export function trimStringWithEllipsis(input: string, cap: number): string { + return trimString(input, cap, '\u2026'); +} \ No newline at end of file diff --git a/tests/page/elementhandle-convenience.spec.ts b/tests/page/elementhandle-convenience.spec.ts index 21ef4c85bf..5020f34ea8 100644 --- a/tests/page/elementhandle-convenience.spec.ts +++ b/tests/page/elementhandle-convenience.spec.ts @@ -30,6 +30,13 @@ it('should have a nice preview', async ({ page, server }) => { expect(String(check)).toBe('JSHandle@'); }); +it('should have a nice preview for non-ascii attributes/children', async ({ page, server }) => { + await page.goto(server.EMPTY_PAGE); + await page.setContent(`
${'😛'.repeat(100)}`); + const handle = await page.$('div'); + await expect.poll(() => String(handle)).toBe(`JSHandle@
😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛…
`); +}); + it('getAttribute should work', async ({ page, server }) => { await page.goto(`${server.PREFIX}/dom.html`); const handle = await page.$('#outer'); diff --git a/tests/page/page-wait-for-request.spec.ts b/tests/page/page-wait-for-request.spec.ts index 555ace31ad..3ba6e9304f 100644 --- a/tests/page/page-wait-for-request.spec.ts +++ b/tests/page/page-wait-for-request.spec.ts @@ -63,7 +63,7 @@ it('should respect default timeout', async ({ page, playwright }) => { it('should log the url', async ({ page }) => { const error = await page.waitForRequest('long-long-long-long-long-long-long-long-long-long-long-long-long-long.css', { timeout: 1000 }).catch(e => e); - expect(error.message).toContain('waiting for request "long-long-long-long-long-long-long-long-long-long-…"'); + expect(error.message).toContain('waiting for request "long-long-long-long-long-long-long-long-long-long…"'); }); it('should work with no timeout', async ({ page, server }) => { From b004c1a0a70d8c38b6f2b220e63dedaba8423f90 Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Wed, 8 Nov 2023 20:09:58 -0800 Subject: [PATCH 017/491] chore: rework assert dialog (#28043) --- .../{highlight.css.ts => highlight.css} | 94 +++++- .../src/server/injected/highlight.ts | 79 +++-- .../src/server/injected/recorder.ts | 272 ++++++++---------- .../src/server/recorder/recorderActions.ts | 1 + 4 files changed, 244 insertions(+), 202 deletions(-) rename packages/playwright-core/src/server/injected/{highlight.css.ts => highlight.css} (89%) diff --git a/packages/playwright-core/src/server/injected/highlight.css.ts b/packages/playwright-core/src/server/injected/highlight.css similarity index 89% rename from packages/playwright-core/src/server/injected/highlight.css.ts rename to packages/playwright-core/src/server/injected/highlight.css index 18955755ba..0d0a74e7b3 100644 --- a/packages/playwright-core/src/server/injected/highlight.css.ts +++ b/packages/playwright-core/src/server/injected/highlight.css @@ -14,16 +14,18 @@ * limitations under the License. */ -export const highlightCSS = ` +:host { + font-size: 13px; + font-family: system-ui, "Ubuntu", "Droid Sans", sans-serif; + color: #333; +} + x-pw-tooltip { backdrop-filter: blur(5px); background-color: white; - color: #222; border-radius: 6px; box-shadow: 0 0.5rem 1.2rem rgba(0,0,0,.3); display: none; - font-family: 'Dank Mono', 'Operator Mono', Inconsolata, 'Fira Mono', - 'SF Mono', Monaco, 'Droid Sans Mono', 'Source Code Pro', monospace; font-size: 12.8px; font-weight: normal; left: 0; @@ -31,11 +33,27 @@ x-pw-tooltip { max-width: 600px; position: absolute; top: 0; + padding: 4px; } -x-pw-tooltip-body { - align-items: center; - padding: 3.2px 5.12px 3.2px; + +x-pw-dialog { + background-color: white; + pointer-events: auto; + border-radius: 6px; + box-shadow: 0 0.5rem 1.2rem rgba(0,0,0,.3); + display: flex; + flex-direction: column; + position: absolute; + min-width: 500px; + min-height: 200px; } + +x-pw-dialog-body { + display: flex; + flex-direction: column; + flex: auto; +} + x-pw-highlight { position: absolute; top: 0; @@ -43,6 +61,7 @@ x-pw-highlight { width: 0; height: 0; } + x-pw-action-point { position: absolute; width: 20px; @@ -52,20 +71,24 @@ x-pw-action-point { margin: -10px 0 0 -10px; z-index: 2; } + x-pw-separator { height: 1px; margin: 6px 9px; background: rgb(148 148 148 / 90%); } + x-pw-tool-gripper { height: 28px; width: 24px; margin: 2px 0; cursor: grab; } + x-pw-tool-gripper:active { cursor: grabbing; } + x-pw-tool-gripper > x-div { width: 100%; height: 100%; @@ -79,11 +102,20 @@ x-pw-tool-gripper > x-div { mask-image: url("data:image/svg+xml;utf8,"); background-color: #555555; } + +x-pw-tool-label { + display: flex; + align-items: center; + margin-left: 10px; + user-select: none; +} + x-pw-tools-list { display: flex; width: 100%; border-bottom: 1px solid #dddddd; } + x-pw-tool-item { pointer-events: auto; cursor: pointer; @@ -91,9 +123,11 @@ x-pw-tool-item { width: 28px; border-radius: 3px; } + x-pw-tool-item:not(.disabled):hover { background-color: hsl(0, 0%, 86%); } + x-pw-tool-item > x-div { width: 100%; height: 100%; @@ -105,45 +139,52 @@ x-pw-tool-item > x-div { mask-size: 16px; background-color: #3a3a3a; } + x-pw-tool-item.disabled > x-div { background-color: rgba(97, 97, 97, 0.5); cursor: default; } + x-pw-tool-item.active > x-div { background-color: #006ab1; } + x-pw-tool-item.record.active > x-div { background-color: #a1260d; } + x-pw-tool-item.accept > x-div { background-color: #388a34; } -x-pw-tool-item.cancel > x-div { - background-color: #e51400; -} + x-pw-tool-item.record > x-div { /* codicon: circle-large-filled */ -webkit-mask-image: url("data:image/svg+xml;utf8,"); mask-image: url("data:image/svg+xml;utf8,"); } + x-pw-tool-item.pick-locator > x-div { /* codicon: inspect */ -webkit-mask-image: url("data:image/svg+xml;utf8,"); mask-image: url("data:image/svg+xml;utf8,"); } + x-pw-tool-item.assert > x-div { /* codicon: check-all */ -webkit-mask-image: url("data:image/svg+xml;utf8,"); mask-image: url("data:image/svg+xml;utf8,"); } + x-pw-tool-item.accept > x-div { -webkit-mask-image: url("data:image/svg+xml;utf8,"); mask-image: url("data:image/svg+xml;utf8,"); } + x-pw-tool-item.cancel > x-div { -webkit-mask-image: url("data:image/svg+xml;utf8,"); mask-image: url("data:image/svg+xml;utf8,"); } + x-pw-overlay { position: absolute; top: 0; @@ -152,21 +193,52 @@ x-pw-overlay { background: transparent; pointer-events: auto; } + x-pw-overlay x-pw-tools-list { background-color: #ffffffdd; box-shadow: rgba(0, 0, 0, 0.1) 0px 5px 5px; border-radius: 3px; border-bottom: none; } + x-pw-overlay x-pw-tool-item { margin: 2px; } + +input.locator-editor { + display: flex; + padding: 10px; + flex: none; + border: none; + border-bottom: 1px solid #dddddd; +} + +input.locator-editor:focus, +textarea.text-editor:focus { + outline: none; +} + +textarea.text-editor { + font-family: system-ui, "Ubuntu", "Droid Sans", sans-serif; + flex: auto; + border: none; + padding: 10px; + color: #333; +} + + x-div { display: block; } + +x-spacer { + flex: auto; +} + * { box-sizing: border-box; } + *[hidden] { display: none !important; -}`; +} diff --git a/packages/playwright-core/src/server/injected/highlight.ts b/packages/playwright-core/src/server/injected/highlight.ts index d6253d3292..c629bf092b 100644 --- a/packages/playwright-core/src/server/injected/highlight.ts +++ b/packages/playwright-core/src/server/injected/highlight.ts @@ -19,7 +19,7 @@ import type { ParsedSelector } from '../../utils/isomorphic/selectorParser'; import type { InjectedScript } from './injectedScript'; import { asLocator } from '../../utils/isomorphic/locatorGenerators'; import type { Language } from '../../utils/isomorphic/locatorGenerators'; -import { highlightCSS } from './highlight.css'; +import highlightCSS from './highlight.css?inline'; type HighlightEntry = { targetElement: Element, @@ -34,9 +34,6 @@ type HighlightEntry = { export type HighlightOptions = { tooltipText?: string; color?: string; - anchorGetter?: (element: Element) => DOMRect; - toolbar?: Element[]; - interactive?: boolean; }; export class Highlight { @@ -63,7 +60,12 @@ export class Highlight { this._glassPaneElement.style.pointerEvents = 'none'; this._glassPaneElement.style.display = 'flex'; this._glassPaneElement.style.backgroundColor = 'transparent'; - + for (const eventName of ['click', 'auxclick', 'dragstart', 'input', 'keydown', 'keyup', 'pointerdown', 'pointerup', 'mousedown', 'mouseup', 'mousemove', 'mouseleave', 'focus', 'scroll']) { + this._glassPaneElement.addEventListener(eventName, e => { + e.stopPropagation(); + e.stopImmediatePropagation(); + }); + } this._actionPointElement = document.createElement('x-pw-action-point'); this._actionPointElement.setAttribute('hidden', 'true'); this._glassPaneShadow = this._glassPaneElement.attachShadow({ mode: this._isUnderTest ? 'open' : 'closed' }); @@ -145,26 +147,12 @@ export class Highlight { let tooltipElement; if (options.tooltipText) { tooltipElement = this._injectedScript.document.createElement('x-pw-tooltip'); + this._glassPaneShadow.appendChild(tooltipElement); + const suffix = elements.length > 1 ? ` [${i + 1} of ${elements.length}]` : ''; + tooltipElement.textContent = options.tooltipText + suffix; tooltipElement.style.top = '0'; tooltipElement.style.left = '0'; tooltipElement.style.display = 'flex'; - tooltipElement.style.flexDirection = 'column'; - tooltipElement.style.alignItems = 'start'; - if (options.interactive) - tooltipElement.style.pointerEvents = 'auto'; - - if (options.toolbar) { - const toolbar = this._injectedScript.document.createElement('x-pw-tools-list'); - tooltipElement.appendChild(toolbar); - for (const toolbarElement of options.toolbar) - toolbar.appendChild(toolbarElement); - } - const bodyElement = this._injectedScript.document.createElement('x-pw-tooltip-body'); - tooltipElement.appendChild(bodyElement); - - this._glassPaneShadow.appendChild(tooltipElement); - const suffix = elements.length > 1 ? ` [${i + 1} of ${elements.length}]` : ''; - bodyElement.textContent = options.tooltipText + suffix; } this._highlightEntries.push({ targetElement: elements[i], tooltipElement, highlightElement, tooltipText: options.tooltipText }); } @@ -176,25 +164,7 @@ export class Highlight { continue; // Position tooltip, if any. - const tooltipWidth = entry.tooltipElement.offsetWidth; - const tooltipHeight = entry.tooltipElement.offsetHeight; - const totalWidth = this._glassPaneElement.offsetWidth; - const totalHeight = this._glassPaneElement.offsetHeight; - - const anchorBox = options.anchorGetter ? options.anchorGetter(entry.targetElement) : entry.box; - let anchorLeft = anchorBox.left; - if (anchorLeft + tooltipWidth > totalWidth - 5) - anchorLeft = totalWidth - tooltipWidth - 5; - let anchorTop = anchorBox.bottom + 5; - if (anchorTop + tooltipHeight > totalHeight - 5) { - // If can't fit below, either position above... - if (anchorBox.top > tooltipHeight + 5) { - anchorTop = anchorBox.top - tooltipHeight - 5; - } else { - // Or on top in case of large element - anchorTop = totalHeight - 5 - tooltipHeight; - } - } + const { anchorLeft, anchorTop } = this.tooltipPosition(entry.box, entry.tooltipElement); entry.tooltipTop = anchorTop; entry.tooltipLeft = anchorLeft; } @@ -219,6 +189,33 @@ export class Highlight { console.error('Highlight box for test: ' + JSON.stringify({ x: box.x, y: box.y, width: box.width, height: box.height })); // eslint-disable-line no-console } } + + firstBox(): DOMRect | undefined { + return this._highlightEntries[0]?.box; + } + + tooltipPosition(box: DOMRect, tooltipElement: HTMLElement) { + const tooltipWidth = tooltipElement.offsetWidth; + const tooltipHeight = tooltipElement.offsetHeight; + const totalWidth = this._glassPaneElement.offsetWidth; + const totalHeight = this._glassPaneElement.offsetHeight; + + let anchorLeft = box.left; + if (anchorLeft + tooltipWidth > totalWidth - 5) + anchorLeft = totalWidth - tooltipWidth - 5; + let anchorTop = box.bottom + 5; + if (anchorTop + tooltipHeight > totalHeight - 5) { + // If can't fit below, either position above... + if (box.top > tooltipHeight + 5) { + anchorTop = box.top - tooltipHeight - 5; + } else { + // Or on top in case of large element + anchorTop = totalHeight - 5 - tooltipHeight; + } + } + return { anchorLeft, anchorTop }; + } + private _highlightIsUpToDate(elements: Element[], tooltipText: string | undefined): boolean { if (elements.length !== this._highlightEntries.length) return false; diff --git a/packages/playwright-core/src/server/injected/recorder.ts b/packages/playwright-core/src/server/injected/recorder.ts index 19545c15f5..3ab0845ea6 100644 --- a/packages/playwright-core/src/server/injected/recorder.ts +++ b/packages/playwright-core/src/server/injected/recorder.ts @@ -20,10 +20,12 @@ import { generateSelector } from '../injected/selectorGenerator'; import type { Point } from '../../common/types'; import type { Mode, OverlayState, UIState } from '@recorder/recorderTypes'; import { Highlight, type HighlightOptions } from '../injected/highlight'; -import { enclosingElement, isInsideScope, parentElementOrShadowHost } from './domUtils'; +import { isInsideScope } from './domUtils'; import { elementText } from './selectorUtils'; -import { escapeWithQuotes, normalizeWhiteSpace } from '../../utils/isomorphic/stringUtils'; import { asLocator } from '../../utils/isomorphic/locatorGenerators'; +import { locatorOrSelectorAsSelector } from '@isomorphic/locatorParser'; +import { parseSelector } from '@isomorphic/selectorParser'; +import { normalizeWhiteSpace } from '@isomorphic/stringUtils'; interface RecorderDelegate { performAction?(action: actions.Action): Promise; @@ -442,217 +444,187 @@ class RecordActionTool implements RecorderTool { class TextAssertionTool implements RecorderTool { private _hoverHighlight: HighlightModel | null = null; - private _selectionHighlight: HighlightModel | null = null; - private _selectionText: { selectedText: string, fullText: string } | null = null; - private _inputHighlight: HighlightModel | null = null; + private _action: actions.AssertAction | null = null; + private _dialogElement: HTMLElement | null = null; private _acceptButton: HTMLElement; private _cancelButton: HTMLElement; + private _keyboardListener: ((event: KeyboardEvent) => void) | undefined; constructor(private _recorder: Recorder) { this._acceptButton = this._recorder.document.createElement('x-pw-tool-item'); + this._acceptButton.title = 'Accept'; this._acceptButton.classList.add('accept'); this._acceptButton.appendChild(this._recorder.document.createElement('x-div')); - this._acceptButton.addEventListener('click', () => this._commitAction()); + this._acceptButton.addEventListener('click', () => this._commit()); this._cancelButton = this._recorder.document.createElement('x-pw-tool-item'); + this._cancelButton.title = 'Close'; this._cancelButton.classList.add('cancel'); this._cancelButton.appendChild(this._recorder.document.createElement('x-div')); - this._cancelButton.addEventListener('click', () => this._cancelAction()); + this._cancelButton.addEventListener('click', () => this._closeDialog()); } cursor() { - return 'text'; + return 'pointer'; } cleanup() { + this._closeDialog(); this._hoverHighlight = null; - this._selectionHighlight = null; - this._selectionText = null; - this._inputHighlight = null; } onClick(event: MouseEvent) { + if (!this._dialogElement) + this._showDialog(); consumeEvent(event); - const selection = this._recorder.document.getSelection(); - if (event.detail === 1 && selection && !selection.toString() && !this._inputHighlight) { - const target = this._recorder.deepEventTarget(event); - selection.selectAllChildren(target); - this._updateSelectionHighlight(); - } - } - - onMouseDown(event: MouseEvent) { - const target = this._recorder.deepEventTarget(event); - if (['INPUT', 'TEXTAREA'].includes(target.nodeName)) { - this._recorder.injectedScript.window.getSelection()?.empty(); - this._inputHighlight = generateSelector(this._recorder.injectedScript, target, { testIdAttributeName: this._recorder.state.testIdAttributeName }); - this._showHighlight(true); - consumeEvent(event); - return; - } - - this._inputHighlight = null; - this._hoverHighlight = null; - this._updateSelectionHighlight(); - } - - onMouseUp(event: MouseEvent) { - this._updateSelectionHighlight(); } onMouseMove(event: MouseEvent) { - const selection = this._recorder.document.getSelection(); - if (selection && selection.toString()) { - this._updateSelectionHighlight(); - return; - } - if (this._inputHighlight || event.buttons) + if (this._dialogElement) return; const target = this._recorder.deepEventTarget(event); if (this._hoverHighlight?.elements[0] === target) return; - this._hoverHighlight = elementText(new Map(), target).full ? { elements: [target], selector: '' } : null; + this._hoverHighlight = target.nodeName === 'INPUT' || target.nodeName === 'TEXTAREA' || elementText(new Map(), target).full ? { elements: [target], selector: '' } : null; this._recorder.updateHighlight(this._hoverHighlight, true, { color: '#8acae480' }); } - onDragStart(event: DragEvent) { - consumeEvent(event); - } - onKeyDown(event: KeyboardEvent) { - if (event.key === 'Escape') { - const selection = this._recorder.document.getSelection(); - if (selection && selection.toString()) - this._resetSelectionAndHighlight(); - else - this._recorder.delegate.setMode?.('recording'); - consumeEvent(event); - return; - } - - if (event.key === 'Enter') { - this._commitAction(); - consumeEvent(event); - return; - } - - // Only allow keys that control text selection. - if (!['ArrowLeft', 'ArrowUp', 'ArrowRight', 'ArrowDown', 'Shift', 'Control', 'Meta', 'Alt', 'AltGraph'].includes(event.key)) { - consumeEvent(event); - return; - } - } - - onKeyUp(event: KeyboardEvent) { + if (event.key === 'Escape') + this._recorder.delegate.setMode?.('recording'); consumeEvent(event); } - onScroll(event: Event) { - this._hoverHighlight = null; - this._showHighlight(false); - } - - private _generateAction(): actions.Action | null { - if (this._inputHighlight) { - const target = this._inputHighlight.elements[0] as HTMLInputElement; - if (target.nodeName === 'INPUT' && ['checkbox', 'radio'].includes(target.type.toLowerCase())) { + private _generateAction(): actions.AssertAction | null { + const target = this._hoverHighlight?.elements[0]; + if (!target) + return null; + if (target.nodeName === 'INPUT' || target.nodeName === 'TEXTAREA') { + const { selector } = generateSelector(this._recorder.injectedScript, target, { testIdAttributeName: this._recorder.state.testIdAttributeName }); + if (target.nodeName === 'INPUT' && ['checkbox', 'radio'].includes((target as HTMLInputElement).type.toLowerCase())) { return { name: 'assertChecked', - selector: this._inputHighlight.selector, + selector, signals: [], // Interestingly, inputElement.checked is reversed inside this event handler. - checked: !(target as HTMLInputElement).checked, + checked: (target as HTMLInputElement).checked, }; } else { return { name: 'assertValue', - selector: this._inputHighlight.selector, + selector, signals: [], - value: target.value, + value: (target as HTMLInputElement).value, }; } - } else if (this._selectionText && this._selectionHighlight) { + } else { + const { selector } = generateSelector(this._recorder.injectedScript, target, { testIdAttributeName: this._recorder.state.testIdAttributeName, forTextExpect: true }); return { name: 'assertText', - selector: this._selectionHighlight.selector, + selector, signals: [], - text: this._selectionText.selectedText, - substring: this._selectionText.fullText !== this._selectionText.selectedText, + text: target.textContent!, + substring: true, }; } - return null; } - private _generateActionPreview() { - const action = this._generateAction(); - // TODO: support other languages, maybe unify with code generator? + private _renderValue(action: actions.Action) { if (action?.name === 'assertText') - return `expect(${asLocator(this._recorder.state.language, action.selector)}).${action.substring ? 'toContainText' : 'toHaveText'}(${escapeWithQuotes(action.text)})`; + return normalizeWhiteSpace(action.text); if (action?.name === 'assertChecked') - return `expect(${asLocator(this._recorder.state.language, action.selector)})${action.checked ? '' : '.not'}.toBeChecked()`; - if (action?.name === 'assertValue') { - const assertion = action.value ? `toHaveValue(${escapeWithQuotes(action.value)})` : `toBeEmpty()`; - return `expect(${asLocator(this._recorder.state.language, action.selector)}).${assertion}`; - } + return String(action.checked); + if (action?.name === 'assertValue') + return action.value; return ''; } - private _commitAction() { - const action = this._generateAction(); - if (action) { - this._resetSelectionAndHighlight(); - this._recorder.delegate.recordAction?.(action); - this._recorder.delegate.setMode?.('recording'); - } - } - - private _cancelAction() { - this._resetSelectionAndHighlight(); - } - - private _resetSelectionAndHighlight() { - this._selectionHighlight = null; - this._selectionText = null; - this._inputHighlight = null; - this._recorder.injectedScript.window.getSelection()?.empty(); - this._recorder.updateHighlight(null, false); - } - - private _updateSelectionHighlight() { - if (this._inputHighlight) + private _commit() { + if (!this._action || !this._dialogElement) return; - const selection = this._recorder.document.getSelection(); - const selectedText = normalizeWhiteSpace(selection?.toString() || ''); - let highlight: HighlightModel | null = null; - if (selection && selection.focusNode && selection.anchorNode && selectedText) { - const focusElement = enclosingElement(selection.focusNode); - let lcaElement = focusElement ? enclosingElement(selection.anchorNode) : undefined; - while (lcaElement && !isInsideScope(lcaElement, focusElement)) - lcaElement = parentElementOrShadowHost(lcaElement); - highlight = lcaElement ? generateSelector(this._recorder.injectedScript, lcaElement, { testIdAttributeName: this._recorder.state.testIdAttributeName, forTextExpect: true }) : null; - } - const fullText = highlight ? normalizeWhiteSpace(elementText(new Map(), highlight.elements[0]).full) : ''; - const selectionText = highlight ? { selectedText, fullText } : null; - if (highlight?.selector === this._selectionHighlight?.selector && this._selectionText?.fullText === selectionText?.fullText && this._selectionText?.selectedText === selectionText?.selectedText) - return; - this._selectionHighlight = highlight; - this._selectionText = selectionText; - this._showHighlight(true); + this._closeDialog(); + this._recorder.delegate.recordAction?.(this._action); + this._recorder.delegate.setMode?.('recording'); } - private _showHighlight(userGesture: boolean) { - const options: HighlightOptions = { - color: '#6fdcbd38', - tooltipText: this._generateActionPreview(), - toolbar: [this._acceptButton, this._cancelButton], - interactive: true, + private _showDialog() { + const target = this._hoverHighlight?.elements[0]; + if (!target) + return; + this._action = this._generateAction(); + if (!this._action) + return; + + this._dialogElement = this._recorder.document.createElement('x-pw-dialog'); + this._keyboardListener = (event: KeyboardEvent) => { + if (event.key === 'Escape') { + this._closeDialog(); + return; + } + if (event.key === 'Enter' && (event.ctrlKey || event.metaKey)) { + if (this._dialogElement) + this._commit(); + return; + } }; - if (this._inputHighlight) { - this._recorder.updateHighlight(this._inputHighlight, userGesture, options); - } else { - options.anchorGetter = (e: Element) => this._recorder.document.getSelection()?.getRangeAt(0)?.getBoundingClientRect() || e.getBoundingClientRect(); - this._recorder.updateHighlight(this._selectionHighlight, userGesture, options); - } + this._recorder.document.addEventListener('keydown', this._keyboardListener, true); + const toolbarElement = this._recorder.document.createElement('x-pw-tools-list'); + toolbarElement.appendChild(this._createLabel(this._action)); + toolbarElement.appendChild(this._recorder.document.createElement('x-spacer')); + toolbarElement.appendChild(this._acceptButton); + toolbarElement.appendChild(this._cancelButton); + + this._dialogElement.appendChild(toolbarElement); + const bodyElement = this._recorder.document.createElement('x-pw-dialog-body'); + const locatorElement = this._recorder.document.createElement('input'); + locatorElement.classList.add('locator-editor'); + locatorElement.value = asLocator(this._recorder.state.language, this._action.selector); + locatorElement.addEventListener('input', () => { + if (this._action) { + const selector = locatorOrSelectorAsSelector(this._recorder.state.language, locatorElement.value, this._recorder.state.testIdAttributeName); + const model: HighlightModel = { + selector, + elements: this._recorder.injectedScript.querySelectorAll(parseSelector(selector), this._recorder.document), + }; + this._action.selector = selector; + this._recorder.updateHighlight(model, true); + } + }); + const textElement = this._recorder.document.createElement('textarea'); + textElement.value = this._renderValue(this._action); + textElement.classList.add('text-editor'); + + textElement.addEventListener('input', () => { + if (this._action?.name === 'assertText') + this._action.text = normalizeWhiteSpace(elementText(new Map(), textElement).full); + if (this._action?.name === 'assertChecked') + this._action.checked = textElement.value === 'true'; + if (this._action?.name === 'assertValue') + this._action.value = textElement.value; + }); + + bodyElement.appendChild(locatorElement); + bodyElement.appendChild(textElement); + this._dialogElement.appendChild(bodyElement); + this._recorder.highlight.appendChild(this._dialogElement); + const position = this._recorder.highlight.tooltipPosition(this._recorder.highlight.firstBox()!, this._dialogElement); + this._dialogElement.style.top = position.anchorTop + 'px'; + this._dialogElement.style.left = position.anchorLeft + 'px'; + textElement.focus(); + } + + private _createLabel(action: actions.AssertAction) { + const labelElement = this._recorder.document.createElement('x-pw-tool-label'); + labelElement.textContent = action.name === 'assertText' ? 'Assert text' : action.name === 'assertValue' ? 'Assert value' : 'Assert checked'; + return labelElement; + } + + private _closeDialog() { + if (!this._dialogElement) + return; + this._dialogElement.remove(); + this._recorder.document.removeEventListener('keydown', this._keyboardListener!); + this._dialogElement = null; } } diff --git a/packages/playwright-core/src/server/recorder/recorderActions.ts b/packages/playwright-core/src/server/recorder/recorderActions.ts index 5be43e9ea9..3a4bbab325 100644 --- a/packages/playwright-core/src/server/recorder/recorderActions.ts +++ b/packages/playwright-core/src/server/recorder/recorderActions.ts @@ -114,6 +114,7 @@ export type AssertCheckedAction = ActionBase & { }; export type Action = ClickAction | CheckAction | ClosesPageAction | OpenPageAction | UncheckAction | FillAction | NavigateAction | PressAction | SelectAction | SetInputFilesAction | AssertTextAction | AssertValueAction | AssertCheckedAction; +export type AssertAction = AssertCheckedAction | AssertValueAction | AssertTextAction; // Signals. From 93d202c48035907f7fb50312077c3ba3a343a881 Mon Sep 17 00:00:00 2001 From: Playwright Service <89237858+playwrightmachine@users.noreply.github.com> Date: Thu, 9 Nov 2023 06:16:20 -0800 Subject: [PATCH 018/491] feat(chromium): roll to r1090 (#28052) Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> --- README.md | 4 +- packages/playwright-core/browsers.json | 8 +- .../src/server/chromium/protocol.d.ts | 2 +- .../src/server/deviceDescriptorsSource.json | 96 +++++++++---------- packages/playwright-core/types/protocol.d.ts | 2 +- 5 files changed, 56 insertions(+), 56 deletions(-) diff --git a/README.md b/README.md index 50b0de8f45..6398a385cd 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-120.0.6099.5-blue.svg?logo=google-chrome)](https://www.chromium.org/Home) [![Firefox version](https://img.shields.io/badge/firefox-119.0-blue.svg?logo=firefoxbrowser)](https://www.mozilla.org/en-US/firefox/new/) [![WebKit version](https://img.shields.io/badge/webkit-17.4-blue.svg?logo=safari)](https://webkit.org/) +[![npm version](https://img.shields.io/npm/v/playwright.svg)](https://www.npmjs.com/package/playwright) [![Chromium version](https://img.shields.io/badge/chromium-120.0.6099.18-blue.svg?logo=google-chrome)](https://www.chromium.org/Home) [![Firefox version](https://img.shields.io/badge/firefox-119.0-blue.svg?logo=firefoxbrowser)](https://www.mozilla.org/en-US/firefox/new/) [![WebKit version](https://img.shields.io/badge/webkit-17.4-blue.svg?logo=safari)](https://webkit.org/) ## [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 120.0.6099.5 | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| Chromium 120.0.6099.18 | :white_check_mark: | :white_check_mark: | :white_check_mark: | | WebKit 17.4 | :white_check_mark: | :white_check_mark: | :white_check_mark: | | Firefox 119.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 2978c5c966..4631abe422 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -3,15 +3,15 @@ "browsers": [ { "name": "chromium", - "revision": "1089", + "revision": "1090", "installByDefault": true, - "browserVersion": "120.0.6099.5" + "browserVersion": "120.0.6099.18" }, { "name": "chromium-with-symbols", - "revision": "1089", + "revision": "1090", "installByDefault": false, - "browserVersion": "120.0.6099.5" + "browserVersion": "120.0.6099.18" }, { "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 8b390444aa..e6115a8e7f 100644 --- a/packages/playwright-core/src/server/chromium/protocol.d.ts +++ b/packages/playwright-core/src/server/chromium/protocol.d.ts @@ -878,7 +878,7 @@ Should be updated alongside RequestIdTokenStatus in third_party/blink/public/mojom/devtools/inspector_issue.mojom to include all cases except for success. */ - export type FederatedAuthRequestIssueReason = "ShouldEmbargo"|"TooManyRequests"|"WellKnownHttpNotFound"|"WellKnownNoResponse"|"WellKnownInvalidResponse"|"WellKnownListEmpty"|"WellKnownInvalidContentType"|"ConfigNotInWellKnown"|"WellKnownTooBig"|"ConfigHttpNotFound"|"ConfigNoResponse"|"ConfigInvalidResponse"|"ConfigInvalidContentType"|"ClientMetadataHttpNotFound"|"ClientMetadataNoResponse"|"ClientMetadataInvalidResponse"|"ClientMetadataInvalidContentType"|"DisabledInSettings"|"ErrorFetchingSignin"|"InvalidSigninResponse"|"AccountsHttpNotFound"|"AccountsNoResponse"|"AccountsInvalidResponse"|"AccountsListEmpty"|"AccountsInvalidContentType"|"IdTokenHttpNotFound"|"IdTokenNoResponse"|"IdTokenInvalidResponse"|"IdTokenInvalidRequest"|"IdTokenInvalidContentType"|"ErrorIdToken"|"Canceled"|"RpPageNotVisible"|"SilentMediationFailure"|"ThirdPartyCookiesBlocked"|"NotSignedInWithIdp"; + export type FederatedAuthRequestIssueReason = "ShouldEmbargo"|"TooManyRequests"|"WellKnownHttpNotFound"|"WellKnownNoResponse"|"WellKnownInvalidResponse"|"WellKnownListEmpty"|"WellKnownInvalidContentType"|"ConfigNotInWellKnown"|"WellKnownTooBig"|"ConfigHttpNotFound"|"ConfigNoResponse"|"ConfigInvalidResponse"|"ConfigInvalidContentType"|"ClientMetadataHttpNotFound"|"ClientMetadataNoResponse"|"ClientMetadataInvalidResponse"|"ClientMetadataInvalidContentType"|"DisabledInSettings"|"ErrorFetchingSignin"|"InvalidSigninResponse"|"AccountsHttpNotFound"|"AccountsNoResponse"|"AccountsInvalidResponse"|"AccountsListEmpty"|"AccountsInvalidContentType"|"IdTokenHttpNotFound"|"IdTokenNoResponse"|"IdTokenInvalidResponse"|"IdTokenIdpErrorResponse"|"IdTokenCrossSiteIdpErrorResponse"|"IdTokenInvalidRequest"|"IdTokenInvalidContentType"|"ErrorIdToken"|"Canceled"|"RpPageNotVisible"|"SilentMediationFailure"|"ThirdPartyCookiesBlocked"|"NotSignedInWithIdp"; export interface FederatedAuthUserInfoRequestIssueDetails { federatedAuthUserInfoRequestIssueReason: FederatedAuthUserInfoRequestIssueReason; } diff --git a/packages/playwright-core/src/server/deviceDescriptorsSource.json b/packages/playwright-core/src/server/deviceDescriptorsSource.json index bfc39f856c..1a7a6c1fa1 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/120.0.6099.5 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 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/120.0.6099.5 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 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/120.0.6099.5 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 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/120.0.6099.5 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 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/120.0.6099.5 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 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/120.0.6099.5 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 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/120.0.6099.5 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 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/120.0.6099.5 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 Safari/537.36", "viewport": { "width": 1138, "height": 712 @@ -978,7 +978,7 @@ "defaultBrowserType": "webkit" }, "LG Optimus L70": { - "userAgent": "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/120.0.6099.5 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/120.0.6099.18 Mobile Safari/537.36", "viewport": { "width": 384, "height": 640 @@ -989,7 +989,7 @@ "defaultBrowserType": "chromium" }, "LG Optimus L70 landscape": { - "userAgent": "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/120.0.6099.5 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/120.0.6099.18 Mobile Safari/537.36", "viewport": { "width": 640, "height": 384 @@ -1000,7 +1000,7 @@ "defaultBrowserType": "chromium" }, "Microsoft Lumia 550": { - "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.5 Mobile Safari/537.36 Edge/14.14263", + "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 Mobile Safari/537.36 Edge/14.14263", "viewport": { "width": 640, "height": 360 @@ -1011,7 +1011,7 @@ "defaultBrowserType": "chromium" }, "Microsoft Lumia 550 landscape": { - "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.5 Mobile Safari/537.36 Edge/14.14263", + "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 Mobile Safari/537.36 Edge/14.14263", "viewport": { "width": 360, "height": 640 @@ -1022,7 +1022,7 @@ "defaultBrowserType": "chromium" }, "Microsoft Lumia 950": { - "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.5 Mobile Safari/537.36 Edge/14.14263", + "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 Mobile Safari/537.36 Edge/14.14263", "viewport": { "width": 360, "height": 640 @@ -1033,7 +1033,7 @@ "defaultBrowserType": "chromium" }, "Microsoft Lumia 950 landscape": { - "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.5 Mobile Safari/537.36 Edge/14.14263", + "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 Mobile Safari/537.36 Edge/14.14263", "viewport": { "width": 640, "height": 360 @@ -1044,7 +1044,7 @@ "defaultBrowserType": "chromium" }, "Nexus 10": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.5 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 Safari/537.36", "viewport": { "width": 800, "height": 1280 @@ -1055,7 +1055,7 @@ "defaultBrowserType": "chromium" }, "Nexus 10 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.5 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 Safari/537.36", "viewport": { "width": 1280, "height": 800 @@ -1066,7 +1066,7 @@ "defaultBrowserType": "chromium" }, "Nexus 4": { - "userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.5 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 Mobile Safari/537.36", "viewport": { "width": 384, "height": 640 @@ -1077,7 +1077,7 @@ "defaultBrowserType": "chromium" }, "Nexus 4 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.5 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 Mobile Safari/537.36", "viewport": { "width": 640, "height": 384 @@ -1088,7 +1088,7 @@ "defaultBrowserType": "chromium" }, "Nexus 5": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.5 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 Mobile Safari/537.36", "viewport": { "width": 360, "height": 640 @@ -1099,7 +1099,7 @@ "defaultBrowserType": "chromium" }, "Nexus 5 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.5 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 Mobile Safari/537.36", "viewport": { "width": 640, "height": 360 @@ -1110,7 +1110,7 @@ "defaultBrowserType": "chromium" }, "Nexus 5X": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.5 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 Mobile Safari/537.36", "viewport": { "width": 412, "height": 732 @@ -1121,7 +1121,7 @@ "defaultBrowserType": "chromium" }, "Nexus 5X landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.5 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 Mobile Safari/537.36", "viewport": { "width": 732, "height": 412 @@ -1132,7 +1132,7 @@ "defaultBrowserType": "chromium" }, "Nexus 6": { - "userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.5 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 Mobile Safari/537.36", "viewport": { "width": 412, "height": 732 @@ -1143,7 +1143,7 @@ "defaultBrowserType": "chromium" }, "Nexus 6 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.5 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 Mobile Safari/537.36", "viewport": { "width": 732, "height": 412 @@ -1154,7 +1154,7 @@ "defaultBrowserType": "chromium" }, "Nexus 6P": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.5 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 Mobile Safari/537.36", "viewport": { "width": 412, "height": 732 @@ -1165,7 +1165,7 @@ "defaultBrowserType": "chromium" }, "Nexus 6P landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.5 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 Mobile Safari/537.36", "viewport": { "width": 732, "height": 412 @@ -1176,7 +1176,7 @@ "defaultBrowserType": "chromium" }, "Nexus 7": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.5 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 Safari/537.36", "viewport": { "width": 600, "height": 960 @@ -1187,7 +1187,7 @@ "defaultBrowserType": "chromium" }, "Nexus 7 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.5 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 Safari/537.36", "viewport": { "width": 960, "height": 600 @@ -1242,7 +1242,7 @@ "defaultBrowserType": "webkit" }, "Pixel 2": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.5 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 Mobile Safari/537.36", "viewport": { "width": 411, "height": 731 @@ -1253,7 +1253,7 @@ "defaultBrowserType": "chromium" }, "Pixel 2 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.5 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 Mobile Safari/537.36", "viewport": { "width": 731, "height": 411 @@ -1264,7 +1264,7 @@ "defaultBrowserType": "chromium" }, "Pixel 2 XL": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.5 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 Mobile Safari/537.36", "viewport": { "width": 411, "height": 823 @@ -1275,7 +1275,7 @@ "defaultBrowserType": "chromium" }, "Pixel 2 XL landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.5 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 Mobile Safari/537.36", "viewport": { "width": 823, "height": 411 @@ -1286,7 +1286,7 @@ "defaultBrowserType": "chromium" }, "Pixel 3": { - "userAgent": "Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.5 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 Mobile Safari/537.36", "viewport": { "width": 393, "height": 786 @@ -1297,7 +1297,7 @@ "defaultBrowserType": "chromium" }, "Pixel 3 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.5 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 Mobile Safari/537.36", "viewport": { "width": 786, "height": 393 @@ -1308,7 +1308,7 @@ "defaultBrowserType": "chromium" }, "Pixel 4": { - "userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.5 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 Mobile Safari/537.36", "viewport": { "width": 353, "height": 745 @@ -1319,7 +1319,7 @@ "defaultBrowserType": "chromium" }, "Pixel 4 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.5 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 Mobile Safari/537.36", "viewport": { "width": 745, "height": 353 @@ -1330,7 +1330,7 @@ "defaultBrowserType": "chromium" }, "Pixel 4a (5G)": { - "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.5 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 Mobile Safari/537.36", "screen": { "width": 412, "height": 892 @@ -1345,7 +1345,7 @@ "defaultBrowserType": "chromium" }, "Pixel 4a (5G) landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.5 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 Mobile Safari/537.36", "screen": { "height": 892, "width": 412 @@ -1360,7 +1360,7 @@ "defaultBrowserType": "chromium" }, "Pixel 5": { - "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.5 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 Mobile Safari/537.36", "screen": { "width": 393, "height": 851 @@ -1375,7 +1375,7 @@ "defaultBrowserType": "chromium" }, "Pixel 5 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.5 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 Mobile Safari/537.36", "screen": { "width": 851, "height": 393 @@ -1390,7 +1390,7 @@ "defaultBrowserType": "chromium" }, "Pixel 7": { - "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.5 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 Mobile Safari/537.36", "screen": { "width": 412, "height": 915 @@ -1405,7 +1405,7 @@ "defaultBrowserType": "chromium" }, "Pixel 7 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.5 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 Mobile Safari/537.36", "screen": { "width": 915, "height": 412 @@ -1420,7 +1420,7 @@ "defaultBrowserType": "chromium" }, "Moto G4": { - "userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.5 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 Mobile Safari/537.36", "viewport": { "width": 360, "height": 640 @@ -1431,7 +1431,7 @@ "defaultBrowserType": "chromium" }, "Moto G4 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.5 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 Mobile Safari/537.36", "viewport": { "width": 640, "height": 360 @@ -1442,7 +1442,7 @@ "defaultBrowserType": "chromium" }, "Desktop Chrome HiDPI": { - "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.5 Safari/537.36", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 Safari/537.36", "screen": { "width": 1792, "height": 1120 @@ -1457,7 +1457,7 @@ "defaultBrowserType": "chromium" }, "Desktop Edge HiDPI": { - "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.5 Safari/537.36 Edg/120.0.6099.5", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 Safari/537.36 Edg/120.0.6099.18", "screen": { "width": 1792, "height": 1120 @@ -1502,7 +1502,7 @@ "defaultBrowserType": "webkit" }, "Desktop Chrome": { - "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.5 Safari/537.36", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 Safari/537.36", "screen": { "width": 1920, "height": 1080 @@ -1517,7 +1517,7 @@ "defaultBrowserType": "chromium" }, "Desktop Edge": { - "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.5 Safari/537.36 Edg/120.0.6099.5", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 Safari/537.36 Edg/120.0.6099.18", "screen": { "width": 1920, "height": 1080 diff --git a/packages/playwright-core/types/protocol.d.ts b/packages/playwright-core/types/protocol.d.ts index 8b390444aa..e6115a8e7f 100644 --- a/packages/playwright-core/types/protocol.d.ts +++ b/packages/playwright-core/types/protocol.d.ts @@ -878,7 +878,7 @@ Should be updated alongside RequestIdTokenStatus in third_party/blink/public/mojom/devtools/inspector_issue.mojom to include all cases except for success. */ - export type FederatedAuthRequestIssueReason = "ShouldEmbargo"|"TooManyRequests"|"WellKnownHttpNotFound"|"WellKnownNoResponse"|"WellKnownInvalidResponse"|"WellKnownListEmpty"|"WellKnownInvalidContentType"|"ConfigNotInWellKnown"|"WellKnownTooBig"|"ConfigHttpNotFound"|"ConfigNoResponse"|"ConfigInvalidResponse"|"ConfigInvalidContentType"|"ClientMetadataHttpNotFound"|"ClientMetadataNoResponse"|"ClientMetadataInvalidResponse"|"ClientMetadataInvalidContentType"|"DisabledInSettings"|"ErrorFetchingSignin"|"InvalidSigninResponse"|"AccountsHttpNotFound"|"AccountsNoResponse"|"AccountsInvalidResponse"|"AccountsListEmpty"|"AccountsInvalidContentType"|"IdTokenHttpNotFound"|"IdTokenNoResponse"|"IdTokenInvalidResponse"|"IdTokenInvalidRequest"|"IdTokenInvalidContentType"|"ErrorIdToken"|"Canceled"|"RpPageNotVisible"|"SilentMediationFailure"|"ThirdPartyCookiesBlocked"|"NotSignedInWithIdp"; + export type FederatedAuthRequestIssueReason = "ShouldEmbargo"|"TooManyRequests"|"WellKnownHttpNotFound"|"WellKnownNoResponse"|"WellKnownInvalidResponse"|"WellKnownListEmpty"|"WellKnownInvalidContentType"|"ConfigNotInWellKnown"|"WellKnownTooBig"|"ConfigHttpNotFound"|"ConfigNoResponse"|"ConfigInvalidResponse"|"ConfigInvalidContentType"|"ClientMetadataHttpNotFound"|"ClientMetadataNoResponse"|"ClientMetadataInvalidResponse"|"ClientMetadataInvalidContentType"|"DisabledInSettings"|"ErrorFetchingSignin"|"InvalidSigninResponse"|"AccountsHttpNotFound"|"AccountsNoResponse"|"AccountsInvalidResponse"|"AccountsListEmpty"|"AccountsInvalidContentType"|"IdTokenHttpNotFound"|"IdTokenNoResponse"|"IdTokenInvalidResponse"|"IdTokenIdpErrorResponse"|"IdTokenCrossSiteIdpErrorResponse"|"IdTokenInvalidRequest"|"IdTokenInvalidContentType"|"ErrorIdToken"|"Canceled"|"RpPageNotVisible"|"SilentMediationFailure"|"ThirdPartyCookiesBlocked"|"NotSignedInWithIdp"; export interface FederatedAuthUserInfoRequestIssueDetails { federatedAuthUserInfoRequestIssueReason: FederatedAuthUserInfoRequestIssueReason; } From 6c2abf016ec53445ffbfca9dd3b752685b489a50 Mon Sep 17 00:00:00 2001 From: Playwright Service <89237858+playwrightmachine@users.noreply.github.com> Date: Thu, 9 Nov 2023 06:16:43 -0800 Subject: [PATCH 019/491] feat(chromium-tip-of-tree): roll to r1167 (#28053) 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 4631abe422..3b6c409c7a 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -15,9 +15,9 @@ }, { "name": "chromium-tip-of-tree", - "revision": "1166", + "revision": "1167", "installByDefault": false, - "browserVersion": "121.0.6113.0" + "browserVersion": "121.0.6117.0" }, { "name": "firefox", From 7f10fe935a9efa337f294c9571e5a4414ccd49aa Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Thu, 9 Nov 2023 08:27:34 -0800 Subject: [PATCH 020/491] test: add a test for concurrent hover (#28042) References #27969. --- tests/library/emulation-focus.spec.ts | 33 +++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/tests/library/emulation-focus.spec.ts b/tests/library/emulation-focus.spec.ts index bc4bbb801c..94f18927b4 100644 --- a/tests/library/emulation-focus.spec.ts +++ b/tests/library/emulation-focus.spec.ts @@ -186,3 +186,36 @@ browserTest('should focus with more than one page/context', async ({ contextFact expect(await page1.evaluate(() => !!window['gotFocus'])).toBe(true); expect(await page2.evaluate(() => !!window['gotFocus'])).toBe(true); }); + +browserTest('should trigger hover state concurrently', async ({ browserType, browserName }) => { + browserTest.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/27969' }); + browserTest.fixme(browserName === 'firefox'); + + const browser1 = await browserType.launch(); + const context1 = await browser1.newContext(); + const page1 = await context1.newPage(); + const page2 = await context1.newPage(); + const browser2 = await browserType.launch(); + const page3 = await browser2.newPage(); + + for (const page of [page1, page2, page3]) { + await page.setContent(` + +
hover me
+ `); + } + + for (const page of [page1, page2, page3]) + await page.hover('span'); + for (const page of [page1, page2, page3]) + await page.click('button'); + for (const page of [page1, page2, page3]) + expect(await page.evaluate('window.clicked')).toBe(1); + for (const page of [page1, page2, page3]) + await page.click('button'); + for (const page of [page1, page2, page3]) + expect(await page.evaluate('window.clicked')).toBe(2); +}); From 62b6af3a7f67b08e6588699a49753b7ed3f1e3df Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Thu, 9 Nov 2023 08:36:05 -0800 Subject: [PATCH 021/491] fix(android): respect recordHar option (#28046) Fixes #28015. --- packages/playwright-core/src/client/android.ts | 6 ++++-- tests/android/browser.spec.ts | 17 +++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/packages/playwright-core/src/client/android.ts b/packages/playwright-core/src/client/android.ts index 6c7a9e44ae..651bcbe3e9 100644 --- a/packages/playwright-core/src/client/android.ts +++ b/packages/playwright-core/src/client/android.ts @@ -274,8 +274,10 @@ export class AndroidDevice extends ChannelOwner i async launchBrowser(options: types.BrowserContextOptions & { pkg?: string } = {}): Promise { const contextOptions = await prepareBrowserContextParams(options); - const { context } = await this._channel.launchBrowser(contextOptions); - return BrowserContext.from(context) as BrowserContext; + const result = await this._channel.launchBrowser(contextOptions); + const context = BrowserContext.from(result.context) as BrowserContext; + context._setOptions(contextOptions, {}); + return context; } async waitForEvent(event: string, optionsOrPredicate: types.WaitForEventOptions = {}): Promise { diff --git a/tests/android/browser.spec.ts b/tests/android/browser.spec.ts index 2590ef791b..d1b54206b9 100644 --- a/tests/android/browser.spec.ts +++ b/tests/android/browser.spec.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import fs from 'fs'; import { androidTest as test, expect } from './androidTest'; test.afterAll(async ({ androidDevice }) => { @@ -155,3 +156,19 @@ test('should be able to pass context options', async ({ androidDevice, httpsServ expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: light)').matches)).toBe(false); await context.close(); }); + +test('should record har', async ({ androidDevice }) => { + test.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/28015' }); + const harPath = test.info().outputPath('test.har'); + + const context = await androidDevice.launchBrowser({ + recordHar: { path: harPath } + }); + const [page] = context.pages(); + await page.goto('data:text/html,Hello'); + await page.waitForLoadState('domcontentloaded'); + await context.close(); + + const log = JSON.parse(fs.readFileSync(harPath).toString())['log']; + expect(log.pages[0].title).toBe('Hello'); +}); From 6d3913f459570df5d12cdda65f6a2e0a50cd6900 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Thu, 9 Nov 2023 21:59:28 +0100 Subject: [PATCH 022/491] chore: fix Ubuntu 22.04 WebKit on 20.04 host (#28068) https://github.com/microsoft/playwright/issues/27313 --- utils/docker/Dockerfile.jammy | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/utils/docker/Dockerfile.jammy b/utils/docker/Dockerfile.jammy index fc0364de5c..d7c70a729c 100644 --- a/utils/docker/Dockerfile.jammy +++ b/utils/docker/Dockerfile.jammy @@ -40,6 +40,15 @@ RUN mkdir /ms-playwright && \ npm i /tmp/playwright-core.tar.gz && \ npm exec --no -- playwright-core mark-docker-image "${DOCKER_IMAGE_NAME_TEMPLATE}" && \ npm exec --no -- playwright-core install --with-deps && rm -rf /var/lib/apt/lists/* && \ + # Workaround for https://github.com/microsoft/playwright/issues/27313 + # While the gstreamer plugin load process can be in-process, it ended up throwing + # an error that it can't have libsoup2 and libsoup3 in the same process because + # libgstwebrtc is linked against libsoup2. So we just remove the plugin. + if [ "$(uname -m)" = "aarch64" ]; then \ + rm /usr/lib/aarch64-linux-gnu/gstreamer-1.0/libgstwebrtc.so; \ + else \ + rm /usr/lib/x86_64-linux-gnu/gstreamer-1.0/libgstwebrtc.so; \ + fi && \ rm /tmp/playwright-core.tar.gz && \ rm -rf /ms-playwright-agent && \ rm -rf ~/.npm/ && \ From 9b1b1e02ed3a2b3337d40febd948a5f06da67967 Mon Sep 17 00:00:00 2001 From: Playwright Service <89237858+playwrightmachine@users.noreply.github.com> Date: Thu, 9 Nov 2023 13:45:04 -0800 Subject: [PATCH 023/491] feat(webkit): roll to r1941 (#28069) Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> --- packages/playwright-core/browsers.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/playwright-core/browsers.json b/packages/playwright-core/browsers.json index 3b6c409c7a..620739906c 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -39,7 +39,7 @@ }, { "name": "webkit", - "revision": "1932", + "revision": "1941", "installByDefault": true, "revisionOverrides": { "mac10.14": "1446", From 1b22c43c3546bc4363071b4dd0b164ea9bec5e36 Mon Sep 17 00:00:00 2001 From: Playwright Service <89237858+playwrightmachine@users.noreply.github.com> Date: Fri, 10 Nov 2023 00:31:18 -0800 Subject: [PATCH 024/491] feat(webkit): roll to r1942 (#28073) --- packages/playwright-core/browsers.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/playwright-core/browsers.json b/packages/playwright-core/browsers.json index 620739906c..34b6f1374f 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -39,7 +39,7 @@ }, { "name": "webkit", - "revision": "1941", + "revision": "1942", "installByDefault": true, "revisionOverrides": { "mac10.14": "1446", From 2954e1263e81546baa4f82d35d8da9500cfdaf5c Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Fri, 10 Nov 2023 16:28:45 +0100 Subject: [PATCH 025/491] test: skip dispatchEvent(deviceOrientation) tests on Android (#28077) Its [only available to SecureContexts](https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/modules/device_orientation/device_orientation_event.idl;l=34?q=device_orientation_event.idl&ss=chromium%2Fchromium%2Fsrc) which our loopback in Android is not treated as a SecureContext. We could either move it into the library tests, but then loose page test coverage or just skip it. I decided for the latter. Relates https://github.com/microsoft/playwright/pull/27960. --- tests/page/page-dispatchevent.spec.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/page/page-dispatchevent.spec.ts b/tests/page/page-dispatchevent.spec.ts index 840c525788..44dfa90592 100644 --- a/tests/page/page-dispatchevent.spec.ts +++ b/tests/page/page-dispatchevent.spec.ts @@ -172,7 +172,8 @@ it('should dispatch wheel event', async ({ page, server }) => { expect(await eventsHandle.evaluate(e => ({ deltaX: e[0].deltaX, deltaY: e[0].deltaY }))).toEqual({ deltaX: 100, deltaY: 200 }); }); -it('should dispatch device orientation event', async ({ page, server }) => { +it('should dispatch device orientation event', async ({ page, server, isAndroid }) => { + it.skip(isAndroid, 'DeviceOrientationEvent is only available in a secure context. While Androids loopback is not treated as secure.'); await page.goto(server.PREFIX + '/device-orientation.html'); await page.locator('html').dispatchEvent('deviceorientation', { alpha: 10, beta: 20, gamma: 30 }); expect(await page.evaluate('result')).toBe('Oriented'); @@ -182,7 +183,8 @@ it('should dispatch device orientation event', async ({ page, server }) => { expect(await page.evaluate('absolute')).toBeFalsy(); }); -it('should dispatch absolute device orientation event', async ({ page, server }) => { +it('should dispatch absolute device orientation event', async ({ page, server, isAndroid }) => { + it.skip(isAndroid, 'DeviceOrientationEvent is only available in a secure context. While Androids loopback is not treated as secure.'); await page.goto(server.PREFIX + '/device-orientation.html'); await page.locator('html').dispatchEvent('deviceorientationabsolute', { alpha: 10, beta: 20, gamma: 30, absolute: true }); expect(await page.evaluate('result')).toBe('Oriented'); From 1aee48f2d065cc899e862dedef536ddda382f618 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Fri, 10 Nov 2023 16:44:02 +0100 Subject: [PATCH 026/491] test: COEP/COOP/CORP isolated iframe should work (#28083) https://github.com/microsoft/playwright/issues/28082 --- tests/page/locator-frame.spec.ts | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/tests/page/locator-frame.spec.ts b/tests/page/locator-frame.spec.ts index 50151a4257..08da3dff0a 100644 --- a/tests/page/locator-frame.spec.ts +++ b/tests/page/locator-frame.spec.ts @@ -268,3 +268,33 @@ it('wait for hidden should succeed when frame is not in dom', async ({ page }) = const error = await button.waitFor({ state: 'attached', timeout: 1000 }).catch(e => e); expect(error.message).toContain('Timeout 1000ms exceeded'); }); + +it('should work with COEP/COOP/CORP isolated iframe', async ({ page, server, browserName }) => { + it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/28082' }); + it.fixme(browserName === 'firefox'); + await page.route('**/empty.html', route => { + return route.fulfill({ + body: ``, + contentType: 'text/html', + headers: { + 'cross-origin-embedder-policy': 'require-corp', + 'cross-origin-opener-policy': 'same-origin', + 'cross-origin-resource-policy': 'cross-origin', + } + }); + }); + await page.route('**/btn.html', route => { + return route.fulfill({ + body: '', + contentType: 'text/html', + headers: { + 'cross-origin-embedder-policy': 'require-corp', + 'cross-origin-opener-policy': 'same-origin', + 'cross-origin-resource-policy': 'cross-origin', + } + }); + }); + await page.goto(server.EMPTY_PAGE); + await page.frameLocator('iframe').getByRole('button').click(); + expect(await page.frames()[1].evaluate(() => window['__clicked'])).toBe(true); +}); From fae5dd898a523d61017a8a263f84dd415ab30e5d Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Fri, 10 Nov 2023 15:24:31 -0800 Subject: [PATCH 027/491] chore: remove from client check if browser is co-located with server (#28071) Reference https://github.com/microsoft/playwright/issues/27792 --- .../src/client/browserContext.ts | 4 - .../src/client/elementHandle.ts | 77 ++++--------------- packages/playwright-core/src/client/frame.ts | 9 +-- .../playwright-core/src/protocol/debug.ts | 6 -- .../playwright-core/src/protocol/validator.ts | 27 ++----- .../dispatchers/elementHandlerDispatcher.ts | 17 +--- .../src/server/dispatchers/frameDispatcher.ts | 17 +--- packages/playwright-core/src/server/dom.ts | 34 ++++---- .../src/server/fileUploadUtils.ts | 77 +++++++++++++++++++ packages/playwright-core/src/server/frames.ts | 9 ++- .../playwright-core/src/utils/fileUtils.ts | 2 + packages/protocol/src/channels.ts | 56 +++++--------- packages/protocol/src/protocol.yml | 37 ++------- 13 files changed, 149 insertions(+), 223 deletions(-) create mode 100644 packages/playwright-core/src/server/fileUploadUtils.ts diff --git a/packages/playwright-core/src/client/browserContext.ts b/packages/playwright-core/src/client/browserContext.ts index 9ed649e02a..a74996bbe2 100644 --- a/packages/playwright-core/src/client/browserContext.ts +++ b/packages/playwright-core/src/client/browserContext.ts @@ -151,10 +151,6 @@ export class BrowserContext extends ChannelOwner this.tracing._tracesDir = browserOptions.tracesDir; } - _isLocalBrowserOnServer(): boolean { - return this._initializer.isLocalBrowserOnServer; - } - private _onPage(page: Page): void { this._pages.add(page); this.emit(Events.BrowserContext.Page, page); diff --git a/packages/playwright-core/src/client/elementHandle.ts b/packages/playwright-core/src/client/elementHandle.ts index 794e88134f..0a31ff96ff 100644 --- a/packages/playwright-core/src/client/elementHandle.ts +++ b/packages/playwright-core/src/client/elementHandle.ts @@ -24,14 +24,13 @@ import fs from 'fs'; import { mime } from '../utilsBundle'; import path from 'path'; import { assert, isString } from '../utils'; -import { mkdirIfNeeded } from '../utils/fileUtils'; +import { fileUploadSizeLimit, mkdirIfNeeded } from '../utils/fileUtils'; import type * as api from '../../types/types'; import type * as structs from '../../types/structs'; import type { BrowserContext } from './browserContext'; import { WritableStream } from './writableStream'; import { pipeline } from 'stream'; import { promisify } from 'util'; -import { debugLogger } from '../common/debugLogger'; const pipelineAsync = promisify(pipeline); @@ -151,13 +150,7 @@ export class ElementHandle extends JSHandle implements if (!frame) throw new Error('Cannot set input files to detached element'); const converted = await convertInputFiles(files, frame.page().context()); - if (converted.files) { - debugLogger.log('api', 'setting input buffers'); - await this._elementChannel.setInputFiles({ files: converted.files, ...options }); - } else { - debugLogger.log('api', 'setting input file paths'); - await this._elementChannel.setInputFilePaths({ ...converted, ...options }); - } + await this._elementChannel.setInputFiles({ ...converted, ...options }); } async focus(): Promise { @@ -257,36 +250,13 @@ export function convertSelectOptionValues(values: string | api.ElementHandle | S return { options: values as SelectOption[] }; } -type SetInputFilesFiles = channels.ElementHandleSetInputFilesParams['files']; -type InputFilesList = { - files?: SetInputFilesFiles; - localPaths?: string[]; - streams?: channels.WritableStreamChannel[]; -}; - -const filePayloadSizeLimit = 50 * 1024 * 1024; +type SetInputFilesFiles = Pick; function filePayloadExceedsSizeLimit(payloads: FilePayload[]) { - return payloads.reduce((size, item) => size + (item.buffer ? item.buffer.byteLength : 0), 0) >= filePayloadSizeLimit; + return payloads.reduce((size, item) => size + (item.buffer ? item.buffer.byteLength : 0), 0) >= fileUploadSizeLimit; } -async function filesExceedSizeLimit(files: string[]) { - const sizes = await Promise.all(files.map(async file => (await fs.promises.stat(file)).size)); - return sizes.reduce((total, size) => total + size, 0) >= filePayloadSizeLimit; -} - -async function readFilesIntoBuffers(items: string[]): Promise { - const filePayloads: SetInputFilesFiles = await Promise.all((items as string[]).map(async item => { - return { - name: path.basename(item), - buffer: await fs.promises.readFile(item), - lastModifiedMs: (await fs.promises.stat(item)).mtimeMs, - }; - })); - return filePayloads; -} - -export async function convertInputFiles(files: string | FilePayload | string[] | FilePayload[], context: BrowserContext): Promise { +export async function convertInputFiles(files: string | FilePayload | string[] | FilePayload[], context: BrowserContext): Promise { const items: (string | FilePayload)[] = Array.isArray(files) ? files.slice() : [files]; if (items.some(item => typeof item === 'string')) { @@ -294,35 +264,22 @@ export async function convertInputFiles(files: string | FilePayload | string[] | throw new Error('File paths cannot be mixed with buffers'); if (context._connection.isRemote()) { - if (context._isLocalBrowserOnServer()) { - const streams: channels.WritableStreamChannel[] = await Promise.all((items as string[]).map(async item => { - const lastModifiedMs = (await fs.promises.stat(item)).mtimeMs; - const { writableStream: stream } = await context._channel.createTempFile({ name: path.basename(item), lastModifiedMs }); - const writable = WritableStream.from(stream); - await pipelineAsync(fs.createReadStream(item), writable.stream()); - return stream; - })); - return { streams }; - } - if (await filesExceedSizeLimit(items as string[])) - throw new Error('Cannot transfer files larger than 50Mb to a browser not co-located with the server'); - return { files: await readFilesIntoBuffers(items as string[]) }; + const streams: channels.WritableStreamChannel[] = await Promise.all((items as string[]).map(async item => { + const lastModifiedMs = (await fs.promises.stat(item)).mtimeMs; + const { writableStream: stream } = await context._channel.createTempFile({ name: path.basename(item), lastModifiedMs }); + const writable = WritableStream.from(stream); + await pipelineAsync(fs.createReadStream(item), writable.stream()); + return stream; + })); + return { streams }; } - if (context._isLocalBrowserOnServer()) - return { localPaths: items.map(f => path.resolve(f as string)) as string[] }; - if (await filesExceedSizeLimit(items as string[])) - throw new Error('Cannot transfer files larger than 50Mb to a browser not co-located with the server'); - return { files: await readFilesIntoBuffers(items as string[]) }; + return { localPaths: items.map(f => path.resolve(f as string)) as string[] }; } const payloads = items as FilePayload[]; - if (filePayloadExceedsSizeLimit(payloads)) { - let error = 'Cannot set buffer larger than 50Mb'; - if (context._isLocalBrowserOnServer()) - error += ', please write it to a file and pass its path instead.'; - throw new Error(error); - } - return { files: payloads }; + if (filePayloadExceedsSizeLimit(payloads)) + throw new Error('Cannot set buffer larger than 50Mb, please write it to a file and pass its path instead.'); + return { payloads }; } export function determineScreenshotType(options: { path?: string, type?: 'png' | 'jpeg' }): 'png' | 'jpeg' | undefined { diff --git a/packages/playwright-core/src/client/frame.ts b/packages/playwright-core/src/client/frame.ts index 2114ee9f1f..18d36c732c 100644 --- a/packages/playwright-core/src/client/frame.ts +++ b/packages/playwright-core/src/client/frame.ts @@ -35,7 +35,6 @@ import { kLifecycleEvents } from './types'; import { urlMatches } from '../utils/network'; import type * as api from '../../types/types'; import type * as structs from '../../types/structs'; -import { debugLogger } from '../common/debugLogger'; export type WaitForNavigationOptions = { timeout?: number, @@ -401,13 +400,7 @@ export class Frame extends ChannelOwner implements api.Fr async setInputFiles(selector: string, files: string | FilePayload | string[] | FilePayload[], options: channels.FrameSetInputFilesOptions = {}): Promise { const converted = await convertInputFiles(files, this.page().context()); - if (converted.files) { - debugLogger.log('api', 'setting input buffers'); - await this._channel.setInputFiles({ selector, files: converted.files, ...options }); - } else { - debugLogger.log('api', 'setting input file paths'); - await this._channel.setInputFilePaths({ selector, ...converted, ...options }); - } + await this._channel.setInputFiles({ selector, ...converted, ...options }); } async type(selector: string, text: string, options: channels.FrameTypeOptions = {}) { diff --git a/packages/playwright-core/src/protocol/debug.ts b/packages/playwright-core/src/protocol/debug.ts index 5699f40b59..4f44f5941e 100644 --- a/packages/playwright-core/src/protocol/debug.ts +++ b/packages/playwright-core/src/protocol/debug.ts @@ -44,7 +44,6 @@ export const slowMoActions = new Set([ 'Frame.press', 'Frame.selectOption', 'Frame.setInputFiles', - 'Frame.setInputFilePaths', 'Frame.tap', 'Frame.type', 'Frame.uncheck', @@ -60,7 +59,6 @@ export const slowMoActions = new Set([ 'ElementHandle.selectOption', 'ElementHandle.selectText', 'ElementHandle.setInputFiles', - 'ElementHandle.setInputFilePaths', 'ElementHandle.tap', 'ElementHandle.type', 'ElementHandle.uncheck' @@ -121,7 +119,6 @@ export const commandsWithTracingSnapshots = new Set([ 'Frame.selectOption', 'Frame.setContent', 'Frame.setInputFiles', - 'Frame.setInputFilePaths', 'Frame.tap', 'Frame.textContent', 'Frame.type', @@ -158,7 +155,6 @@ export const commandsWithTracingSnapshots = new Set([ 'ElementHandle.selectOption', 'ElementHandle.selectText', 'ElementHandle.setInputFiles', - 'ElementHandle.setInputFilePaths', 'ElementHandle.tap', 'ElementHandle.textContent', 'ElementHandle.type', @@ -177,7 +173,6 @@ export const pausesBeforeInputActions = new Set([ 'Frame.press', 'Frame.selectOption', 'Frame.setInputFiles', - 'Frame.setInputFilePaths', 'Frame.tap', 'Frame.type', 'Frame.uncheck', @@ -189,7 +184,6 @@ export const pausesBeforeInputActions = new Set([ 'ElementHandle.press', 'ElementHandle.selectOption', 'ElementHandle.setInputFiles', - 'ElementHandle.setInputFilePaths', 'ElementHandle.tap', 'ElementHandle.type', 'ElementHandle.uncheck' diff --git a/packages/playwright-core/src/protocol/validator.ts b/packages/playwright-core/src/protocol/validator.ts index eb1fd7dc24..de0d84cf1f 100644 --- a/packages/playwright-core/src/protocol/validator.ts +++ b/packages/playwright-core/src/protocol/validator.ts @@ -762,7 +762,6 @@ scheme.ElectronApplicationWaitForEventInfoResult = tType('EventTargetWaitForEven scheme.AndroidDeviceWaitForEventInfoResult = tType('EventTargetWaitForEventInfoResult'); scheme.BrowserContextInitializer = tObject({ isChromium: tBoolean, - isLocalBrowserOnServer: tBoolean, requestContext: tChannel(['APIRequestContext']), tracing: tChannel(['Tracing']), }); @@ -1557,25 +1556,17 @@ scheme.FrameSetContentResult = tOptional(tObject({})); scheme.FrameSetInputFilesParams = tObject({ selector: tString, strict: tOptional(tBoolean), - files: tArray(tObject({ + payloads: tOptional(tArray(tObject({ name: tString, mimeType: tOptional(tString), buffer: tBinary, - lastModifiedMs: tOptional(tNumber), - })), - timeout: tOptional(tNumber), - noWaitAfter: tOptional(tBoolean), -}); -scheme.FrameSetInputFilesResult = tOptional(tObject({})); -scheme.FrameSetInputFilePathsParams = tObject({ - selector: tString, - strict: tOptional(tBoolean), + }))), localPaths: tOptional(tArray(tString)), streams: tOptional(tArray(tChannel(['WritableStream']))), timeout: tOptional(tNumber), noWaitAfter: tOptional(tBoolean), }); -scheme.FrameSetInputFilePathsResult = tOptional(tObject({})); +scheme.FrameSetInputFilesResult = tOptional(tObject({})); scheme.FrameTapParams = tObject({ selector: tString, strict: tOptional(tBoolean), @@ -1931,23 +1922,17 @@ scheme.ElementHandleSelectTextParams = tObject({ }); scheme.ElementHandleSelectTextResult = tOptional(tObject({})); scheme.ElementHandleSetInputFilesParams = tObject({ - files: tArray(tObject({ + payloads: tOptional(tArray(tObject({ name: tString, mimeType: tOptional(tString), buffer: tBinary, - lastModifiedMs: tOptional(tNumber), - })), - timeout: tOptional(tNumber), - noWaitAfter: tOptional(tBoolean), -}); -scheme.ElementHandleSetInputFilesResult = tOptional(tObject({})); -scheme.ElementHandleSetInputFilePathsParams = tObject({ + }))), localPaths: tOptional(tArray(tString)), streams: tOptional(tArray(tChannel(['WritableStream']))), timeout: tOptional(tNumber), noWaitAfter: tOptional(tBoolean), }); -scheme.ElementHandleSetInputFilePathsResult = tOptional(tObject({})); +scheme.ElementHandleSetInputFilesResult = tOptional(tObject({})); scheme.ElementHandleTapParams = tObject({ force: tOptional(tBoolean), noWaitAfter: tOptional(tBoolean), diff --git a/packages/playwright-core/src/server/dispatchers/elementHandlerDispatcher.ts b/packages/playwright-core/src/server/dispatchers/elementHandlerDispatcher.ts index 45ada314d6..d4d0280ec4 100644 --- a/packages/playwright-core/src/server/dispatchers/elementHandlerDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/elementHandlerDispatcher.ts @@ -23,9 +23,6 @@ import { JSHandleDispatcher, serializeResult, parseArgument } from './jsHandleDi import type { JSHandleDispatcherParentScope } from './jsHandleDispatcher'; import { FrameDispatcher } from './frameDispatcher'; import type { CallMetadata } from '../instrumentation'; -import type { WritableStreamDispatcher } from './writableStreamDispatcher'; -import { assert } from '../../utils'; -import path from 'path'; import { BrowserContextDispatcher } from './browserContextDispatcher'; import { PageDispatcher, WorkerDispatcher } from './pageDispatcher'; @@ -151,19 +148,7 @@ export class ElementHandleDispatcher extends JSHandleDispatcher implements chann } async setInputFiles(params: channels.ElementHandleSetInputFilesParams, metadata: CallMetadata): Promise { - return await this._elementHandle.setInputFiles(metadata, { files: params.files }, params); - } - - async setInputFilePaths(params: channels.ElementHandleSetInputFilePathsParams, metadata: CallMetadata): Promise { - let { localPaths } = params; - if (!localPaths) { - if (!params.streams) - throw new Error('Neither localPaths nor streams is specified'); - localPaths = params.streams.map(c => (c as WritableStreamDispatcher).path()); - } - for (const p of localPaths) - assert(path.isAbsolute(p) && path.resolve(p) === p, 'Paths provided to localPaths must be absolute and fully resolved.'); - return await this._elementHandle.setInputFiles(metadata, { localPaths }, params); + return await this._elementHandle.setInputFiles(metadata, params); } async focus(params: channels.ElementHandleFocusParams, metadata: CallMetadata): Promise { diff --git a/packages/playwright-core/src/server/dispatchers/frameDispatcher.ts b/packages/playwright-core/src/server/dispatchers/frameDispatcher.ts index 39c46e27e3..c8fd0c9e24 100644 --- a/packages/playwright-core/src/server/dispatchers/frameDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/frameDispatcher.ts @@ -23,9 +23,6 @@ import { parseArgument, serializeResult } from './jsHandleDispatcher'; import { ResponseDispatcher } from './networkDispatchers'; import { RequestDispatcher } from './networkDispatchers'; import type { CallMetadata } from '../instrumentation'; -import type { WritableStreamDispatcher } from './writableStreamDispatcher'; -import { assert } from '../../utils'; -import path from 'path'; import type { BrowserContextDispatcher } from './browserContextDispatcher'; import type { PageDispatcher } from './pageDispatcher'; @@ -218,19 +215,7 @@ export class FrameDispatcher extends Dispatcher { - return await this._frame.setInputFiles(metadata, params.selector, { files: params.files }, params); - } - - async setInputFilePaths(params: channels.FrameSetInputFilePathsParams, metadata: CallMetadata): Promise { - let { localPaths } = params; - if (!localPaths) { - if (!params.streams) - throw new Error('Neither localPaths nor streams is specified'); - localPaths = params.streams.map(c => (c as WritableStreamDispatcher).path()); - } - for (const p of localPaths) - assert(path.isAbsolute(p) && path.resolve(p) === p, 'Paths provided to localPaths must be absolute and fully resolved.'); - return await this._frame.setInputFiles(metadata, params.selector, { localPaths }, params); + return await this._frame.setInputFiles(metadata, params.selector, params); } async type(params: channels.FrameTypeParams, metadata: CallMetadata): Promise { diff --git a/packages/playwright-core/src/server/dom.ts b/packages/playwright-core/src/server/dom.ts index a49024b046..ded96eec5f 100644 --- a/packages/playwright-core/src/server/dom.ts +++ b/packages/playwright-core/src/server/dom.ts @@ -14,9 +14,8 @@ * limitations under the License. */ -import { mime } from '../utilsBundle'; -import * as injectedScriptSource from '../generated/injectedScriptSource'; import type * as channels from '@protocol/channels'; +import * as injectedScriptSource from '../generated/injectedScriptSource'; import { isSessionClosedError } from './protocolError'; import type { ScreenshotOptions } from './screenshotter'; import type * as frames from './frames'; @@ -29,9 +28,13 @@ import { ProgressController } from './progress'; import type * as types from './types'; import type { TimeoutOptions } from '../common/types'; import { isUnderTest } from '../utils'; +import { prepareFilesForUpload } from './fileUploadUtils'; + +export type InputFilesItems = { + filePayloads?: types.FilePayload[], + localPaths?: string[] +}; -type SetInputFilesFiles = channels.ElementHandleSetInputFilesParams['files']; -export type InputFilesItems = { files?: SetInputFilesFiles, localPaths?: string[] }; type ActionName = 'click' | 'hover' | 'dblclick' | 'tap' | 'move and up' | 'move and down'; export class NonRecoverableDOMError extends Error { @@ -579,29 +582,18 @@ export class ElementHandle extends js.JSHandle { }, this._page._timeoutSettings.timeout(options)); } - async setInputFiles(metadata: CallMetadata, items: InputFilesItems, options: types.NavigatingActionWaitOptions) { + async setInputFiles(metadata: CallMetadata, params: channels.ElementHandleSetInputFilesParams) { + const inputFileItems = await prepareFilesForUpload(this._frame, params); const controller = new ProgressController(metadata, this); return controller.run(async progress => { - const result = await this._setInputFiles(progress, items, options); + const result = await this._setInputFiles(progress, inputFileItems, params); return assertDone(throwRetargetableDOMError(result)); - }, this._page._timeoutSettings.timeout(options)); + }, this._page._timeoutSettings.timeout(params)); } async _setInputFiles(progress: Progress, items: InputFilesItems, options: types.NavigatingActionWaitOptions): Promise<'error:notconnected' | 'done'> { - const { files, localPaths } = items; - let filePayloads: types.FilePayload[] | undefined; - if (files) { - filePayloads = []; - for (const payload of files) { - filePayloads.push({ - name: payload.name, - mimeType: payload.mimeType || mime.getType(payload.name) || 'application/octet-stream', - buffer: payload.buffer.toString('base64'), - lastModifiedMs: payload.lastModifiedMs - }); - } - } - const multiple = files && files.length > 1 || localPaths && localPaths.length > 1; + const { filePayloads, localPaths } = items; + const multiple = filePayloads && filePayloads.length > 1 || localPaths && localPaths.length > 1; const result = await this.evaluateHandleInUtility(([injected, node, multiple]): Element | undefined => { const element = injected.retarget(node, 'follow-label'); if (!element) diff --git a/packages/playwright-core/src/server/fileUploadUtils.ts b/packages/playwright-core/src/server/fileUploadUtils.ts new file mode 100644 index 0000000000..89fc0cf7e1 --- /dev/null +++ b/packages/playwright-core/src/server/fileUploadUtils.ts @@ -0,0 +1,77 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type * as channels from '@protocol/channels'; +import fs from 'fs'; +import path from 'path'; +import { assert, fileUploadSizeLimit } from '../utils'; +import { mime } from '../utilsBundle'; +import type { WritableStreamDispatcher } from './dispatchers/writableStreamDispatcher'; +import type { InputFilesItems } from './dom'; +import type { Frame } from './frames'; +import type * as types from './types'; + +async function filesExceedUploadLimit(files: string[]) { + const sizes = await Promise.all(files.map(async file => (await fs.promises.stat(file)).size)); + return sizes.reduce((total, size) => total + size, 0) >= fileUploadSizeLimit; +} + +export async function prepareFilesForUpload(frame: Frame, params: channels.ElementHandleSetInputFilesParams): Promise { + const { payloads, streams } = params; + let { localPaths } = params; + + if ([payloads, localPaths, streams].filter(Boolean).length !== 1) + throw new Error('Exactly one of payloads, localPaths and streams must be provided'); + + if (streams) + localPaths = streams.map(c => (c as WritableStreamDispatcher).path()); + if (localPaths) { + for (const p of localPaths) + assert(path.isAbsolute(p) && path.resolve(p) === p, 'Paths provided to localPaths must be absolute and fully resolved.'); + } + + let fileBuffers: { + name: string, + mimeType?: string, + buffer: Buffer, + lastModifiedMs?: number, + }[] | undefined = payloads; + + if (!frame._page._browserContext._browser._isCollocatedWithServer) { + // If the browser is on a different machine read files into buffers. + if (localPaths) { + if (await filesExceedUploadLimit(localPaths)) + throw new Error('Cannot transfer files larger than 50Mb to a browser not co-located with the server'); + fileBuffers = await Promise.all(localPaths.map(async item => { + return { + name: path.basename(item), + buffer: await fs.promises.readFile(item), + lastModifiedMs: (await fs.promises.stat(item)).mtimeMs, + }; + })); + localPaths = undefined; + } + } + + const filePayloads: types.FilePayload[] | undefined = fileBuffers?.map(payload => ({ + name: payload.name, + mimeType: payload.mimeType || mime.getType(payload.name) || 'application/octet-stream', + buffer: payload.buffer.toString('base64'), + lastModifiedMs: payload.lastModifiedMs + })); + + return { localPaths, filePayloads }; +} \ No newline at end of file diff --git a/packages/playwright-core/src/server/frames.ts b/packages/playwright-core/src/server/frames.ts index 488a30fbab..f46484c579 100644 --- a/packages/playwright-core/src/server/frames.ts +++ b/packages/playwright-core/src/server/frames.ts @@ -38,10 +38,10 @@ import type { InjectedScript, ElementStateWithoutStable, FrameExpectParams, Inje import { isSessionClosedError } from './protocolError'; import { type ParsedSelector, isInvalidSelectorError } from '../utils/isomorphic/selectorParser'; import type { ScreenshotOptions } from './screenshotter'; -import type { InputFilesItems } from './dom'; import { asLocator } from '../utils/isomorphic/locatorGenerators'; import { FrameSelectors } from './frameSelectors'; import { TimeoutError } from './errors'; +import { prepareFilesForUpload } from './fileUploadUtils'; type ContextData = { contextPromise: ManualPromise; @@ -1319,11 +1319,12 @@ export class Frame extends SdkObject { }, this._page._timeoutSettings.timeout(options)); } - async setInputFiles(metadata: CallMetadata, selector: string, items: InputFilesItems, options: types.NavigatingActionWaitOptions = {}): Promise { + async setInputFiles(metadata: CallMetadata, selector: string, params: channels.FrameSetInputFilesParams): Promise { + const inputFileItems = await prepareFilesForUpload(this, params); const controller = new ProgressController(metadata, this); return controller.run(async progress => { - return dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, options.strict, handle => handle._setInputFiles(progress, items, options))); - }, this._page._timeoutSettings.timeout(options)); + return dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, params.strict, handle => handle._setInputFiles(progress, inputFileItems, params))); + }, this._page._timeoutSettings.timeout(params)); } async type(metadata: CallMetadata, selector: string, text: string, options: { delay?: number } & types.NavigatingActionWaitOptions = {}) { diff --git a/packages/playwright-core/src/utils/fileUtils.ts b/packages/playwright-core/src/utils/fileUtils.ts index 1a9f552f58..287899d509 100644 --- a/packages/playwright-core/src/utils/fileUtils.ts +++ b/packages/playwright-core/src/utils/fileUtils.ts @@ -17,6 +17,8 @@ import fs from 'fs'; import path from 'path'; +export const fileUploadSizeLimit = 50 * 1024 * 1024; + export const existsAsync = (path: string): Promise => new Promise(resolve => fs.stat(path, err => resolve(!err))); export async function mkdirIfNeeded(filePath: string) { diff --git a/packages/protocol/src/channels.ts b/packages/protocol/src/channels.ts index 6fd4bd651a..497fed66d0 100644 --- a/packages/protocol/src/channels.ts +++ b/packages/protocol/src/channels.ts @@ -1407,7 +1407,6 @@ export interface EventTargetEvents { // ----------- BrowserContext ----------- export type BrowserContextInitializer = { isChromium: boolean, - isLocalBrowserOnServer: boolean, requestContext: APIRequestContextChannel, tracing: TracingChannel, }; @@ -2305,7 +2304,6 @@ export interface FrameChannel extends FrameEventTarget, Channel { selectOption(params: FrameSelectOptionParams, metadata?: CallMetadata): Promise; setContent(params: FrameSetContentParams, metadata?: CallMetadata): Promise; setInputFiles(params: FrameSetInputFilesParams, metadata?: CallMetadata): Promise; - setInputFilePaths(params: FrameSetInputFilePathsParams, metadata?: CallMetadata): Promise; tap(params: FrameTapParams, metadata?: CallMetadata): Promise; textContent(params: FrameTextContentParams, metadata?: CallMetadata): Promise; title(params?: FrameTitleParams, metadata?: CallMetadata): Promise; @@ -2792,37 +2790,29 @@ export type FrameSetContentResult = void; export type FrameSetInputFilesParams = { selector: string, strict?: boolean, - files: { + payloads?: { name: string, mimeType?: string, buffer: Binary, - lastModifiedMs?: number, }[], + localPaths?: string[], + streams?: WritableStreamChannel[], timeout?: number, noWaitAfter?: boolean, }; export type FrameSetInputFilesOptions = { strict?: boolean, + payloads?: { + name: string, + mimeType?: string, + buffer: Binary, + }[], + localPaths?: string[], + streams?: WritableStreamChannel[], timeout?: number, noWaitAfter?: boolean, }; export type FrameSetInputFilesResult = void; -export type FrameSetInputFilePathsParams = { - selector: string, - strict?: boolean, - localPaths?: string[], - streams?: WritableStreamChannel[], - timeout?: number, - noWaitAfter?: boolean, -}; -export type FrameSetInputFilePathsOptions = { - strict?: boolean, - localPaths?: string[], - streams?: WritableStreamChannel[], - timeout?: number, - noWaitAfter?: boolean, -}; -export type FrameSetInputFilePathsResult = void; export type FrameTapParams = { selector: string, strict?: boolean, @@ -3115,7 +3105,6 @@ export interface ElementHandleChannel extends ElementHandleEventTarget, JSHandle selectOption(params: ElementHandleSelectOptionParams, metadata?: CallMetadata): Promise; selectText(params: ElementHandleSelectTextParams, metadata?: CallMetadata): Promise; setInputFiles(params: ElementHandleSetInputFilesParams, metadata?: CallMetadata): Promise; - setInputFilePaths(params: ElementHandleSetInputFilePathsParams, metadata?: CallMetadata): Promise; tap(params: ElementHandleTapParams, metadata?: CallMetadata): Promise; textContent(params?: ElementHandleTextContentParams, metadata?: CallMetadata): Promise; type(params: ElementHandleTypeParams, metadata?: CallMetadata): Promise; @@ -3423,33 +3412,28 @@ export type ElementHandleSelectTextOptions = { }; export type ElementHandleSelectTextResult = void; export type ElementHandleSetInputFilesParams = { - files: { + payloads?: { name: string, mimeType?: string, buffer: Binary, - lastModifiedMs?: number, }[], + localPaths?: string[], + streams?: WritableStreamChannel[], timeout?: number, noWaitAfter?: boolean, }; export type ElementHandleSetInputFilesOptions = { + payloads?: { + name: string, + mimeType?: string, + buffer: Binary, + }[], + localPaths?: string[], + streams?: WritableStreamChannel[], timeout?: number, noWaitAfter?: boolean, }; export type ElementHandleSetInputFilesResult = void; -export type ElementHandleSetInputFilePathsParams = { - localPaths?: string[], - streams?: WritableStreamChannel[], - timeout?: number, - noWaitAfter?: boolean, -}; -export type ElementHandleSetInputFilePathsOptions = { - localPaths?: string[], - streams?: WritableStreamChannel[], - timeout?: number, - noWaitAfter?: boolean, -}; -export type ElementHandleSetInputFilePathsResult = void; export type ElementHandleTapParams = { force?: boolean, noWaitAfter?: boolean, diff --git a/packages/protocol/src/protocol.yml b/packages/protocol/src/protocol.yml index 56f6652717..75b3f19d40 100644 --- a/packages/protocol/src/protocol.yml +++ b/packages/protocol/src/protocol.yml @@ -1012,7 +1012,6 @@ BrowserContext: initializer: isChromium: boolean - isLocalBrowserOnServer: boolean requestContext: APIRequestContext tracing: Tracing @@ -2111,28 +2110,15 @@ Frame: parameters: selector: string strict: boolean? - files: - type: array + # Only one of payloads, localPaths and streams should be present. + payloads: + type: array? items: type: object properties: name: string mimeType: string? buffer: binary - lastModifiedMs: number? - timeout: number? - noWaitAfter: boolean? - flags: - slowMo: true - snapshot: true - pausesBeforeInput: true - - # This method should be used if one of the files is large (>50Mb). - setInputFilePaths: - parameters: - selector: string - strict: boolean? - # Only one of localPaths and streams should be present. localPaths: type: array? items: string @@ -2680,26 +2666,15 @@ ElementHandle: setInputFiles: parameters: - files: - type: array + # Only one of payloads, localPaths and streams should be present. + payloads: + type: array? items: type: object properties: name: string mimeType: string? buffer: binary - lastModifiedMs: number? - timeout: number? - noWaitAfter: boolean? - flags: - slowMo: true - snapshot: true - pausesBeforeInput: true - - # This method should be used if one of the files is large (>50Mb). - setInputFilePaths: - parameters: - # Only one of localPaths and streams should be present. localPaths: type: array? items: string From 1b3349d0913d330c7ee4d9777af693c15a432e07 Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Fri, 10 Nov 2023 22:00:28 -0800 Subject: [PATCH 028/491] chore: use codemirror in the on-hover locator editor (#28090) --- package-lock.json | 20 ++-- .../playwright-core/ThirdPartyNotices.txt | 8 +- .../src/server/injected/highlight.css | 47 +++++---- .../src/server/injected/highlight.ts | 2 +- .../src/server/injected/recorder.ts | 96 +++++++++++++++---- packages/playwright/ThirdPartyNotices.txt | 8 +- packages/web/package.json | 2 +- .../web/src/components/codeMirrorModule.tsx | 18 ++-- utils/check_deps.js | 14 ++- utils/generate_third_party_notice.js | 2 +- 10 files changed, 149 insertions(+), 68 deletions(-) diff --git a/package-lock.json b/package-lock.json index 325ada8095..0d7725e9e8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2283,10 +2283,10 @@ "mimic-response": "^1.0.0" } }, - "node_modules/codemirror": { - "version": "5.65.9", - "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.65.9.tgz", - "integrity": "sha512-19Jox5sAKpusTDgqgKB5dawPpQcY+ipQK7xoEI+MVucEF9qqFaXpeqY1KaoyGBso/wHQoDa4HMMxMjdsS3Zzzw==" + "node_modules/codemirror-shadow-1": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/codemirror-shadow-1/-/codemirror-shadow-1-0.0.1.tgz", + "integrity": "sha512-kD3OZpCCHr3LHRKfbGx5IogHTWq4Uo9jH2bXPVa7/n6ppkgI66rx4tniQY1BpqWp/JNhQmQsXhQoaZ1TH6t0xQ==" }, "node_modules/color-convert": { "version": "1.9.3", @@ -7310,7 +7310,7 @@ "packages/web": { "version": "0.0.0", "dependencies": { - "codemirror": "^5.65.9", + "codemirror-shadow-1": "0.0.1", "xterm": "^5.1.0", "xterm-addon-fit": "^0.7.0" } @@ -8962,10 +8962,10 @@ "mimic-response": "^1.0.0" } }, - "codemirror": { - "version": "5.65.9", - "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.65.9.tgz", - "integrity": "sha512-19Jox5sAKpusTDgqgKB5dawPpQcY+ipQK7xoEI+MVucEF9qqFaXpeqY1KaoyGBso/wHQoDa4HMMxMjdsS3Zzzw==" + "codemirror-shadow-1": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/codemirror-shadow-1/-/codemirror-shadow-1-0.0.1.tgz", + "integrity": "sha512-kD3OZpCCHr3LHRKfbGx5IogHTWq4Uo9jH2bXPVa7/n6ppkgI66rx4tniQY1BpqWp/JNhQmQsXhQoaZ1TH6t0xQ==" }, "color-convert": { "version": "1.9.3", @@ -11862,7 +11862,7 @@ "web": { "version": "file:packages/web", "requires": { - "codemirror": "^5.65.9", + "codemirror-shadow-1": "0.0.1", "xterm": "^5.1.0", "xterm-addon-fit": "^0.7.0" } diff --git a/packages/playwright-core/ThirdPartyNotices.txt b/packages/playwright-core/ThirdPartyNotices.txt index e2c4d1dcac..f18b4af05a 100644 --- a/packages/playwright-core/ThirdPartyNotices.txt +++ b/packages/playwright-core/ThirdPartyNotices.txt @@ -10,7 +10,7 @@ This project incorporates components from the projects listed below. The origina - balanced-match@1.0.2 (https://github.com/juliangruber/balanced-match) - brace-expansion@1.1.11 (https://github.com/juliangruber/brace-expansion) - buffer-crc32@0.2.13 (https://github.com/brianloveswords/buffer-crc32) -- codemirror@5.65.9 (https://github.com/codemirror/CodeMirror) +- codemirror-shadow-1@0.0.1 (https://github.com/codemirror/CodeMirror) - colors@1.4.0 (https://github.com/Marak/colors.js) - commander@8.3.0 (https://github.com/tj/commander.js) - concat-map@0.0.1 (https://github.com/substack/node-concat-map) @@ -326,11 +326,11 @@ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEAL ========================================= END OF buffer-crc32@0.2.13 AND INFORMATION -%% codemirror@5.65.9 NOTICES AND INFORMATION BEGIN HERE +%% codemirror-shadow-1@0.0.1 NOTICES AND INFORMATION BEGIN HERE ========================================= MIT License -Copyright (C) 2017 by Marijn Haverbeke and others +Copyright (C) 2017 by Marijn Haverbeke and others Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -350,7 +350,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ========================================= -END OF codemirror@5.65.9 AND INFORMATION +END OF codemirror-shadow-1@0.0.1 AND INFORMATION %% colors@1.4.0 NOTICES AND INFORMATION BEGIN HERE ========================================= diff --git a/packages/playwright-core/src/server/injected/highlight.css b/packages/playwright-core/src/server/injected/highlight.css index 0d0a74e7b3..f4db846697 100644 --- a/packages/playwright-core/src/server/injected/highlight.css +++ b/packages/playwright-core/src/server/injected/highlight.css @@ -44,8 +44,9 @@ x-pw-dialog { display: flex; flex-direction: column; position: absolute; - min-width: 500px; - min-height: 200px; + width: 500px; + height: 200px; + z-index: 10; } x-pw-dialog-body { @@ -54,6 +55,17 @@ x-pw-dialog-body { flex: auto; } +x-pw-dialog-body label { + margin: 10px; + display: flex; + align-items: center; + cursor: pointer; +} + +x-pw-dialog-body input { + cursor: pointer; +} + x-pw-highlight { position: absolute; top: 0; @@ -205,27 +217,17 @@ x-pw-overlay x-pw-tool-item { margin: 2px; } -input.locator-editor { - display: flex; - padding: 10px; - flex: none; - border: none; - border-bottom: 1px solid #dddddd; -} - -input.locator-editor:focus, -textarea.text-editor:focus { - outline: none; -} - textarea.text-editor { font-family: system-ui, "Ubuntu", "Droid Sans", sans-serif; flex: auto; border: none; - padding: 10px; + margin: 10px; color: #333; } +textarea.text-editor:focus { + outline: none; +} x-div { display: block; @@ -242,3 +244,16 @@ x-spacer { *[hidden] { display: none !important; } + +x-locator-editor { + flex: none; + width: 100%; + height: 60px; + padding: 4px; + border-bottom: 1px solid #dddddd; +} + +.CodeMirror { + width: 100% !important; + height: 100% !important; +} diff --git a/packages/playwright-core/src/server/injected/highlight.ts b/packages/playwright-core/src/server/injected/highlight.ts index c629bf092b..b9b8739656 100644 --- a/packages/playwright-core/src/server/injected/highlight.ts +++ b/packages/playwright-core/src/server/injected/highlight.ts @@ -60,7 +60,7 @@ export class Highlight { this._glassPaneElement.style.pointerEvents = 'none'; this._glassPaneElement.style.display = 'flex'; this._glassPaneElement.style.backgroundColor = 'transparent'; - for (const eventName of ['click', 'auxclick', 'dragstart', 'input', 'keydown', 'keyup', 'pointerdown', 'pointerup', 'mousedown', 'mouseup', 'mousemove', 'mouseleave', 'focus', 'scroll']) { + for (const eventName of ['click', 'auxclick', 'dragstart', 'input', 'keydown', 'keyup', 'pointerdown', 'pointerup', 'mousedown', 'mouseup', 'mouseleave', 'focus', 'scroll']) { this._glassPaneElement.addEventListener(eventName, e => { e.stopPropagation(); e.stopImmediatePropagation(); diff --git a/packages/playwright-core/src/server/injected/recorder.ts b/packages/playwright-core/src/server/injected/recorder.ts index 3ab0845ea6..d98b072907 100644 --- a/packages/playwright-core/src/server/injected/recorder.ts +++ b/packages/playwright-core/src/server/injected/recorder.ts @@ -23,10 +23,28 @@ import { Highlight, type HighlightOptions } from '../injected/highlight'; import { isInsideScope } from './domUtils'; import { elementText } from './selectorUtils'; import { asLocator } from '../../utils/isomorphic/locatorGenerators'; +import type { Language } from '../../utils/isomorphic/locatorGenerators'; import { locatorOrSelectorAsSelector } from '@isomorphic/locatorParser'; import { parseSelector } from '@isomorphic/selectorParser'; import { normalizeWhiteSpace } from '@isomorphic/stringUtils'; +// @ts-ignore @no-check-deps +import CodeMirrorImpl from 'codemirror-shadow-1'; +import type CodeMirrorType from 'codemirror'; +// @no-check-deps +import codemirrorCSS from 'codemirror-shadow-1/lib/codemirror.css?inline'; +// @no-check-deps +import 'codemirror-shadow-1/mode/css/css'; +// @no-check-deps +import 'codemirror-shadow-1/mode/htmlmixed/htmlmixed'; +// @no-check-deps +import 'codemirror-shadow-1/mode/javascript/javascript'; +// @no-check-deps +import 'codemirror-shadow-1/mode/python/python'; +// @no-check-deps +import 'codemirror-shadow-1/mode/clike/clike'; +const CodeMirror = CodeMirrorImpl as typeof CodeMirrorType; + interface RecorderDelegate { performAction?(action: actions.Action): Promise; recordAction?(action: actions.Action): Promise; @@ -507,7 +525,7 @@ class TextAssertionTool implements RecorderTool { selector, signals: [], // Interestingly, inputElement.checked is reversed inside this event handler. - checked: (target as HTMLInputElement).checked, + checked: !(target as HTMLInputElement).checked, }; } else { return { @@ -576,12 +594,21 @@ class TextAssertionTool implements RecorderTool { this._dialogElement.appendChild(toolbarElement); const bodyElement = this._recorder.document.createElement('x-pw-dialog-body'); - const locatorElement = this._recorder.document.createElement('input'); - locatorElement.classList.add('locator-editor'); - locatorElement.value = asLocator(this._recorder.state.language, this._action.selector); - locatorElement.addEventListener('input', () => { + const cmStyle = this._recorder.document.createElement('style'); + const cmElement = this._recorder.document.createElement('x-locator-editor'); + cmStyle.textContent = codemirrorCSS; + bodyElement.appendChild(cmStyle); + bodyElement.appendChild(cmElement); + const cm = CodeMirror(cmElement, { + value: asLocator(this._recorder.state.language, this._action.selector), + mode: cmModeForLanguage(this._recorder.state.language), + readOnly: false, + lineNumbers: false, + lineWrapping: true, + }); + cm.on('change', () => { if (this._action) { - const selector = locatorOrSelectorAsSelector(this._recorder.state.language, locatorElement.value, this._recorder.state.testIdAttributeName); + const selector = locatorOrSelectorAsSelector(this._recorder.state.language, cm.getValue(), this._recorder.state.testIdAttributeName); const model: HighlightModel = { selector, elements: this._recorder.injectedScript.querySelectorAll(parseSelector(selector), this._recorder.document), @@ -590,27 +617,46 @@ class TextAssertionTool implements RecorderTool { this._recorder.updateHighlight(model, true); } }); - const textElement = this._recorder.document.createElement('textarea'); - textElement.value = this._renderValue(this._action); - textElement.classList.add('text-editor'); - textElement.addEventListener('input', () => { - if (this._action?.name === 'assertText') - this._action.text = normalizeWhiteSpace(elementText(new Map(), textElement).full); - if (this._action?.name === 'assertChecked') - this._action.checked = textElement.value === 'true'; - if (this._action?.name === 'assertValue') - this._action.value = textElement.value; - }); + let elementToFocus: HTMLElement | null = null; + if (this._action.name !== 'assertChecked') { + const textElement = this._recorder.document.createElement('textarea'); + textElement.setAttribute('spellcheck', 'false'); + textElement.value = this._renderValue(this._action); + textElement.classList.add('text-editor'); + + textElement.addEventListener('input', () => { + if (this._action?.name === 'assertText') + this._action.text = normalizeWhiteSpace(elementText(new Map(), textElement).full); + if (this._action?.name === 'assertChecked') + this._action.checked = textElement.value === 'true'; + if (this._action?.name === 'assertValue') + this._action.value = textElement.value; + }); + bodyElement.appendChild(textElement); + elementToFocus = textElement; + } else { + const labelElement = this._recorder.document.createElement('label'); + labelElement.textContent = 'Value:'; + const checkboxElement = this._recorder.document.createElement('input'); + labelElement.appendChild(checkboxElement); + checkboxElement.type = 'checkbox'; + checkboxElement.checked = this._action.checked; + checkboxElement.addEventListener('change', () => { + if (this._action?.name === 'assertChecked') + this._action.checked = checkboxElement.checked; + }); + bodyElement.appendChild(labelElement); + elementToFocus = labelElement; + } - bodyElement.appendChild(locatorElement); - bodyElement.appendChild(textElement); this._dialogElement.appendChild(bodyElement); this._recorder.highlight.appendChild(this._dialogElement); const position = this._recorder.highlight.tooltipPosition(this._recorder.highlight.firstBox()!, this._dialogElement); this._dialogElement.style.top = position.anchorTop + 'px'; this._dialogElement.style.left = position.anchorLeft + 'px'; - textElement.focus(); + elementToFocus?.focus(); + cm.refresh(); } private _createLabel(action: actions.AssertAction) { @@ -1131,4 +1177,14 @@ export class PollingRecorder implements RecorderDelegate { } } +function cmModeForLanguage(language: Language): string { + if (language === 'python') + return 'python'; + if (language === 'java') + return 'text/x-java'; + if (language === 'csharp') + return 'text/x-csharp'; + return 'javascript'; +} + export default PollingRecorder; diff --git a/packages/playwright/ThirdPartyNotices.txt b/packages/playwright/ThirdPartyNotices.txt index 3adafd64c3..32ee414789 100644 --- a/packages/playwright/ThirdPartyNotices.txt +++ b/packages/playwright/ThirdPartyNotices.txt @@ -99,7 +99,7 @@ This project incorporates components from the projects listed below. The origina - chalk@4.1.2 (https://github.com/chalk/chalk) - chokidar@3.5.3 (https://github.com/paulmillr/chokidar) - ci-info@3.8.0 (https://github.com/watson/ci-info) -- codemirror@5.65.9 (https://github.com/codemirror/CodeMirror) +- codemirror-shadow-1@0.0.1 (https://github.com/codemirror/CodeMirror) - color-convert@1.9.3 (https://github.com/Qix-/color-convert) - color-convert@2.0.1 (https://github.com/Qix-/color-convert) - color-name@1.1.3 (https://github.com/dfcreative/color-name) @@ -3156,11 +3156,11 @@ SOFTWARE. ========================================= END OF ci-info@3.8.0 AND INFORMATION -%% codemirror@5.65.9 NOTICES AND INFORMATION BEGIN HERE +%% codemirror-shadow-1@0.0.1 NOTICES AND INFORMATION BEGIN HERE ========================================= MIT License -Copyright (C) 2017 by Marijn Haverbeke and others +Copyright (C) 2017 by Marijn Haverbeke and others Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -3180,7 +3180,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ========================================= -END OF codemirror@5.65.9 AND INFORMATION +END OF codemirror-shadow-1@0.0.1 AND INFORMATION %% color-convert@1.9.3 NOTICES AND INFORMATION BEGIN HERE ========================================= diff --git a/packages/web/package.json b/packages/web/package.json index 2b21846455..3898ac1bbb 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -4,7 +4,7 @@ "version": "0.0.0", "scripts": {}, "dependencies": { - "codemirror": "^5.65.9", + "codemirror-shadow-1": "0.0.1", "xterm": "^5.1.0", "xterm-addon-fit": "^0.7.0" } diff --git a/packages/web/src/components/codeMirrorModule.tsx b/packages/web/src/components/codeMirrorModule.tsx index 9009eface0..56686c45b6 100644 --- a/packages/web/src/components/codeMirrorModule.tsx +++ b/packages/web/src/components/codeMirrorModule.tsx @@ -14,13 +14,15 @@ limitations under the License. */ -import codemirror from 'codemirror'; -import 'codemirror/lib/codemirror.css'; -import 'codemirror/mode/css/css'; -import 'codemirror/mode/htmlmixed/htmlmixed'; -import 'codemirror/mode/javascript/javascript'; -import 'codemirror/mode/python/python'; -import 'codemirror/mode/clike/clike'; +// @ts-ignore +import codemirror from 'codemirror-shadow-1'; +import type codemirrorType from 'codemirror'; +import 'codemirror-shadow-1/lib/codemirror.css'; +import 'codemirror-shadow-1/mode/css/css'; +import 'codemirror-shadow-1/mode/htmlmixed/htmlmixed'; +import 'codemirror-shadow-1/mode/javascript/javascript'; +import 'codemirror-shadow-1/mode/python/python'; +import 'codemirror-shadow-1/mode/clike/clike'; -export type CodeMirror = typeof codemirror; +export type CodeMirror = typeof codemirrorType; export default codemirror; diff --git a/utils/check_deps.js b/utils/check_deps.js index ab1ed12072..fcfb278a18 100644 --- a/utils/check_deps.js +++ b/utils/check_deps.js @@ -79,7 +79,7 @@ async function innerCheckDeps(root) { }); const sourceFiles = program.getSourceFiles(); const errors = []; - sourceFiles.filter(x => !x.fileName.includes('node_modules')).map(x => visit(x, x.fileName)); + sourceFiles.filter(x => !x.fileName.includes('node_modules')).map(x => visit(x, x.fileName, x.getFullText())); if (errors.length) { for (const error of errors) @@ -112,7 +112,7 @@ async function innerCheckDeps(root) { return packageJSON; - function visit(node, fileName) { + function visit(node, fileName, text) { if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)) { if (node.importClause) { if (node.importClause.isTypeOnly) @@ -151,6 +151,14 @@ async function innerCheckDeps(root) { return; } + const fullStart = node.getFullStart(); + const commentRanges = ts.getLeadingCommentRanges(text, fullStart); + for (const range of commentRanges || []) { + const comment = text.substring(range.pos, range.end); + if (comment.includes('@no-check-deps')) + return; + } + if (importName.startsWith('@')) deps.add(importName.split('/').slice(0, 2).join('/')); else @@ -159,7 +167,7 @@ async function innerCheckDeps(root) { if (!allowExternalImport(importName, packageJSON)) errors.push(`Disallowed external dependency ${importName} from ${path.relative(root, fileName)}`); } - ts.forEachChild(node, x => visit(x, fileName)); + ts.forEachChild(node, x => visit(x, fileName, text)); } function calculateDeps(from) { diff --git a/utils/generate_third_party_notice.js b/utils/generate_third_party_notice.js index 8028b4b98f..71c250cfaf 100644 --- a/utils/generate_third_party_notice.js +++ b/utils/generate_third_party_notice.js @@ -59,7 +59,7 @@ This project incorporates components from the projects listed below. The origina } } - const packages = await checkDir('node_modules/codemirror'); + const packages = await checkDir('node_modules/codemirror-shadow-1'); for (const [key, value] of Object.entries(packages)) { if (value.licenseText) allPackages[key] = value; From d94c3e04e888abaea9a9e9e1a8756521592623d4 Mon Sep 17 00:00:00 2001 From: Playwright Service <89237858+playwrightmachine@users.noreply.github.com> Date: Mon, 13 Nov 2023 02:19:39 -0800 Subject: [PATCH 029/491] feat(webkit): roll to r1943 (#28089) Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> --- packages/playwright-core/browsers.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/playwright-core/browsers.json b/packages/playwright-core/browsers.json index 34b6f1374f..7a7217fb55 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -39,7 +39,7 @@ }, { "name": "webkit", - "revision": "1942", + "revision": "1943", "installByDefault": true, "revisionOverrides": { "mac10.14": "1446", From 0ade89c12150b8d4fe0c159c50ec6065b8606fd3 Mon Sep 17 00:00:00 2001 From: divdavem Date: Mon, 13 Nov 2023 17:54:58 +0100 Subject: [PATCH 030/491] Partial documentation update, page.evaluate never returns a handle (#28080) The documentation is misleading because `page.evaluate` never returns a handle. Some other parts of the documentation may need to be updated as well (especially other languages than javascript, I think I saw this issue also on other pages). --------- Signed-off-by: divdavem Co-authored-by: Max Schmitt --- docs/src/evaluating.md | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/docs/src/evaluating.md b/docs/src/evaluating.md index 65f8c4a646..50851110d0 100644 --- a/docs/src/evaluating.md +++ b/docs/src/evaluating.md @@ -83,15 +83,15 @@ await page.evaluate(array => array.length, [1, 2, 3]); await page.evaluate(object => object.foo, { foo: 'bar' }); // A single handle. -const button = await page.evaluate('window.button'); +const button = await page.evaluateHandle('window.button'); await page.evaluate(button => button.textContent, button); // Alternative notation using elementHandle.evaluate. await button.evaluate((button, from) => button.textContent.substring(from), 5); // Object with multiple handles. -const button1 = await page.evaluate('window.button1'); -const button2 = await page.evaluate('window.button2'); +const button1 = await page.evaluateHandle('window.button1'); +const button2 = await page.evaluateHandle('window.button2'); await page.evaluate( o => o.button1.textContent + o.button2.textContent, { button1, button2 }); @@ -128,15 +128,15 @@ obj.put("foo", "bar"); page.evaluate("object => object.foo", obj); // A single handle. -ElementHandle button = page.evaluate("window.button"); +ElementHandle button = page.evaluateHandle("window.button"); page.evaluate("button => button.textContent", button); // Alternative notation using elementHandle.evaluate. button.evaluate("(button, from) => button.textContent.substring(from)", 5); // Object with multiple handles. -ElementHandle button1 = page.evaluate("window.button1"); -ElementHandle button2 = page.evaluate("window.button2"); +ElementHandle button1 = page.evaluateHandle("window.button1"); +ElementHandle button2 = page.evaluateHandle("window.button2"); Map arg = new HashMap<>(); arg.put("button1", button1); arg.put("button2", button2); @@ -177,15 +177,15 @@ await page.evaluate('array => array.length', [1, 2, 3]) await page.evaluate('object => object.foo', { 'foo': 'bar' }) # A single handle. -button = await page.evaluate('button') +button = await page.evaluate_handle('button') await page.evaluate('button => button.textContent', button) # Alternative notation using elementHandle.evaluate. await button.evaluate('(button, from) => button.textContent.substring(from)', 5) # Object with multiple handles. -button1 = await page.query_selector('window.button1') -button2 = await page.query_selector('window.button2') +button1 = await page.evaluate_handle('window.button1') +button2 = await page.evaluate_handle('window.button2') await page.evaluate(""" o => o.button1.textContent + o.button2.textContent""", { 'button1': button1, 'button2': button2 }) @@ -220,15 +220,15 @@ page.evaluate('array => array.length', [1, 2, 3]) page.evaluate('object => object.foo', { 'foo': 'bar' }) # A single handle. -button = page.evaluate('window.button') +button = page.evaluate_handle('window.button') page.evaluate('button => button.textContent', button) # Alternative notation using elementHandle.evaluate. button.evaluate('(button, from) => button.textContent.substring(from)', 5) # Object with multiple handles. -button1 = page.evaluate('window.button1') -button2 = page.evaluate('.button2') +button1 = page.evaluate_handle('window.button1') +button2 = page.evaluate_handle('.button2') page.evaluate("""o => o.button1.textContent + o.button2.textContent""", { 'button1': button1, 'button2': button2 }) @@ -262,15 +262,15 @@ await page.EvaluateAsync("array => array.length", new[] { 1, 2, 3 }); await page.EvaluateAsync("object => object.foo", new { foo = "bar" }); // A single handle. -var button = await page.EvaluateAsync("window.button"); +var button = await page.EvaluateHandleAsync("window.button"); await page.EvaluateAsync("button => button.textContent", button); // Alternative notation using elementHandle.EvaluateAsync. await button.EvaluateAsync("(button, from) => button.textContent.substring(from)", 5); // Object with multiple handles. -var button1 = await page.EvaluateAsync("window.button1"); -var button2 = await page.EvaluateAsync("window.button2"); +var button1 = await page.EvaluateHandleAsync("window.button1"); +var button2 = await page.EvaluateHandleAsync("window.button2"); await page.EvaluateAsync("o => o.button1.textContent + o.button2.textContent", new { button1, button2 }); // Object destructuring works. Note that property names must match From c6d154f9c42f95b6cdd42fb28da4f6355105e490 Mon Sep 17 00:00:00 2001 From: Mattias Wallander Date: Mon, 13 Nov 2023 17:58:46 +0100 Subject: [PATCH 031/491] feat: Add support for dispatching device motion events (#28067) References #27887. --- .../src/server/injected/injectedScript.ts | 17 ++++++++++- tests/assets/device-motion.html | 28 +++++++++++++++++++ tests/page/page-dispatchevent.spec.ts | 22 +++++++++++++++ 3 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 tests/assets/device-motion.html diff --git a/packages/playwright-core/src/server/injected/injectedScript.ts b/packages/playwright-core/src/server/injected/injectedScript.ts index 13a35cdb53..fe0affdab4 100644 --- a/packages/playwright-core/src/server/injected/injectedScript.ts +++ b/packages/playwright-core/src/server/injected/injectedScript.ts @@ -71,6 +71,10 @@ interface WebKitLegacyDeviceOrientationEvent extends DeviceOrientationEvent { readonly initDeviceOrientationEvent: (type: string, bubbles: boolean, cancelable: boolean, alpha: number, beta: number, gamma: number, absolute: boolean) => void; } +interface WebKitLegacyDeviceMotionEvent extends DeviceMotionEvent { + readonly initDeviceMotionEvent: (type: string, bubbles: boolean, cancelable: boolean, acceleration: DeviceMotionEventAcceleration, accelerationIncludingGravity: DeviceMotionEventAcceleration, rotationRate: DeviceMotionEventRotationRate, interval: number) => void; +} + export class InjectedScript { private _engines: Map; _evaluator: SelectorEvaluatorImpl; @@ -1049,6 +1053,15 @@ export class InjectedScript { event.initDeviceOrientationEvent(type, bubbles, cancelable, alpha, beta, gamma, absolute); } break; + case 'devicemotion': + try { + event = new DeviceMotionEvent(type, eventInit); + } catch { + const { bubbles, cancelable, acceleration, accelerationIncludingGravity, rotationRate, interval } = eventInit as {bubbles: boolean, cancelable: boolean, acceleration: DeviceMotionEventAcceleration, accelerationIncludingGravity: DeviceMotionEventAcceleration, rotationRate: DeviceMotionEventRotationRate, interval: number}; + event = this.document.createEvent('DeviceMotionEvent') as WebKitLegacyDeviceMotionEvent; + event.initDeviceMotionEvent(type, bubbles, cancelable, acceleration, accelerationIncludingGravity, rotationRate, interval); + } + break; default: event = new Event(type, eventInit); break; } node.dispatchEvent(event); @@ -1380,7 +1393,7 @@ function oneLine(s: string): string { return s.replace(/\n/g, '↵').replace(/\t/g, '⇆'); } -const eventType = new Map([ +const eventType = new Map([ ['auxclick', 'mouse'], ['click', 'mouse'], ['dblclick', 'mouse'], @@ -1431,6 +1444,8 @@ const eventType = new Map + + + + Device motion test + + + + + + + diff --git a/tests/page/page-dispatchevent.spec.ts b/tests/page/page-dispatchevent.spec.ts index 44dfa90592..a6562b5976 100644 --- a/tests/page/page-dispatchevent.spec.ts +++ b/tests/page/page-dispatchevent.spec.ts @@ -193,3 +193,25 @@ it('should dispatch absolute device orientation event', async ({ page, server, i expect(await page.evaluate('gamma')).toBe(30); expect(await page.evaluate('absolute')).toBeTruthy(); }); + +it('should dispatch device motion event', async ({ page, server, isAndroid }) => { + it.skip(isAndroid, 'DeviceOrientationEvent is only available in a secure context. While Androids loopback is not treated as secure.'); + await page.goto(server.PREFIX + '/device-motion.html'); + await page.locator('html').dispatchEvent('devicemotion', { + acceleration: { x: 10, y: 20, z: 30 }, + accelerationIncludingGravity: { x: 15, y: 25, z: 35 }, + rotationRate: { alpha: 5, beta: 10, gamma: 15 }, + interval: 16, + }); + expect(await page.evaluate('result')).toBe('Moved'); + expect(await page.evaluate('acceleration.x')).toBe(10); + expect(await page.evaluate('acceleration.y')).toBe(20); + expect(await page.evaluate('acceleration.z')).toBe(30); + expect(await page.evaluate('accelerationIncludingGravity.x')).toBe(15); + expect(await page.evaluate('accelerationIncludingGravity.y')).toBe(25); + expect(await page.evaluate('accelerationIncludingGravity.z')).toBe(35); + expect(await page.evaluate('rotationRate.alpha')).toBe(5); + expect(await page.evaluate('rotationRate.beta')).toBe(10); + expect(await page.evaluate('rotationRate.gamma')).toBe(15); + expect(await page.evaluate('interval')).toBe(16); +}); From d9ccc80d0c99e8ae190188c9ad2beced08a57df7 Mon Sep 17 00:00:00 2001 From: faulpeltz Date: Mon, 13 Nov 2023 18:02:10 +0100 Subject: [PATCH 032/491] fix: ubuntu version detection for linux mint (#28085) --- packages/playwright-core/src/utils/hostPlatform.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/playwright-core/src/utils/hostPlatform.ts b/packages/playwright-core/src/utils/hostPlatform.ts index 796c1f04d0..1cf354d574 100644 --- a/packages/playwright-core/src/utils/hostPlatform.ts +++ b/packages/playwright-core/src/utils/hostPlatform.ts @@ -72,6 +72,12 @@ function calculatePlatform(): { hostPlatform: HostPlatform, isOfficiallySupporte return { hostPlatform: ('ubuntu20.04' + archSuffix) as HostPlatform, isOfficiallySupportedPlatform }; return { hostPlatform: ('ubuntu22.04' + archSuffix) as HostPlatform, isOfficiallySupportedPlatform }; } + // Linux Mint is ubuntu-based but does not have the same versions + if (distroInfo?.id === 'linuxmint') { + if (parseInt(distroInfo.version, 10) <= 20) + return { hostPlatform: ('ubuntu20.04' + archSuffix) as HostPlatform, isOfficiallySupportedPlatform: false }; + return { hostPlatform: ('ubuntu22.04' + archSuffix) as HostPlatform, isOfficiallySupportedPlatform: false }; + } if (distroInfo?.id === 'debian' || distroInfo?.id === 'raspbian') { const isOfficiallySupportedPlatform = distroInfo?.id === 'debian'; if (distroInfo?.version === '11') From 39a555513b01c4dc157f64c1fb0d52b4523c7877 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jozef=20Holl=C3=BD?= <1708197+j2ghz@users.noreply.github.com> Date: Mon, 13 Nov 2023 19:49:39 +0100 Subject: [PATCH 033/491] Add HOME=/root to container samples in docs (#27832) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reference: #6500 (https://github.com/microsoft/playwright/issues/6500#issuecomment-1664210041) It seems that this is now required, so this PR applies that to the docs. Signed-off-by: Jozef Hollý <1708197+j2ghz@users.noreply.github.com> --- docs/src/ci-intro.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/src/ci-intro.md b/docs/src/ci-intro.md index 10e3e4d37f..85741e24ef 100644 --- a/docs/src/ci-intro.md +++ b/docs/src/ci-intro.md @@ -189,6 +189,8 @@ jobs: run: npm ci - name: Run your tests run: npx playwright test + env: + HOME: /root ``` ```yml python title=".github/workflows/playwright.yml" @@ -217,6 +219,8 @@ jobs: pip install -e . - name: Run your tests run: pytest + env: + HOME: /root ``` ```yml java title=".github/workflows/playwright.yml" @@ -242,6 +246,8 @@ jobs: run: mvn -B install -D skipTests --no-transfer-progress - name: Run tests run: mvn test + env: + HOME: /root ``` ```yml csharp title=".github/workflows/playwright.yml" @@ -266,6 +272,8 @@ jobs: - run: dotnet build - name: Run your tests run: dotnet test + env: + HOME: /root ``` ### On deployment From 120f0228c5617df254e3b2bd698bc59a85d2f0c9 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Mon, 13 Nov 2023 11:30:16 -0800 Subject: [PATCH 034/491] feat(selector generator): try improving text candidate with heuristics (#28074) - Drop number-like prefixes and/or suffixes. - Trim long texts to a word boundary around 15-25 character. --- .../src/server/injected/selectorGenerator.ts | 91 +++++++++++++++---- tests/library/selector-generator.spec.ts | 33 ++++++- 2 files changed, 104 insertions(+), 20 deletions(-) diff --git a/packages/playwright-core/src/server/injected/selectorGenerator.ts b/packages/playwright-core/src/server/injected/selectorGenerator.ts index e353ad1556..edeb2b5b8e 100644 --- a/packages/playwright-core/src/server/injected/selectorGenerator.ts +++ b/packages/playwright-core/src/server/injected/selectorGenerator.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { cssEscape, escapeForAttributeSelector, escapeForTextSelector, normalizeWhiteSpace, quoteCSSAttributeValue, trimString } from '../../utils/isomorphic/stringUtils'; +import { cssEscape, escapeForAttributeSelector, escapeForTextSelector, normalizeWhiteSpace, quoteCSSAttributeValue } from '../../utils/isomorphic/stringUtils'; import { closestCrossShadow, isInsideScope, parentElementOrShadowHost } from './domUtils'; import type { InjectedScript } from './injectedScript'; import { getAriaRole, getElementAccessibleName, beginAriaCaches, endAriaCaches } from './roleUtils'; @@ -229,16 +229,18 @@ function buildNoTextCandidates(injectedScript: InjectedScript, element: Element, if (element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA') { const input = element as HTMLInputElement | HTMLTextAreaElement; if (input.placeholder) { - candidates.push({ engine: 'internal:attr', selector: `[placeholder=${escapeForAttributeSelector(input.placeholder, false)}]`, score: kPlaceholderScore }); candidates.push({ engine: 'internal:attr', selector: `[placeholder=${escapeForAttributeSelector(input.placeholder, true)}]`, score: kPlaceholderScoreExact }); + for (const alternative of suitableTextAlternatives(input.placeholder)) + candidates.push({ engine: 'internal:attr', selector: `[placeholder=${escapeForAttributeSelector(alternative.text, false)}]`, score: kPlaceholderScore - alternative.scoreBouns }); } } const labels = getElementLabels(injectedScript._evaluator._cacheText, element); for (const label of labels) { const labelText = label.full.trim(); - candidates.push({ engine: 'internal:label', selector: escapeForTextSelector(labelText, false), score: kLabelScore }); candidates.push({ engine: 'internal:label', selector: escapeForTextSelector(labelText, true), score: kLabelScoreExact }); + for (const alternative of suitableTextAlternatives(labelText)) + candidates.push({ engine: 'internal:label', selector: escapeForTextSelector(alternative.text, false), score: kLabelScore - alternative.scoreBouns }); } const ariaRole = getAriaRole(element); @@ -265,36 +267,43 @@ function buildTextCandidates(injectedScript: InjectedScript, element: Element, i return []; const candidates: SelectorToken[][] = []; - if (element.getAttribute('title')) { - candidates.push([{ engine: 'internal:attr', selector: `[title=${escapeForAttributeSelector(element.getAttribute('title')!, false)}]`, score: kTitleScore }]); - candidates.push([{ engine: 'internal:attr', selector: `[title=${escapeForAttributeSelector(element.getAttribute('title')!, true)}]`, score: kTitleScoreExact }]); + const title = element.getAttribute('title'); + if (title) { + candidates.push([{ engine: 'internal:attr', selector: `[title=${escapeForAttributeSelector(title, true)}]`, score: kTitleScoreExact }]); + for (const alternative of suitableTextAlternatives(title)) + candidates.push([{ engine: 'internal:attr', selector: `[title=${escapeForAttributeSelector(alternative.text, false)}]`, score: kTitleScore - alternative.scoreBouns }]); } - if (element.getAttribute('alt') && ['APPLET', 'AREA', 'IMG', 'INPUT'].includes(element.nodeName)) { - candidates.push([{ engine: 'internal:attr', selector: `[alt=${escapeForAttributeSelector(element.getAttribute('alt')!, false)}]`, score: kAltTextScore }]); - candidates.push([{ engine: 'internal:attr', selector: `[alt=${escapeForAttributeSelector(element.getAttribute('alt')!, true)}]`, score: kAltTextScoreExact }]); + const alt = element.getAttribute('alt'); + if (alt && ['APPLET', 'AREA', 'IMG', 'INPUT'].includes(element.nodeName)) { + candidates.push([{ engine: 'internal:attr', selector: `[alt=${escapeForAttributeSelector(alt, true)}]`, score: kAltTextScoreExact }]); + for (const alternative of suitableTextAlternatives(alt)) + candidates.push([{ engine: 'internal:attr', selector: `[alt=${escapeForAttributeSelector(alternative.text, false)}]`, score: kAltTextScore - alternative.scoreBouns }]); } - const fullText = normalizeWhiteSpace(elementText(injectedScript._evaluator._cacheText, element).full); - const text = trimString(fullText, 80); + const text = normalizeWhiteSpace(elementText(injectedScript._evaluator._cacheText, element).full); if (text) { - const escaped = escapeForTextSelector(text, false); + const alternatives = suitableTextAlternatives(text); if (isTargetNode) { - candidates.push([{ engine: 'internal:text', selector: escaped, score: kTextScore }]); - candidates.push([{ engine: 'internal:text', selector: escapeForTextSelector(text, true), score: kTextScoreExact }]); + if (text.length <= 80) + candidates.push([{ engine: 'internal:text', selector: escapeForTextSelector(text, true), score: kTextScoreExact }]); + for (const alternative of alternatives) + candidates.push([{ engine: 'internal:text', selector: escapeForTextSelector(alternative.text, false), score: kTextScore - alternative.scoreBouns }]); } const cssToken: SelectorToken = { engine: 'css', selector: cssEscape(element.nodeName.toLowerCase()), score: kCSSTagNameScore }; - candidates.push([cssToken, { engine: 'internal:has-text', selector: escaped, score: kTextScore }]); - if (fullText.length <= 80) - candidates.push([cssToken, { engine: 'internal:has-text', selector: '/^' + escapeRegExp(fullText) + '$/', score: kTextScoreRegex }]); + for (const alternative of alternatives) + candidates.push([cssToken, { engine: 'internal:has-text', selector: escapeForTextSelector(alternative.text, false), score: kTextScore - alternative.scoreBouns }]); + if (text.length <= 80) + candidates.push([cssToken, { engine: 'internal:has-text', selector: '/^' + escapeRegExp(text) + '$/', score: kTextScoreRegex }]); } const ariaRole = getAriaRole(element); if (ariaRole && !['none', 'presentation'].includes(ariaRole)) { const ariaName = getElementAccessibleName(element, false); if (ariaName) { - candidates.push([{ engine: 'internal:role', selector: `${ariaRole}[name=${escapeForAttributeSelector(ariaName, false)}]`, score: kRoleWithNameScore }]); candidates.push([{ engine: 'internal:role', selector: `${ariaRole}[name=${escapeForAttributeSelector(ariaName, true)}]`, score: kRoleWithNameScoreExact }]); + for (const alternative of suitableTextAlternatives(ariaName)) + candidates.push([{ engine: 'internal:role', selector: `${ariaRole}[name=${escapeForAttributeSelector(alternative.text, false)}]`, score: kRoleWithNameScore - alternative.scoreBouns }]); } } @@ -466,3 +475,49 @@ function escapeRegExp(s: string) { // From https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string } + +function trimWordBoundary(text: string, maxLength: number) { + if (text.length <= maxLength) + return text; + text = text.substring(0, maxLength); + // Find last word boundary in the text. + const match = text.match(/^(.*)\b(.+?)$/); + if (!match) + return ''; + return match[1].trimEnd(); +} + +function suitableTextAlternatives(text: string) { + let result: { text: string, scoreBouns: number }[] = []; + + { + const match = text.match(/^([\d.,]+)[^.,\w]/); + const leadingNumberLength = match ? match[1].length : 0; + if (leadingNumberLength) { + const alt = text.substring(leadingNumberLength).trimStart(); + result.push({ text: alt, scoreBouns: alt.length <= 30 ? 2 : 1 }); + } + } + + { + const match = text.match(/[^.,\w]([\d.,]+)$/); + const trailingNumberLength = match ? match[1].length : 0; + if (trailingNumberLength) { + const alt = text.substring(0, text.length - trailingNumberLength).trimEnd(); + result.push({ text: alt, scoreBouns: alt.length <= 30 ? 2 : 1 }); + } + } + + if (text.length <= 30) { + result.push({ text, scoreBouns: 0 }); + } else { + result.push({ text: trimWordBoundary(text, 80), scoreBouns: 0 }); + result.push({ text: trimWordBoundary(text, 30), scoreBouns: 1 }); + } + + result = result.filter(r => r.text); + if (!result.length) + result.push({ text: text.substring(0, 80), scoreBouns: 0 }); + + return result; +} diff --git a/tests/library/selector-generator.spec.ts b/tests/library/selector-generator.spec.ts index 44093d0a5e..6b87b6171c 100644 --- a/tests/library/selector-generator.spec.ts +++ b/tests/library/selector-generator.spec.ts @@ -54,10 +54,38 @@ it.describe('selector generator', () => { }); it('should trim text', async ({ page }) => { - await page.setContent(`
Text0123456789Text0123456789Text0123456789Text0123456789Text0123456789Text0123456789Text0123456789Text0123456789Text0123456789Text0123456789
`); + await page.setContent(` +
Text0123456789Text0123456789Text0123456789Text0123456789Text0123456789Text0123456789Text0123456789Text0123456789Text0123456789Text0123456789
+
Text0123456789Text0123456789Text0123456789Text0123456789Text0123456789!Text0123456789Text0123456789Text0123456789Text0123456789Text0123456789
+ `); expect(await generate(page, 'div')).toBe('internal:text="Text0123456789Text0123456789Text0123456789Text0123456789Text0123456789Text012345"i'); }); + it('should try to improve role name', async ({ page }) => { + await page.setContent(`
Issues 23
`); + expect(await generate(page, 'div')).toBe('internal:role=button[name="Issues"i]'); + }); + + it('should try to improve text', async ({ page }) => { + await page.setContent(`
23 Issues
`); + expect(await generate(page, 'div')).toBe('internal:text="Issues"i'); + }); + + it('should try to improve text by shortening', async ({ page }) => { + await page.setContent(`
Longest verbose description of the item
`); + expect(await generate(page, 'div')).toBe('internal:text="Longest verbose description"i'); + }); + + it('should try to improve label text by shortening', async ({ page }) => { + await page.setContent(``); + expect(await generate(page, 'input')).toBe('internal:label="Longest verbose description"i'); + }); + + it('should not improve guid text', async ({ page }) => { + await page.setContent(`
91b1b23
`); + expect(await generate(page, 'div')).toBe('internal:text="91b1b23"i'); + }); + it('should not escape text with >>', async ({ page }) => { await page.setContent(`
text>>text
`); expect(await generate(page, 'div')).toBe('internal:text="text>>text"i'); @@ -206,9 +234,10 @@ it.describe('selector generator', () => {
Text that goes on and on and on and on and on and on and on and on and on and on and on and on and on and on and on
+
Text that goes on and on and on and on and on and on and on and on and X on and on and on and on and on and on and on
`); - expect(await generate(page, '#id > div')).toBe(`#id >> internal:text="Text that goes on and on and on and on and on and on and on and on and on and on"i`); + expect(await generate(page, '#id > div')).toBe(`#id >> internal:text="Text that goes on and on and on and on and on and on and on and on and on and"i`); }); it('should use nested ordinals', async ({ page }) => { From 40b8df7217d37889d9d1ab2a71af7ff24e0d3e63 Mon Sep 17 00:00:00 2001 From: Elijah Mock <28277163+ekcom@users.noreply.github.com> Date: Mon, 13 Nov 2023 13:31:29 -0600 Subject: [PATCH 035/491] docs(running-tests-*.md): Correct "running tests" typos (#28055) I have corrected some typos and grammar issues that I found when reading your documentation. --------- Signed-off-by: Elijah Mock <28277163+ekcom@users.noreply.github.com> --- docs/src/running-tests-csharp.md | 4 ++-- docs/src/running-tests-java.md | 10 +++++----- docs/src/running-tests-js.md | 34 ++++++++++++++++---------------- docs/src/running-tests-python.md | 30 ++++++++++++++-------------- 4 files changed, 39 insertions(+), 39 deletions(-) diff --git a/docs/src/running-tests-csharp.md b/docs/src/running-tests-csharp.md index 14e26386d7..2956e6df64 100644 --- a/docs/src/running-tests-csharp.md +++ b/docs/src/running-tests-csharp.md @@ -5,7 +5,7 @@ title: "Running and debugging tests" ## Introduction -You can run a single test, a set of tests or all tests. Tests can be run on different browsers. By default tests are run in a headless manner meaning no browser window will be opened while running the tests and results will be seen in the terminal. If you prefer you can run your tests in headed mode by using the `headless` test run parameter. +You can run a single test, a set of tests or all tests. Tests can be run on different browsers. By default, tests are run in a headless manner, meaning no browser window will be opened while running the tests and results will be seen in the terminal. If you prefer, you can run your tests in headed mode by using the `headless` test run parameter. - Running all tests @@ -39,7 +39,7 @@ You can run a single test, a set of tests or all tests. Tests can be run on diff - Running Tests on multiple browsers - To run your test on multiple browsers or configurations you need to invoke the `dotnet test` command multiple times. There you can then either specify the `BROWSER` environment variable or set the `Playwright.BrowserName` via the runsettings file: + To run your test on multiple browsers or configurations, you need to invoke the `dotnet test` command multiple times. There you can then either specify the `BROWSER` environment variable or set the `Playwright.BrowserName` via the runsettings file: ```bash dotnet test --settings:chromium.runsettings diff --git a/docs/src/running-tests-java.md b/docs/src/running-tests-java.md index 1cacca7710..d769a73e51 100644 --- a/docs/src/running-tests-java.md +++ b/docs/src/running-tests-java.md @@ -5,12 +5,12 @@ title: "Running and debugging tests" ## Introduction -Playwright tests can be run in a variety of ways. We recommend hooking it up to your favorite test runner, e.g. [JUnit](./test-runners.md) since it gives you the ability to run tests in parallel, run single test, etc. +Playwright tests can be run in a variety of ways. We recommend hooking it up to your favorite test runner, e.g., [JUnit](./test-runners.md), since it gives you the ability to run tests in parallel, run single test, etc. -You can run a single test, a set of tests or all tests. Tests can be run on one browser or multiple browsers. By default tests are run in a headless manner meaning no browser window will be opened while running the tests and results will be seen in the terminal. If you prefer you can run your tests in headed mode by using the `launch(new BrowserType.LaunchOptions().setHeadless(false))` option. +You can run a single test, a set of tests or all tests. Tests can be run on one browser or multiple browsers. By default tests are run in a headless manner meaning no browser window will be opened while running the tests and results will be seen in the terminal. If you prefer, you can run your tests in headed mode by using the `launch(new BrowserType.LaunchOptions().setHeadless(false))` option. -In [JUnit](https://junit.org/junit5/) you can initialize [Playwright] and [Browser] in [@BeforeAll](https://junit.org/junit5/docs/current/api/org.junit.jupiter.api/org/junit/jupiter/api/BeforeAll.html) method and -destroy them in [@AfterAll](https://junit.org/junit5/docs/current/api/org.junit.jupiter.api/org/junit/jupiter/api/AfterAll.html). In the example below all three test methods use the same +In [JUnit](https://junit.org/junit5/), you can initialize [Playwright] and [Browser] in [@BeforeAll](https://junit.org/junit5/docs/current/api/org.junit.jupiter.api/org/junit/jupiter/api/BeforeAll.html) method and +destroy them in [@AfterAll](https://junit.org/junit5/docs/current/api/org.junit.jupiter.api/org/junit/jupiter/api/AfterAll.html). In the example below, all three test methods use the same [Browser]. Each test uses its own [BrowserContext] and [Page]. ```java @@ -81,7 +81,7 @@ public class TestExample { } ``` -See [here](./test-runners.md) for further details on how to run tests in parallel etc.. +See [here](./test-runners.md) for further details on how to run tests in parallel, etc. ## What's Next diff --git a/docs/src/running-tests-js.md b/docs/src/running-tests-js.md index 2ae7591469..79aaa2ef04 100644 --- a/docs/src/running-tests-js.md +++ b/docs/src/running-tests-js.md @@ -4,7 +4,7 @@ title: "Running and debugging tests" --- ## Introduction -With Playwright you can run a single test, a set of tests or all tests. Tests can be run on one browser or multiple browsers by using the `--project` flag. Tests are run in parallel by default and are run in a headless manner meaning no browser window will be opened while running the tests and results will be seen in the terminal. However you can run tests in headed mode by using the `--headed` CLI argument or you can run your tests in [UI mode](./test-ui-mode.md), by using the `--ui` flag, and see a full trace of your tests complete with watch mode, time travel debugging and more. +With Playwright you can run a single test, a set of tests or all tests. Tests can be run on one browser or multiple browsers by using the `--project` flag. Tests are run in parallel by default and are run in a headless manner, meaning no browser window will be opened while running the tests and results will be seen in the terminal. However, you can run tests in headed mode by using the `--headed` CLI argument, or you can run your tests in [UI mode](./test-ui-mode.md) by using the `--ui` flag. See a full trace of your tests complete with watch mode, time travel debugging and more. **You will learn** @@ -25,7 +25,7 @@ npx playwright test ### Run tests in UI mode -We highly recommend running your tests with [UI Mode](./test-ui-mode.md) for a better developer experience where you can easily walk through each step of the test and visually see what was happening before, during, and after each step. UI mode also comes with many other features such as the locator picker, watch mode and more. +We highly recommend running your tests with [UI Mode](./test-ui-mode.md) for a better developer experience where you can easily walk through each step of the test and visually see what was happening before, during and after each step. UI mode also comes with many other features such as the locator picker, watch mode and more. ```bash npx playwright test --ui @@ -37,7 +37,7 @@ Check out or [detailed guide on UI Mode](./test-ui-mode.md) to learn more about ### Run tests in headed mode -To run your tests in headed mode use the `--headed` flag. This will give you the ability to visually see, how Playwright interacts with the website. +To run your tests in headed mode, use the `--headed` flag. This will give you the ability to visually see how Playwright interacts with the website. ```bash npx playwright test --headed @@ -45,13 +45,13 @@ npx playwright test --headed ### Run tests on different browsers -To specify which browser you would like to run your tests on use the `--project` flag followed by the name of the browser. +To specify which browser you would like to run your tests on, use the `--project` flag followed by the name of the browser. ```bash npx playwright test --project webkit ``` -To specify multiple browsers to run your tests on use the `--project` flag multiple times followed by the name of each browser. +To specify multiple browsers to run your tests on, use the `--project` flag multiple times followed by the name of each browser. ```bash npx playwright test --project webkit --project firefox @@ -59,25 +59,25 @@ npx playwright test --project webkit --project firefox ### Run specific tests -To run a single test file pass in the name of the test file that you want to run. +To run a single test file, pass in the name of the test file that you want to run. ```bash npx playwright test landing-page.spec.ts ``` -To run a set of test files from different directories pass in the names of the directories that you want to run the tests in. +To run a set of test files from different directories, pass in the names of the directories that you want to run the tests in. ```bash npx playwright test tests/todo-page/ tests/landing-page/ ``` -To run files that have `landing` or `login` in the file name simply pass in these keywords to the CLI. +To run files that have `landing` or `login` in the file name, simply pass in these keywords to the CLI. ```bash npx playwright test landing login ``` -To run a test with a specific title use the `-g` flag followed by the title of the test. +To run a test with a specific title, use the `-g` flag followed by the title of the test. ```bash npx playwright test -g "add a todo item" @@ -91,11 +91,11 @@ Tests can be run right from VS Code using the [VS Code extension](https://market ## Debugging tests -Since Playwright runs in Node.js, you can debug it with your debugger of choice e.g. using `console.log` or inside your IDE or directly in VS Code with the [VS Code Extension](./getting-started-vscode.md). Playwright comes with [UI Mode](./test-ui-mode.md), where you can easily walk through each step of the test, see logs, errors, network requests, inspect the DOM snapshot and more. You can also use the [Playwright Inspector](./debug.md#playwright-inspector) which allows you to step through Playwright API calls, see their debug logs and explore [locators](./locators.md). +Since Playwright runs in Node.js, you can debug it with your debugger of choice e.g. using `console.log` or inside your IDE or directly in VS Code with the [VS Code Extension](./getting-started-vscode.md). Playwright comes with [UI Mode](./test-ui-mode.md), where you can easily walk through each step of the test, see logs, errors, network requests, inspect the DOM snapshot and more. You can also use the [Playwright Inspector](./debug.md#playwright-inspector), which allows you to step through Playwright API calls, see their debug logs and explore [locators](./locators.md). ### Debug tests in UI mode -We highly recommend debugging your tests with [UI Mode](./test-ui-mode.md) for a better developer experience where you can easily walk through each step of the test and visually see what was happening before, during, and after each step. UI mode also comes with many other features such as the locator picker, watch mode and more. +We highly recommend debugging your tests with [UI Mode](./test-ui-mode.md) for a better developer experience where you can easily walk through each step of the test and visually see what was happening before, during and after each step. UI mode also comes with many other features such as the locator picker, watch mode and more. ```bash npx playwright test --ui @@ -103,15 +103,15 @@ npx playwright test --ui ![showing errors in ui mode](https://github.com/microsoft/playwright/assets/13063165/ffca2fd1-5349-41fb-ade9-ace143bb2c58) -While debugging you can use the Pick Locator button to select an element on the page and see the locator that Playwright would use to find that element. You an also edit the locator in the locator playground and see it highlighting live on the Browser window. Use the Copy Locator button to copy the locator to your clipboard and then paste it into you test. +While debugging you can use the Pick Locator button to select an element on the page and see the locator that Playwright would use to find that element. You can also edit the locator in the locator playground and see it highlighting live on the Browser window. Use the Copy Locator button to copy the locator to your clipboard and then paste it into you test. ![pick locator in ui mode](https://github.com/microsoft/playwright/assets/13063165/9e7eeb84-bd26-4010-8614-75e24b56c716) -Check out or [detailed guide on UI Mode](./test-ui-mode.md) to learn more about it's features. +Check out our [detailed guide on UI Mode](./test-ui-mode.md) to learn more about it's features. ### Debug tests with the Playwright Inspector -To debug all tests run the Playwright test command followed by the `--debug` flag. +To debug all tests, run the Playwright test command followed by the `--debug` flag. ```bash npx playwright test --debug @@ -119,15 +119,15 @@ npx playwright test --debug ![Debugging Tests with the Playwright inspector](https://github.com/microsoft/playwright/assets/13063165/6b3b3caa-d258-4cb8-aa05-cd407f501626) -This command will open up a Browser window as well as the Playwright Inspector. You can use the step over button at the top of the inspector to step through your test. Or press the play button to run your test from start to finish. Once the test has finished the browser window will close. +This command will open up a Browser window as well as the Playwright Inspector. You can use the step over button at the top of the inspector to step through your test. Or, press the play button to run your test from start to finish. Once the test has finished, the browser window will close. -To debug one test file run the Playwright test command with the name of the test file that you want to debug followed by the `--debug` flag. +To debug one test file, run the Playwright test command with the name of the test file that you want to debug followed by the `--debug` flag. ```bash npx playwright test example.spec.ts --debug ``` -To debug a specific test from the line number where the `test(..` is defined add a colon followed by the line number at the end of the test file name, followed by the `--debug` flag. +To debug a specific test from the line number where the `test(..` is defined, add a colon followed by the line number at the end of the test file name, followed by the `--debug` flag. ```bash npx playwright test example.spec.ts:10 --debug diff --git a/docs/src/running-tests-python.md b/docs/src/running-tests-python.md index dca252c960..8b2752eef5 100644 --- a/docs/src/running-tests-python.md +++ b/docs/src/running-tests-python.md @@ -4,7 +4,7 @@ title: "Running and debugging tests" --- ## Introduction -You can run a single test, a set of tests or all tests. Tests can be run on one browser or multiple browsers by using the `--browser` flag. By default tests are run in a headless manner meaning no browser window will be opened while running the tests and results will be seen in the terminal. If you prefer you can run your tests in headed mode by using the `--headed` CLI argument. +You can run a single test, a set of tests or all tests. Tests can be run on one browser or multiple browsers by using the `--browser` flag. By default, tests are run in a headless manner, meaning no browser window will be opened while running the tests and results will be seen in the terminal. If you prefer, you can run your tests in headed mode by using the `--headed` CLI argument. **You will learn** @@ -15,7 +15,7 @@ You can run a single test, a set of tests or all tests. Tests can be run on one ### Command Line -To run your tests use the `pytest` command. This will run your tests on the Chromium browser by default. Tests run in headless mode by default meaning no browser window will be opened while running the tests and results will be seen in the terminal. +To run your tests, use the `pytest` command. This will run your tests on the Chromium browser by default. Tests run in headless mode by default meaning no browser window will be opened while running the tests and results will be seen in the terminal. ```bash pytest @@ -23,20 +23,20 @@ pytest ### Run tests in headed mode -To run your tests in headed mode use the `--headed` flag. This will open up a browser window while running your tests and once finished the browser window will close. +To run your tests in headed mode, use the `--headed` flag. This will open up a browser window while running your tests and once finished the browser window will close. ```bash pytest --headed ``` ### Run tests on different browsers -To specify which browser you would like to run your tests on use the `--browser` flag followed by the name of the browser. +To specify which browser you would like to run your tests on, use the `--browser` flag followed by the name of the browser. ```bash pytest --browser webkit ``` -To specify multiple browsers to run your tests on use the `--browser` flag multiple times followed by the name of each browser. +To specify multiple browsers to run your tests on, use the `--browser` flag multiple times followed by the name of each browser. ```bash @@ -45,19 +45,19 @@ pytest --browser webkit --browser firefox ### Run specific tests -To run a single test file pass in the name of the test file that you want to run. +To run a single test file, pass in the name of the test file that you want to run. ```bash pytest test_login.py ``` -To run a set of test files pass in the names of the test files that you want to run. +To run a set of test files, pass in the names of the test files that you want to run. ```bash pytest tests/test_todo_page.py tests/test_landing_page.py ``` -To run a specific test pass in the function name of the test you want to run. +To run a specific test, pass in the function name of the test you want to run. ```bash pytest -k test_add_a_todo_item @@ -65,7 +65,7 @@ To run a specific test pass in the function name of the test you want to run. ### Run tests in parallel -To run your tests in parallel use the `--numprocesses` flag followed by the number of processes you would like to run your tests on. We recommend half of logical CPU cores. +To run your tests in parallel, use the `--numprocesses` flag followed by the number of processes you would like to run your tests on. We recommend half of logical CPU cores. ```bash pytest --numprocesses 2 @@ -73,13 +73,13 @@ To run your tests in parallel use the `--numprocesses` flag followed by the numb (This assumes `pytest-xdist` is installed. For more information see [here](./test-runners.md#parallelism-running-multiple-tests-at-once).) -For more information see [Playwright Pytest usage](./test-runners.md) or the Pytest documentation for [general CLI usage](https://docs.pytest.org/en/stable/usage.html). +For more information, see [Playwright Pytest usage](./test-runners.md) or the Pytest documentation for [general CLI usage](https://docs.pytest.org/en/stable/usage.html). ## Debugging tests -Since Playwright runs in Python, you can debug it with your debugger of choice with e.g. the [Python extension](https://code.visualstudio.com/docs/python/python-tutorial) in Visual Studio Code. Playwright comes with the Playwright Inspector which allows you to step through Playwright API calls, see their debug logs and explore [locators](./locators.md). +Since Playwright runs in Python, you can debug it with your debugger of choice, e.g., with the [Python extension](https://code.visualstudio.com/docs/python/python-tutorial) in Visual Studio Code. Playwright comes with the Playwright Inspector which allows you to step through Playwright API calls, see their debug logs and explore [locators](./locators.md). -To debug all tests run the following command. +To debug all tests, run the following command. ```bash tab=bash-bash lang=python PWDEBUG=1 pytest -s @@ -95,7 +95,7 @@ $env:PWDEBUG=1 pytest -s ``` -To debug one test file run the command followed by the name of the test file that you want to debug. +To debug one test file, run the command followed by the name of the test file that you want to debug. ```bash tab=bash-bash lang=python PWDEBUG=1 pytest -s test_example.py @@ -111,7 +111,7 @@ $env:PWDEBUG=1 pytest -s test_example.py ``` -To debug a specific test add `-k` followed by the name of the test that you want to debug. +To debug a specific test, add `-k` followed by the name of the test that you want to debug. ```bash tab=bash-bash lang=python PWDEBUG=1 pytest -s -k test_get_started_link @@ -127,7 +127,7 @@ $env:PWDEBUG=1 pytest -s -k test_get_started_link ``` -This command will open up a Browser window as well as the Playwright Inspector. You can use the step over button at the top of the inspector to step through your test. Or press the play button to run your test from start to finish. Once the test has finished the browser window will close. +This command will open up a Browser window as well as the Playwright Inspector. You can use the step over button at the top of the inspector to step through your test. Or press the play button to run your test from start to finish. Once the test has finished, the browser window will close. While debugging you can use the Pick Locator button to select an element on the page and see the locator that Playwright would use to find that element. You an also edit the locator and see it highlighting live on the Browser window. Use the Copy Locator button to copy the locator to your clipboard and then paste it into you test. From db38f0d2df3f7aeb0b69c4f6fe7ee930ca90b748 Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Mon, 13 Nov 2023 11:44:06 -0800 Subject: [PATCH 036/491] chore: flag text mismatch when editing (#28106) --- .../src/server/injected/highlight.css | 5 +++-- .../src/server/injected/recorder.ts | 17 ++++++++++++----- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/packages/playwright-core/src/server/injected/highlight.css b/packages/playwright-core/src/server/injected/highlight.css index f4db846697..25187d1cd6 100644 --- a/packages/playwright-core/src/server/injected/highlight.css +++ b/packages/playwright-core/src/server/injected/highlight.css @@ -223,10 +223,11 @@ textarea.text-editor { border: none; margin: 10px; color: #333; + outline: 1px solid transparent !important; } -textarea.text-editor:focus { - outline: none; +textarea.text-editor.does-not-match { + outline: 1px solid red !important; } x-div { diff --git a/packages/playwright-core/src/server/injected/recorder.ts b/packages/playwright-core/src/server/injected/recorder.ts index d98b072907..eb7b03fb6b 100644 --- a/packages/playwright-core/src/server/injected/recorder.ts +++ b/packages/playwright-core/src/server/injected/recorder.ts @@ -22,6 +22,7 @@ import type { Mode, OverlayState, UIState } from '@recorder/recorderTypes'; import { Highlight, type HighlightOptions } from '../injected/highlight'; import { isInsideScope } from './domUtils'; import { elementText } from './selectorUtils'; +import type { ElementText } from './selectorUtils'; import { asLocator } from '../../utils/isomorphic/locatorGenerators'; import type { Language } from '../../utils/isomorphic/locatorGenerators'; import { locatorOrSelectorAsSelector } from '@isomorphic/locatorParser'; @@ -467,6 +468,7 @@ class TextAssertionTool implements RecorderTool { private _acceptButton: HTMLElement; private _cancelButton: HTMLElement; private _keyboardListener: ((event: KeyboardEvent) => void) | undefined; + private _textCache = new Map(); constructor(private _recorder: Recorder) { this._acceptButton = this._recorder.document.createElement('x-pw-tool-item'); @@ -514,6 +516,7 @@ class TextAssertionTool implements RecorderTool { } private _generateAction(): actions.AssertAction | null { + this._textCache.clear(); const target = this._hoverHighlight?.elements[0]; if (!target) return null; @@ -541,7 +544,7 @@ class TextAssertionTool implements RecorderTool { name: 'assertText', selector, signals: [], - text: target.textContent!, + text: normalizeWhiteSpace(elementText(this._textCache, target).full), substring: true, }; } @@ -626,12 +629,16 @@ class TextAssertionTool implements RecorderTool { textElement.classList.add('text-editor'); textElement.addEventListener('input', () => { - if (this._action?.name === 'assertText') - this._action.text = normalizeWhiteSpace(elementText(new Map(), textElement).full); - if (this._action?.name === 'assertChecked') + if (this._action?.name === 'assertText') { + const newValue = normalizeWhiteSpace(textElement.value); + this._action.text = newValue; + const targetText = normalizeWhiteSpace(elementText(this._textCache, target).full); + textElement.classList.toggle('does-not-match', !!newValue && !targetText.includes(newValue)); + } else if (this._action?.name === 'assertChecked') { this._action.checked = textElement.value === 'true'; - if (this._action?.name === 'assertValue') + } else if (this._action?.name === 'assertValue') { this._action.value = textElement.value; + } }); bodyElement.appendChild(textElement); elementToFocus = textElement; From cd70d51aa809fb3a3a9c13468fdf8a7a1ed14a9e Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Mon, 13 Nov 2023 11:44:25 -0800 Subject: [PATCH 037/491] chore: remove redundant check for highlight liveness (#28108) Closes https://github.com/microsoft/playwright/issues/28002 --- packages/playwright-core/src/server/injected/highlight.ts | 4 ---- packages/playwright-core/src/server/injected/recorder.ts | 3 --- 2 files changed, 7 deletions(-) diff --git a/packages/playwright-core/src/server/injected/highlight.ts b/packages/playwright-core/src/server/injected/highlight.ts index b9b8739656..66e05491bc 100644 --- a/packages/playwright-core/src/server/injected/highlight.ts +++ b/packages/playwright-core/src/server/injected/highlight.ts @@ -96,10 +96,6 @@ export class Highlight { this._glassPaneElement.remove(); } - isInstalled(): boolean { - return this._glassPaneElement.parentElement === this._injectedScript.document.documentElement && !this._glassPaneElement.nextElementSibling; - } - showActionPoint(x: number, y: number) { this._actionPointElement.style.top = y + 'px'; this._actionPointElement.style.left = x + 'px'; diff --git a/packages/playwright-core/src/server/injected/recorder.ts b/packages/playwright-core/src/server/injected/recorder.ts index eb7b03fb6b..b3c16ee125 100644 --- a/packages/playwright-core/src/server/injected/recorder.ts +++ b/packages/playwright-core/src/server/injected/recorder.ts @@ -842,9 +842,6 @@ export class Recorder { } installListeners() { - // Ensure we are attached to the current document, and we are on top (last element); - if (this.highlight.isInstalled()) - return; removeEventListeners(this._listeners); this._listeners = [ addEventListener(this.document, 'click', event => this._onClick(event as MouseEvent), true), From 35aeace47616417dba1e1bba2ff6ba421bf846fb Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Mon, 13 Nov 2023 12:28:50 -0800 Subject: [PATCH 038/491] docs(assertions): note on whitespace normalization (#28110) Fixes https://github.com/microsoft/playwright-java/issues/1419 --- docs/src/api/class-locatorassertions.md | 10 ++++++++++ packages/playwright/types/test.d.ts | 10 ++++++++++ 2 files changed, 20 insertions(+) diff --git a/docs/src/api/class-locatorassertions.md b/docs/src/api/class-locatorassertions.md index 1569c69c2e..11244c0867 100644 --- a/docs/src/api/class-locatorassertions.md +++ b/docs/src/api/class-locatorassertions.md @@ -929,6 +929,11 @@ await Expect( Ensures the [Locator] points to an element that contains the given text. You can use regular expressions for the value as well. +**Details** + +When `expected` parameter is a string, Playwright will normalize whitespaces and line breaks both in the actual text and +in the expected string before matching. When regular expression is used, the actual text is matched as is. + **Usage** ```js @@ -1598,6 +1603,11 @@ Note that screenshot assertions only work with Playwright test runner. Ensures the [Locator] points to an element with the given text. You can use regular expressions for the value as well. +**Details** + +When `expected` parameter is a string, Playwright will normalize whitespaces and line breaks both in the actual text and +in the expected string before matching. When regular expression is used, the actual text is matched as is. + **Usage** ```js diff --git a/packages/playwright/types/test.d.ts b/packages/playwright/types/test.d.ts index e605859592..78d89b89d1 100644 --- a/packages/playwright/types/test.d.ts +++ b/packages/playwright/types/test.d.ts @@ -5632,6 +5632,11 @@ interface LocatorAssertions { * Ensures the {@link Locator} points to an element that contains the given text. You can use regular expressions for * the value as well. * + * **Details** + * + * When `expected` parameter is a string, Playwright will normalize whitespaces and line breaks both in the actual + * text and in the expected string before matching. When regular expression is used, the actual text is matched as is. + * * **Usage** * * ```js @@ -6029,6 +6034,11 @@ interface LocatorAssertions { * Ensures the {@link Locator} points to an element with the given text. You can use regular expressions for the value * as well. * + * **Details** + * + * When `expected` parameter is a string, Playwright will normalize whitespaces and line breaks both in the actual + * text and in the expected string before matching. When regular expression is used, the actual text is matched as is. + * * **Usage** * * ```js From 8b1c637c1682c8fa529f6145cb22f25d2f497410 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Mon, 13 Nov 2023 21:54:30 +0100 Subject: [PATCH 039/491] fix(codegen): generate expect import for library (#28107) --- .../src/server/recorder/javascript.ts | 2 +- .../library/inspector/cli-codegen-javascript.spec.ts | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/playwright-core/src/server/recorder/javascript.ts b/packages/playwright-core/src/server/recorder/javascript.ts index 6ffbb00dcf..71e60d6f7b 100644 --- a/packages/playwright-core/src/server/recorder/javascript.ts +++ b/packages/playwright-core/src/server/recorder/javascript.ts @@ -169,7 +169,7 @@ ${useText ? '\ntest.use(' + useText + ');\n' : ''} generateStandaloneHeader(options: LanguageGeneratorOptions): string { const formatter = new JavaScriptFormatter(); formatter.add(` - const { ${options.browserName}${options.deviceName ? ', devices' : ''} } = require('playwright'); + const { expect, ${options.browserName}${options.deviceName ? ', devices' : ''} } = require('@playwright/test'); (async () => { const browser = await ${options.browserName}.launch(${formatObjectOrVoid(options.launchOptions)}); diff --git a/tests/library/inspector/cli-codegen-javascript.spec.ts b/tests/library/inspector/cli-codegen-javascript.spec.ts index f2c34899d8..198721bb06 100644 --- a/tests/library/inspector/cli-codegen-javascript.spec.ts +++ b/tests/library/inspector/cli-codegen-javascript.spec.ts @@ -26,7 +26,7 @@ const launchOptions = (channel: string) => { test('should print the correct imports and context options', async ({ browserName, channel, runCLI }) => { const cli = runCLI(['--target=javascript', emptyHTML]); - const expectedResult = `const { ${browserName} } = require('playwright'); + const expectedResult = `const { expect, ${browserName} } = require('@playwright/test'); (async () => { const browser = await ${browserName}.launch({ @@ -38,7 +38,7 @@ test('should print the correct imports and context options', async ({ browserNam test('should print the correct context options for custom settings', async ({ browserName, channel, runCLI }) => { const cli = runCLI(['--color-scheme=light', '--target=javascript', emptyHTML]); - const expectedResult = `const { ${browserName} } = require('playwright'); + const expectedResult = `const { expect, ${browserName} } = require('@playwright/test'); (async () => { const browser = await ${browserName}.launch({ @@ -55,7 +55,7 @@ test('should print the correct context options when using a device', async ({ br test.skip(browserName !== 'chromium'); const cli = runCLI(['--device=Pixel 2', '--target=javascript', emptyHTML]); - const expectedResult = `const { chromium, devices } = require('playwright'); + const expectedResult = `const { expect, chromium, devices } = require('@playwright/test'); (async () => { const browser = await chromium.launch({ @@ -71,7 +71,7 @@ test('should print the correct context options when using a device and additiona test.skip(browserName !== 'webkit'); const cli = runCLI(['--color-scheme=light', '--device=iPhone 11', '--target=javascript', emptyHTML]); - const expectedResult = `const { webkit, devices } = require('playwright'); + const expectedResult = `const { expect, webkit, devices } = require('@playwright/test'); (async () => { const browser = await webkit.launch({ @@ -91,7 +91,7 @@ test('should save the codegen output to a file if specified', async ({ browserNa }); await cli.waitForCleanExit(); const content = fs.readFileSync(tmpFile); - expect(content.toString()).toBe(`const { ${browserName} } = require('playwright'); + expect(content.toString()).toBe(`const { expect, ${browserName} } = require('@playwright/test'); (async () => { const browser = await ${browserName}.launch({ @@ -113,7 +113,7 @@ test('should print load/save storageState', async ({ browserName, channel, runCL const saveFileName = testInfo.outputPath('save.json'); await fs.promises.writeFile(loadFileName, JSON.stringify({ cookies: [], origins: [] }), 'utf8'); const cli = runCLI([`--load-storage=${loadFileName}`, `--save-storage=${saveFileName}`, '--target=javascript', emptyHTML]); - const expectedResult1 = `const { ${browserName} } = require('playwright'); + const expectedResult1 = `const { expect, ${browserName} } = require('@playwright/test'); (async () => { const browser = await ${browserName}.launch({ From ae5cdf16f0de7d8b07f7bd450008b1fccfaebc57 Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Mon, 13 Nov 2023 14:38:04 -0800 Subject: [PATCH 040/491] chore: allow asserting substring (#28111) --- .../src/server/injected/highlight.css | 4 +- .../src/server/injected/recorder.ts | 64 +++++++++++++------ 2 files changed, 48 insertions(+), 20 deletions(-) diff --git a/packages/playwright-core/src/server/injected/highlight.css b/packages/playwright-core/src/server/injected/highlight.css index 25187d1cd6..938957cdeb 100644 --- a/packages/playwright-core/src/server/injected/highlight.css +++ b/packages/playwright-core/src/server/injected/highlight.css @@ -115,10 +115,10 @@ x-pw-tool-gripper > x-div { background-color: #555555; } -x-pw-tool-label { +x-pw-tools-list > label { display: flex; align-items: center; - margin-left: 10px; + margin: 0 10px; user-select: none; } diff --git a/packages/playwright-core/src/server/injected/recorder.ts b/packages/playwright-core/src/server/injected/recorder.ts index b3c16ee125..242dd376f8 100644 --- a/packages/playwright-core/src/server/injected/recorder.ts +++ b/packages/playwright-core/src/server/injected/recorder.ts @@ -539,10 +539,13 @@ class TextAssertionTool implements RecorderTool { }; } } else { - const { selector } = generateSelector(this._recorder.injectedScript, target, { testIdAttributeName: this._recorder.state.testIdAttributeName, forTextExpect: true }); + this._hoverHighlight = generateSelector(this._recorder.injectedScript, target, { testIdAttributeName: this._recorder.state.testIdAttributeName, forTextExpect: true }); + // forTextExpect can update the target, re-highlight it. + this._recorder.updateHighlight(this._hoverHighlight, true, { color: '#8acae480' }); + return { name: 'assertText', - selector, + selector: this._hoverHighlight.selector, signals: [], text: normalizeWhiteSpace(elementText(this._textCache, target).full), substring: true, @@ -588,6 +591,7 @@ class TextAssertionTool implements RecorderTool { return; } }; + this._recorder.document.addEventListener('keydown', this._keyboardListener, true); const toolbarElement = this._recorder.document.createElement('x-pw-tools-list'); toolbarElement.appendChild(this._createLabel(this._action)); @@ -622,36 +626,60 @@ class TextAssertionTool implements RecorderTool { }); let elementToFocus: HTMLElement | null = null; - if (this._action.name !== 'assertChecked') { + const action = this._action; + if (action.name === 'assertText') { + const textElement = this._recorder.document.createElement('textarea'); + textElement.setAttribute('spellcheck', 'false'); + textElement.value = this._renderValue(this._action); + textElement.classList.add('text-editor'); + + const updateAndValidate = () => { + const newValue = normalizeWhiteSpace(textElement.value); + action.text = newValue; + const targetText = normalizeWhiteSpace(elementText(this._textCache, target).full); + const matches = action.substring ? newValue && targetText.includes(newValue) : targetText === newValue; + textElement.classList.toggle('does-not-match', !matches); + }; + textElement.addEventListener('input', updateAndValidate); + bodyElement.appendChild(textElement); + + // Add a toolbar substring checkbox. + const substringElement = this._recorder.document.createElement('label'); + substringElement.style.cursor = 'pointer'; + const checkboxElement = this._recorder.document.createElement('input'); + substringElement.appendChild(checkboxElement); + substringElement.appendChild(this._recorder.document.createTextNode('Substring')); + checkboxElement.type = 'checkbox'; + checkboxElement.style.cursor = 'pointer'; + checkboxElement.checked = action.substring; + checkboxElement.addEventListener('change', () => { + action.substring = checkboxElement.checked; + updateAndValidate(); + }); + toolbarElement.insertBefore(substringElement, this._acceptButton); + + elementToFocus = textElement; + } else if (action.name === 'assertValue') { const textElement = this._recorder.document.createElement('textarea'); textElement.setAttribute('spellcheck', 'false'); textElement.value = this._renderValue(this._action); textElement.classList.add('text-editor'); textElement.addEventListener('input', () => { - if (this._action?.name === 'assertText') { - const newValue = normalizeWhiteSpace(textElement.value); - this._action.text = newValue; - const targetText = normalizeWhiteSpace(elementText(this._textCache, target).full); - textElement.classList.toggle('does-not-match', !!newValue && !targetText.includes(newValue)); - } else if (this._action?.name === 'assertChecked') { - this._action.checked = textElement.value === 'true'; - } else if (this._action?.name === 'assertValue') { - this._action.value = textElement.value; - } + action.value = textElement.value; }); bodyElement.appendChild(textElement); elementToFocus = textElement; - } else { + } else if (action.name === 'assertChecked') { const labelElement = this._recorder.document.createElement('label'); labelElement.textContent = 'Value:'; const checkboxElement = this._recorder.document.createElement('input'); labelElement.appendChild(checkboxElement); checkboxElement.type = 'checkbox'; - checkboxElement.checked = this._action.checked; + checkboxElement.checked = action.checked; checkboxElement.addEventListener('change', () => { - if (this._action?.name === 'assertChecked') - this._action.checked = checkboxElement.checked; + if (action.name === 'assertChecked') + action.checked = checkboxElement.checked; }); bodyElement.appendChild(labelElement); elementToFocus = labelElement; @@ -667,7 +695,7 @@ class TextAssertionTool implements RecorderTool { } private _createLabel(action: actions.AssertAction) { - const labelElement = this._recorder.document.createElement('x-pw-tool-label'); + const labelElement = this._recorder.document.createElement('label'); labelElement.textContent = action.name === 'assertText' ? 'Assert text' : action.name === 'assertValue' ? 'Assert value' : 'Assert checked'; return labelElement; } From b0f75a6a3ad1a9c6894bfc8188017de5dc4b11d4 Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Mon, 13 Nov 2023 15:42:46 -0800 Subject: [PATCH 041/491] chore: allow editing locator while matching text (#28115) --- .../src/server/injected/highlight.css | 18 ++++++------------ .../src/server/injected/recorder.ts | 19 +++++++++++-------- 2 files changed, 17 insertions(+), 20 deletions(-) diff --git a/packages/playwright-core/src/server/injected/highlight.css b/packages/playwright-core/src/server/injected/highlight.css index 938957cdeb..c159615ac8 100644 --- a/packages/playwright-core/src/server/injected/highlight.css +++ b/packages/playwright-core/src/server/injected/highlight.css @@ -55,17 +55,6 @@ x-pw-dialog-body { flex: auto; } -x-pw-dialog-body label { - margin: 10px; - display: flex; - align-items: center; - cursor: pointer; -} - -x-pw-dialog-body input { - cursor: pointer; -} - x-pw-highlight { position: absolute; top: 0; @@ -221,7 +210,7 @@ textarea.text-editor { font-family: system-ui, "Ubuntu", "Droid Sans", sans-serif; flex: auto; border: none; - margin: 10px; + margin: 6px; color: #333; outline: 1px solid transparent !important; } @@ -252,6 +241,11 @@ x-locator-editor { height: 60px; padding: 4px; border-bottom: 1px solid #dddddd; + outline: 1px solid transparent; +} + +x-locator-editor.does-not-match { + outline: 1px solid red; } .CodeMirror { diff --git a/packages/playwright-core/src/server/injected/recorder.ts b/packages/playwright-core/src/server/injected/recorder.ts index 242dd376f8..baca6d957b 100644 --- a/packages/playwright-core/src/server/injected/recorder.ts +++ b/packages/playwright-core/src/server/injected/recorder.ts @@ -572,8 +572,7 @@ class TextAssertionTool implements RecorderTool { } private _showDialog() { - const target = this._hoverHighlight?.elements[0]; - if (!target) + if (!this._hoverHighlight?.elements[0]) return; this._action = this._generateAction(); if (!this._action) @@ -616,12 +615,14 @@ class TextAssertionTool implements RecorderTool { cm.on('change', () => { if (this._action) { const selector = locatorOrSelectorAsSelector(this._recorder.state.language, cm.getValue(), this._recorder.state.testIdAttributeName); - const model: HighlightModel = { + const elements = this._recorder.injectedScript.querySelectorAll(parseSelector(selector), this._recorder.document); + cmElement.classList.toggle('does-not-match', !elements.length); + this._hoverHighlight = elements.length ? { selector, - elements: this._recorder.injectedScript.querySelectorAll(parseSelector(selector), this._recorder.document), - }; + elements, + } : null; this._action.selector = selector; - this._recorder.updateHighlight(model, true); + this._recorder.updateHighlight(this._hoverHighlight, true); } }); @@ -635,6 +636,9 @@ class TextAssertionTool implements RecorderTool { const updateAndValidate = () => { const newValue = normalizeWhiteSpace(textElement.value); + const target = this._hoverHighlight?.elements[0]; + if (!target) + return; action.text = newValue; const targetText = normalizeWhiteSpace(elementText(this._textCache, target).full); const matches = action.substring ? newValue && targetText.includes(newValue) : targetText === newValue; @@ -678,8 +682,7 @@ class TextAssertionTool implements RecorderTool { checkboxElement.type = 'checkbox'; checkboxElement.checked = action.checked; checkboxElement.addEventListener('change', () => { - if (action.name === 'assertChecked') - action.checked = checkboxElement.checked; + action.checked = checkboxElement.checked; }); bodyElement.appendChild(labelElement); elementToFocus = labelElement; From ec4893d23580b37f1a4d6c7a3d6b536482484817 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Mon, 13 Nov 2023 15:56:50 -0800 Subject: [PATCH 042/491] docs: update phrasing for 1.40 features (#28113) --- docs/src/api/class-download.md | 5 ++--- docs/src/api/class-elementhandle.md | 5 ++++- docs/src/api/class-frame.md | 5 ++++- docs/src/api/class-locator.md | 5 ++++- docs/src/api/class-page.md | 5 ++++- packages/playwright-core/types/types.d.ts | 26 +++++++++++++++++------ 6 files changed, 37 insertions(+), 14 deletions(-) diff --git a/docs/src/api/class-download.md b/docs/src/api/class-download.md index 8f54048c9f..3333d36996 100644 --- a/docs/src/api/class-download.md +++ b/docs/src/api/class-download.md @@ -72,7 +72,7 @@ Upon successful cancellations, `download.failure()` would resolve to `'canceled' * langs: java, js, csharp - returns: <[Readable]> -Returns readable stream for current download or `null` if download failed. +Returns a readable stream for a successful download, or throws for a failed/canceled download. ## async method: Download.delete * since: v1.8 @@ -95,8 +95,7 @@ Get the page that the download belongs to. * since: v1.8 - returns: <[path]> -Returns path to the downloaded file in case of successful download. The method will -wait for the download to finish if necessary. The method throws when connected remotely. +Returns path to the downloaded file for a successful download, or throws for a failed/canceled download. The method will wait for the download to finish if necessary. The method throws when connected remotely. Note that the download's file name is a random GUID, use [`method: Download.suggestedFilename`] to get suggested file name. diff --git a/docs/src/api/class-elementhandle.md b/docs/src/api/class-elementhandle.md index 62a61e808e..1a7297be31 100644 --- a/docs/src/api/class-elementhandle.md +++ b/docs/src/api/class-elementhandle.md @@ -322,13 +322,16 @@ default. Since [`param: eventInit`] is event-specific, please refer to the events documentation for the lists of initial properties: +* [DeviceMotionEvent](https://developer.mozilla.org/en-US/docs/Web/API/DeviceMotionEvent/DeviceMotionEvent) +* [DeviceOrientationEvent](https://developer.mozilla.org/en-US/docs/Web/API/DeviceOrientationEvent/DeviceOrientationEvent) * [DragEvent](https://developer.mozilla.org/en-US/docs/Web/API/DragEvent/DragEvent) +* [Event](https://developer.mozilla.org/en-US/docs/Web/API/Event/Event) * [FocusEvent](https://developer.mozilla.org/en-US/docs/Web/API/FocusEvent/FocusEvent) * [KeyboardEvent](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/KeyboardEvent) * [MouseEvent](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/MouseEvent) * [PointerEvent](https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/PointerEvent) * [TouchEvent](https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent/TouchEvent) -* [Event](https://developer.mozilla.org/en-US/docs/Web/API/Event/Event) +* [WheelEvent](https://developer.mozilla.org/en-US/docs/Web/API/WheelEvent/WheelEvent) You can also specify `JSHandle` as the property value if you want live objects to be passed into the event: diff --git a/docs/src/api/class-frame.md b/docs/src/api/class-frame.md index 5ea3d8e6e0..336ba55bd3 100644 --- a/docs/src/api/class-frame.md +++ b/docs/src/api/class-frame.md @@ -382,13 +382,16 @@ default. Since [`param: eventInit`] is event-specific, please refer to the events documentation for the lists of initial properties: +* [DeviceMotionEvent](https://developer.mozilla.org/en-US/docs/Web/API/DeviceMotionEvent/DeviceMotionEvent) +* [DeviceOrientationEvent](https://developer.mozilla.org/en-US/docs/Web/API/DeviceOrientationEvent/DeviceOrientationEvent) * [DragEvent](https://developer.mozilla.org/en-US/docs/Web/API/DragEvent/DragEvent) +* [Event](https://developer.mozilla.org/en-US/docs/Web/API/Event/Event) * [FocusEvent](https://developer.mozilla.org/en-US/docs/Web/API/FocusEvent/FocusEvent) * [KeyboardEvent](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/KeyboardEvent) * [MouseEvent](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/MouseEvent) * [PointerEvent](https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/PointerEvent) * [TouchEvent](https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent/TouchEvent) -* [Event](https://developer.mozilla.org/en-US/docs/Web/API/Event/Event) +* [WheelEvent](https://developer.mozilla.org/en-US/docs/Web/API/WheelEvent/WheelEvent) You can also specify `JSHandle` as the property value if you want live objects to be passed into the event: diff --git a/docs/src/api/class-locator.md b/docs/src/api/class-locator.md index f74c82367d..5781878f6f 100644 --- a/docs/src/api/class-locator.md +++ b/docs/src/api/class-locator.md @@ -561,13 +561,16 @@ default. Since [`param: eventInit`] is event-specific, please refer to the events documentation for the lists of initial properties: +* [DeviceMotionEvent](https://developer.mozilla.org/en-US/docs/Web/API/DeviceMotionEvent/DeviceMotionEvent) +* [DeviceOrientationEvent](https://developer.mozilla.org/en-US/docs/Web/API/DeviceOrientationEvent/DeviceOrientationEvent) * [DragEvent](https://developer.mozilla.org/en-US/docs/Web/API/DragEvent/DragEvent) +* [Event](https://developer.mozilla.org/en-US/docs/Web/API/Event/Event) * [FocusEvent](https://developer.mozilla.org/en-US/docs/Web/API/FocusEvent/FocusEvent) * [KeyboardEvent](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/KeyboardEvent) * [MouseEvent](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/MouseEvent) * [PointerEvent](https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/PointerEvent) * [TouchEvent](https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent/TouchEvent) -* [Event](https://developer.mozilla.org/en-US/docs/Web/API/Event/Event) +* [WheelEvent](https://developer.mozilla.org/en-US/docs/Web/API/WheelEvent/WheelEvent) You can also specify [JSHandle] as the property value if you want live objects to be passed into the event: diff --git a/docs/src/api/class-page.md b/docs/src/api/class-page.md index c6a9418ce0..e875547905 100644 --- a/docs/src/api/class-page.md +++ b/docs/src/api/class-page.md @@ -954,13 +954,16 @@ default. Since [`param: eventInit`] is event-specific, please refer to the events documentation for the lists of initial properties: +* [DeviceMotionEvent](https://developer.mozilla.org/en-US/docs/Web/API/DeviceMotionEvent/DeviceMotionEvent) +* [DeviceOrientationEvent](https://developer.mozilla.org/en-US/docs/Web/API/DeviceOrientationEvent/DeviceOrientationEvent) * [DragEvent](https://developer.mozilla.org/en-US/docs/Web/API/DragEvent/DragEvent) +* [Event](https://developer.mozilla.org/en-US/docs/Web/API/Event/Event) * [FocusEvent](https://developer.mozilla.org/en-US/docs/Web/API/FocusEvent/FocusEvent) * [KeyboardEvent](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/KeyboardEvent) * [MouseEvent](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/MouseEvent) * [PointerEvent](https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/PointerEvent) * [TouchEvent](https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent/TouchEvent) -* [Event](https://developer.mozilla.org/en-US/docs/Web/API/Event/Event) +* [WheelEvent](https://developer.mozilla.org/en-US/docs/Web/API/WheelEvent/WheelEvent) You can also specify `JSHandle` as the property value if you want live objects to be passed into the event: diff --git a/packages/playwright-core/types/types.d.ts b/packages/playwright-core/types/types.d.ts index 8483e5af54..51979c06cd 100644 --- a/packages/playwright-core/types/types.d.ts +++ b/packages/playwright-core/types/types.d.ts @@ -2124,13 +2124,16 @@ export interface Page { * properties and dispatches it on the element. Events are `composed`, `cancelable` and bubble by default. * * Since `eventInit` is event-specific, please refer to the events documentation for the lists of initial properties: + * - [DeviceMotionEvent](https://developer.mozilla.org/en-US/docs/Web/API/DeviceMotionEvent/DeviceMotionEvent) + * - [DeviceOrientationEvent](https://developer.mozilla.org/en-US/docs/Web/API/DeviceOrientationEvent/DeviceOrientationEvent) * - [DragEvent](https://developer.mozilla.org/en-US/docs/Web/API/DragEvent/DragEvent) + * - [Event](https://developer.mozilla.org/en-US/docs/Web/API/Event/Event) * - [FocusEvent](https://developer.mozilla.org/en-US/docs/Web/API/FocusEvent/FocusEvent) * - [KeyboardEvent](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/KeyboardEvent) * - [MouseEvent](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/MouseEvent) * - [PointerEvent](https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/PointerEvent) * - [TouchEvent](https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent/TouchEvent) - * - [Event](https://developer.mozilla.org/en-US/docs/Web/API/Event/Event) + * - [WheelEvent](https://developer.mozilla.org/en-US/docs/Web/API/WheelEvent/WheelEvent) * * You can also specify `JSHandle` as the property value if you want live objects to be passed into the event: * @@ -5736,13 +5739,16 @@ export interface Frame { * properties and dispatches it on the element. Events are `composed`, `cancelable` and bubble by default. * * Since `eventInit` is event-specific, please refer to the events documentation for the lists of initial properties: + * - [DeviceMotionEvent](https://developer.mozilla.org/en-US/docs/Web/API/DeviceMotionEvent/DeviceMotionEvent) + * - [DeviceOrientationEvent](https://developer.mozilla.org/en-US/docs/Web/API/DeviceOrientationEvent/DeviceOrientationEvent) * - [DragEvent](https://developer.mozilla.org/en-US/docs/Web/API/DragEvent/DragEvent) + * - [Event](https://developer.mozilla.org/en-US/docs/Web/API/Event/Event) * - [FocusEvent](https://developer.mozilla.org/en-US/docs/Web/API/FocusEvent/FocusEvent) * - [KeyboardEvent](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/KeyboardEvent) * - [MouseEvent](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/MouseEvent) * - [PointerEvent](https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/PointerEvent) * - [TouchEvent](https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent/TouchEvent) - * - [Event](https://developer.mozilla.org/en-US/docs/Web/API/Event/Event) + * - [WheelEvent](https://developer.mozilla.org/en-US/docs/Web/API/WheelEvent/WheelEvent) * * You can also specify `JSHandle` as the property value if you want live objects to be passed into the event: * @@ -9654,13 +9660,16 @@ export interface ElementHandle extends JSHandle { * properties and dispatches it on the element. Events are `composed`, `cancelable` and bubble by default. * * Since `eventInit` is event-specific, please refer to the events documentation for the lists of initial properties: + * - [DeviceMotionEvent](https://developer.mozilla.org/en-US/docs/Web/API/DeviceMotionEvent/DeviceMotionEvent) + * - [DeviceOrientationEvent](https://developer.mozilla.org/en-US/docs/Web/API/DeviceOrientationEvent/DeviceOrientationEvent) * - [DragEvent](https://developer.mozilla.org/en-US/docs/Web/API/DragEvent/DragEvent) + * - [Event](https://developer.mozilla.org/en-US/docs/Web/API/Event/Event) * - [FocusEvent](https://developer.mozilla.org/en-US/docs/Web/API/FocusEvent/FocusEvent) * - [KeyboardEvent](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/KeyboardEvent) * - [MouseEvent](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/MouseEvent) * - [PointerEvent](https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/PointerEvent) * - [TouchEvent](https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent/TouchEvent) - * - [Event](https://developer.mozilla.org/en-US/docs/Web/API/Event/Event) + * - [WheelEvent](https://developer.mozilla.org/en-US/docs/Web/API/WheelEvent/WheelEvent) * * You can also specify `JSHandle` as the property value if you want live objects to be passed into the event: * @@ -11023,13 +11032,16 @@ export interface Locator { * properties and dispatches it on the element. Events are `composed`, `cancelable` and bubble by default. * * Since `eventInit` is event-specific, please refer to the events documentation for the lists of initial properties: + * - [DeviceMotionEvent](https://developer.mozilla.org/en-US/docs/Web/API/DeviceMotionEvent/DeviceMotionEvent) + * - [DeviceOrientationEvent](https://developer.mozilla.org/en-US/docs/Web/API/DeviceOrientationEvent/DeviceOrientationEvent) * - [DragEvent](https://developer.mozilla.org/en-US/docs/Web/API/DragEvent/DragEvent) + * - [Event](https://developer.mozilla.org/en-US/docs/Web/API/Event/Event) * - [FocusEvent](https://developer.mozilla.org/en-US/docs/Web/API/FocusEvent/FocusEvent) * - [KeyboardEvent](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/KeyboardEvent) * - [MouseEvent](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/MouseEvent) * - [PointerEvent](https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/PointerEvent) * - [TouchEvent](https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent/TouchEvent) - * - [Event](https://developer.mozilla.org/en-US/docs/Web/API/Event/Event) + * - [WheelEvent](https://developer.mozilla.org/en-US/docs/Web/API/WheelEvent/WheelEvent) * * You can also specify {@link JSHandle} as the property value if you want live objects to be passed into the event: * @@ -16919,7 +16931,7 @@ export interface Download { cancel(): Promise; /** - * Returns readable stream for current download or `null` if download failed. + * Returns a readable stream for a successful download, or throws for a failed/canceled download. */ createReadStream(): Promise; @@ -16939,8 +16951,8 @@ export interface Download { page(): Page; /** - * Returns path to the downloaded file in case of successful download. The method will wait for the download to finish - * if necessary. The method throws when connected remotely. + * Returns path to the downloaded file for a successful download, or throws for a failed/canceled download. The method + * will wait for the download to finish if necessary. The method throws when connected remotely. * * Note that the download's file name is a random GUID, use * [download.suggestedFilename()](https://playwright.dev/docs/api/class-download#download-suggested-filename) to get From 2c3955a28c2af6b97a430dfc97fe756d40fcfbda Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Mon, 13 Nov 2023 16:39:05 -0800 Subject: [PATCH 043/491] chore: remove fake `error` from expect calls (#28112) We used to have a fake `error` property, so that trace viewer shows failed expectes as such. Today, we have a step for each expect that contains a proper error. Sending the fake error to the client confuses language ports. --- .../playwright-core/src/server/dispatchers/frameDispatcher.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/playwright-core/src/server/dispatchers/frameDispatcher.ts b/packages/playwright-core/src/server/dispatchers/frameDispatcher.ts index c8fd0c9e24..b8e69d070d 100644 --- a/packages/playwright-core/src/server/dispatchers/frameDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/frameDispatcher.ts @@ -256,8 +256,6 @@ export class FrameDispatcher extends Dispatcher Date: Mon, 13 Nov 2023 16:39:14 -0800 Subject: [PATCH 044/491] chore(recorder): standby mode, expose setModeRequested in DebugController (#28117) --- packages/playwright-core/src/protocol/validator.ts | 3 +++ .../playwright-core/src/server/debugController.ts | 5 +++++ .../dispatchers/debugControllerDispatcher.ts | 5 ++++- .../src/server/injected/recorder.ts | 14 ++++++++++---- packages/playwright-core/src/server/recorder.ts | 2 +- .../src/server/recorder/recorderApp.ts | 2 +- packages/protocol/src/channels.ts | 5 +++++ packages/protocol/src/protocol.yml | 4 ++++ packages/recorder/src/recorder.tsx | 7 ++++--- packages/recorder/src/recorderTypes.ts | 2 +- 10 files changed, 38 insertions(+), 11 deletions(-) diff --git a/packages/playwright-core/src/protocol/validator.ts b/packages/playwright-core/src/protocol/validator.ts index de0d84cf1f..a9bb86a892 100644 --- a/packages/playwright-core/src/protocol/validator.ts +++ b/packages/playwright-core/src/protocol/validator.ts @@ -367,6 +367,9 @@ scheme.DebugControllerInspectRequestedEvent = tObject({ selector: tString, locator: tString, }); +scheme.DebugControllerSetModeRequestedEvent = tObject({ + mode: tString, +}); scheme.DebugControllerStateChangedEvent = tObject({ pageCount: tNumber, }); diff --git a/packages/playwright-core/src/server/debugController.ts b/packages/playwright-core/src/server/debugController.ts index fb0bc2053c..f084ed1ece 100644 --- a/packages/playwright-core/src/server/debugController.ts +++ b/packages/playwright-core/src/server/debugController.ts @@ -35,6 +35,7 @@ export class DebugController extends SdkObject { InspectRequested: 'inspectRequested', SourceChanged: 'sourceChanged', Paused: 'paused', + SetModeRequested: 'setModeRequested', }; private _autoCloseTimer: NodeJS.Timeout | undefined; @@ -240,4 +241,8 @@ class InspectingRecorderApp extends EmptyRecorderApp { override async setPaused(paused: boolean) { this._debugController.emit(DebugController.Events.Paused, { paused }); } + + override async setMode(mode: Mode) { + this._debugController.emit(DebugController.Events.SetModeRequested, { mode }); + } } diff --git a/packages/playwright-core/src/server/dispatchers/debugControllerDispatcher.ts b/packages/playwright-core/src/server/dispatchers/debugControllerDispatcher.ts index 251ed1e3c7..34c4d3b4ca 100644 --- a/packages/playwright-core/src/server/dispatchers/debugControllerDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/debugControllerDispatcher.ts @@ -40,7 +40,10 @@ export class DebugControllerDispatcher extends Dispatcher { this._dispatchEvent('paused', ({ paused })); - }) + }), + eventsHelper.addEventListener(this._object, DebugController.Events.SetModeRequested, ({ mode }) => { + this._dispatchEvent('setModeRequested', ({ mode })); + }), ]; } diff --git a/packages/playwright-core/src/server/injected/recorder.ts b/packages/playwright-core/src/server/injected/recorder.ts index baca6d957b..1ff89efe39 100644 --- a/packages/playwright-core/src/server/injected/recorder.ts +++ b/packages/playwright-core/src/server/injected/recorder.ts @@ -740,7 +740,7 @@ class Overlay { this._recordToggle.classList.add('record'); this._recordToggle.appendChild(this._recorder.injectedScript.document.createElement('x-div')); this._recordToggle.addEventListener('click', () => { - this._recorder.delegate.setMode?.(this._recorder.state.mode === 'none' || this._recorder.state.mode === 'inspecting' ? 'recording' : 'none'); + this._recorder.delegate.setMode?.(this._recorder.state.mode === 'none' || this._recorder.state.mode === 'standby' || this._recorder.state.mode === 'inspecting' ? 'recording' : 'standby'); }); toolsListElement.appendChild(this._recordToggle); @@ -750,8 +750,9 @@ class Overlay { this._pickLocatorToggle.appendChild(this._recorder.injectedScript.document.createElement('x-div')); this._pickLocatorToggle.addEventListener('click', () => { const newMode: Record = { - 'inspecting': 'none', + 'inspecting': 'standby', 'none': 'inspecting', + 'standby': 'inspecting', 'recording': 'recording-inspecting', 'recording-inspecting': 'recording', 'assertingText': 'recording-inspecting', @@ -786,11 +787,15 @@ class Overlay { this._recordToggle.classList.toggle('active', state.mode === 'recording' || state.mode === 'assertingText' || state.mode === 'recording-inspecting'); this._pickLocatorToggle.classList.toggle('active', state.mode === 'inspecting' || state.mode === 'recording-inspecting'); this._assertToggle.classList.toggle('active', state.mode === 'assertingText'); - this._assertToggle.classList.toggle('disabled', state.mode === 'none' || state.mode === 'inspecting'); + this._assertToggle.classList.toggle('disabled', state.mode === 'none' || state.mode === 'standby' || state.mode === 'inspecting'); if (this._offsetX !== state.overlay.offsetX) { this._offsetX = state.overlay.offsetX; this._updateVisualPosition(); } + if (state.mode === 'none') + this._overlayElement.setAttribute('hidden', 'true'); + else + this._overlayElement.removeAttribute('hidden'); } private _updateVisualPosition() { @@ -851,6 +856,7 @@ export class Recorder { this.highlight = new Highlight(injectedScript); this._tools = { 'none': new NoneTool(), + 'standby': new NoneTool(), 'inspecting': new InspectTool(this), 'recording': new RecordActionTool(this), 'recording-inspecting': new InspectTool(this), @@ -929,7 +935,7 @@ export class Recorder { this._actionSelectorModel = null; if (state.actionSelector !== this._actionSelectorModel?.selector) this._actionSelectorModel = state.actionSelector ? querySelector(this.injectedScript, state.actionSelector, this.document) : null; - if (this.state.mode === 'none') + if (this.state.mode === 'none' || this.state.mode === 'standby') this.updateHighlight(this._actionSelectorModel, false); } diff --git a/packages/playwright-core/src/server/recorder.ts b/packages/playwright-core/src/server/recorder.ts index f5329072f9..b2b5e50014 100644 --- a/packages/playwright-core/src/server/recorder.ts +++ b/packages/playwright-core/src/server/recorder.ts @@ -248,7 +248,7 @@ export class Recorder implements InstrumentationListener { this._recorderApp?.setMode(this._mode); this._contextRecorder.setEnabled(this._mode === 'recording' || this._mode === 'assertingText'); this._debugger.setMuted(this._mode === 'recording' || this._mode === 'assertingText'); - if (this._mode !== 'none' && this._context.pages().length === 1) + if (this._mode !== 'none' && this._mode !== 'standby' && this._context.pages().length === 1) this._context.pages()[0].bringToFront().catch(() => {}); this._refreshOverlay(); } diff --git a/packages/playwright-core/src/server/recorder/recorderApp.ts b/packages/playwright-core/src/server/recorder/recorderApp.ts index 37ff723dff..1324393a06 100644 --- a/packages/playwright-core/src/server/recorder/recorderApp.ts +++ b/packages/playwright-core/src/server/recorder/recorderApp.ts @@ -170,7 +170,7 @@ export class RecorderApp extends EventEmitter implements IRecorderApp { async setSelector(selector: string, userGesture?: boolean): Promise { if (userGesture) { if (this._recorder.mode() === 'inspecting') { - this._recorder.setMode('none'); + this._recorder.setMode('standby'); this._page.bringToFront(); } else { this._recorder.setMode('recording'); diff --git a/packages/protocol/src/channels.ts b/packages/protocol/src/channels.ts index 497fed66d0..68d28335e1 100644 --- a/packages/protocol/src/channels.ts +++ b/packages/protocol/src/channels.ts @@ -636,6 +636,7 @@ export type RecorderSource = { export type DebugControllerInitializer = {}; export interface DebugControllerEventTarget { on(event: 'inspectRequested', callback: (params: DebugControllerInspectRequestedEvent) => void): this; + on(event: 'setModeRequested', callback: (params: DebugControllerSetModeRequestedEvent) => void): this; on(event: 'stateChanged', callback: (params: DebugControllerStateChangedEvent) => void): this; on(event: 'sourceChanged', callback: (params: DebugControllerSourceChangedEvent) => void): this; on(event: 'paused', callback: (params: DebugControllerPausedEvent) => void): this; @@ -658,6 +659,9 @@ export type DebugControllerInspectRequestedEvent = { selector: string, locator: string, }; +export type DebugControllerSetModeRequestedEvent = { + mode: string, +}; export type DebugControllerStateChangedEvent = { pageCount: number, }; @@ -732,6 +736,7 @@ export type DebugControllerCloseAllBrowsersResult = void; export interface DebugControllerEvents { 'inspectRequested': DebugControllerInspectRequestedEvent; + 'setModeRequested': DebugControllerSetModeRequestedEvent; 'stateChanged': DebugControllerStateChangedEvent; 'sourceChanged': DebugControllerSourceChangedEvent; 'paused': DebugControllerPausedEvent; diff --git a/packages/protocol/src/protocol.yml b/packages/protocol/src/protocol.yml index 75b3f19d40..aac64bdc50 100644 --- a/packages/protocol/src/protocol.yml +++ b/packages/protocol/src/protocol.yml @@ -763,6 +763,10 @@ DebugController: selector: string locator: string + setModeRequested: + parameters: + mode: string + stateChanged: parameters: pageCount: number diff --git a/packages/recorder/src/recorder.tsx b/packages/recorder/src/recorder.tsx index 7892791183..a6d1e0571b 100644 --- a/packages/recorder/src/recorder.tsx +++ b/packages/recorder/src/recorder.tsx @@ -116,20 +116,21 @@ export const Recorder: React.FC = ({ return
{ - window.dispatch({ event: 'setMode', params: { mode: mode === 'none' || mode === 'inspecting' ? 'recording' : 'none' } }); + window.dispatch({ event: 'setMode', params: { mode: mode === 'none' || mode === 'standby' || mode === 'inspecting' ? 'recording' : 'none' } }); }}>Record { const newMode = { - 'inspecting': 'none', + 'inspecting': 'standby', 'none': 'inspecting', + 'standby': 'inspecting', 'recording': 'recording-inspecting', 'recording-inspecting': 'recording', 'assertingText': 'recording-inspecting', }[mode]; window.dispatch({ event: 'setMode', params: { mode: newMode } }).catch(() => { }); }}>Pick locator - { + { window.dispatch({ event: 'setMode', params: { mode: mode === 'assertingText' ? 'recording' : 'assertingText' } }); }}>Assert diff --git a/packages/recorder/src/recorderTypes.ts b/packages/recorder/src/recorderTypes.ts index 0d4523d09f..144ff5fdbc 100644 --- a/packages/recorder/src/recorderTypes.ts +++ b/packages/recorder/src/recorderTypes.ts @@ -18,7 +18,7 @@ import type { Language } from '../../playwright-core/src/utils/isomorphic/locato export type Point = { x: number, y: number }; -export type Mode = 'inspecting' | 'recording' | 'none' | 'assertingText' | 'recording-inspecting'; +export type Mode = 'inspecting' | 'recording' | 'none' | 'assertingText' | 'recording-inspecting' | 'standby'; export type EventData = { event: 'clear' | 'resume' | 'step' | 'pause' | 'setMode' | 'selectorUpdated' | 'fileChanged'; From 16aee8b5d090eabfc451b1dbec56d6824c391abf Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Mon, 13 Nov 2023 16:56:27 -0800 Subject: [PATCH 045/491] fix(codegen): comment-out generated expects for library scripts (#28118) - reverts "fix(codegen): generate expect import for library (https://github.com/microsoft/playwright/pull/28107)"; - comments-out generated expects. --- .../src/server/recorder/javascript.ts | 8 ++++---- .../library/inspector/cli-codegen-javascript.spec.ts | 12 ++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/playwright-core/src/server/recorder/javascript.ts b/packages/playwright-core/src/server/recorder/javascript.ts index 71e60d6f7b..a68d3212fa 100644 --- a/packages/playwright-core/src/server/recorder/javascript.ts +++ b/packages/playwright-core/src/server/recorder/javascript.ts @@ -126,12 +126,12 @@ export class JavaScriptLanguageGenerator implements LanguageGenerator { case 'select': return `await ${subject}.${this._asLocator(action.selector)}.selectOption(${formatObject(action.options.length > 1 ? action.options : action.options[0])});`; case 'assertText': - return `await expect(${subject}.${this._asLocator(action.selector)}).${action.substring ? 'toContainText' : 'toHaveText'}(${quote(action.text)});`; + return `${this._isTest ? '' : '// '}await expect(${subject}.${this._asLocator(action.selector)}).${action.substring ? 'toContainText' : 'toHaveText'}(${quote(action.text)});`; case 'assertChecked': - return `await expect(${subject}.${this._asLocator(action.selector)})${action.checked ? '' : '.not'}.toBeChecked();`; + return `${this._isTest ? '' : '// '}await expect(${subject}.${this._asLocator(action.selector)})${action.checked ? '' : '.not'}.toBeChecked();`; case 'assertValue': { const assertion = action.value ? `toHaveValue(${quote(action.value)})` : `toBeEmpty()`; - return `await expect(${subject}.${this._asLocator(action.selector)}).${assertion};`; + return `${this._isTest ? '' : '// '}await expect(${subject}.${this._asLocator(action.selector)}).${assertion};`; } } } @@ -169,7 +169,7 @@ ${useText ? '\ntest.use(' + useText + ');\n' : ''} generateStandaloneHeader(options: LanguageGeneratorOptions): string { const formatter = new JavaScriptFormatter(); formatter.add(` - const { expect, ${options.browserName}${options.deviceName ? ', devices' : ''} } = require('@playwright/test'); + const { ${options.browserName}${options.deviceName ? ', devices' : ''} } = require('playwright'); (async () => { const browser = await ${options.browserName}.launch(${formatObjectOrVoid(options.launchOptions)}); diff --git a/tests/library/inspector/cli-codegen-javascript.spec.ts b/tests/library/inspector/cli-codegen-javascript.spec.ts index 198721bb06..f2c34899d8 100644 --- a/tests/library/inspector/cli-codegen-javascript.spec.ts +++ b/tests/library/inspector/cli-codegen-javascript.spec.ts @@ -26,7 +26,7 @@ const launchOptions = (channel: string) => { test('should print the correct imports and context options', async ({ browserName, channel, runCLI }) => { const cli = runCLI(['--target=javascript', emptyHTML]); - const expectedResult = `const { expect, ${browserName} } = require('@playwright/test'); + const expectedResult = `const { ${browserName} } = require('playwright'); (async () => { const browser = await ${browserName}.launch({ @@ -38,7 +38,7 @@ test('should print the correct imports and context options', async ({ browserNam test('should print the correct context options for custom settings', async ({ browserName, channel, runCLI }) => { const cli = runCLI(['--color-scheme=light', '--target=javascript', emptyHTML]); - const expectedResult = `const { expect, ${browserName} } = require('@playwright/test'); + const expectedResult = `const { ${browserName} } = require('playwright'); (async () => { const browser = await ${browserName}.launch({ @@ -55,7 +55,7 @@ test('should print the correct context options when using a device', async ({ br test.skip(browserName !== 'chromium'); const cli = runCLI(['--device=Pixel 2', '--target=javascript', emptyHTML]); - const expectedResult = `const { expect, chromium, devices } = require('@playwright/test'); + const expectedResult = `const { chromium, devices } = require('playwright'); (async () => { const browser = await chromium.launch({ @@ -71,7 +71,7 @@ test('should print the correct context options when using a device and additiona test.skip(browserName !== 'webkit'); const cli = runCLI(['--color-scheme=light', '--device=iPhone 11', '--target=javascript', emptyHTML]); - const expectedResult = `const { expect, webkit, devices } = require('@playwright/test'); + const expectedResult = `const { webkit, devices } = require('playwright'); (async () => { const browser = await webkit.launch({ @@ -91,7 +91,7 @@ test('should save the codegen output to a file if specified', async ({ browserNa }); await cli.waitForCleanExit(); const content = fs.readFileSync(tmpFile); - expect(content.toString()).toBe(`const { expect, ${browserName} } = require('@playwright/test'); + expect(content.toString()).toBe(`const { ${browserName} } = require('playwright'); (async () => { const browser = await ${browserName}.launch({ @@ -113,7 +113,7 @@ test('should print load/save storageState', async ({ browserName, channel, runCL const saveFileName = testInfo.outputPath('save.json'); await fs.promises.writeFile(loadFileName, JSON.stringify({ cookies: [], origins: [] }), 'utf8'); const cli = runCLI([`--load-storage=${loadFileName}`, `--save-storage=${saveFileName}`, '--target=javascript', emptyHTML]); - const expectedResult1 = `const { expect, ${browserName} } = require('@playwright/test'); + const expectedResult1 = `const { ${browserName} } = require('playwright'); (async () => { const browser = await ${browserName}.launch({ From bf4c315b0992a35cfcf12e862ed74ea56b71abab Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Mon, 13 Nov 2023 18:37:50 -0800 Subject: [PATCH 046/491] fix(types): explicit ExpectMatcherState type, optional Expect arg (#28119) Fixes #28035. --- packages/playwright/types/test.d.ts | 13 ++++--------- tests/playwright-test/expect.spec.ts | 9 ++++++++- utils/generate_types/overrides-test.d.ts | 13 ++++--------- 3 files changed, 16 insertions(+), 19 deletions(-) diff --git a/packages/playwright/types/test.d.ts b/packages/playwright/types/test.d.ts index 78d89b89d1..7e29ef87a2 100644 --- a/packages/playwright/types/test.d.ts +++ b/packages/playwright/types/test.d.ts @@ -5236,7 +5236,7 @@ export interface ExpectMatcherUtils { stringify(object: unknown, maxDepth?: number, maxWidth?: number): string; } -type State = { +export type ExpectMatcherState = { isNot: boolean; promise: 'rejects' | 'resolves' | ''; utils: ExpectMatcherUtils; @@ -5268,7 +5268,7 @@ type MakeMatchers = { rejects: MakeMatchers, any, ExtendedMatchers>; } & IfAny, SpecificMatchers & ToUserMatcherObject>; -export type Expect = { +export type Expect = { (actual: T, messageOrOptions?: string | { message?: string }): MakeMatchers; soft: (actual: T, messageOrOptions?: string | { message?: string }) => MakeMatchers; poll: (actual: () => T | Promise, messageOrOptions?: string | { message?: string, timeout?: number, intervals?: number[] }) => BaseMatchers, T> & { @@ -5277,18 +5277,13 @@ export type Expect = { */ not: BaseMatchers, T>; }; - extend MatcherReturnType | Promise>>(matchers: MoreMatchers): Expect; + extend MatcherReturnType | Promise>>(matchers: MoreMatchers): Expect; configure: (configuration: { message?: string, timeout?: number, soft?: boolean, }) => Expect; - getState(): { - expand?: boolean; - isNot?: boolean; - promise?: string; - utils: any; - }; + getState(): ExpectMatcherState; not: Omit; } & AsymmetricMatchers; diff --git a/tests/playwright-test/expect.spec.ts b/tests/playwright-test/expect.spec.ts index 0167dd0e50..f725711943 100644 --- a/tests/playwright-test/expect.spec.ts +++ b/tests/playwright-test/expect.spec.ts @@ -713,7 +713,7 @@ test('should chain expect matchers and expose matcher utils (TSC)', async ({ run const result = await runTSC({ 'a.spec.ts': ` import { test, expect as baseExpect } from '@playwright/test'; - import type { Page, Locator } from '@playwright/test'; + import type { Page, Locator, ExpectMatcherState, Expect } from '@playwright/test'; function callLogText(log: string[] | undefined): string { if (!log) @@ -721,8 +721,15 @@ test('should chain expect matchers and expose matcher utils (TSC)', async ({ run return log.join('\\n'); } + const dummy: Expect = baseExpect; + const dummy2: Expect<{}> = baseExpect; + const expect = baseExpect.extend({ async toHaveAmount(locator: Locator, expected: string, options?: { timeout?: number }) { + // Make sure "this" is inferred as ExpectMatcherState. + const self: ExpectMatcherState = this; + const self2: ReturnType = self; + const baseAmount = locator.locator('.base-amount'); let pass: boolean; diff --git a/utils/generate_types/overrides-test.d.ts b/utils/generate_types/overrides-test.d.ts index ac89c360a4..5a8c5e5135 100644 --- a/utils/generate_types/overrides-test.d.ts +++ b/utils/generate_types/overrides-test.d.ts @@ -370,7 +370,7 @@ export interface ExpectMatcherUtils { stringify(object: unknown, maxDepth?: number, maxWidth?: number): string; } -type State = { +export type ExpectMatcherState = { isNot: boolean; promise: 'rejects' | 'resolves' | ''; utils: ExpectMatcherUtils; @@ -402,7 +402,7 @@ type MakeMatchers = { rejects: MakeMatchers, any, ExtendedMatchers>; } & IfAny, SpecificMatchers & ToUserMatcherObject>; -export type Expect = { +export type Expect = { (actual: T, messageOrOptions?: string | { message?: string }): MakeMatchers; soft: (actual: T, messageOrOptions?: string | { message?: string }) => MakeMatchers; poll: (actual: () => T | Promise, messageOrOptions?: string | { message?: string, timeout?: number, intervals?: number[] }) => BaseMatchers, T> & { @@ -411,18 +411,13 @@ export type Expect = { */ not: BaseMatchers, T>; }; - extend MatcherReturnType | Promise>>(matchers: MoreMatchers): Expect; + extend MatcherReturnType | Promise>>(matchers: MoreMatchers): Expect; configure: (configuration: { message?: string, timeout?: number, soft?: boolean, }) => Expect; - getState(): { - expand?: boolean; - isNot?: boolean; - promise?: string; - utils: any; - }; + getState(): ExpectMatcherState; not: Omit; } & AsymmetricMatchers; From 78293053b4ec88458c7a00c40dd729a6c91a40c2 Mon Sep 17 00:00:00 2001 From: Playwright Service <89237858+playwrightmachine@users.noreply.github.com> Date: Tue, 14 Nov 2023 05:18:05 -0800 Subject: [PATCH 047/491] feat(chromium-tip-of-tree): roll to r1168 (#28128) 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 7a7217fb55..26426381ed 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -15,9 +15,9 @@ }, { "name": "chromium-tip-of-tree", - "revision": "1167", + "revision": "1168", "installByDefault": false, - "browserVersion": "121.0.6117.0" + "browserVersion": "121.0.6127.0" }, { "name": "firefox", From 60a37f37efc543038890865016ef61f077125c96 Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Tue, 14 Nov 2023 08:13:29 -0800 Subject: [PATCH 048/491] chore: allow tabbing from codemirror locator editor (#28116) --- .../src/server/injected/recorder.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/playwright-core/src/server/injected/recorder.ts b/packages/playwright-core/src/server/injected/recorder.ts index 1ff89efe39..03ac6d92b5 100644 --- a/packages/playwright-core/src/server/injected/recorder.ts +++ b/packages/playwright-core/src/server/injected/recorder.ts @@ -499,6 +499,12 @@ class TextAssertionTool implements RecorderTool { consumeEvent(event); } + onMouseDown(event: MouseEvent) { + const target = this._recorder.deepEventTarget(event); + if (target.nodeName === 'SELECT') + event.preventDefault(); + } + onMouseMove(event: MouseEvent) { if (this._dialogElement) return; @@ -520,7 +526,7 @@ class TextAssertionTool implements RecorderTool { const target = this._hoverHighlight?.elements[0]; if (!target) return null; - if (target.nodeName === 'INPUT' || target.nodeName === 'TEXTAREA') { + if (target.nodeName === 'INPUT' || target.nodeName === 'TEXTAREA' || target.nodeName === 'SELECT') { const { selector } = generateSelector(this._recorder.injectedScript, target, { testIdAttributeName: this._recorder.state.testIdAttributeName }); if (target.nodeName === 'INPUT' && ['checkbox', 'radio'].includes((target as HTMLInputElement).type.toLowerCase())) { return { @@ -535,7 +541,7 @@ class TextAssertionTool implements RecorderTool { name: 'assertValue', selector, signals: [], - value: (target as HTMLInputElement).value, + value: (target as (HTMLInputElement | HTMLSelectElement)).value, }; } } else { @@ -612,6 +618,10 @@ class TextAssertionTool implements RecorderTool { lineNumbers: false, lineWrapping: true, }); + cm.on('keydown', (_, event) => { + if (event.key === 'Tab') + (event as any).codemirrorIgnore = true; + }); cm.on('change', () => { if (this._action) { const selector = locatorOrSelectorAsSelector(this._recorder.state.language, cm.getValue(), this._recorder.state.testIdAttributeName); From 2ac1cde879b667d0373e4b445ccdd6d33a5ed846 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Tue, 14 Nov 2023 18:07:27 +0100 Subject: [PATCH 049/491] fix(recorder): resize of assert overlay textarea (#28137) --- packages/playwright-core/src/server/injected/highlight.css | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/playwright-core/src/server/injected/highlight.css b/packages/playwright-core/src/server/injected/highlight.css index c159615ac8..7c7105c131 100644 --- a/packages/playwright-core/src/server/injected/highlight.css +++ b/packages/playwright-core/src/server/injected/highlight.css @@ -213,6 +213,7 @@ textarea.text-editor { margin: 6px; color: #333; outline: 1px solid transparent !important; + resize: none; } textarea.text-editor.does-not-match { From ec2c7024b6608829c78dc50d3043185b4ad00370 Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Tue, 14 Nov 2023 10:18:04 -0800 Subject: [PATCH 050/491] docs: fix ignoreCase description (#28121) --- docs/src/api/class-locatorassertions.md | 2 +- packages/playwright/types/test.d.ts | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/src/api/class-locatorassertions.md b/docs/src/api/class-locatorassertions.md index 11244c0867..20e7c3f0fe 100644 --- a/docs/src/api/class-locatorassertions.md +++ b/docs/src/api/class-locatorassertions.md @@ -1156,7 +1156,7 @@ Expected attribute value. ### option: LocatorAssertions.toHaveAttribute.timeout = %%-csharp-java-python-assertions-timeout-%% * since: v1.18 -### option: LocatorAssertions.toHaveAttribute.ignoreCase = %%-js-assertions-timeout-%% +### option: LocatorAssertions.toHaveAttribute.ignoreCase * since: v1.40 - `ignoreCase` <[boolean]> diff --git a/packages/playwright/types/test.d.ts b/packages/playwright/types/test.d.ts index 7e29ef87a2..bbf9da8314 100644 --- a/packages/playwright/types/test.d.ts +++ b/packages/playwright/types/test.d.ts @@ -5709,8 +5709,6 @@ interface LocatorAssertions { */ toHaveAttribute(name: string, value: string|RegExp, options?: { /** - * Time to retry the assertion for in milliseconds. Defaults to `timeout` in `TestConfig.expect`. - * * Whether to perform case-insensitive match. `ignoreCase` option takes precedence over the corresponding regular * expression flag if specified. */ From 03031a6d2c78f79385b5180cc3a2ca38b5c4389a Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Tue, 14 Nov 2023 10:18:18 -0800 Subject: [PATCH 051/491] chore: update browser patches to d8f2e2984 (#28139) --- browser_patches/firefox/UPSTREAM_CONFIG.sh | 2 +- browser_patches/firefox/juggler/Helper.js | 1 - .../firefox/juggler/NetworkObserver.js | 1 - .../firefox/juggler/TargetRegistry.js | 1 - .../firefox/juggler/components/Juggler.js | 1 - .../juggler/content/JugglerFrameChild.jsm | 1 - .../firefox/juggler/content/PageAgent.js | 5 +- .../firefox/juggler/content/Runtime.js | 19 +- .../firefox/juggler/content/main.js | 1 - .../juggler/protocol/BrowserHandler.js | 1 - .../firefox/juggler/protocol/PageHandler.js | 1 - .../firefox/patches/bootstrap.diff | 322 ++-- browser_patches/webkit/UPSTREAM_CONFIG.sh | 2 +- browser_patches/webkit/patches/bootstrap.diff | 1670 ++++++++--------- browser_patches/webkit/pw_run.sh | 20 +- 15 files changed, 1008 insertions(+), 1040 deletions(-) diff --git a/browser_patches/firefox/UPSTREAM_CONFIG.sh b/browser_patches/firefox/UPSTREAM_CONFIG.sh index 55da63f51e..1d0ae8eca7 100644 --- a/browser_patches/firefox/UPSTREAM_CONFIG.sh +++ b/browser_patches/firefox/UPSTREAM_CONFIG.sh @@ -1,3 +1,3 @@ REMOTE_URL="https://github.com/mozilla/gecko-dev" BASE_BRANCH="release" -BASE_REVISION="f5bc1abb4f0841558f7531e0c15a7577d23ed21c" +BASE_REVISION="bf57fe91c49f319e7f65636ed223e5f7b4b7738a" diff --git a/browser_patches/firefox/juggler/Helper.js b/browser_patches/firefox/juggler/Helper.js index 797acd4627..9572aa37ad 100644 --- a/browser_patches/firefox/juggler/Helper.js +++ b/browser_patches/firefox/juggler/Helper.js @@ -3,7 +3,6 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ const uuidGen = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator); -const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm"); class Helper { decorateAsEventEmitter(objectToDecorate) { diff --git a/browser_patches/firefox/juggler/NetworkObserver.js b/browser_patches/firefox/juggler/NetworkObserver.js index c21c9effbe..a8a8321974 100644 --- a/browser_patches/firefox/juggler/NetworkObserver.js +++ b/browser_patches/firefox/juggler/NetworkObserver.js @@ -5,7 +5,6 @@ "use strict"; const {Helper} = ChromeUtils.import('chrome://juggler/content/Helper.js'); -const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm"); const {NetUtil} = ChromeUtils.import('resource://gre/modules/NetUtil.jsm'); const { ChannelEventSinkFactory } = ChromeUtils.import("chrome://remote/content/cdp/observers/ChannelEventSink.jsm"); diff --git a/browser_patches/firefox/juggler/TargetRegistry.js b/browser_patches/firefox/juggler/TargetRegistry.js index 479d04d144..25be3c67af 100644 --- a/browser_patches/firefox/juggler/TargetRegistry.js +++ b/browser_patches/firefox/juggler/TargetRegistry.js @@ -4,7 +4,6 @@ const {Helper} = ChromeUtils.import('chrome://juggler/content/Helper.js'); const {SimpleChannel} = ChromeUtils.import('chrome://juggler/content/SimpleChannel.js'); -const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm"); const {Preferences} = ChromeUtils.import("resource://gre/modules/Preferences.jsm"); const {ContextualIdentityService} = ChromeUtils.import("resource://gre/modules/ContextualIdentityService.jsm"); const {NetUtil} = ChromeUtils.import('resource://gre/modules/NetUtil.jsm'); diff --git a/browser_patches/firefox/juggler/components/Juggler.js b/browser_patches/firefox/juggler/components/Juggler.js index a3740acc45..d919935fb5 100644 --- a/browser_patches/firefox/juggler/components/Juggler.js +++ b/browser_patches/firefox/juggler/components/Juggler.js @@ -6,7 +6,6 @@ var EXPORTED_SYMBOLS = ["Juggler", "JugglerFactory"]; const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm"); const {ComponentUtils} = ChromeUtils.import("resource://gre/modules/ComponentUtils.jsm"); -const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm"); const {Dispatcher} = ChromeUtils.import("chrome://juggler/content/protocol/Dispatcher.js"); const {BrowserHandler} = ChromeUtils.import("chrome://juggler/content/protocol/BrowserHandler.js"); const {NetworkObserver} = ChromeUtils.import("chrome://juggler/content/NetworkObserver.js"); diff --git a/browser_patches/firefox/juggler/content/JugglerFrameChild.jsm b/browser_patches/firefox/juggler/content/JugglerFrameChild.jsm index 80b532c6b9..529f6d3b58 100644 --- a/browser_patches/firefox/juggler/content/JugglerFrameChild.jsm +++ b/browser_patches/firefox/juggler/content/JugglerFrameChild.jsm @@ -1,7 +1,6 @@ "use strict"; const { Helper } = ChromeUtils.import('chrome://juggler/content/Helper.js'); -const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); const { initialize } = ChromeUtils.import('chrome://juggler/content/content/main.js'); const Ci = Components.interfaces; diff --git a/browser_patches/firefox/juggler/content/PageAgent.js b/browser_patches/firefox/juggler/content/PageAgent.js index f3a3e745c2..614d5b5101 100644 --- a/browser_patches/firefox/juggler/content/PageAgent.js +++ b/browser_patches/firefox/juggler/content/PageAgent.js @@ -3,7 +3,6 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; -const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm"); const Ci = Components.interfaces; const Cr = Components.results; @@ -462,6 +461,10 @@ class PageAgent { } async _dispatchKeyEvent({type, keyCode, code, key, repeat, location, text}) { + if (code === 'OSLeft') + code = 'MetaLeft'; + else if (code === 'OSRight') + code = 'MetaRight'; const frame = this._frameTree.mainFrame(); const tip = frame.textInputProcessor(); if (key === 'Meta' && Services.appinfo.OS !== 'Darwin') diff --git a/browser_patches/firefox/juggler/content/Runtime.js b/browser_patches/firefox/juggler/content/Runtime.js index 3a132ae542..a5fa7a03dd 100644 --- a/browser_patches/firefox/juggler/content/Runtime.js +++ b/browser_patches/firefox/juggler/content/Runtime.js @@ -63,7 +63,6 @@ class Runtime { if (isWorker) { this._registerWorkerConsoleHandler(); } else { - const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm"); this._registerConsoleServiceListener(Services); this._registerConsoleAPIListener(Services); } @@ -240,8 +239,8 @@ class Runtime { return {success: true, obj: obj.promiseValue}; if (obj.promiseState === 'rejected') { const debuggee = executionContext._debuggee; - exceptionDetails.text = debuggee.executeInGlobalWithBindings('e.message', {e: obj.promiseReason}).return; - exceptionDetails.stack = debuggee.executeInGlobalWithBindings('e.stack', {e: obj.promiseReason}).return; + exceptionDetails.text = debuggee.executeInGlobalWithBindings('e.message', {e: obj.promiseReason}, {useInnerBindings: true}).return; + exceptionDetails.stack = debuggee.executeInGlobalWithBindings('e.stack', {e: obj.promiseReason}, {useInnerBindings: true}).return; return {success: false, obj: null}; } let resolve, reject; @@ -268,8 +267,8 @@ class Runtime { return; }; const debuggee = pendingPromise.executionContext._debuggee; - pendingPromise.exceptionDetails.text = debuggee.executeInGlobalWithBindings('e.message', {e: obj.promiseReason}).return; - pendingPromise.exceptionDetails.stack = debuggee.executeInGlobalWithBindings('e.stack', {e: obj.promiseReason}).return; + pendingPromise.exceptionDetails.text = debuggee.executeInGlobalWithBindings('e.message', {e: obj.promiseReason}, {useInnerBindings: true}).return; + pendingPromise.exceptionDetails.stack = debuggee.executeInGlobalWithBindings('e.stack', {e: obj.promiseReason}, {useInnerBindings: true}).return; pendingPromise.resolve({success: false, obj: null}); } @@ -442,7 +441,7 @@ class ExecutionContext { _instanceOf(debuggerObj, rawObj, className) { if (this._domWindow) return rawObj instanceof this._domWindow[className]; - return this._debuggee.executeInGlobalWithBindings('o instanceof this[className]', {o: debuggerObj, className: this._debuggee.makeDebuggeeValue(className)}).return; + return this._debuggee.executeInGlobalWithBindings('o instanceof this[className]', {o: debuggerObj, className: this._debuggee.makeDebuggeeValue(className)}, {useInnerBindings: true}).return; } _createRemoteObject(debuggerObj) { @@ -532,7 +531,7 @@ class ExecutionContext { } _serialize(obj) { - const result = this._debuggee.executeInGlobalWithBindings('stringify(e)', {e: obj, stringify: this._jsonStringifyObject}); + const result = this._debuggee.executeInGlobalWithBindings('stringify(e)', {e: obj, stringify: this._jsonStringifyObject}, {useInnerBindings: true}); if (result.throw) throw new Error('Object is not serializable'); return result.return === undefined ? undefined : JSON.parse(result.return); @@ -564,9 +563,9 @@ class ExecutionContext { if (!completionValue) throw new Error('evaluation terminated'); if (completionValue.throw) { - if (this._debuggee.executeInGlobalWithBindings('e instanceof Error', {e: completionValue.throw}).return) { - exceptionDetails.text = this._debuggee.executeInGlobalWithBindings('e.message', {e: completionValue.throw}).return; - exceptionDetails.stack = this._debuggee.executeInGlobalWithBindings('e.stack', {e: completionValue.throw}).return; + if (this._debuggee.executeInGlobalWithBindings('e instanceof Error', {e: completionValue.throw}, {useInnerBindings: true}).return) { + exceptionDetails.text = this._debuggee.executeInGlobalWithBindings('e.message', {e: completionValue.throw}, {useInnerBindings: true}).return; + exceptionDetails.stack = this._debuggee.executeInGlobalWithBindings('e.stack', {e: completionValue.throw}, {useInnerBindings: true}).return; } else { exceptionDetails.value = this._serialize(completionValue.throw); } diff --git a/browser_patches/firefox/juggler/content/main.js b/browser_patches/firefox/juggler/content/main.js index eb971b5d4d..4b891b3a76 100644 --- a/browser_patches/firefox/juggler/content/main.js +++ b/browser_patches/firefox/juggler/content/main.js @@ -2,7 +2,6 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm"); const {Helper} = ChromeUtils.import('chrome://juggler/content/Helper.js'); const {FrameTree} = ChromeUtils.import('chrome://juggler/content/content/FrameTree.js'); const {SimpleChannel} = ChromeUtils.import('chrome://juggler/content/SimpleChannel.js'); diff --git a/browser_patches/firefox/juggler/protocol/BrowserHandler.js b/browser_patches/firefox/juggler/protocol/BrowserHandler.js index df32c10bb6..7bec58108a 100644 --- a/browser_patches/firefox/juggler/protocol/BrowserHandler.js +++ b/browser_patches/firefox/juggler/protocol/BrowserHandler.js @@ -5,7 +5,6 @@ "use strict"; const {AddonManager} = ChromeUtils.import("resource://gre/modules/AddonManager.jsm"); -const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm"); const {TargetRegistry} = ChromeUtils.import("chrome://juggler/content/TargetRegistry.js"); const {Helper} = ChromeUtils.import('chrome://juggler/content/Helper.js'); const {PageHandler} = ChromeUtils.import("chrome://juggler/content/protocol/PageHandler.js"); diff --git a/browser_patches/firefox/juggler/protocol/PageHandler.js b/browser_patches/firefox/juggler/protocol/PageHandler.js index 7d148fa113..d526e93caa 100644 --- a/browser_patches/firefox/juggler/protocol/PageHandler.js +++ b/browser_patches/firefox/juggler/protocol/PageHandler.js @@ -5,7 +5,6 @@ "use strict"; const {Helper, EventWatcher} = ChromeUtils.import('chrome://juggler/content/Helper.js'); -const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm"); const {NetUtil} = ChromeUtils.import('resource://gre/modules/NetUtil.jsm'); const {NetworkObserver, PageNetwork} = ChromeUtils.import('chrome://juggler/content/NetworkObserver.js'); const {PageTarget} = ChromeUtils.import('chrome://juggler/content/TargetRegistry.js'); diff --git a/browser_patches/firefox/patches/bootstrap.diff b/browser_patches/firefox/patches/bootstrap.diff index 4881645247..a16414d2d7 100644 --- a/browser_patches/firefox/patches/bootstrap.diff +++ b/browser_patches/firefox/patches/bootstrap.diff @@ -26,10 +26,10 @@ index 1886621c373fe1fd5ff54092afc4c64e9ca9a8fd..a0febf72885410b45227171c63e823ec + readonly attribute boolean isUpdatePendingForJugglerAccessibility; }; diff --git a/accessible/xpcom/xpcAccessibleDocument.cpp b/accessible/xpcom/xpcAccessibleDocument.cpp -index 94b04cf2f653a4b7274897eb6051dccdd4fe8404..fd6775e71c3d7c348ab2f1c5b4ea2dc4c18f787c 100644 +index d616e476b2149de5703077563680905e40db0459..7a8a48d5e7303a298a3e2e9fdf64558b3cdbe654 100644 --- a/accessible/xpcom/xpcAccessibleDocument.cpp +++ b/accessible/xpcom/xpcAccessibleDocument.cpp -@@ -132,6 +132,13 @@ xpcAccessibleDocument::GetChildDocumentAt(uint32_t aIndex, +@@ -131,6 +131,13 @@ xpcAccessibleDocument::GetChildDocumentAt(uint32_t aIndex, return *aDocument ? NS_OK : NS_ERROR_INVALID_ARG; } @@ -106,10 +106,10 @@ index 93e7dbae2281680827abb482f6f34e90076f3b3a..f7045a29c6ed50876775abe2d45a47df gmp-clearkey/0.1/manifest.json i686/gmp-clearkey/0.1/manifest.json diff --git a/browser/installer/package-manifest.in b/browser/installer/package-manifest.in -index 41d13e23c4f4160e469aa81a632b5ed336130edb..3d690727ba3394485093f062093f26b2739ee497 100644 +index 5296d2b4b195d6c5d3f61aa43ea8d1d2fdad053f..f60e585d11167579418592f702788fb0ff6d5db5 100644 --- a/browser/installer/package-manifest.in +++ b/browser/installer/package-manifest.in -@@ -203,6 +203,9 @@ +@@ -197,6 +197,9 @@ @RESPATH@/chrome/remote.manifest #endif @@ -167,10 +167,10 @@ index 4236ec2921bd57c58cfffdf1cdcf509d76fca3db..23d0cb1f06bb8c7a1cac8fcec94a99fb const transportProvider = { setListener(upgradeListener) { diff --git a/docshell/base/BrowsingContext.cpp b/docshell/base/BrowsingContext.cpp -index db232f63e48e2950a997baac63f2b3cc069ddb18..3d2088308789276ad8e940dda4ebf3800f487e3a 100644 +index ebf183695a5473c4b01fd2a27682128c9f678349..1180f2334ac46b3a824b2d541e5f7d9dc9f81895 100644 --- a/docshell/base/BrowsingContext.cpp +++ b/docshell/base/BrowsingContext.cpp -@@ -113,6 +113,20 @@ struct ParamTraits +@@ -114,6 +114,20 @@ struct ParamTraits mozilla::dom::PrefersColorSchemeOverride::None, mozilla::dom::PrefersColorSchemeOverride::EndGuard_> {}; @@ -191,7 +191,7 @@ index db232f63e48e2950a997baac63f2b3cc069ddb18..3d2088308789276ad8e940dda4ebf380 template <> struct ParamTraits : public ContiguousEnumSerializer< -@@ -2746,6 +2760,40 @@ void BrowsingContext::DidSet(FieldIndex, +@@ -2781,6 +2795,40 @@ void BrowsingContext::DidSet(FieldIndex, PresContextAffectingFieldChanged(); } @@ -233,10 +233,10 @@ index db232f63e48e2950a997baac63f2b3cc069ddb18..3d2088308789276ad8e940dda4ebf380 nsString&& aOldValue) { MOZ_ASSERT(IsTop()); diff --git a/docshell/base/BrowsingContext.h b/docshell/base/BrowsingContext.h -index 4c245337b7db24f94011ad75fa2a3b32c9a3574c..946b4592794499455b7e2d7d208b7ca43242a414 100644 +index 3c8a11e5dcbb37ad915d63bbd5ad9fa11b3dcac8..92dc6aa8961ba35a9c0c2943f7201be55c29b9da 100644 --- a/docshell/base/BrowsingContext.h +++ b/docshell/base/BrowsingContext.h -@@ -199,10 +199,10 @@ struct EmbedderColorSchemes { +@@ -200,10 +200,10 @@ struct EmbedderColorSchemes { FIELD(GVInaudibleAutoplayRequestStatus, GVAutoplayRequestStatus) \ /* ScreenOrientation-related APIs */ \ FIELD(CurrentOrientationAngle, float) \ @@ -249,7 +249,7 @@ index 4c245337b7db24f94011ad75fa2a3b32c9a3574c..946b4592794499455b7e2d7d208b7ca4 FIELD(EmbedderElementType, Maybe) \ FIELD(MessageManagerGroup, nsString) \ FIELD(MaxTouchPointsOverride, uint8_t) \ -@@ -240,6 +240,10 @@ struct EmbedderColorSchemes { +@@ -241,6 +241,10 @@ struct EmbedderColorSchemes { * embedder element. */ \ FIELD(EmbedderColorSchemes, EmbedderColorSchemes) \ FIELD(DisplayMode, dom::DisplayMode) \ @@ -260,7 +260,7 @@ index 4c245337b7db24f94011ad75fa2a3b32c9a3574c..946b4592794499455b7e2d7d208b7ca4 /* The number of entries added to the session history because of this \ * browsing context. */ \ FIELD(HistoryEntryCount, uint32_t) \ -@@ -924,6 +928,14 @@ class BrowsingContext : public nsILoadContext, public nsWrapperCache { +@@ -919,6 +923,14 @@ class BrowsingContext : public nsILoadContext, public nsWrapperCache { return GetPrefersColorSchemeOverride(); } @@ -275,7 +275,7 @@ index 4c245337b7db24f94011ad75fa2a3b32c9a3574c..946b4592794499455b7e2d7d208b7ca4 bool IsInBFCache() const; bool AllowJavascript() const { return GetAllowJavascript(); } -@@ -1085,6 +1097,23 @@ class BrowsingContext : public nsILoadContext, public nsWrapperCache { +@@ -1083,6 +1095,23 @@ class BrowsingContext : public nsILoadContext, public nsWrapperCache { void WalkPresContexts(Callback&&); void PresContextAffectingFieldChanged(); @@ -300,10 +300,10 @@ index 4c245337b7db24f94011ad75fa2a3b32c9a3574c..946b4592794499455b7e2d7d208b7ca4 bool CanSet(FieldIndex, bool, ContentParent*) { diff --git a/docshell/base/CanonicalBrowsingContext.cpp b/docshell/base/CanonicalBrowsingContext.cpp -index 42a6700afa0e9ff3c5161b1d18f327b3d9ed76f8..3b83cafc2e549fe3affad3547a1b145f821a888d 100644 +index 2f383a90e6600e051bd5ade5c131b4f54f14eb0f..73c17acec9c187dba8250659fede89ce6360d0dc 100644 --- a/docshell/base/CanonicalBrowsingContext.cpp +++ b/docshell/base/CanonicalBrowsingContext.cpp -@@ -1456,6 +1456,12 @@ void CanonicalBrowsingContext::LoadURI(nsIURI* aURI, +@@ -1467,6 +1467,12 @@ void CanonicalBrowsingContext::LoadURI(nsIURI* aURI, return; } @@ -317,7 +317,7 @@ index 42a6700afa0e9ff3c5161b1d18f327b3d9ed76f8..3b83cafc2e549fe3affad3547a1b145f } diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp -index fb1b70fbcff37a233a4396a768358a36e773ddc3..c1c654f99462f2868607729c9126531eac3d5da9 100644 +index 85b49f3d8d380275125ff5bff205b66a587d0071..2dbd5d543a0e688beba81d7ed58e779e38e9e2f6 100644 --- a/docshell/base/nsDocShell.cpp +++ b/docshell/base/nsDocShell.cpp @@ -15,6 +15,12 @@ @@ -357,15 +357,15 @@ index fb1b70fbcff37a233a4396a768358a36e773ddc3..c1c654f99462f2868607729c9126531e #include "nsIDocumentLoaderFactory.h" #include "nsIDOMWindow.h" #include "nsIEditingSession.h" -@@ -207,6 +216,7 @@ - #include "nsFocusManager.h" - #include "nsGlobalWindow.h" +@@ -208,6 +217,7 @@ + #include "nsGlobalWindowInner.h" + #include "nsGlobalWindowOuter.h" #include "nsJSEnvironment.h" +#include "nsJSUtils.h" #include "nsNetCID.h" #include "nsNetUtil.h" #include "nsObjectLoadingContent.h" -@@ -349,6 +359,14 @@ nsDocShell::nsDocShell(BrowsingContext* aBrowsingContext, +@@ -350,6 +360,14 @@ nsDocShell::nsDocShell(BrowsingContext* aBrowsingContext, mAllowDNSPrefetch(true), mAllowWindowControl(true), mCSSErrorReportingEnabled(false), @@ -380,7 +380,7 @@ index fb1b70fbcff37a233a4396a768358a36e773ddc3..c1c654f99462f2868607729c9126531e mAllowAuth(mItemType == typeContent), mAllowKeywordFixup(false), mDisableMetaRefreshWhenInactive(false), -@@ -3197,6 +3215,234 @@ nsDocShell::GetMessageManager(ContentFrameMessageManager** aMessageManager) { +@@ -3203,6 +3221,234 @@ nsDocShell::GetMessageManager(ContentFrameMessageManager** aMessageManager) { return NS_OK; } @@ -615,7 +615,7 @@ index fb1b70fbcff37a233a4396a768358a36e773ddc3..c1c654f99462f2868607729c9126531e NS_IMETHODIMP nsDocShell::GetIsNavigating(bool* aOut) { *aOut = mIsNavigating; -@@ -4876,7 +5122,7 @@ nsDocShell::GetVisibility(bool* aVisibility) { +@@ -4894,7 +5140,7 @@ nsDocShell::GetVisibility(bool* aVisibility) { } void nsDocShell::ActivenessMaybeChanged() { @@ -624,7 +624,7 @@ index fb1b70fbcff37a233a4396a768358a36e773ddc3..c1c654f99462f2868607729c9126531e if (RefPtr presShell = GetPresShell()) { presShell->ActivenessMaybeChanged(); } -@@ -6809,6 +7055,10 @@ bool nsDocShell::CanSavePresentation(uint32_t aLoadType, +@@ -6829,6 +7075,10 @@ bool nsDocShell::CanSavePresentation(uint32_t aLoadType, return false; // no entry to save into } @@ -635,7 +635,7 @@ index fb1b70fbcff37a233a4396a768358a36e773ddc3..c1c654f99462f2868607729c9126531e MOZ_ASSERT(!mozilla::SessionHistoryInParent(), "mOSHE cannot be non-null with SHIP"); nsCOMPtr viewer = mOSHE->GetContentViewer(); -@@ -8590,6 +8840,12 @@ nsresult nsDocShell::PerformRetargeting(nsDocShellLoadState* aLoadState) { +@@ -8613,6 +8863,12 @@ nsresult nsDocShell::PerformRetargeting(nsDocShellLoadState* aLoadState) { true, // aForceNoOpener getter_AddRefs(newBC)); MOZ_ASSERT(!newBC); @@ -648,7 +648,7 @@ index fb1b70fbcff37a233a4396a768358a36e773ddc3..c1c654f99462f2868607729c9126531e return rv; } -@@ -9665,6 +9921,16 @@ nsresult nsDocShell::InternalLoad(nsDocShellLoadState* aLoadState, +@@ -9688,6 +9944,16 @@ nsresult nsDocShell::InternalLoad(nsDocShellLoadState* aLoadState, nsINetworkPredictor::PREDICT_LOAD, attrs, nullptr); nsCOMPtr req; @@ -665,7 +665,7 @@ index fb1b70fbcff37a233a4396a768358a36e773ddc3..c1c654f99462f2868607729c9126531e rv = DoURILoad(aLoadState, aCacheKey, getter_AddRefs(req)); if (NS_SUCCEEDED(rv)) { -@@ -12825,6 +13091,9 @@ class OnLinkClickEvent : public Runnable { +@@ -12861,6 +13127,9 @@ class OnLinkClickEvent : public Runnable { mHandler->OnLinkClickSync(mContent, mLoadState, mNoOpenerImplied, mTriggeringPrincipal); } @@ -675,7 +675,7 @@ index fb1b70fbcff37a233a4396a768358a36e773ddc3..c1c654f99462f2868607729c9126531e return NS_OK; } -@@ -12909,6 +13178,8 @@ nsresult nsDocShell::OnLinkClick( +@@ -12945,6 +13214,8 @@ nsresult nsDocShell::OnLinkClick( nsCOMPtr ev = new OnLinkClickEvent(this, aContent, loadState, noOpenerImplied, aIsTrusted, aTriggeringPrincipal); @@ -685,7 +685,7 @@ index fb1b70fbcff37a233a4396a768358a36e773ddc3..c1c654f99462f2868607729c9126531e } diff --git a/docshell/base/nsDocShell.h b/docshell/base/nsDocShell.h -index 0f360bf1f5f2e9067f42d270b4fb6745116a0ee2..8eee7e4d1a287b38d2d2aa1af1ac719a6c7c940b 100644 +index 21cd7c944b391bf0333c7bdc815200db33ef0afe..aa8d79b0d3bd34419c5d625678f3cd1231e2b46f 100644 --- a/docshell/base/nsDocShell.h +++ b/docshell/base/nsDocShell.h @@ -16,6 +16,7 @@ @@ -729,7 +729,7 @@ index 0f360bf1f5f2e9067f42d270b4fb6745116a0ee2..8eee7e4d1a287b38d2d2aa1af1ac719a // Handles retrieval of subframe session history for nsDocShell::LoadURI. If a // load is requested in a subframe of the current DocShell, the subframe // loadType may need to reflect the loadType of the parent document, or in -@@ -1323,6 +1336,17 @@ class nsDocShell final : public nsDocLoader, +@@ -1327,6 +1340,17 @@ class nsDocShell final : public nsDocLoader, bool mAllowDNSPrefetch : 1; bool mAllowWindowControl : 1; bool mCSSErrorReportingEnabled : 1; @@ -804,10 +804,10 @@ index 68f32e968c7e1bc1d0b2b2894320a177a9ae44d2..9e61465ffad927d7b3e972f753940196 * This attempts to save any applicable layout history state (like * scroll position) in the nsISHEntry. This is normally done diff --git a/dom/base/Document.cpp b/dom/base/Document.cpp -index b8d736619d4ea457b7db95d1815bd85697475133..d128299ab7a5e7f8fb00474571805361df0e00d2 100644 +index e8788de4fff443ee3dfc0830b759ba5eadd24591..ee7556aa27ea65af6dd0997cbe7fdef376915ed8 100644 --- a/dom/base/Document.cpp +++ b/dom/base/Document.cpp -@@ -3682,6 +3682,9 @@ void Document::SendToConsole(nsCOMArray& aMessages) { +@@ -3674,6 +3674,9 @@ void Document::SendToConsole(nsCOMArray& aMessages) { } void Document::ApplySettingsFromCSP(bool aSpeculative) { @@ -817,7 +817,7 @@ index b8d736619d4ea457b7db95d1815bd85697475133..d128299ab7a5e7f8fb00474571805361 nsresult rv = NS_OK; if (!aSpeculative) { // 1) apply settings from regular CSP -@@ -3739,6 +3742,11 @@ nsresult Document::InitCSP(nsIChannel* aChannel) { +@@ -3731,6 +3734,11 @@ nsresult Document::InitCSP(nsIChannel* aChannel) { MOZ_ASSERT(!mScriptGlobalObject, "CSP must be initialized before mScriptGlobalObject is set!"); @@ -829,7 +829,7 @@ index b8d736619d4ea457b7db95d1815bd85697475133..d128299ab7a5e7f8fb00474571805361 // If this is a data document - no need to set CSP. if (mLoadedAsData) { return NS_OK; -@@ -4578,6 +4586,10 @@ bool Document::HasFocus(ErrorResult& rv) const { +@@ -4568,6 +4576,10 @@ bool Document::HasFocus(ErrorResult& rv) const { return false; } @@ -840,7 +840,7 @@ index b8d736619d4ea457b7db95d1815bd85697475133..d128299ab7a5e7f8fb00474571805361 if (!fm->IsInActiveWindow(bc)) { return false; } -@@ -18471,6 +18483,68 @@ ColorScheme Document::PreferredColorScheme(IgnoreRFP aIgnoreRFP) const { +@@ -18552,6 +18564,68 @@ ColorScheme Document::PreferredColorScheme(IgnoreRFP aIgnoreRFP) const { return LookAndFeel::PreferredColorSchemeForContent(); } @@ -910,10 +910,10 @@ index b8d736619d4ea457b7db95d1815bd85697475133..d128299ab7a5e7f8fb00474571805361 if (!sLoadingForegroundTopLevelContentDocument) { return false; diff --git a/dom/base/Document.h b/dom/base/Document.h -index bb4e5a8e8114167d442e3593d240de74e9a6b12a..1a7b0a0bb8431c6c17dc0ccb1600ad97bbe005cd 100644 +index 11df804cff46806024f735c5cc907a9554ba1bd6..f6ce23bea6824dcedd545da6d95cde59037f24a6 100644 --- a/dom/base/Document.h +++ b/dom/base/Document.h -@@ -4067,6 +4067,9 @@ class Document : public nsINode, +@@ -4059,6 +4059,9 @@ class Document : public nsINode, // color-scheme meta tag. ColorScheme DefaultColorScheme() const; @@ -924,10 +924,10 @@ index bb4e5a8e8114167d442e3593d240de74e9a6b12a..1a7b0a0bb8431c6c17dc0ccb1600ad97 static bool AutomaticStorageAccessPermissionCanBeGranted( diff --git a/dom/base/Navigator.cpp b/dom/base/Navigator.cpp -index eedeee0364cefb0bcf5412483f452cb5454eb1a5..922775338a270ecd70f2ad284e627da63b37cb5b 100644 +index 9a5f6913b0682ad39824a2504734e58af9ae845e..baf24386e37daac95700d0715bf00cca1ebcd84f 100644 --- a/dom/base/Navigator.cpp +++ b/dom/base/Navigator.cpp -@@ -330,14 +330,18 @@ void Navigator::GetAppName(nsAString& aAppName, CallerType aCallerType) const { +@@ -327,14 +327,18 @@ void Navigator::GetAppName(nsAString& aAppName) const { * for more detail. */ /* static */ @@ -948,7 +948,7 @@ index eedeee0364cefb0bcf5412483f452cb5454eb1a5..922775338a270ecd70f2ad284e627da6 // Split values on commas. for (nsDependentSubstring lang : -@@ -389,7 +393,13 @@ void Navigator::GetLanguage(nsAString& aLanguage) { +@@ -386,7 +390,13 @@ void Navigator::GetLanguage(nsAString& aLanguage) { } void Navigator::GetLanguages(nsTArray& aLanguages) { @@ -963,7 +963,7 @@ index eedeee0364cefb0bcf5412483f452cb5454eb1a5..922775338a270ecd70f2ad284e627da6 // The returned value is cached by the binding code. The window listens to the // accept languages change and will clear the cache when needed. It has to -@@ -564,7 +574,13 @@ bool Navigator::CookieEnabled() { +@@ -561,7 +571,13 @@ bool Navigator::CookieEnabled() { return granted; } @@ -979,10 +979,10 @@ index eedeee0364cefb0bcf5412483f452cb5454eb1a5..922775338a270ecd70f2ad284e627da6 void Navigator::GetBuildID(nsAString& aBuildID, CallerType aCallerType, ErrorResult& aRv) const { diff --git a/dom/base/Navigator.h b/dom/base/Navigator.h -index cbe8d6bb27eb75b1c0eb920c69eccc99fd6133b2..49da35b1f9ec2a81c5886f277fd52ec492ca8418 100644 +index f878c11dff3d448dfa2520c7fe7e4e9cb63f7ea7..c1a30391eb31e28e1c22dff82bb9526bc7e058dd 100644 --- a/dom/base/Navigator.h +++ b/dom/base/Navigator.h -@@ -216,7 +216,7 @@ class Navigator final : public nsISupports, public nsWrapperCache { +@@ -213,7 +213,7 @@ class Navigator final : public nsISupports, public nsWrapperCache { StorageManager* Storage(); @@ -992,10 +992,10 @@ index cbe8d6bb27eb75b1c0eb920c69eccc99fd6133b2..49da35b1f9ec2a81c5886f277fd52ec4 dom::MediaCapabilities* MediaCapabilities(); dom::MediaSession* MediaSession(); diff --git a/dom/base/nsContentUtils.cpp b/dom/base/nsContentUtils.cpp -index 5cb5a088269287b6f9a3d36cca658043464cd5cc..d0b4929c5996efc03392b7093d14e01cabab8acf 100644 +index 52650a856f8e69069c857327fb63d27f4be1ccfb..0a125653d377065b79ef8d63a1062ab83525a643 100644 --- a/dom/base/nsContentUtils.cpp +++ b/dom/base/nsContentUtils.cpp -@@ -8520,7 +8520,8 @@ nsresult nsContentUtils::SendMouseEvent( +@@ -8554,7 +8554,8 @@ nsresult nsContentUtils::SendMouseEvent( bool aIgnoreRootScrollFrame, float aPressure, unsigned short aInputSourceArg, uint32_t aIdentifier, bool aToWindow, PreventDefaultResult* aPreventDefault, bool aIsDOMEventSynthesized, @@ -1005,7 +1005,7 @@ index 5cb5a088269287b6f9a3d36cca658043464cd5cc..d0b4929c5996efc03392b7093d14e01c nsPoint offset; nsCOMPtr widget = GetWidget(aPresShell, &offset); if (!widget) return NS_ERROR_FAILURE; -@@ -8528,6 +8529,7 @@ nsresult nsContentUtils::SendMouseEvent( +@@ -8562,6 +8563,7 @@ nsresult nsContentUtils::SendMouseEvent( EventMessage msg; Maybe exitFrom; bool contextMenuKey = false; @@ -1013,7 +1013,7 @@ index 5cb5a088269287b6f9a3d36cca658043464cd5cc..d0b4929c5996efc03392b7093d14e01c if (aType.EqualsLiteral("mousedown")) { msg = eMouseDown; } else if (aType.EqualsLiteral("mouseup")) { -@@ -8552,6 +8554,12 @@ nsresult nsContentUtils::SendMouseEvent( +@@ -8586,6 +8588,12 @@ nsresult nsContentUtils::SendMouseEvent( msg = eMouseHitTest; } else if (aType.EqualsLiteral("MozMouseExploreByTouch")) { msg = eMouseExploreByTouch; @@ -1026,7 +1026,7 @@ index 5cb5a088269287b6f9a3d36cca658043464cd5cc..d0b4929c5996efc03392b7093d14e01c } else { return NS_ERROR_FAILURE; } -@@ -8560,12 +8568,21 @@ nsresult nsContentUtils::SendMouseEvent( +@@ -8594,12 +8602,21 @@ nsresult nsContentUtils::SendMouseEvent( aInputSourceArg = MouseEvent_Binding::MOZ_SOURCE_MOUSE; } @@ -1050,7 +1050,7 @@ index 5cb5a088269287b6f9a3d36cca658043464cd5cc..d0b4929c5996efc03392b7093d14e01c event.pointerId = aIdentifier; event.mModifiers = GetWidgetModifiers(aModifiers); event.mButton = aButton; -@@ -8576,8 +8593,10 @@ nsresult nsContentUtils::SendMouseEvent( +@@ -8610,8 +8627,10 @@ nsresult nsContentUtils::SendMouseEvent( event.mPressure = aPressure; event.mInputSource = aInputSourceArg; event.mClickCount = aClickCount; @@ -1062,10 +1062,10 @@ index 5cb5a088269287b6f9a3d36cca658043464cd5cc..d0b4929c5996efc03392b7093d14e01c nsPresContext* presContext = aPresShell->GetPresContext(); if (!presContext) return NS_ERROR_FAILURE; diff --git a/dom/base/nsContentUtils.h b/dom/base/nsContentUtils.h -index 1259b13761c2d51e1ddca54433a49b2a5949a790..ec356bb54f04249d19a984e1f4b2d5d57009b64f 100644 +index b09dacc2be6db55a3c11a7f8b89eee9a131fde91..fbeb1b2fe9f14e63c416b0501d08cd7855d95476 100644 --- a/dom/base/nsContentUtils.h +++ b/dom/base/nsContentUtils.h -@@ -2934,7 +2934,8 @@ class nsContentUtils { +@@ -2947,7 +2947,8 @@ class nsContentUtils { int32_t aModifiers, bool aIgnoreRootScrollFrame, float aPressure, unsigned short aInputSourceArg, uint32_t aIdentifier, bool aToWindow, mozilla::PreventDefaultResult* aPreventDefault, @@ -1076,7 +1076,7 @@ index 1259b13761c2d51e1ddca54433a49b2a5949a790..ec356bb54f04249d19a984e1f4b2d5d5 static void FirePageShowEventForFrameLoaderSwap( nsIDocShellTreeItem* aItem, diff --git a/dom/base/nsDOMWindowUtils.cpp b/dom/base/nsDOMWindowUtils.cpp -index 2ccd99469917b6fc0b0c99f8ff579d7040e65669..2968275ddc32b7bb9eb7337ef90328bb00c1aabf 100644 +index 991fe527e558d47ed79b7ae46fe0f6ee12d9fdab..c688fab82de76b00cc8b45dd3d465a7e6fcb6fd5 100644 --- a/dom/base/nsDOMWindowUtils.cpp +++ b/dom/base/nsDOMWindowUtils.cpp @@ -684,6 +684,26 @@ nsDOMWindowUtils::GetPresShellId(uint32_t* aPresShellId) { @@ -1154,10 +1154,10 @@ index 63968c9b7a4e418e4c0de6e7a75fa215a36a9105..decf3ea3833ccdffd49a7aded2d600f9 MOZ_CAN_RUN_SCRIPT nsresult SendTouchEventCommon( diff --git a/dom/base/nsFocusManager.cpp b/dom/base/nsFocusManager.cpp -index ba77a1f00d43be4ec98b698cf0d9f4fab8343dd2..f146d1dfadbaa2e75f8b60f3dafe056a179ef018 100644 +index 141617e93a6bf4d0b15f7461c87124b9d5d2b69d..9bfd6ba7e28a9aeb78993d9c6a7639b176fa4363 100644 --- a/dom/base/nsFocusManager.cpp +++ b/dom/base/nsFocusManager.cpp -@@ -1656,6 +1656,10 @@ Maybe nsFocusManager::SetFocusInner(Element* aNewContent, +@@ -1671,6 +1671,10 @@ Maybe nsFocusManager::SetFocusInner(Element* aNewContent, (GetActiveBrowsingContext() == newRootBrowsingContext); } @@ -1168,7 +1168,7 @@ index ba77a1f00d43be4ec98b698cf0d9f4fab8343dd2..f146d1dfadbaa2e75f8b60f3dafe056a // Exit fullscreen if a website focuses another window if (StaticPrefs::full_screen_api_exit_on_windowRaise() && !isElementInActiveWindow && (aFlags & FLAG_RAISE)) { -@@ -2946,7 +2950,9 @@ void nsFocusManager::RaiseWindow(nsPIDOMWindowOuter* aWindow, +@@ -2942,7 +2946,9 @@ void nsFocusManager::RaiseWindow(nsPIDOMWindowOuter* aWindow, } } @@ -1180,10 +1180,10 @@ index ba77a1f00d43be4ec98b698cf0d9f4fab8343dd2..f146d1dfadbaa2e75f8b60f3dafe056a // care of lowering the present active window. This happens in // a separate runnable to avoid touching multiple windows in diff --git a/dom/base/nsGlobalWindowOuter.cpp b/dom/base/nsGlobalWindowOuter.cpp -index 9893fb0ed13eeebe55f8eda7bb3d898ff6eebba7..247784c89d5d68840638f40f77523e600f13bbf6 100644 +index c9a45b8a21e63f344704637d325383d240584fa7..661c41aecad66814690190d8229c30e32a94da21 100644 --- a/dom/base/nsGlobalWindowOuter.cpp +++ b/dom/base/nsGlobalWindowOuter.cpp -@@ -2482,7 +2482,7 @@ nsresult nsGlobalWindowOuter::SetNewDocument(Document* aDocument, +@@ -2489,7 +2489,7 @@ nsresult nsGlobalWindowOuter::SetNewDocument(Document* aDocument, &nsGlobalWindowInner::FireOnNewGlobalObject)); } @@ -1192,7 +1192,7 @@ index 9893fb0ed13eeebe55f8eda7bb3d898ff6eebba7..247784c89d5d68840638f40f77523e60 // We should probably notify. However if this is the, arguably bad, // situation when we're creating a temporary non-chrome-about-blank // document in a chrome docshell, don't notify just yet. Instead wait -@@ -2501,10 +2501,16 @@ nsresult nsGlobalWindowOuter::SetNewDocument(Document* aDocument, +@@ -2508,10 +2508,16 @@ nsresult nsGlobalWindowOuter::SetNewDocument(Document* aDocument, }(); if (!isContentAboutBlankInChromeDocshell) { @@ -1213,7 +1213,7 @@ index 9893fb0ed13eeebe55f8eda7bb3d898ff6eebba7..247784c89d5d68840638f40f77523e60 } } -@@ -2625,6 +2631,19 @@ void nsGlobalWindowOuter::DispatchDOMWindowCreated() { +@@ -2632,6 +2638,19 @@ void nsGlobalWindowOuter::DispatchDOMWindowCreated() { } } @@ -1234,10 +1234,10 @@ index 9893fb0ed13eeebe55f8eda7bb3d898ff6eebba7..247784c89d5d68840638f40f77523e60 void nsGlobalWindowOuter::SetDocShell(nsDocShell* aDocShell) { diff --git a/dom/base/nsGlobalWindowOuter.h b/dom/base/nsGlobalWindowOuter.h -index 0919dfe52ab1ced87c5483d0a60945f688f0eefe..c826b05d8599b7bf80415bdad1969a84a467a7ba 100644 +index 8a891ca19a56ff0cdecab26e1d6bb78f32b91abd..c05023ca6a88e0caef5b709a4f8c2846894d5c3c 100644 --- a/dom/base/nsGlobalWindowOuter.h +++ b/dom/base/nsGlobalWindowOuter.h -@@ -325,6 +325,7 @@ class nsGlobalWindowOuter final : public mozilla::dom::EventTarget, +@@ -314,6 +314,7 @@ class nsGlobalWindowOuter final : public mozilla::dom::EventTarget, // Outer windows only. void DispatchDOMWindowCreated(); @@ -1246,7 +1246,7 @@ index 0919dfe52ab1ced87c5483d0a60945f688f0eefe..c826b05d8599b7bf80415bdad1969a84 // Outer windows only. virtual void EnsureSizeAndPositionUpToDate() override; diff --git a/dom/base/nsINode.cpp b/dom/base/nsINode.cpp -index f2b1afabd27c3652632074c1788c4320277ef89f..bc60ca7e4820f9db27a4296acd27e86684ce59eb 100644 +index 77016f314939bf6ac11b48db1f71d1d3a82d4e83..67440e2643eb3f098e8e790179634216da7f851f 100644 --- a/dom/base/nsINode.cpp +++ b/dom/base/nsINode.cpp @@ -1358,6 +1358,61 @@ void nsINode::GetBoxQuadsFromWindowOrigin(const BoxQuadOptions& aOptions, @@ -1312,10 +1312,10 @@ index f2b1afabd27c3652632074c1788c4320277ef89f..bc60ca7e4820f9db27a4296acd27e866 DOMQuad& aQuad, const GeometryNode& aFrom, const ConvertCoordinateOptions& aOptions, CallerType aCallerType, diff --git a/dom/base/nsINode.h b/dom/base/nsINode.h -index 3617a4a3e31592c1cde00170ecf7b86cf4ab4737..51db576475772b5b9e1e5f87bb690577151b724b 100644 +index 9554650de24d35077ad20d977b7d3b1f1aa1be36..7a7ab8f2d62605ee80501ef4c26a4f6c5ab7f4b3 100644 --- a/dom/base/nsINode.h +++ b/dom/base/nsINode.h -@@ -2193,6 +2193,10 @@ class nsINode : public mozilla::dom::EventTarget { +@@ -2201,6 +2201,10 @@ class nsINode : public mozilla::dom::EventTarget { nsTArray>& aResult, ErrorResult& aRv); @@ -1327,10 +1327,10 @@ index 3617a4a3e31592c1cde00170ecf7b86cf4ab4737..51db576475772b5b9e1e5f87bb690577 DOMQuad& aQuad, const TextOrElementOrDocument& aFrom, const ConvertCoordinateOptions& aOptions, CallerType aCallerType, diff --git a/dom/base/nsJSUtils.cpp b/dom/base/nsJSUtils.cpp -index 86fe04583c34bd84f7239c3515c9f335d84f48a2..b6705bc48f216e856b556349d206756a0cf91867 100644 +index 66b0a09dda9e57f41643da11abb079896b9634d9..eb1dacdce7c8426de3f3cd34d2c22d1d13f49b5a 100644 --- a/dom/base/nsJSUtils.cpp +++ b/dom/base/nsJSUtils.cpp -@@ -169,6 +169,11 @@ bool nsJSUtils::GetScopeChainForElement( +@@ -177,6 +177,11 @@ bool nsJSUtils::GetScopeChainForElement( return true; } @@ -1343,10 +1343,10 @@ index 86fe04583c34bd84f7239c3515c9f335d84f48a2..b6705bc48f216e856b556349d206756a void nsJSUtils::ResetTimeZone() { JS::ResetTimeZone(); } diff --git a/dom/base/nsJSUtils.h b/dom/base/nsJSUtils.h -index 67682173f45c6a83cbad176c2922263d4f7dece9..7dd97f27bdf07673289fce62aaebe3b96492a2eb 100644 +index 36e906061588aab50dee129cc46dd2e4d3e153f8..c3591f98d4df19b166fc5c99332e559b1d499049 100644 --- a/dom/base/nsJSUtils.h +++ b/dom/base/nsJSUtils.h -@@ -78,6 +78,7 @@ class nsJSUtils { +@@ -79,6 +79,7 @@ class nsJSUtils { JSContext* aCx, mozilla::dom::Element* aElement, JS::MutableHandleVector aScopeChain); @@ -1397,7 +1397,7 @@ index db60c475931caa32110d12ba63bb56980a2b36cc..5d1d8fdceec7a73541799cbac367b173 * A unique identifier for the browser element that is hosting this * BrowsingContext tree. Every BrowsingContext in the element's tree will diff --git a/dom/geolocation/Geolocation.cpp b/dom/geolocation/Geolocation.cpp -index ff6fe276e3f5a19e3e22d98c4a38222880797d99..96157d17485534f97a4e39675ee77808ac495bfe 100644 +index 197146d71e9772af04e577663dbc0213c26a62cb..0e357893cdcf0d6b627bca803aa6041107079184 100644 --- a/dom/geolocation/Geolocation.cpp +++ b/dom/geolocation/Geolocation.cpp @@ -23,6 +23,7 @@ @@ -1405,10 +1405,10 @@ index ff6fe276e3f5a19e3e22d98c4a38222880797d99..96157d17485534f97a4e39675ee77808 #include "nsContentPermissionHelper.h" #include "nsContentUtils.h" +#include "nsDocShell.h" - #include "nsGlobalWindow.h" + #include "nsGlobalWindowInner.h" #include "mozilla/dom/Document.h" #include "nsINamed.h" -@@ -260,10 +261,8 @@ nsGeolocationRequest::Allow(JS::Handle aChoices) { +@@ -259,10 +260,8 @@ nsGeolocationRequest::Allow(JS::Handle aChoices) { return NS_OK; } @@ -1421,7 +1421,7 @@ index ff6fe276e3f5a19e3e22d98c4a38222880797d99..96157d17485534f97a4e39675ee77808 CachedPositionAndAccuracy lastPosition = gs->GetCachedPosition(); if (lastPosition.position) { EpochTimeStamp cachedPositionTime_ms; -@@ -441,8 +440,7 @@ void nsGeolocationRequest::Shutdown() { +@@ -440,8 +439,7 @@ void nsGeolocationRequest::Shutdown() { // If there are no other high accuracy requests, the geolocation service will // notify the provider to switch to the default accuracy. if (mOptions && mOptions->mEnableHighAccuracy) { @@ -1431,7 +1431,7 @@ index ff6fe276e3f5a19e3e22d98c4a38222880797d99..96157d17485534f97a4e39675ee77808 if (gs) { gs->UpdateAccuracy(); } -@@ -732,8 +730,14 @@ void nsGeolocationService::StopDevice() { +@@ -730,8 +728,14 @@ void nsGeolocationService::StopDevice() { StaticRefPtr nsGeolocationService::sService; already_AddRefed @@ -1447,7 +1447,7 @@ index ff6fe276e3f5a19e3e22d98c4a38222880797d99..96157d17485534f97a4e39675ee77808 if (nsGeolocationService::sService) { result = nsGeolocationService::sService; -@@ -825,7 +829,9 @@ nsresult Geolocation::Init(nsPIDOMWindowInner* aContentDom) { +@@ -823,7 +827,9 @@ nsresult Geolocation::Init(nsPIDOMWindowInner* aContentDom) { // If no aContentDom was passed into us, we are being used // by chrome/c++ and have no mOwner, no mPrincipal, and no need // to prompt. @@ -1496,10 +1496,10 @@ index 7e1af00d05fbafa2d828e2c7e4dcc5c82d115f5b..e85af9718d064e4d2865bc944e9d4ba1 ~Geolocation(); diff --git a/dom/html/HTMLInputElement.cpp b/dom/html/HTMLInputElement.cpp -index 4a3e5b2c935ad1cfb1bff706933fc2373f90a937..d8eac957b88ea2a5edbf1f0f4fde177edcb07600 100644 +index 75536c2488bdb82f429693506cc412243ab7a49a..4049dd79838cd70ce285d64fd338fdf537dc7e2e 100644 --- a/dom/html/HTMLInputElement.cpp +++ b/dom/html/HTMLInputElement.cpp -@@ -57,6 +57,7 @@ +@@ -58,6 +58,7 @@ #include "mozilla/dom/Document.h" #include "mozilla/dom/HTMLDataListElement.h" #include "mozilla/dom/HTMLOptionElement.h" @@ -1507,7 +1507,7 @@ index 4a3e5b2c935ad1cfb1bff706933fc2373f90a937..d8eac957b88ea2a5edbf1f0f4fde177e #include "nsIFormControlFrame.h" #include "nsITextControlFrame.h" #include "nsIFrame.h" -@@ -780,6 +781,12 @@ nsresult HTMLInputElement::InitFilePicker(FilePickerType aType) { +@@ -782,6 +783,12 @@ nsresult HTMLInputElement::InitFilePicker(FilePickerType aType) { return NS_ERROR_FAILURE; } @@ -1521,10 +1521,10 @@ index 4a3e5b2c935ad1cfb1bff706933fc2373f90a937..d8eac957b88ea2a5edbf1f0f4fde177e return NS_OK; } diff --git a/dom/interfaces/base/nsIDOMWindowUtils.idl b/dom/interfaces/base/nsIDOMWindowUtils.idl -index 82f7d4d206c7274858a945d5db61aa02c366e472..a23386a5749c4af48b9bb86c8c48928da6aa3a9e 100644 +index 4170a79023a2503831d080a6e65d5e143f34f241..3af08d6ea5f1cfbdc373774764a0c45fe3aa0e27 100644 --- a/dom/interfaces/base/nsIDOMWindowUtils.idl +++ b/dom/interfaces/base/nsIDOMWindowUtils.idl -@@ -374,6 +374,26 @@ interface nsIDOMWindowUtils : nsISupports { +@@ -373,6 +373,26 @@ interface nsIDOMWindowUtils : nsISupports { [optional] in long aButtons, [optional] in unsigned long aIdentifier); @@ -1552,10 +1552,10 @@ index 82f7d4d206c7274858a945d5db61aa02c366e472..a23386a5749c4af48b9bb86c8c48928d * touchstart, touchend, touchmove, and touchcancel * diff --git a/dom/ipc/BrowserChild.cpp b/dom/ipc/BrowserChild.cpp -index 084c717432a853b8f95a087463dcce93215ca6e8..2689ba865758ce6085f68d86a5d09a27551d229c 100644 +index 9b4096d7126c03bdc1fcc12785d3f05e65212747..13e86cea216314ea77135a397a3cad67247b84e0 100644 --- a/dom/ipc/BrowserChild.cpp +++ b/dom/ipc/BrowserChild.cpp -@@ -1672,6 +1672,21 @@ void BrowserChild::HandleRealMouseButtonEvent(const WidgetMouseEvent& aEvent, +@@ -1670,6 +1670,21 @@ void BrowserChild::HandleRealMouseButtonEvent(const WidgetMouseEvent& aEvent, if (postLayerization) { postLayerization->Register(); } @@ -1592,7 +1592,7 @@ index 5aa445d2e0a6169e57c44569974d557b3baf7064..671f71979b407f0ca17c66f13805e851 } diff --git a/dom/media/systemservices/video_engine/desktop_capture_impl.cc b/dom/media/systemservices/video_engine/desktop_capture_impl.cc -index 2274a21e8a287932342bb4fb58af728d13b89224..367466efc8457f99c87af1d285131f7b6c71c8ef 100644 +index 3f03789fa3948bbf2528975ce112efb7eb987c24..2194e4144de537edb9a765857cc37b0af42dd8fd 100644 --- a/dom/media/systemservices/video_engine/desktop_capture_impl.cc +++ b/dom/media/systemservices/video_engine/desktop_capture_impl.cc @@ -135,11 +135,12 @@ int32_t ScreenDeviceInfoImpl::GetOrientation(const char* aDeviceUniqueIdUTF8, @@ -1611,7 +1611,7 @@ index 2274a21e8a287932342bb4fb58af728d13b89224..367466efc8457f99c87af1d285131f7b } int32_t WindowDeviceInfoImpl::Init() { -@@ -408,7 +409,7 @@ static bool UsePipewire() { +@@ -405,7 +406,7 @@ static bool UsePipewire() { static std::unique_ptr CreateDesktopCapturerAndThread( CaptureDeviceType aDeviceType, DesktopCapturer::SourceId aSourceId, @@ -1620,7 +1620,7 @@ index 2274a21e8a287932342bb4fb58af728d13b89224..367466efc8457f99c87af1d285131f7b DesktopCaptureOptions options = CreateDesktopCaptureOptions(); std::unique_ptr capturer; -@@ -458,8 +459,10 @@ static std::unique_ptr CreateDesktopCapturerAndThread( +@@ -455,8 +456,10 @@ static std::unique_ptr CreateDesktopCapturerAndThread( capturer->SelectSource(aSourceId); @@ -1633,7 +1633,7 @@ index 2274a21e8a287932342bb4fb58af728d13b89224..367466efc8457f99c87af1d285131f7b } else if (aDeviceType == CaptureDeviceType::Browser) { // XXX We don't capture cursors, so avoid the extra indirection layer. We // could also pass null for the pMouseCursorMonitor. -@@ -476,7 +479,8 @@ static std::unique_ptr CreateDesktopCapturerAndThread( +@@ -473,7 +476,8 @@ static std::unique_ptr CreateDesktopCapturerAndThread( } DesktopCaptureImpl::DesktopCaptureImpl(const int32_t aId, const char* aUniqueId, @@ -1643,7 +1643,7 @@ index 2274a21e8a287932342bb4fb58af728d13b89224..367466efc8457f99c87af1d285131f7b : mModuleId(aId), mTrackingId(mozilla::TrackingId(CaptureEngineToTrackingSourceStr([&] { switch (aType) { -@@ -493,6 +497,7 @@ DesktopCaptureImpl::DesktopCaptureImpl(const int32_t aId, const char* aUniqueId, +@@ -490,6 +494,7 @@ DesktopCaptureImpl::DesktopCaptureImpl(const int32_t aId, const char* aUniqueId, aId)), mDeviceUniqueId(aUniqueId), mDeviceType(aType), @@ -1651,7 +1651,7 @@ index 2274a21e8a287932342bb4fb58af728d13b89224..367466efc8457f99c87af1d285131f7b mControlThread(mozilla::GetCurrentSerialEventTarget()), mNextFrameMinimumTime(Timestamp::Zero()), mCallbacks("DesktopCaptureImpl::mCallbacks") {} -@@ -517,6 +522,19 @@ void DesktopCaptureImpl::DeRegisterCaptureDataCallback( +@@ -514,6 +519,19 @@ void DesktopCaptureImpl::DeRegisterCaptureDataCallback( } } @@ -1671,7 +1671,7 @@ index 2274a21e8a287932342bb4fb58af728d13b89224..367466efc8457f99c87af1d285131f7b int32_t DesktopCaptureImpl::StopCaptureIfAllClientsClose() { { auto callbacks = mCallbacks.Lock(); -@@ -549,7 +567,7 @@ int32_t DesktopCaptureImpl::StartCapture( +@@ -546,7 +564,7 @@ int32_t DesktopCaptureImpl::StartCapture( DesktopCapturer::SourceId sourceId = std::stoi(mDeviceUniqueId); std::unique_ptr capturer = CreateDesktopCapturerAndThread( @@ -1680,7 +1680,7 @@ index 2274a21e8a287932342bb4fb58af728d13b89224..367466efc8457f99c87af1d285131f7b MOZ_ASSERT(!capturer == !mCaptureThread); if (!capturer) { -@@ -650,6 +668,15 @@ void DesktopCaptureImpl::OnCaptureResult(DesktopCapturer::Result aResult, +@@ -647,6 +665,15 @@ void DesktopCaptureImpl::OnCaptureResult(DesktopCapturer::Result aResult, frameInfo.height = aFrame->size().height(); frameInfo.videoType = VideoType::kARGB; @@ -1830,7 +1830,7 @@ index 1f2d92bcb5d989bf9ecc044f8c51006f991b0007..9cf5dd885e658e0fe5e7ab75e7fc1f97 return aGlobalOrNull; diff --git a/dom/security/nsCSPUtils.cpp b/dom/security/nsCSPUtils.cpp -index a7b02a55a73c0ab459278ce475459e43beeedc8f..b8f292420f1b563bf409e47e01972f23ac1e852f 100644 +index d1bd1d060d749de3ac16bd6e9fd3383383e4dd9a..e6262c210accf12c3d071d42031b432b2a6332b5 100644 --- a/dom/security/nsCSPUtils.cpp +++ b/dom/security/nsCSPUtils.cpp @@ -22,6 +22,7 @@ @@ -1877,10 +1877,10 @@ index 2f71b284ee5f7e11f117c447834b48355784448c..2640bd57123c2b03bf4b06a2419cd020 * returned quads are further translated relative to the window * origin -- which is not the layout origin. Further translation diff --git a/dom/workers/RuntimeService.cpp b/dom/workers/RuntimeService.cpp -index f049e706028b59f05f20b1091c5706449f396f8b..b37a9b721cf86cf6f06a2d723ed019f3a46a1176 100644 +index 9c49ad97054ec46cfc52082202d36bb2b53482fc..46d22e51cfeaded274e63b9673e0c3c83b517e7a 100644 --- a/dom/workers/RuntimeService.cpp +++ b/dom/workers/RuntimeService.cpp -@@ -985,7 +985,7 @@ void PrefLanguagesChanged(const char* /* aPrefName */, void* /* aClosure */) { +@@ -986,7 +986,7 @@ void PrefLanguagesChanged(const char* /* aPrefName */, void* /* aClosure */) { AssertIsOnMainThread(); nsTArray languages; @@ -1889,7 +1889,7 @@ index f049e706028b59f05f20b1091c5706449f396f8b..b37a9b721cf86cf6f06a2d723ed019f3 RuntimeService* runtime = RuntimeService::GetService(); if (runtime) { -@@ -1187,8 +1187,7 @@ bool RuntimeService::RegisterWorker(WorkerPrivate& aWorkerPrivate) { +@@ -1173,8 +1173,7 @@ bool RuntimeService::RegisterWorker(WorkerPrivate& aWorkerPrivate) { } // The navigator overridden properties should have already been read. @@ -1899,7 +1899,7 @@ index f049e706028b59f05f20b1091c5706449f396f8b..b37a9b721cf86cf6f06a2d723ed019f3 mNavigatorPropertiesLoaded = true; } -@@ -1800,6 +1799,13 @@ void RuntimeService::PropagateStorageAccessPermissionGranted( +@@ -1778,6 +1777,13 @@ void RuntimeService::PropagateStorageAccessPermissionGranted( } } @@ -1913,7 +1913,7 @@ index f049e706028b59f05f20b1091c5706449f396f8b..b37a9b721cf86cf6f06a2d723ed019f3 template void RuntimeService::BroadcastAllWorkers(const Func& aFunc) { AssertIsOnMainThread(); -@@ -2322,6 +2328,14 @@ void PropagateStorageAccessPermissionGrantedToWorkers( +@@ -2295,6 +2301,14 @@ void PropagateStorageAccessPermissionGrantedToWorkers( } } @@ -1929,10 +1929,10 @@ index f049e706028b59f05f20b1091c5706449f396f8b..b37a9b721cf86cf6f06a2d723ed019f3 MOZ_ASSERT(!NS_IsMainThread()); MOZ_ASSERT(aCx); diff --git a/dom/workers/RuntimeService.h b/dom/workers/RuntimeService.h -index a770d7330edb2f99483ab0363211817ae40028b0..f677f14e2ac42c94483726bac8538b52129615cc 100644 +index e6deb81f357043a937d032bb4b6c38207203f4d9..ff16582af9fbf550dfb7b5639658c34199524c45 100644 --- a/dom/workers/RuntimeService.h +++ b/dom/workers/RuntimeService.h -@@ -110,6 +110,8 @@ class RuntimeService final : public nsIObserver { +@@ -108,6 +108,8 @@ class RuntimeService final : public nsIObserver { void PropagateStorageAccessPermissionGranted( const nsPIDOMWindowInner& aWindow); @@ -1955,7 +1955,7 @@ index d10dabb5c5ff8e17851edf2bd2efc08e74584d8e..53c4070c5fde43b27fb8fbfdcf4c23d8 bool IsWorkerGlobal(JSObject* global); diff --git a/dom/workers/WorkerPrivate.cpp b/dom/workers/WorkerPrivate.cpp -index 3c6a2fa249f4b993a440563247a68435cd7820d8..c82dc3c4d740abb74735d909b48b600ead193c76 100644 +index 574fe73ea8cf29b64a507a390ec8d55a5f8ad498..85eb9e92b2f55f62f8bee797679917eff35963de 100644 --- a/dom/workers/WorkerPrivate.cpp +++ b/dom/workers/WorkerPrivate.cpp @@ -709,6 +709,18 @@ class UpdateContextOptionsRunnable final : public WorkerControlRunnable { @@ -1994,7 +1994,7 @@ index 3c6a2fa249f4b993a440563247a68435cd7820d8..c82dc3c4d740abb74735d909b48b600e void WorkerPrivate::UpdateLanguages(const nsTArray& aLanguages) { AssertIsOnParentThread(); -@@ -5412,6 +5434,15 @@ void WorkerPrivate::UpdateContextOptionsInternal( +@@ -5409,6 +5431,15 @@ void WorkerPrivate::UpdateContextOptionsInternal( } } @@ -2011,7 +2011,7 @@ index 3c6a2fa249f4b993a440563247a68435cd7820d8..c82dc3c4d740abb74735d909b48b600e const nsTArray& aLanguages) { WorkerGlobalScope* globalScope = GlobalScope(); diff --git a/dom/workers/WorkerPrivate.h b/dom/workers/WorkerPrivate.h -index e78423f757d81bebf6a82ad261348f15af30f063..ed1d5eca153a3eab9268fa5d6402d40c34281f2c 100644 +index 6eb840b8e64b5a4db3c621599780561ef2fdaef4..59e27e6949dfda389516d64a7b4ac0f0e5a60148 100644 --- a/dom/workers/WorkerPrivate.h +++ b/dom/workers/WorkerPrivate.h @@ -413,6 +413,8 @@ class WorkerPrivate final @@ -2085,10 +2085,10 @@ index dd82415624e1f05eaad818d68b8588ffb1b64ab1..c48ab77757aff777658fd4e37db6bdea inline ClippedTime TimeClip(double time); diff --git a/js/src/debugger/Object.cpp b/js/src/debugger/Object.cpp -index 9c3a652b60e09013f77b9a7f7da03d376d21cb6a..091daaee4a3402a0c572a21142517e4f9e706257 100644 +index 49525b426a9f8656a471192ccf62f47f555a90a4..f6c832af063326a5e9e7166662bb21bc4e41cdca 100644 --- a/js/src/debugger/Object.cpp +++ b/js/src/debugger/Object.cpp -@@ -2426,7 +2426,11 @@ Maybe DebuggerObject::call(JSContext* cx, +@@ -2413,7 +2413,11 @@ Maybe DebuggerObject::call(JSContext* cx, invokeArgs[i].set(args2[i]); } @@ -2255,10 +2255,10 @@ index dac899f7558b26d6848da8b98ed8a93555c8751a..2a07d67fa1c2840b25085566e84dc3b2 // No boxes to return return; diff --git a/layout/base/PresShell.cpp b/layout/base/PresShell.cpp -index bd8cfa12fa79306b8d41011f2abf4ed40c12c2c4..63c77656c2e7c6c9a4d713ca9561411f304e187e 100644 +index 60059ca8e6704f5d9031ced546694de08f63fe77..0f3a96f0c3c95480299a917a60ec5f9924a2f2eb 100644 --- a/layout/base/PresShell.cpp +++ b/layout/base/PresShell.cpp -@@ -10896,7 +10896,9 @@ auto PresShell::ComputeActiveness() const -> Activeness { +@@ -10913,7 +10913,9 @@ auto PresShell::ComputeActiveness() const -> Activeness { if (!browserChild->IsVisible()) { MOZ_LOG(gLog, LogLevel::Debug, (" > BrowserChild %p is not visible", browserChild)); @@ -2270,10 +2270,10 @@ index bd8cfa12fa79306b8d41011f2abf4ed40c12c2c4..63c77656c2e7c6c9a4d713ca9561411f // If the browser is visible but just due to be preserving layers diff --git a/layout/style/GeckoBindings.h b/layout/style/GeckoBindings.h -index 9e5ffc264bbbfa13b0cdb37a5c987074f2d8e8c9..9b7164b10703b48f538ee983b1ef82146876e2bb 100644 +index 1d5d60bb4e7cc0d93ff7e6662c9102bde59509c1..ee1436d6e06f13a4386314e8bb8e4e3998ae5a0c 100644 --- a/layout/style/GeckoBindings.h +++ b/layout/style/GeckoBindings.h -@@ -629,6 +629,7 @@ float Gecko_MediaFeatures_GetResolution(const mozilla::dom::Document*); +@@ -630,6 +630,7 @@ float Gecko_MediaFeatures_GetResolution(const mozilla::dom::Document*); bool Gecko_MediaFeatures_PrefersReducedMotion(const mozilla::dom::Document*); bool Gecko_MediaFeatures_PrefersReducedTransparency( const mozilla::dom::Document*); @@ -2282,10 +2282,10 @@ index 9e5ffc264bbbfa13b0cdb37a5c987074f2d8e8c9..9b7164b10703b48f538ee983b1ef8214 const mozilla::dom::Document*); mozilla::StylePrefersColorScheme Gecko_MediaFeatures_PrefersColorScheme( diff --git a/layout/style/nsMediaFeatures.cpp b/layout/style/nsMediaFeatures.cpp -index 581b6da29ba6bc321c549802b8bd5e3364724460..33827a19b46bc4814a0554629cea41e3d566bf8e 100644 +index 5e4fe65abcc373e6c0fba40458677cebb085266b..9425984faca3579cb90e96ae46ed47e66c2dc664 100644 --- a/layout/style/nsMediaFeatures.cpp +++ b/layout/style/nsMediaFeatures.cpp -@@ -277,11 +277,11 @@ bool Gecko_MediaFeatures_MatchesPlatform(StylePlatform aPlatform) { +@@ -261,11 +261,11 @@ bool Gecko_MediaFeatures_MatchesPlatform(StylePlatform aPlatform) { } bool Gecko_MediaFeatures_PrefersReducedMotion(const Document* aDocument) { @@ -2303,10 +2303,10 @@ index 581b6da29ba6bc321c549802b8bd5e3364724460..33827a19b46bc4814a0554629cea41e3 bool Gecko_MediaFeatures_PrefersReducedTransparency(const Document* aDocument) { diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js -index a366fdaeb88daf820734508f7916ec68c70273fd..ebbbe8cee358517a851cfec085738b58c3791421 100644 +index a190b6caff8c21fb6bedaf37b9c25c812cd7114a..ac972936eedf5ea9a0f2e3d9d2dc899e848504d4 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js -@@ -3968,7 +3968,9 @@ pref("devtools.f12_enabled", true); +@@ -3939,7 +3939,9 @@ pref("devtools.f12_enabled", true); // doesn't provide a way to lock the pref pref("dom.postMessage.sharedArrayBuffer.bypassCOOP_COEP.insecure.enabled", false); #else @@ -2318,10 +2318,10 @@ index a366fdaeb88daf820734508f7916ec68c70273fd..ebbbe8cee358517a851cfec085738b58 // Whether sites require the open-protocol-handler permission to open a diff --git a/netwerk/base/LoadInfo.cpp b/netwerk/base/LoadInfo.cpp -index f7c5829c964217132bbd3b67b961a183df42fb77..cd1006e2234f50facc916f25e996a2b0bc444d0b 100644 +index 5f0543bbb117887b8c4438ebf24199ab20f514c2..5dde8690a1699fec0433a0c5a1f396653f00db2e 100644 --- a/netwerk/base/LoadInfo.cpp +++ b/netwerk/base/LoadInfo.cpp -@@ -645,7 +645,8 @@ LoadInfo::LoadInfo(const LoadInfo& rhs) +@@ -640,7 +640,8 @@ LoadInfo::LoadInfo(const LoadInfo& rhs) mUnstrippedURI(rhs.mUnstrippedURI), mInterceptionInfo(rhs.mInterceptionInfo), mHasInjectedCookieForCookieBannerHandling( @@ -2331,7 +2331,7 @@ index f7c5829c964217132bbd3b67b961a183df42fb77..cd1006e2234f50facc916f25e996a2b0 LoadInfo::LoadInfo( nsIPrincipal* aLoadingPrincipal, nsIPrincipal* aTriggeringPrincipal, -@@ -2298,4 +2299,16 @@ LoadInfo::SetHasInjectedCookieForCookieBannerHandling( +@@ -2320,4 +2321,16 @@ LoadInfo::SetHasInjectedCookieForCookieBannerHandling( return NS_OK; } @@ -2349,10 +2349,10 @@ index f7c5829c964217132bbd3b67b961a183df42fb77..cd1006e2234f50facc916f25e996a2b0 + } // namespace mozilla::net diff --git a/netwerk/base/LoadInfo.h b/netwerk/base/LoadInfo.h -index dace62e98dff10497c0271dac1c010afd3733a67..18e30006978a249ebdacdc8e07ce0b980e9c70ea 100644 +index f97d2389297ea1c4771ae2c7e55a0b3eade0743a..89c47840301480ffce5dd385bb5b0a34bfdd7390 100644 --- a/netwerk/base/LoadInfo.h +++ b/netwerk/base/LoadInfo.h -@@ -384,6 +384,8 @@ class LoadInfo final : public nsILoadInfo { +@@ -387,6 +387,8 @@ class LoadInfo final : public nsILoadInfo { nsCOMPtr mInterceptionInfo; bool mHasInjectedCookieForCookieBannerHandling = false; @@ -2362,10 +2362,10 @@ index dace62e98dff10497c0271dac1c010afd3733a67..18e30006978a249ebdacdc8e07ce0b98 // This is exposed solely for testing purposes and should not be used outside of diff --git a/netwerk/base/TRRLoadInfo.cpp b/netwerk/base/TRRLoadInfo.cpp -index 2b9360cd23f1f2009ee2788470c4ff30a2e6e6c1..f6f7ca21ef0509a8751bfa9e08e24219b32a5c3e 100644 +index 37b0b7bfe516ca69441e4cdd58861de9d595c692..6d3bb900624b6ad9e9449ce6f462a87dacfd4cb9 100644 --- a/netwerk/base/TRRLoadInfo.cpp +++ b/netwerk/base/TRRLoadInfo.cpp -@@ -827,5 +827,16 @@ TRRLoadInfo::SetHasInjectedCookieForCookieBannerHandling( +@@ -845,5 +845,16 @@ TRRLoadInfo::SetHasInjectedCookieForCookieBannerHandling( return NS_ERROR_NOT_IMPLEMENTED; } @@ -2383,10 +2383,10 @@ index 2b9360cd23f1f2009ee2788470c4ff30a2e6e6c1..f6f7ca21ef0509a8751bfa9e08e24219 } // namespace net } // namespace mozilla diff --git a/netwerk/base/nsILoadInfo.idl b/netwerk/base/nsILoadInfo.idl -index f3f8304a1b88ba7b1b7465963a717997d812a2b4..06402586a95efdcc01397837d2cad06b4503f0b7 100644 +index 7c84f976ea52f06b2cc0dfb59bcfe598d98d10e0..6c60b34f78d98046298cb9b8b847da5b8cf779b4 100644 --- a/netwerk/base/nsILoadInfo.idl +++ b/netwerk/base/nsILoadInfo.idl -@@ -1488,4 +1488,6 @@ interface nsILoadInfo : nsISupports +@@ -1504,4 +1504,6 @@ interface nsILoadInfo : nsISupports * handle a cookie banner. This is only done for top-level requests. */ [infallible] attribute boolean hasInjectedCookieForCookieBannerHandling; @@ -2406,10 +2406,10 @@ index d72dc570dc82ff9d576942b9e7c23d8a74d68049..a5fcddc4b0e53a862e5a77120b4ccff8 /** * Set the status and reason for the forthcoming synthesized response. diff --git a/netwerk/ipc/DocumentLoadListener.cpp b/netwerk/ipc/DocumentLoadListener.cpp -index 7ce1ef0a864bc5fa6a616170dab03974c62aa8ed..d514ea18fd18d03ddb898636ba34d724d41cfdc2 100644 +index cd363cacf1fe2222a320996df93b56f94611ed61..5ca0439d2a3e1e64078cfaed7ba87db173826d70 100644 --- a/netwerk/ipc/DocumentLoadListener.cpp +++ b/netwerk/ipc/DocumentLoadListener.cpp -@@ -163,6 +163,7 @@ static auto CreateDocumentLoadInfo(CanonicalBrowsingContext* aBrowsingContext, +@@ -165,6 +165,7 @@ static auto CreateDocumentLoadInfo(CanonicalBrowsingContext* aBrowsingContext, loadInfo->SetHasValidUserGestureActivation( aLoadState->HasValidUserGestureActivation()); loadInfo->SetIsMetaRefresh(aLoadState->IsMetaRefresh()); @@ -2418,7 +2418,7 @@ index 7ce1ef0a864bc5fa6a616170dab03974c62aa8ed..d514ea18fd18d03ddb898636ba34d724 return loadInfo.forget(); } diff --git a/netwerk/protocol/http/InterceptedHttpChannel.cpp b/netwerk/protocol/http/InterceptedHttpChannel.cpp -index c493259905d8b4e6b3a860cd6436c2606e8e8d29..7060deb04d3b3deb3e1cd90b75848229cf2252f2 100644 +index 6e733cc3601352ec600f69420148c96007cb3f78..c36ee059df0dc648beb074bf673b550a59fe2216 100644 --- a/netwerk/protocol/http/InterceptedHttpChannel.cpp +++ b/netwerk/protocol/http/InterceptedHttpChannel.cpp @@ -729,6 +729,14 @@ NS_IMPL_ISUPPORTS(ResetInterceptionHeaderVisitor, nsIHttpHeaderVisitor) @@ -2436,7 +2436,7 @@ index c493259905d8b4e6b3a860cd6436c2606e8e8d29..7060deb04d3b3deb3e1cd90b75848229 NS_IMETHODIMP InterceptedHttpChannel::ResetInterception(bool aBypass) { INTERCEPTED_LOG(("InterceptedHttpChannel::ResetInterception [%p] bypass: %s", -@@ -1071,11 +1079,18 @@ InterceptedHttpChannel::OnStartRequest(nsIRequest* aRequest) { +@@ -1072,11 +1080,18 @@ InterceptedHttpChannel::OnStartRequest(nsIRequest* aRequest) { GetCallback(mProgressSink); } @@ -2456,10 +2456,10 @@ index c493259905d8b4e6b3a860cd6436c2606e8e8d29..7060deb04d3b3deb3e1cd90b75848229 if (mPump && mLoadFlags & LOAD_CALL_CONTENT_SNIFFERS) { mPump->PeekStream(CallTypeSniffers, static_cast(this)); diff --git a/parser/html/nsHtml5TreeOpExecutor.cpp b/parser/html/nsHtml5TreeOpExecutor.cpp -index 0908642956b66e867be59c5777f26e4c9f95d5ec..3d7677c454c5a0d2169686c2abad7b332f2413ce 100644 +index 177fbd0a496bacd402f2a0594342e5c50e882b4b..82324fed89a80c17fd0160bb8d45e7a07c23c58a 100644 --- a/parser/html/nsHtml5TreeOpExecutor.cpp +++ b/parser/html/nsHtml5TreeOpExecutor.cpp -@@ -1372,6 +1372,10 @@ void nsHtml5TreeOpExecutor::UpdateReferrerInfoFromMeta( +@@ -1370,6 +1370,10 @@ void nsHtml5TreeOpExecutor::UpdateReferrerInfoFromMeta( void nsHtml5TreeOpExecutor::AddSpeculationCSP(const nsAString& aCSP) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); @@ -2544,7 +2544,7 @@ index 6dfd07d6b676a99993408921de8dea9d561f201d..e3c6794363cd6336effbeac83a179f37 readonly attribute boolean securityCheckDisabled; }; diff --git a/services/settings/Utils.sys.mjs b/services/settings/Utils.sys.mjs -index 697409ab07c5274696b51c5033cb07ca408d5332..4bb56c915535d3578525e82a3309435ad333fba2 100644 +index c2db005b686d2500398c41301613cacd3e44148c..093265e5871e5c9effdad3270015a05f63e2c844 100644 --- a/services/settings/Utils.sys.mjs +++ b/services/settings/Utils.sys.mjs @@ -95,7 +95,7 @@ function _isUndefined(value) { @@ -2557,7 +2557,7 @@ index 697409ab07c5274696b51c5033cb07ca408d5332..4bb56c915535d3578525e82a3309435a : AppConstants.REMOTE_SETTINGS_SERVER_URL; }, diff --git a/servo/components/style/gecko/media_features.rs b/servo/components/style/gecko/media_features.rs -index 4ca746ea84d917b95dfb66953660ca0a4572e647..bafeb667f3dbc7fa66d4baf811bf5cd5eb728c60 100644 +index 3441dd3a3774231f0aa92486ad0b07dbf98cba8b..f3d8ee86ecea36b6464f3895f40cc537646ac5b7 100644 --- a/servo/components/style/gecko/media_features.rs +++ b/servo/components/style/gecko/media_features.rs @@ -291,10 +291,15 @@ pub enum ForcedColors { @@ -2594,10 +2594,10 @@ index 54de3abab5757dd706e3d909ccef6a0bed5deacc..f5c5480cd052ede0c76e5eec733dbb92 // ignored for Linux. const unsigned long CHROME_SUPPRESS_ANIMATION = 0x01000000; diff --git a/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs b/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs -index acea04b6fd130ae60a9ddbd8d5e6ebfcec2d547a..74019bfce26447a8a900b0303f30db42c24325a9 100644 +index 61eda006f090e391b3c0f209e9400920f480c115..8fea8caf17913a2736884caca8f6087f1fb48d15 100644 --- a/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs +++ b/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs -@@ -110,6 +110,12 @@ EnterprisePoliciesManager.prototype = { +@@ -108,6 +108,12 @@ EnterprisePoliciesManager.prototype = { Services.prefs.clearUserPref(PREF_POLICIES_APPLIED); } @@ -2639,10 +2639,10 @@ index 654903fadb709be976b72f36f155e23bc0622152..815b3dc24c9fda6b1db6c4666ac68904 int32_t aMaxSelfProgress, int32_t aCurTotalProgress, diff --git a/toolkit/components/windowwatcher/nsWindowWatcher.cpp b/toolkit/components/windowwatcher/nsWindowWatcher.cpp -index 72ee99c7341de54f2282e3f2a686d6a33b26ecaf..4a739e7cad294cf809a697da905253adee4e24b1 100644 +index 64e6db10f000a44d78ace878ec49c526cbbc007d..c0855bef1fda02643f8df5fa1111ab2bfd684c55 100644 --- a/toolkit/components/windowwatcher/nsWindowWatcher.cpp +++ b/toolkit/components/windowwatcher/nsWindowWatcher.cpp -@@ -1875,7 +1875,11 @@ uint32_t nsWindowWatcher::CalculateChromeFlagsForContent( +@@ -1881,7 +1881,11 @@ uint32_t nsWindowWatcher::CalculateChromeFlagsForContent( // Open a minimal popup. *aIsPopupRequested = true; @@ -2656,10 +2656,10 @@ index 72ee99c7341de54f2282e3f2a686d6a33b26ecaf..4a739e7cad294cf809a697da905253ad /** diff --git a/toolkit/mozapps/update/UpdateService.sys.mjs b/toolkit/mozapps/update/UpdateService.sys.mjs -index a0ba7fe61e0449a7302302429993091075950c21..15e89c7d8000e66bbbf97dd0bb00da3e710e5ec0 100644 +index 1043c4bc4e0df857f9e9a8bd83756dbe8b0053bd..964ce8b3ff04dda5856b5d1fa447022b0ddd0066 100644 --- a/toolkit/mozapps/update/UpdateService.sys.mjs +++ b/toolkit/mozapps/update/UpdateService.sys.mjs -@@ -3848,6 +3848,8 @@ UpdateService.prototype = { +@@ -3820,6 +3820,8 @@ UpdateService.prototype = { }, get disabledForTesting() { @@ -2681,23 +2681,22 @@ index d2ccd8748228b04c84754f9a6dce2ca3bf991e47..d3a8ea1d9994f724cd52cecd4d2cd285 ] diff --git a/toolkit/xre/nsWindowsWMain.cpp b/toolkit/xre/nsWindowsWMain.cpp -index ea14a59b80bbfbaa17d7569734b8409d9d21fcde..28cb052c3115f91e6a036ad8466385ff1d740cd0 100644 +index 7eb9e1104682d4eb47060654f43a1efa8b2a6bb2..a8315d6decf654b5302bea5beeea34140c300ded 100644 --- a/toolkit/xre/nsWindowsWMain.cpp +++ b/toolkit/xre/nsWindowsWMain.cpp -@@ -14,9 +14,11 @@ +@@ -14,8 +14,10 @@ #endif #include "mozilla/Char16.h" +#include "mozilla/CmdLineAndEnvUtils.h" #include "nsUTF8Utils.h" - #include "nsWindowsHelpers.h" +#include #include - #include - -@@ -130,6 +132,19 @@ int wmain(int argc, WCHAR** argv) { + #ifdef __MINGW32__ +@@ -114,6 +116,19 @@ static void FreeAllocStrings(int argc, char** argv) { + int wmain(int argc, WCHAR** argv) { SanitizeEnvironmentVariables(); SetDllDirectoryW(L""); + bool hasJugglerPipe = @@ -2717,10 +2716,10 @@ index ea14a59b80bbfbaa17d7569734b8409d9d21fcde..28cb052c3115f91e6a036ad8466385ff // Only run this code if LauncherProcessWin.h was included beforehand, thus // signalling that the hosting process should support launcher mode. diff --git a/uriloader/base/nsDocLoader.cpp b/uriloader/base/nsDocLoader.cpp -index e1e46ccdceae595f95d100116ff480905047e82b..eaa0252e768140120158525723ad867b8cb020be 100644 +index 1215c0e32691e50605787a9af4a511e8958c64c3..3545cd1c7d65da2deffd761ed7abc77fd81a600f 100644 --- a/uriloader/base/nsDocLoader.cpp +++ b/uriloader/base/nsDocLoader.cpp -@@ -830,6 +830,13 @@ void nsDocLoader::DocLoaderIsEmpty(bool aFlushLayout, +@@ -828,6 +828,13 @@ void nsDocLoader::DocLoaderIsEmpty(bool aFlushLayout, ("DocLoader:%p: Firing load event for document.open\n", this)); @@ -2735,7 +2734,7 @@ index e1e46ccdceae595f95d100116ff480905047e82b..eaa0252e768140120158525723ad867b // nsDocumentViewer::LoadComplete that doesn't do various things // that are not relevant here because this wasn't an actual diff --git a/uriloader/exthandler/nsExternalHelperAppService.cpp b/uriloader/exthandler/nsExternalHelperAppService.cpp -index ee210396f0e7265180f07f6f034c9389a87a02ce..6c0997ecb153547558ad1a0c146b0ca4e8ffbc46 100644 +index 4573e28470c5112f5ac2c5dd53e7a9d1ceedb943..b53b86d8e39f1de4b0d0f1a8d5d7295ea050b878 100644 --- a/uriloader/exthandler/nsExternalHelperAppService.cpp +++ b/uriloader/exthandler/nsExternalHelperAppService.cpp @@ -112,6 +112,7 @@ @@ -2746,7 +2745,7 @@ index ee210396f0e7265180f07f6f034c9389a87a02ce..6c0997ecb153547558ad1a0c146b0ca4 #include "mozilla/Preferences.h" #include "mozilla/ipc/URIUtils.h" -@@ -836,6 +837,12 @@ NS_IMETHODIMP nsExternalHelperAppService::ApplyDecodingForExtension( +@@ -831,6 +832,12 @@ NS_IMETHODIMP nsExternalHelperAppService::ApplyDecodingForExtension( return NS_OK; } @@ -2759,7 +2758,7 @@ index ee210396f0e7265180f07f6f034c9389a87a02ce..6c0997ecb153547558ad1a0c146b0ca4 nsresult nsExternalHelperAppService::GetFileTokenForPath( const char16_t* aPlatformAppPath, nsIFile** aFile) { nsDependentString platformAppPath(aPlatformAppPath); -@@ -1446,7 +1453,12 @@ nsresult nsExternalAppHandler::SetUpTempFile(nsIChannel* aChannel) { +@@ -1442,7 +1449,12 @@ nsresult nsExternalAppHandler::SetUpTempFile(nsIChannel* aChannel) { // Strip off the ".part" from mTempLeafName mTempLeafName.Truncate(mTempLeafName.Length() - ArrayLength(".part") + 1); @@ -2772,7 +2771,7 @@ index ee210396f0e7265180f07f6f034c9389a87a02ce..6c0997ecb153547558ad1a0c146b0ca4 mSaver = do_CreateInstance(NS_BACKGROUNDFILESAVERSTREAMLISTENER_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); -@@ -1635,7 +1647,36 @@ NS_IMETHODIMP nsExternalAppHandler::OnStartRequest(nsIRequest* request) { +@@ -1631,7 +1643,36 @@ NS_IMETHODIMP nsExternalAppHandler::OnStartRequest(nsIRequest* request) { return NS_OK; } @@ -2810,7 +2809,7 @@ index ee210396f0e7265180f07f6f034c9389a87a02ce..6c0997ecb153547558ad1a0c146b0ca4 if (NS_FAILED(rv)) { nsresult transferError = rv; -@@ -1687,6 +1728,9 @@ NS_IMETHODIMP nsExternalAppHandler::OnStartRequest(nsIRequest* request) { +@@ -1683,6 +1724,9 @@ NS_IMETHODIMP nsExternalAppHandler::OnStartRequest(nsIRequest* request) { bool alwaysAsk = true; mMimeInfo->GetAlwaysAskBeforeHandling(&alwaysAsk); @@ -2820,7 +2819,7 @@ index ee210396f0e7265180f07f6f034c9389a87a02ce..6c0997ecb153547558ad1a0c146b0ca4 if (alwaysAsk) { // But we *don't* ask if this mimeInfo didn't come from // our user configuration datastore and the user has said -@@ -2193,6 +2237,16 @@ nsExternalAppHandler::OnSaveComplete(nsIBackgroundFileSaver* aSaver, +@@ -2199,6 +2243,16 @@ nsExternalAppHandler::OnSaveComplete(nsIBackgroundFileSaver* aSaver, NotifyTransfer(aStatus); } @@ -2837,7 +2836,7 @@ index ee210396f0e7265180f07f6f034c9389a87a02ce..6c0997ecb153547558ad1a0c146b0ca4 return NS_OK; } -@@ -2674,6 +2728,15 @@ NS_IMETHODIMP nsExternalAppHandler::Cancel(nsresult aReason) { +@@ -2680,6 +2734,15 @@ NS_IMETHODIMP nsExternalAppHandler::Cancel(nsresult aReason) { } } @@ -2854,7 +2853,7 @@ index ee210396f0e7265180f07f6f034c9389a87a02ce..6c0997ecb153547558ad1a0c146b0ca4 // OnStartRequest) mDialog = nullptr; diff --git a/uriloader/exthandler/nsExternalHelperAppService.h b/uriloader/exthandler/nsExternalHelperAppService.h -index 6c8cbc5871d3aa721a3f1a3ff6c0ef8b0044c63e..8e7c9af1a2cfe60c9c543af1ab55f6c229000bd4 100644 +index 205f73cfa127e15e171854165c92551fea957e84..6399525ea0abb55655c824b086a043d022392113 100644 --- a/uriloader/exthandler/nsExternalHelperAppService.h +++ b/uriloader/exthandler/nsExternalHelperAppService.h @@ -257,6 +257,8 @@ class nsExternalHelperAppService : public nsIExternalHelperAppService, @@ -2866,7 +2865,7 @@ index 6c8cbc5871d3aa721a3f1a3ff6c0ef8b0044c63e..8e7c9af1a2cfe60c9c543af1ab55f6c2 }; /** -@@ -456,6 +458,9 @@ class nsExternalAppHandler final : public nsIStreamListener, +@@ -462,6 +464,9 @@ class nsExternalAppHandler final : public nsIStreamListener, * Upon successful return, both mTempFile and mSaver will be valid. */ nsresult SetUpTempFile(nsIChannel* aChannel); @@ -2877,19 +2876,22 @@ index 6c8cbc5871d3aa721a3f1a3ff6c0ef8b0044c63e..8e7c9af1a2cfe60c9c543af1ab55f6c2 * When we download a helper app, we are going to retarget all load * notifications into our own docloader and load group instead of diff --git a/uriloader/exthandler/nsIExternalHelperAppService.idl b/uriloader/exthandler/nsIExternalHelperAppService.idl -index 307e6196a89df52d0bccc3ebd1359f58e32de75d..c3692d0f76178ac3aeb1c77a0e973bfa22359346 100644 +index 4a399acb72d4fd475c9ae43e9eadbc32f261e290..31e9490a7dfd7d7eff69ad23c9ce277f367d1524 100644 --- a/uriloader/exthandler/nsIExternalHelperAppService.idl +++ b/uriloader/exthandler/nsIExternalHelperAppService.idl -@@ -6,6 +6,8 @@ +@@ -6,8 +6,11 @@ #include "nsICancelable.idl" +webidl BrowsingContext; +interface nsIHelperAppLauncher; interface nsIURI; - interface nsIRequest; + interface nsIChannel; ++interface nsIRequest; interface nsIStreamListener; -@@ -15,6 +17,17 @@ interface nsIWebProgressListener2; + interface nsIFile; + interface nsIMIMEInfo; +@@ -15,6 +18,17 @@ interface nsIWebProgressListener2; interface nsIInterfaceRequestor; webidl BrowsingContext; @@ -2907,7 +2909,7 @@ index 307e6196a89df52d0bccc3ebd1359f58e32de75d..c3692d0f76178ac3aeb1c77a0e973bfa /** * The external helper app service is used for finding and launching * platform specific external applications for a given mime content type. -@@ -76,6 +89,7 @@ interface nsIExternalHelperAppService : nsISupports +@@ -76,6 +90,7 @@ interface nsIExternalHelperAppService : nsISupports boolean applyDecodingForExtension(in AUTF8String aExtension, in ACString aEncodingType); @@ -2944,10 +2946,10 @@ index 1c25e9d9a101233f71e92288a0f93125b81ac1c5..22cf67b0f6e3ddd2b3ed725a314ba6a9 } #endif diff --git a/widget/MouseEvents.h b/widget/MouseEvents.h -index 5a19cb4082674ede982a0c66c84bf7c4642abe2b..5fe6ae7b5bf605e5d9130aa164d7cbbb486e54e0 100644 +index dafdcf64661112df3bce1d56b4b7a2519aa49838..58916791b1b79093cd2db188116348dc1544ddaa 100644 --- a/widget/MouseEvents.h +++ b/widget/MouseEvents.h -@@ -203,6 +203,7 @@ class WidgetMouseEvent : public WidgetMouseEventBase, +@@ -204,6 +204,7 @@ class WidgetMouseEvent : public WidgetMouseEventBase, : mReason(eReal), mContextMenuTrigger(eNormal), mClickCount(0), @@ -2955,7 +2957,7 @@ index 5a19cb4082674ede982a0c66c84bf7c4642abe2b..5fe6ae7b5bf605e5d9130aa164d7cbbb mIgnoreRootScrollFrame(false), mUseLegacyNonPrimaryDispatch(false), mClickEventPrevented(false) {} -@@ -213,6 +214,7 @@ class WidgetMouseEvent : public WidgetMouseEventBase, +@@ -216,6 +217,7 @@ class WidgetMouseEvent : public WidgetMouseEventBase, mReason(aReason), mContextMenuTrigger(eNormal), mClickCount(0), @@ -2963,7 +2965,7 @@ index 5a19cb4082674ede982a0c66c84bf7c4642abe2b..5fe6ae7b5bf605e5d9130aa164d7cbbb mIgnoreRootScrollFrame(false), mUseLegacyNonPrimaryDispatch(false), mClickEventPrevented(false) {} -@@ -231,6 +233,7 @@ class WidgetMouseEvent : public WidgetMouseEventBase, +@@ -236,6 +238,7 @@ class WidgetMouseEvent : public WidgetMouseEventBase, mReason(aReason), mContextMenuTrigger(aContextMenuTrigger), mClickCount(0), @@ -2971,7 +2973,7 @@ index 5a19cb4082674ede982a0c66c84bf7c4642abe2b..5fe6ae7b5bf605e5d9130aa164d7cbbb mIgnoreRootScrollFrame(false), mUseLegacyNonPrimaryDispatch(false), mClickEventPrevented(false) { -@@ -280,6 +283,9 @@ class WidgetMouseEvent : public WidgetMouseEventBase, +@@ -285,6 +288,9 @@ class WidgetMouseEvent : public WidgetMouseEventBase, // Otherwise, this must be 0. uint32_t mClickCount; @@ -2981,7 +2983,7 @@ index 5a19cb4082674ede982a0c66c84bf7c4642abe2b..5fe6ae7b5bf605e5d9130aa164d7cbbb // Whether the event should ignore scroll frame bounds during dispatch. bool mIgnoreRootScrollFrame; -@@ -296,6 +302,7 @@ class WidgetMouseEvent : public WidgetMouseEventBase, +@@ -301,6 +307,7 @@ class WidgetMouseEvent : public WidgetMouseEventBase, mExitFrom = aEvent.mExitFrom; mClickCount = aEvent.mClickCount; @@ -3244,7 +3246,7 @@ index 9856991ef32f25f51942f8cd664a09bec2192c70..948947a421179e91c51005aeb83ed0d1 ~HeadlessWidget(); bool mEnabled; diff --git a/widget/nsGUIEventIPC.h b/widget/nsGUIEventIPC.h -index a4cd12a151e7a172389affb34adf0a4085e597a5..fb4c340644255b11a15cf5a3587d950ae4f091d7 100644 +index e8b831233338630c3106fd9debeba128228d3e0c..60422b1a1734e1bdeba7b6083727e29f0e5e9f35 100644 --- a/widget/nsGUIEventIPC.h +++ b/widget/nsGUIEventIPC.h @@ -234,6 +234,7 @@ struct ParamTraits { diff --git a/browser_patches/webkit/UPSTREAM_CONFIG.sh b/browser_patches/webkit/UPSTREAM_CONFIG.sh index d72c74d470..269f98f234 100644 --- a/browser_patches/webkit/UPSTREAM_CONFIG.sh +++ b/browser_patches/webkit/UPSTREAM_CONFIG.sh @@ -1,3 +1,3 @@ REMOTE_URL="https://github.com/WebKit/WebKit.git" BASE_BRANCH="main" -BASE_REVISION="30884546903f1ba774adb0cbef1adc91c6c53c64" +BASE_REVISION="3facd67e2518ff15efe1b6cda0810e4c76e9c482" diff --git a/browser_patches/webkit/patches/bootstrap.diff b/browser_patches/webkit/patches/bootstrap.diff index b3e9920535..15cb218a6c 100644 --- a/browser_patches/webkit/patches/bootstrap.diff +++ b/browser_patches/webkit/patches/bootstrap.diff @@ -1,8 +1,8 @@ diff --git a/Source/JavaScriptCore/CMakeLists.txt b/Source/JavaScriptCore/CMakeLists.txt -index be40cc1430aaa1d3a2cd5a1675e9180a81eddc37..e7b8a2677a349a6ba5c9f64bbc81bd7210c38900 100644 +index 4579f72f38f61e1b14f2e5fc13c7cdd3f5e1f6c8..6cdfdfa08cdb7c7193b33ac7400af5cf74d8b06a 100644 --- a/Source/JavaScriptCore/CMakeLists.txt +++ b/Source/JavaScriptCore/CMakeLists.txt -@@ -1417,22 +1417,27 @@ set(JavaScriptCore_INSPECTOR_DOMAINS +@@ -1418,22 +1418,27 @@ set(JavaScriptCore_INSPECTOR_DOMAINS ${JAVASCRIPTCORE_DIR}/inspector/protocol/CSS.json ${JAVASCRIPTCORE_DIR}/inspector/protocol/Canvas.json ${JAVASCRIPTCORE_DIR}/inspector/protocol/Console.json @@ -199,7 +199,7 @@ index 14585eabd64a109573bed2336643f4c52e11f180..a1c34d3891405f1c8f6148a031f5045d return nullptr; inspectorObject->setValue(name.string(), inspectorValue.releaseNonNull()); diff --git a/Source/JavaScriptCore/inspector/InjectedScriptSource.js b/Source/JavaScriptCore/inspector/InjectedScriptSource.js -index a665049c589ad59f92b147ef2e9e058eb72bb67c..71f4db75938e830e5d8e201c291c17da3aaff6c9 100644 +index 25d86b6deecce7fb5f0b0d0a0031c0bf737531ae..f9a9a09d5659824697cb11eebf69192002bceacf 100644 --- a/Source/JavaScriptCore/inspector/InjectedScriptSource.js +++ b/Source/JavaScriptCore/inspector/InjectedScriptSource.js @@ -172,7 +172,7 @@ let InjectedScript = class InjectedScript extends PrototypelessObjectBase @@ -352,10 +352,10 @@ index 0cc2127c9c12c2d82dea9550bad73f4ffb99ba24..8ca65cc042d435cbc0e05dcc5c5dfc95 } diff --git a/Source/JavaScriptCore/inspector/InspectorTarget.h b/Source/JavaScriptCore/inspector/InspectorTarget.h -index 4b95964db4d902b4b7f4b0b4c40afea51654ff2f..653842a82ed7a7be8603c9ef88ff48d1cda421be 100644 +index 80559d39722b74e325d513ea22054b0d399a4e8f..24977f9dfcfcdb29a20d178be608aca855cca532 100644 --- a/Source/JavaScriptCore/inspector/InspectorTarget.h +++ b/Source/JavaScriptCore/inspector/InspectorTarget.h -@@ -56,8 +56,12 @@ public: +@@ -57,8 +57,12 @@ public: virtual void connect(FrontendChannel::ConnectionType) = 0; virtual void disconnect() = 0; virtual void sendMessageToTargetBackend(const String&) = 0; @@ -400,7 +400,7 @@ index 6e573c4dfd1f356b76ef9b46dcee4254e9a28f27..8855604064f5130211baab6caa89318c void warnUnimplemented(const String& method); void internalAddMessage(MessageType, MessageLevel, JSC::JSGlobalObject*, Ref&&); diff --git a/Source/JavaScriptCore/inspector/agents/InspectorRuntimeAgent.cpp b/Source/JavaScriptCore/inspector/agents/InspectorRuntimeAgent.cpp -index aef96857b46e980a12f2c0a4d38d6035c024ac99..2eb163fe20cbbd975c7f49d9835485152057993d 100644 +index 8b8ca251a7fde9bfa0462f8316eb083f8a3a03dd..846d75708c33ff6aa478e03d722770cedd878d00 100644 --- a/Source/JavaScriptCore/inspector/agents/InspectorRuntimeAgent.cpp +++ b/Source/JavaScriptCore/inspector/agents/InspectorRuntimeAgent.cpp @@ -177,41 +177,43 @@ void InspectorRuntimeAgent::awaitPromise(const Protocol::Runtime::RemoteObjectId @@ -1016,7 +1016,7 @@ index 96af27ece2ac200e11c4311b3ca0d9d3b5a048da..3168f7806fcbdabec07acc5e304bae1e ], "events": [ diff --git a/Source/JavaScriptCore/inspector/protocol/Page.json b/Source/JavaScriptCore/inspector/protocol/Page.json -index b5e2bb2eb58765ec20392b36bf5ef1ac35857a69..83ac8c3cbc4a57a27e99a48323fe61a936c10a8b 100644 +index b5e2bb2eb58765ec20392b36bf5ef1ac35857a69..5a7b2e4d3bd7cb99f182c543da756b394a9dfcd6 100644 --- a/Source/JavaScriptCore/inspector/protocol/Page.json +++ b/Source/JavaScriptCore/inspector/protocol/Page.json @@ -20,7 +20,14 @@ @@ -1048,7 +1048,7 @@ index b5e2bb2eb58765ec20392b36bf5ef1ac35857a69..83ac8c3cbc4a57a27e99a48323fe61a9 { "id": "Frame", "type": "object", -@@ -125,6 +138,51 @@ +@@ -125,6 +138,50 @@ { "name": "secure", "type": "boolean", "description": "True if cookie is secure." }, { "name": "sameSite", "$ref": "CookieSameSitePolicy", "description": "Cookie Same-Site policy." } ] @@ -1069,7 +1069,6 @@ index b5e2bb2eb58765ec20392b36bf5ef1ac35857a69..83ac8c3cbc4a57a27e99a48323fe61a9 + { "name": "expanded", "type": "boolean", "optional": true, "description": "Whether the node is expanded or collapsed."}, + { "name": "focused", "type": "boolean", "optional": true, "description": "Whether the node is focused."}, + { "name": "modal", "type": "boolean", "optional": true, "description": "Whether the node is modal."}, -+ { "name": "multiline", "type": "boolean", "optional": true, "description": "Whether the node text input supports multiline."}, + { "name": "multiselectable", "type": "boolean", "optional": true, "description": "Whether more than one child can be selected."}, + { "name": "readonly", "type": "boolean", "optional": true, "description": "Whether the node is read only."}, + { "name": "required", "type": "boolean", "optional": true, "description": "Whether the node is required."}, @@ -1100,7 +1099,7 @@ index b5e2bb2eb58765ec20392b36bf5ef1ac35857a69..83ac8c3cbc4a57a27e99a48323fe61a9 } ], "commands": [ -@@ -144,6 +202,14 @@ +@@ -144,6 +201,14 @@ { "name": "revalidateAllResources", "type": "boolean", "optional": true, "description": "If true, all cached subresources will be revalidated when the main resource loads. Otherwise, only expired cached subresources will be revalidated (the default behavior for most WebKit clients)." } ] }, @@ -1115,7 +1114,7 @@ index b5e2bb2eb58765ec20392b36bf5ef1ac35857a69..83ac8c3cbc4a57a27e99a48323fe61a9 { "name": "navigate", "description": "Navigates current page to the given URL.", -@@ -160,6 +226,14 @@ +@@ -160,6 +225,14 @@ { "name": "value", "type": "string", "optional": true, "description": "Value to override the user agent with. If this value is not provided, the override is removed. Overrides are removed when Web Inspector closes/disconnects." } ] }, @@ -1130,7 +1129,7 @@ index b5e2bb2eb58765ec20392b36bf5ef1ac35857a69..83ac8c3cbc4a57a27e99a48323fe61a9 { "name": "overrideSetting", "description": "Allows the frontend to override the inspected page's settings.", -@@ -226,7 +300,8 @@ +@@ -226,7 +299,8 @@ "name": "setBootstrapScript", "targetTypes": ["page"], "parameters": [ @@ -1140,7 +1139,7 @@ index b5e2bb2eb58765ec20392b36bf5ef1ac35857a69..83ac8c3cbc4a57a27e99a48323fe61a9 ] }, { -@@ -283,6 +358,28 @@ +@@ -283,6 +357,28 @@ { "name": "media", "type": "string", "description": "Media type to emulate. Empty string disables the override." } ] }, @@ -1169,7 +1168,7 @@ index b5e2bb2eb58765ec20392b36bf5ef1ac35857a69..83ac8c3cbc4a57a27e99a48323fe61a9 { "name": "snapshotNode", "description": "Capture a snapshot of the specified node that does not include unrelated layers.", -@@ -303,7 +400,8 @@ +@@ -303,7 +399,8 @@ { "name": "y", "type": "integer", "description": "Y coordinate" }, { "name": "width", "type": "integer", "description": "Rectangle width" }, { "name": "height", "type": "integer", "description": "Rectangle height" }, @@ -1179,7 +1178,7 @@ index b5e2bb2eb58765ec20392b36bf5ef1ac35857a69..83ac8c3cbc4a57a27e99a48323fe61a9 ], "returns": [ { "name": "dataURL", "type": "string", "description": "Base64-encoded image data (PNG)." } -@@ -321,12 +419,92 @@ +@@ -321,12 +418,92 @@ { "name": "setScreenSizeOverride", "description": "Overrides screen size exposed to DOM and used in media queries for testing with provided values.", @@ -1273,7 +1272,7 @@ index b5e2bb2eb58765ec20392b36bf5ef1ac35857a69..83ac8c3cbc4a57a27e99a48323fe61a9 } ], "events": [ -@@ -334,14 +512,16 @@ +@@ -334,14 +511,16 @@ "name": "domContentEventFired", "targetTypes": ["page"], "parameters": [ @@ -1292,7 +1291,7 @@ index b5e2bb2eb58765ec20392b36bf5ef1ac35857a69..83ac8c3cbc4a57a27e99a48323fe61a9 ] }, { -@@ -351,6 +531,14 @@ +@@ -351,6 +530,14 @@ { "name": "frame", "$ref": "Frame", "description": "Frame object." } ] }, @@ -1307,7 +1306,7 @@ index b5e2bb2eb58765ec20392b36bf5ef1ac35857a69..83ac8c3cbc4a57a27e99a48323fe61a9 { "name": "frameDetached", "description": "Fired when frame has been detached from its parent.", -@@ -379,7 +567,8 @@ +@@ -379,7 +566,8 @@ "targetTypes": ["page"], "parameters": [ { "name": "frameId", "$ref": "Network.FrameId", "description": "Id of the frame that has scheduled a navigation." }, @@ -1317,7 +1316,7 @@ index b5e2bb2eb58765ec20392b36bf5ef1ac35857a69..83ac8c3cbc4a57a27e99a48323fe61a9 ] }, { -@@ -390,6 +579,22 @@ +@@ -390,6 +578,22 @@ { "name": "frameId", "$ref": "Network.FrameId", "description": "Id of the frame that has cleared its scheduled navigation." } ] }, @@ -1340,7 +1339,7 @@ index b5e2bb2eb58765ec20392b36bf5ef1ac35857a69..83ac8c3cbc4a57a27e99a48323fe61a9 { "name": "defaultUserPreferencesDidChange", "description": "Fired when the default value of a user preference changes at the system level.", -@@ -397,6 +602,42 @@ +@@ -397,6 +601,42 @@ "parameters": [ { "name": "preferences", "type": "array", "items": { "$ref": "UserPreference" }, "description": "List of user preferences that can be overriden and their new system (default) values." } ] @@ -1385,10 +1384,10 @@ index b5e2bb2eb58765ec20392b36bf5ef1ac35857a69..83ac8c3cbc4a57a27e99a48323fe61a9 } diff --git a/Source/JavaScriptCore/inspector/protocol/Playwright.json b/Source/JavaScriptCore/inspector/protocol/Playwright.json new file mode 100644 -index 0000000000000000000000000000000000000000..8434303f2aad60557f049d33e50245ddebb07599 +index 0000000000000000000000000000000000000000..978d6f92b18498633c982969299f068332eb2884 --- /dev/null +++ b/Source/JavaScriptCore/inspector/protocol/Playwright.json -@@ -0,0 +1,300 @@ +@@ -0,0 +1,307 @@ +{ + "domain": "Playwright", + "availability": ["web"], @@ -1479,6 +1478,12 @@ index 0000000000000000000000000000000000000000..8434303f2aad60557f049d33e50245dd + "name": "disable" + }, + { ++ "name": "getInfo", ++ "returns": [ ++ { "name": "os", "type": "string", "description": "Name of the operating system where the browser is running (macOS, Linux or Windows)." } ++ ] ++ }, ++ { + "name": "close", + "async": true, + "description": "Close browser." @@ -1536,6 +1541,7 @@ index 0000000000000000000000000000000000000000..8434303f2aad60557f049d33e50245dd + { + "name": "takePageScreenshot", + "description": "Capture a snapshot of the page.", ++ "async": true, + "parameters": [ + { "name": "pageProxyId", "$ref": "PageProxyID", "description": "Unique identifier of the page proxy." }, + { "name": "x", "type": "integer", "description": "X coordinate" }, @@ -1897,7 +1903,7 @@ index 72c81757450ad5ebacd5fd20d2a16095514802ec..b7d8ab1e04d3850180079870468b28ef private: enum ArgumentRequirement { ArgumentRequired, ArgumentNotRequired }; diff --git a/Source/ThirdParty/libwebrtc/CMakeLists.txt b/Source/ThirdParty/libwebrtc/CMakeLists.txt -index 133380fe13fcd59d324d5b1c53bfebf855a04750..5f3fad8408d75dc1b5fb6725569badca6617cf49 100644 +index 0c300bedc697024ca511e43d480f3b7205df3ed6..e54875b46c558903a6b6157833b82ec8ce0ac1f1 100644 --- a/Source/ThirdParty/libwebrtc/CMakeLists.txt +++ b/Source/ThirdParty/libwebrtc/CMakeLists.txt @@ -529,6 +529,11 @@ set(webrtc_SOURCES @@ -1912,7 +1918,7 @@ index 133380fe13fcd59d324d5b1c53bfebf855a04750..5f3fad8408d75dc1b5fb6725569badca Source/third_party/libyuv/source/compare.cc Source/third_party/libyuv/source/compare_common.cc Source/third_party/libyuv/source/compare_gcc.cc -@@ -2124,6 +2129,10 @@ set(webrtc_INCLUDE_DIRECTORIES PRIVATE +@@ -2126,6 +2131,10 @@ set(webrtc_INCLUDE_DIRECTORIES PRIVATE Source/third_party/libsrtp/config Source/third_party/libsrtp/crypto/include Source/third_party/libsrtp/include @@ -1953,7 +1959,7 @@ index c3ba9e19caff72011010471c8c46c91b53a22498..d47a93e1f7fe9f4d52376dcc4dcc9242 +_vpx_codec_version_str +_vpx_codec_vp8_cx diff --git a/Source/ThirdParty/libwebrtc/Configurations/libwebrtc.xcconfig b/Source/ThirdParty/libwebrtc/Configurations/libwebrtc.xcconfig -index 39ddbe721ba57835c45a4e590726c7a7c99022c4..4020e136c002c0b768f266d87f656585e748d498 100644 +index 4d9ece9ff41411d2f1812671ff210289f4b2da01..b5b3f73accea435a4d0434b06787a4d945417807 100644 --- a/Source/ThirdParty/libwebrtc/Configurations/libwebrtc.xcconfig +++ b/Source/ThirdParty/libwebrtc/Configurations/libwebrtc.xcconfig @@ -37,7 +37,7 @@ DYLIB_INSTALL_NAME_BASE_WK_RELOCATABLE_FRAMEWORKS_ = $(NORMAL_UMBRELLA_FRAMEWORK @@ -1987,11 +1993,11 @@ index bcd9e02bc019e17799fe812d7d9a4c7c316b3456..909bbac68574129ea60af831f30de59e rtt = CompactNtpRttToTimeDelta( receive_time_ntp - block.delay_since_last_sr() - block.last_sr()); diff --git a/Source/ThirdParty/libwebrtc/libwebrtc.xcodeproj/project.pbxproj b/Source/ThirdParty/libwebrtc/libwebrtc.xcodeproj/project.pbxproj -index 6550be991328f094d74b7c5aa709fe8c5d399f54..5810c4fd8a1257aaad612f67ab5a350ae90615de 100644 +index c6fde0910102421de6c77e1d1dbc52ffe7ed7098..cb122ff5f30d68fc168fbf80367c85e091097e30 100644 --- a/Source/ThirdParty/libwebrtc/libwebrtc.xcodeproj/project.pbxproj +++ b/Source/ThirdParty/libwebrtc/libwebrtc.xcodeproj/project.pbxproj @@ -6,6 +6,20 @@ - objectVersion = 54; + objectVersion = 52; objects = { +/* Begin PBXAggregateTarget section */ @@ -2011,7 +2017,7 @@ index 6550be991328f094d74b7c5aa709fe8c5d399f54..5810c4fd8a1257aaad612f67ab5a350a /* Begin PBXBuildFile section */ 2D6BFF60280A93DF00A1A74F /* video_coding.h in Headers */ = {isa = PBXBuildFile; fileRef = 4131C45B234C81710028A615 /* video_coding.h */; settings = {ATTRIBUTES = (Public, ); }; }; 2D6BFF61280A93EC00A1A74F /* video_codec_initializer.h in Headers */ = {isa = PBXBuildFile; fileRef = 4131C45E234C81720028A615 /* video_codec_initializer.h */; settings = {ATTRIBUTES = (Public, ); }; }; -@@ -4947,6 +4961,9 @@ +@@ -4952,6 +4966,9 @@ DDF30D9127C5C725006A526F /* receive_side_congestion_controller.h in Headers */ = {isa = PBXBuildFile; fileRef = DDF30D9027C5C725006A526F /* receive_side_congestion_controller.h */; }; DDF30D9527C5C756006A526F /* bwe_defines.h in Headers */ = {isa = PBXBuildFile; fileRef = DDF30D9327C5C756006A526F /* bwe_defines.h */; }; DDF30D9627C5C756006A526F /* remote_bitrate_estimator.h in Headers */ = {isa = PBXBuildFile; fileRef = DDF30D9427C5C756006A526F /* remote_bitrate_estimator.h */; }; @@ -2021,7 +2027,7 @@ index 6550be991328f094d74b7c5aa709fe8c5d399f54..5810c4fd8a1257aaad612f67ab5a350a /* End PBXBuildFile section */ /* Begin PBXBuildRule section */ -@@ -5211,6 +5228,13 @@ +@@ -5230,6 +5247,13 @@ remoteGlobalIDString = DDF30D0527C5C003006A526F; remoteInfo = absl; }; @@ -2035,7 +2041,7 @@ index 6550be991328f094d74b7c5aa709fe8c5d399f54..5810c4fd8a1257aaad612f67ab5a350a /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ -@@ -10562,6 +10586,9 @@ +@@ -10593,6 +10617,9 @@ DDF30D9027C5C725006A526F /* receive_side_congestion_controller.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = receive_side_congestion_controller.h; sourceTree = ""; }; DDF30D9327C5C756006A526F /* bwe_defines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = bwe_defines.h; sourceTree = ""; }; DDF30D9427C5C756006A526F /* remote_bitrate_estimator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = remote_bitrate_estimator.h; sourceTree = ""; }; @@ -2045,7 +2051,7 @@ index 6550be991328f094d74b7c5aa709fe8c5d399f54..5810c4fd8a1257aaad612f67ab5a350a FB39D0D11200F0E300088E69 /* libwebrtc.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = libwebrtc.dylib; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ -@@ -18854,6 +18881,7 @@ +@@ -18919,6 +18946,7 @@ isa = PBXGroup; children = ( CDFD2F9224C4B2F90048DAC3 /* common */, @@ -2053,7 +2059,7 @@ index 6550be991328f094d74b7c5aa709fe8c5d399f54..5810c4fd8a1257aaad612f67ab5a350a CDEBB19224C0191800ADBD44 /* webm_parser */, ); path = libwebm; -@@ -19295,6 +19323,16 @@ +@@ -19360,6 +19388,16 @@ path = include; sourceTree = ""; }; @@ -2070,7 +2076,7 @@ index 6550be991328f094d74b7c5aa709fe8c5d399f54..5810c4fd8a1257aaad612f67ab5a350a FB39D06E1200ED9200088E69 = { isa = PBXGroup; children = ( -@@ -22185,6 +22223,7 @@ +@@ -22287,6 +22325,7 @@ ); dependencies = ( 410B3827292B73E90003E515 /* PBXTargetDependency */, @@ -2078,15 +2084,15 @@ index 6550be991328f094d74b7c5aa709fe8c5d399f54..5810c4fd8a1257aaad612f67ab5a350a DD2E76E827C6B69A00F2A74C /* PBXTargetDependency */, CDEBB4CC24C01AB400ADBD44 /* PBXTargetDependency */, 411ED040212E0811004320BA /* PBXTargetDependency */, -@@ -22244,6 +22283,7 @@ - CDEBB11824C0187400ADBD44 /* webm */, - DDEBB11824C0187400ADBD44 /* aom */, +@@ -22351,6 +22390,7 @@ DDF30D0527C5C003006A526F /* absl */, + 448D48332AB0BDB00065014C /* fuzz-libvpx-vp8 */, + 44C20E892AB39FA80046C6A8 /* fuzz-libvpx-vp9 */, + F31720AC27FE215900EEE407 /* Copy libvpx headers */, ); }; /* End PBXProject section */ -@@ -22326,6 +22366,23 @@ +@@ -22433,6 +22473,23 @@ shellPath = /bin/sh; shellScript = "\"${SRCROOT}/Scripts/create-symlink-to-altroot.sh\"\n"; }; @@ -2110,7 +2116,7 @@ index 6550be991328f094d74b7c5aa709fe8c5d399f54..5810c4fd8a1257aaad612f67ab5a350a /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ -@@ -24077,6 +24134,9 @@ +@@ -24200,6 +24257,9 @@ 5CDD865E1E43B8B500621E92 /* min_max_operations.c in Sources */, 4189395B242A71F5007FDC41 /* min_video_bitrate_experiment.cc in Sources */, 41B8D8FB28CB85CB00E5FA37 /* missing_mandatory_parameter_cause.cc in Sources */, @@ -2120,7 +2126,7 @@ index 6550be991328f094d74b7c5aa709fe8c5d399f54..5810c4fd8a1257aaad612f67ab5a350a 4131C387234B957D0028A615 /* moving_average.cc in Sources */, 41FCBB1521B1F7AA00A5DF27 /* moving_average.cc in Sources */, 5CD286101E6A64C90094FDC8 /* moving_max.cc in Sources */, -@@ -24786,6 +24846,11 @@ +@@ -24919,6 +24979,11 @@ target = DDF30D0527C5C003006A526F /* absl */; targetProxy = DD2E76E727C6B69A00F2A74C /* PBXContainerItemProxy */; }; @@ -2132,7 +2138,7 @@ index 6550be991328f094d74b7c5aa709fe8c5d399f54..5810c4fd8a1257aaad612f67ab5a350a /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ -@@ -25013,6 +25078,27 @@ +@@ -25176,6 +25241,27 @@ }; name = Production; }; @@ -2160,7 +2166,7 @@ index 6550be991328f094d74b7c5aa709fe8c5d399f54..5810c4fd8a1257aaad612f67ab5a350a FB39D0711200ED9200088E69 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 5D7C59C71208C68B001C873E /* DebugRelease.xcconfig */; -@@ -25145,6 +25231,16 @@ +@@ -25328,6 +25414,16 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Production; }; @@ -2178,7 +2184,7 @@ index 6550be991328f094d74b7c5aa709fe8c5d399f54..5810c4fd8a1257aaad612f67ab5a350a isa = XCConfigurationList; buildConfigurations = ( diff --git a/Source/WTF/Scripts/Preferences/UnifiedWebPreferences.yaml b/Source/WTF/Scripts/Preferences/UnifiedWebPreferences.yaml -index b9d4024573d25cdd97c8e45e1cf7629cb3a643c7..706e0334ffd49735aa5473777228bc596a64a950 100644 +index 96f1cf72103b3fd8d12a6fa1d46e22951364065c..1649bb242b75b7f794c93928803de14311725aa5 100644 --- a/Source/WTF/Scripts/Preferences/UnifiedWebPreferences.yaml +++ b/Source/WTF/Scripts/Preferences/UnifiedWebPreferences.yaml @@ -590,6 +590,7 @@ AspectRatioOfImgFromWidthAndHeightEnabled: @@ -2198,7 +2204,7 @@ index b9d4024573d25cdd97c8e45e1cf7629cb3a643c7..706e0334ffd49735aa5473777228bc59 WebCore: default: false -@@ -1863,6 +1864,7 @@ CrossOriginEmbedderPolicyEnabled: +@@ -1953,6 +1954,7 @@ CrossOriginEmbedderPolicyEnabled: WebCore: default: false @@ -2206,7 +2212,7 @@ index b9d4024573d25cdd97c8e45e1cf7629cb3a643c7..706e0334ffd49735aa5473777228bc59 CrossOriginOpenerPolicyEnabled: type: bool status: stable -@@ -1873,7 +1875,7 @@ CrossOriginOpenerPolicyEnabled: +@@ -1963,7 +1965,7 @@ CrossOriginOpenerPolicyEnabled: WebKitLegacy: default: false WebKit: @@ -2215,7 +2221,7 @@ index b9d4024573d25cdd97c8e45e1cf7629cb3a643c7..706e0334ffd49735aa5473777228bc59 WebCore: default: false -@@ -1903,7 +1905,7 @@ CustomPasteboardDataEnabled: +@@ -1993,7 +1995,7 @@ CustomPasteboardDataEnabled: WebKitLegacy: default: false WebKit: @@ -2224,7 +2230,7 @@ index b9d4024573d25cdd97c8e45e1cf7629cb3a643c7..706e0334ffd49735aa5473777228bc59 default: false DNSPrefetchingEnabled: -@@ -1948,6 +1950,7 @@ DOMAudioSessionFullEnabled: +@@ -2038,6 +2040,7 @@ DOMAudioSessionFullEnabled: WebCore: default: false @@ -2232,7 +2238,7 @@ index b9d4024573d25cdd97c8e45e1cf7629cb3a643c7..706e0334ffd49735aa5473777228bc59 DOMPasteAccessRequestsEnabled: type: bool status: internal -@@ -1959,7 +1962,7 @@ DOMPasteAccessRequestsEnabled: +@@ -2049,7 +2052,7 @@ DOMPasteAccessRequestsEnabled: default: false WebKit: "PLATFORM(IOS) || PLATFORM(MAC) || PLATFORM(GTK) || PLATFORM(VISION)": true @@ -2241,7 +2247,7 @@ index b9d4024573d25cdd97c8e45e1cf7629cb3a643c7..706e0334ffd49735aa5473777228bc59 WebCore: default: false -@@ -3380,6 +3383,7 @@ InspectorAttachmentSide: +@@ -3474,6 +3477,7 @@ InspectorAttachmentSide: WebKit: default: 0 @@ -2249,7 +2255,7 @@ index b9d4024573d25cdd97c8e45e1cf7629cb3a643c7..706e0334ffd49735aa5473777228bc59 InspectorStartsAttached: type: bool status: embedder -@@ -3387,7 +3391,7 @@ InspectorStartsAttached: +@@ -3481,7 +3485,7 @@ InspectorStartsAttached: exposed: [ WebKit ] defaultValue: WebKit: @@ -2258,7 +2264,7 @@ index b9d4024573d25cdd97c8e45e1cf7629cb3a643c7..706e0334ffd49735aa5473777228bc59 InspectorWindowFrame: type: String -@@ -3755,6 +3759,7 @@ LayoutViewportHeightExpansionFactor: +@@ -3849,6 +3853,7 @@ LayoutViewportHeightExpansionFactor: WebCore: default: 0 @@ -2266,7 +2272,7 @@ index b9d4024573d25cdd97c8e45e1cf7629cb3a643c7..706e0334ffd49735aa5473777228bc59 LazyIframeLoadingEnabled: type: bool status: stable -@@ -3765,9 +3770,9 @@ LazyIframeLoadingEnabled: +@@ -3859,9 +3864,9 @@ LazyIframeLoadingEnabled: WebKitLegacy: default: true WebKit: @@ -2278,7 +2284,7 @@ index b9d4024573d25cdd97c8e45e1cf7629cb3a643c7..706e0334ffd49735aa5473777228bc59 LazyImageLoadingEnabled: type: bool -@@ -5113,6 +5118,19 @@ PluginsEnabled: +@@ -5193,6 +5198,19 @@ PluginsEnabled: WebCore: default: false @@ -2298,7 +2304,7 @@ index b9d4024573d25cdd97c8e45e1cf7629cb3a643c7..706e0334ffd49735aa5473777228bc59 PopoverAttributeEnabled: type: bool status: stable -@@ -6796,6 +6814,7 @@ UseCGDisplayListsForDOMRendering: +@@ -6877,6 +6895,7 @@ UseCGDisplayListsForDOMRendering: WebKit: default: true @@ -2306,7 +2312,7 @@ index b9d4024573d25cdd97c8e45e1cf7629cb3a643c7..706e0334ffd49735aa5473777228bc59 UseGPUProcessForCanvasRenderingEnabled: type: bool status: stable -@@ -6808,7 +6827,7 @@ UseGPUProcessForCanvasRenderingEnabled: +@@ -6889,7 +6908,7 @@ UseGPUProcessForCanvasRenderingEnabled: defaultValue: WebKit: "ENABLE(GPU_PROCESS_BY_DEFAULT)": true @@ -2315,7 +2321,7 @@ index b9d4024573d25cdd97c8e45e1cf7629cb3a643c7..706e0334ffd49735aa5473777228bc59 default: false UseGPUProcessForDOMRenderingEnabled: -@@ -6850,6 +6869,7 @@ UseGPUProcessForMediaEnabled: +@@ -6931,6 +6950,7 @@ UseGPUProcessForMediaEnabled: "ENABLE(GPU_PROCESS_BY_DEFAULT)": true default: false @@ -2323,7 +2329,7 @@ index b9d4024573d25cdd97c8e45e1cf7629cb3a643c7..706e0334ffd49735aa5473777228bc59 UseGPUProcessForWebGLEnabled: type: bool status: internal -@@ -6861,7 +6881,7 @@ UseGPUProcessForWebGLEnabled: +@@ -6942,7 +6962,7 @@ UseGPUProcessForWebGLEnabled: default: false WebKit: "ENABLE(GPU_PROCESS_BY_DEFAULT) && ENABLE(GPU_PROCESS_WEBGL_BY_DEFAULT)": true @@ -2333,10 +2339,10 @@ index b9d4024573d25cdd97c8e45e1cf7629cb3a643c7..706e0334ffd49735aa5473777228bc59 WebCore: "ENABLE(GPU_PROCESS_BY_DEFAULT) && ENABLE(GPU_PROCESS_WEBGL_BY_DEFAULT)": true diff --git a/Source/WTF/wtf/PlatformEnable.h b/Source/WTF/wtf/PlatformEnable.h -index eea248ca9ab06ddbc55a1ed17bdd334e2ea848ec..f0686b9c4f0a489d78e63c35a612379cdfb01f8a 100644 +index 7dc33dacc7a295c7465857f8b0097d719c563eab..c7628dab1a62dad072ff79ccda1b8e0ae94a9598 100644 --- a/Source/WTF/wtf/PlatformEnable.h +++ b/Source/WTF/wtf/PlatformEnable.h -@@ -408,7 +408,7 @@ +@@ -467,7 +467,7 @@ // ORIENTATION_EVENTS should never get enabled on Desktop, only Mobile. #if !defined(ENABLE_ORIENTATION_EVENTS) @@ -2344,8 +2350,8 @@ index eea248ca9ab06ddbc55a1ed17bdd334e2ea848ec..f0686b9c4f0a489d78e63c35a612379c +#define ENABLE_ORIENTATION_EVENTS 1 #endif - #if OS(WINDOWS) -@@ -473,7 +473,7 @@ + #if !defined(ENABLE_OVERFLOW_SCROLLING_TOUCH) +@@ -584,7 +584,7 @@ #endif #if !defined(ENABLE_TOUCH_EVENTS) @@ -2355,10 +2361,10 @@ index eea248ca9ab06ddbc55a1ed17bdd334e2ea848ec..f0686b9c4f0a489d78e63c35a612379c #if !defined(ENABLE_TOUCH_ACTION_REGIONS) diff --git a/Source/WTF/wtf/PlatformHave.h b/Source/WTF/wtf/PlatformHave.h -index e14052bc7205ea751e71044e0237c7fb44e92d05..ae8d883861e823aef73181f642e0eb0702c65702 100644 +index 78311045d08888229f8a7fe833eac18d95ac4a1c..bb872fce96aa326e02d7ec282d3bc748c3fe9b20 100644 --- a/Source/WTF/wtf/PlatformHave.h +++ b/Source/WTF/wtf/PlatformHave.h -@@ -411,7 +411,7 @@ +@@ -419,7 +419,7 @@ #define HAVE_FOUNDATION_WITH_SAME_SITE_COOKIE_SUPPORT 1 #endif @@ -2367,7 +2373,7 @@ index e14052bc7205ea751e71044e0237c7fb44e92d05..ae8d883861e823aef73181f642e0eb07 #define HAVE_OS_DARK_MODE_SUPPORT 1 #endif -@@ -1238,7 +1238,8 @@ +@@ -1276,7 +1276,8 @@ #endif #if PLATFORM(MAC) @@ -2378,10 +2384,10 @@ index e14052bc7205ea751e71044e0237c7fb44e92d05..ae8d883861e823aef73181f642e0eb07 #if !defined(HAVE_LOCKDOWN_MODE_PDF_ADDITIONS) && \ diff --git a/Source/WebCore/DerivedSources.make b/Source/WebCore/DerivedSources.make -index 8e7c95a1f4884ba9a13275df672c98dfc233705a..6bfa17b518b342e637a3569b20f01636ee7e18f3 100644 +index 7f92dbc826915e61c6bd3cd6b056cc0a3489f401..74a3bbf87faa9961e71b155a4997d742d967a535 100644 --- a/Source/WebCore/DerivedSources.make +++ b/Source/WebCore/DerivedSources.make -@@ -1104,6 +1104,10 @@ JS_BINDING_IDLS := \ +@@ -1117,6 +1117,10 @@ JS_BINDING_IDLS := \ $(WebCore)/dom/Slotable.idl \ $(WebCore)/dom/StaticRange.idl \ $(WebCore)/dom/StringCallback.idl \ @@ -2392,7 +2398,7 @@ index 8e7c95a1f4884ba9a13275df672c98dfc233705a..6bfa17b518b342e637a3569b20f01636 $(WebCore)/dom/Text.idl \ $(WebCore)/dom/TextDecoder.idl \ $(WebCore)/dom/TextDecoderStream.idl \ -@@ -1677,9 +1681,6 @@ ADDITIONAL_BINDING_IDLS = \ +@@ -1691,9 +1695,6 @@ ADDITIONAL_BINDING_IDLS = \ GestureEvent.idl \ Internals+Additions.idl \ InternalsAdditions.idl \ @@ -2466,10 +2472,10 @@ index 99db9b2a0693bddab0b783b47746460cd0b7ffd9..74cbf2811a6f8dbcf631c8a218ad4a13 set(CSS_VALUE_PLATFORM_DEFINES "HAVE_OS_DARK_MODE_SUPPORT=1") diff --git a/Source/WebCore/SourcesCocoa.txt b/Source/WebCore/SourcesCocoa.txt -index 09bbf7edb87d2423485238eb265bb73076848e4f..3c0ff4658e612b4fca148937ff07d6337451f33b 100644 +index 0bec3d28a9ff13276abdd498c7e8ff210261d503..e1b90be0fed408da79c1a14432345d78fe4715a0 100644 --- a/Source/WebCore/SourcesCocoa.txt +++ b/Source/WebCore/SourcesCocoa.txt -@@ -688,3 +688,9 @@ platform/graphics/angle/GraphicsContextGLANGLE.cpp @no-unify +@@ -694,3 +694,9 @@ platform/graphics/angle/GraphicsContextGLANGLE.cpp @no-unify platform/graphics/cocoa/ANGLEUtilitiesCocoa.cpp @no-unify platform/graphics/cocoa/GraphicsContextGLCocoa.mm @no-unify platform/graphics/cv/GraphicsContextGLCVCocoa.cpp @no-unify @@ -2480,10 +2486,10 @@ index 09bbf7edb87d2423485238eb265bb73076848e4f..3c0ff4658e612b4fca148937ff07d633 +JSTouchList.cpp +// Playwright end diff --git a/Source/WebCore/SourcesGTK.txt b/Source/WebCore/SourcesGTK.txt -index e00e78af085a767842d2b08ca40e71fad7741569..1d7b32818538e08dd308a2b3f4967256a88cc9a2 100644 +index c18097a3a2747bcc5133467b7905779cbbab702e..3c28c6c2590e260f827464f6ce4dca4cc833dc2d 100644 --- a/Source/WebCore/SourcesGTK.txt +++ b/Source/WebCore/SourcesGTK.txt -@@ -124,3 +124,10 @@ platform/text/hyphen/HyphenationLibHyphen.cpp +@@ -123,3 +123,10 @@ platform/text/hyphen/HyphenationLibHyphen.cpp platform/unix/LoggingUnix.cpp platform/xdg/MIMETypeRegistryXdg.cpp @@ -2495,7 +2501,7 @@ index e00e78af085a767842d2b08ca40e71fad7741569..1d7b32818538e08dd308a2b3f4967256 +JSSpeechSynthesisEventInit.cpp +// Playwright: end. diff --git a/Source/WebCore/SourcesWPE.txt b/Source/WebCore/SourcesWPE.txt -index b22d8835c52f1f5584c96d68c70cd62164ee39d4..adcf594fc399b93772c1c962b283bef16d253186 100644 +index 06c923a3227befac7680faf2cdb44abd657d6e5f..adcf594fc399b93772c1c962b283bef16d253186 100644 --- a/Source/WebCore/SourcesWPE.txt +++ b/Source/WebCore/SourcesWPE.txt @@ -45,6 +45,8 @@ editing/libwpe/EditorLibWPE.cpp @@ -2507,17 +2513,6 @@ index b22d8835c52f1f5584c96d68c70cd62164ee39d4..adcf594fc399b93772c1c962b283bef1 page/linux/ResourceUsageOverlayLinux.cpp page/linux/ResourceUsageThreadLinux.cpp -@@ -70,8 +72,8 @@ platform/graphics/angle/PlatformDisplayANGLE.cpp @no-unify - - platform/graphics/wpe/SystemFontDatabaseWPE.cpp - --platform/graphics/egl/GLContext.cpp --platform/graphics/egl/GLContextLibWPE.cpp -+platform/graphics/egl/GLContext.cpp @no-unify -+platform/graphics/egl/GLContextLibWPE.cpp @no-unify - - platform/graphics/gbm/GBMBufferSwapchain.cpp - platform/graphics/gbm/GBMDevice.cpp @@ -91,6 +93,17 @@ platform/text/LocaleICU.cpp platform/unix/LoggingUnix.cpp @@ -2537,10 +2532,10 @@ index b22d8835c52f1f5584c96d68c70cd62164ee39d4..adcf594fc399b93772c1c962b283bef1 +JSSpeechSynthesisEventInit.cpp +// Playwright: end. diff --git a/Source/WebCore/WebCore.xcodeproj/project.pbxproj b/Source/WebCore/WebCore.xcodeproj/project.pbxproj -index edaf3a605d842164841fd5f951b0536069e1d007..f2eb66689071b3918cc6dbb892e00f22b403b697 100644 +index f69cbc9c58a85d6c4625b8297f4ca9a1df6246c4..517e26635f3653841c9471cadaea482b9afd85e3 100644 --- a/Source/WebCore/WebCore.xcodeproj/project.pbxproj +++ b/Source/WebCore/WebCore.xcodeproj/project.pbxproj -@@ -6077,6 +6077,13 @@ +@@ -6104,6 +6104,13 @@ EDE3A5000C7A430600956A37 /* ColorMac.h in Headers */ = {isa = PBXBuildFile; fileRef = EDE3A4FF0C7A430600956A37 /* ColorMac.h */; settings = {ATTRIBUTES = (Private, ); }; }; EDEC98030AED7E170059137F /* WebCorePrefix.h in Headers */ = {isa = PBXBuildFile; fileRef = EDEC98020AED7E170059137F /* WebCorePrefix.h */; }; EFCC6C8F20FE914400A2321B /* CanvasActivityRecord.h in Headers */ = {isa = PBXBuildFile; fileRef = EFCC6C8D20FE914000A2321B /* CanvasActivityRecord.h */; settings = {ATTRIBUTES = (Private, ); }; }; @@ -2554,7 +2549,7 @@ index edaf3a605d842164841fd5f951b0536069e1d007..f2eb66689071b3918cc6dbb892e00f22 F12171F616A8CF0B000053CA /* WebVTTElement.h in Headers */ = {isa = PBXBuildFile; fileRef = F12171F416A8BC63000053CA /* WebVTTElement.h */; }; F32BDCD92363AACA0073B6AE /* UserGestureEmulationScope.h in Headers */ = {isa = PBXBuildFile; fileRef = F32BDCD72363AACA0073B6AE /* UserGestureEmulationScope.h */; }; F344C7141125B82C00F26EEE /* InspectorFrontendClient.h in Headers */ = {isa = PBXBuildFile; fileRef = F344C7121125B82C00F26EEE /* InspectorFrontendClient.h */; settings = {ATTRIBUTES = (Private, ); }; }; -@@ -19588,6 +19595,14 @@ +@@ -19701,6 +19708,14 @@ EDEC98020AED7E170059137F /* WebCorePrefix.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = WebCorePrefix.h; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; EFB7287B2124C73D005C2558 /* CanvasActivityRecord.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = CanvasActivityRecord.cpp; sourceTree = ""; }; EFCC6C8D20FE914000A2321B /* CanvasActivityRecord.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CanvasActivityRecord.h; sourceTree = ""; }; @@ -2569,7 +2564,7 @@ index edaf3a605d842164841fd5f951b0536069e1d007..f2eb66689071b3918cc6dbb892e00f22 F12171F316A8BC63000053CA /* WebVTTElement.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = WebVTTElement.cpp; sourceTree = ""; }; F12171F416A8BC63000053CA /* WebVTTElement.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WebVTTElement.h; sourceTree = ""; }; F32BDCD52363AAC90073B6AE /* UserGestureEmulationScope.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = UserGestureEmulationScope.cpp; sourceTree = ""; }; -@@ -27016,6 +27031,11 @@ +@@ -27198,6 +27213,11 @@ BC4A5324256055590028C592 /* TextDirectionSubmenuInclusionBehavior.h */, 2D4F96F11A1ECC240098BF88 /* TextIndicator.cpp */, 2D4F96F21A1ECC240098BF88 /* TextIndicator.h */, @@ -2581,7 +2576,7 @@ index edaf3a605d842164841fd5f951b0536069e1d007..f2eb66689071b3918cc6dbb892e00f22 F48570A42644C76D00C05F71 /* TranslationContextMenuInfo.h */, F4E1965F21F26E4E00285078 /* UndoItem.cpp */, 2ECDBAD521D8906300F00ECD /* UndoItem.h */, -@@ -33199,6 +33219,8 @@ +@@ -33398,6 +33418,8 @@ 29E4D8DF16B0940F00C84704 /* PlatformSpeechSynthesizer.h */, 1AD8F81A11CAB9E900E93E54 /* PlatformStrategies.cpp */, 1AD8F81911CAB9E900E93E54 /* PlatformStrategies.h */, @@ -2589,8 +2584,8 @@ index edaf3a605d842164841fd5f951b0536069e1d007..f2eb66689071b3918cc6dbb892e00f22 + F050E17623AD70C40011CE47 /* PlatformTouchPoint.h */, 0FD7C21D23CE41E30096D102 /* PlatformWheelEvent.cpp */, 935C476A09AC4D4F00A6AAB4 /* PlatformWheelEvent.h */, - BCBB8AB513F1AFB000734DF0 /* PODInterval.h */, -@@ -35747,6 +35769,7 @@ + F491A66A2A9FEFA300F96146 /* PlatformWheelEvent.serialization.in */, +@@ -35987,6 +36009,7 @@ AD6E71AB1668899D00320C13 /* DocumentSharedObjectPool.h */, 6BDB5DC1227BD3B800919770 /* DocumentStorageAccess.cpp */, 6BDB5DC0227BD3B800919770 /* DocumentStorageAccess.h */, @@ -2598,7 +2593,7 @@ index edaf3a605d842164841fd5f951b0536069e1d007..f2eb66689071b3918cc6dbb892e00f22 7CE7FA5B1EF882300060C9D6 /* DocumentTouch.cpp */, 7CE7FA591EF882300060C9D6 /* DocumentTouch.h */, A8185F3209765765005826D9 /* DocumentType.cpp */, -@@ -40349,6 +40372,8 @@ +@@ -40619,6 +40642,8 @@ 1AD8F81B11CAB9E900E93E54 /* PlatformStrategies.h in Headers */, 0F7D07331884C56C00B4AF86 /* PlatformTextTrack.h in Headers */, 074E82BB18A69F0E007EF54C /* PlatformTimeRanges.h in Headers */, @@ -2607,7 +2602,7 @@ index edaf3a605d842164841fd5f951b0536069e1d007..f2eb66689071b3918cc6dbb892e00f22 CDD08ABD277E542600EA3755 /* PlatformTrackConfiguration.h in Headers */, CD1F9B022700323D00617EB6 /* PlatformVideoColorPrimaries.h in Headers */, CD1F9B01270020B700617EB6 /* PlatformVideoColorSpace.h in Headers */, -@@ -41583,6 +41608,7 @@ +@@ -41863,6 +41888,7 @@ 0F54DD081881D5F5003EEDBB /* Touch.h in Headers */, 71B7EE0D21B5C6870031C1EF /* TouchAction.h in Headers */, 0F54DD091881D5F5003EEDBB /* TouchEvent.h in Headers */, @@ -2615,7 +2610,7 @@ index edaf3a605d842164841fd5f951b0536069e1d007..f2eb66689071b3918cc6dbb892e00f22 0F54DD0A1881D5F5003EEDBB /* TouchList.h in Headers */, 070334D71459FFD5008D8D45 /* TrackBase.h in Headers */, BE88E0C21715CE2600658D98 /* TrackListBase.h in Headers */, -@@ -42685,6 +42711,8 @@ +@@ -42976,6 +43002,8 @@ 2D22830323A8470700364B7E /* CursorMac.mm in Sources */, 5CBD59592280E926002B22AA /* CustomHeaderFields.cpp in Sources */, 07E4BDBF2A3A5FAB000D5509 /* DictationCaretAnimator.cpp in Sources */, @@ -2624,7 +2619,7 @@ index edaf3a605d842164841fd5f951b0536069e1d007..f2eb66689071b3918cc6dbb892e00f22 7CE6CBFD187F394900D46BF5 /* FormatConverter.cpp in Sources */, 4667EA3E2968D9DA00BAB1E2 /* GameControllerHapticEffect.mm in Sources */, 46FE73D32968E52000B8064C /* GameControllerHapticEngines.mm in Sources */, -@@ -42764,6 +42792,9 @@ +@@ -43059,6 +43087,9 @@ CE88EE262414467B007F29C2 /* TextAlternativeWithRange.mm in Sources */, BE39137129B267F500FA5D4F /* TextTransformCocoa.cpp in Sources */, 51DF6D800B92A18E00C2DC85 /* ThreadCheck.mm in Sources */, @@ -2635,7 +2630,7 @@ index edaf3a605d842164841fd5f951b0536069e1d007..f2eb66689071b3918cc6dbb892e00f22 538EC8021F96AF81004D22A8 /* UnifiedSource1.cpp in Sources */, 538EC8051F96AF81004D22A8 /* UnifiedSource2-mm.mm in Sources */, diff --git a/Source/WebCore/accessibility/AccessibilityObject.cpp b/Source/WebCore/accessibility/AccessibilityObject.cpp -index f42fb0d0074f63e81aebbd24f899cc71a94deb62..454de188434a84c87bdcd298b72970ef8565822c 100644 +index 7d0f55b409086d4bee10f9b18f91d4584d74748c..cdfac5546dc5cb4d2826bfc306cd4319d4915d6b 100644 --- a/Source/WebCore/accessibility/AccessibilityObject.cpp +++ b/Source/WebCore/accessibility/AccessibilityObject.cpp @@ -64,6 +64,7 @@ @@ -2646,7 +2641,7 @@ index f42fb0d0074f63e81aebbd24f899cc71a94deb62..454de188434a84c87bdcd298b72970ef #include "LocalFrame.h" #include "LocalizedStrings.h" #include "MathMLNames.h" -@@ -3950,9 +3951,14 @@ AccessibilityObjectInclusion AccessibilityObject::defaultObjectInclusion() const +@@ -3946,9 +3947,14 @@ AccessibilityObjectInclusion AccessibilityObject::defaultObjectInclusion() const if (roleValue() == AccessibilityRole::ApplicationDialog) return AccessibilityObjectInclusion::IncludeObject; @@ -2664,10 +2659,10 @@ index f42fb0d0074f63e81aebbd24f899cc71a94deb62..454de188434a84c87bdcd298b72970ef { AXComputedObjectAttributeCache* attributeCache = nullptr; diff --git a/Source/WebCore/bindings/js/WebCoreBuiltinNames.h b/Source/WebCore/bindings/js/WebCoreBuiltinNames.h -index 6bdd79fc280b5c58f041e4a5cc9179cb2e2788d9..db4e4a028e3631b9230d69d8c5287db1143fb013 100644 +index c1132158c6c0fa9c5c94c5a5202d5a142709af19..6c4a0d436ddef50b382cfb926a4441b3c73c3886 100644 --- a/Source/WebCore/bindings/js/WebCoreBuiltinNames.h +++ b/Source/WebCore/bindings/js/WebCoreBuiltinNames.h -@@ -175,6 +175,8 @@ namespace WebCore { +@@ -176,6 +176,8 @@ namespace WebCore { macro(DecompressionStreamTransform) \ macro(DelayNode) \ macro(DeprecationReportBody) \ @@ -2797,20 +2792,22 @@ index f27718c1e2b8cd0a8075e556d4cdba7d9ae8fc54..2b61721594e5435845f3151e0de345e9 ] partial interface Element { undefined requestPointerLock(); diff --git a/Source/WebCore/dom/PointerEvent.cpp b/Source/WebCore/dom/PointerEvent.cpp -index 37ef194cdc90fecbf8733161ff7b538fc566dece..835e442a762d570b48037c1c9190e6d296f4b266 100644 +index 69b66eae141ec206b8c51382e709230034d3bfb7..4a2ce3dd0b527e391de06635b083b4f69dc9722f 100644 --- a/Source/WebCore/dom/PointerEvent.cpp +++ b/Source/WebCore/dom/PointerEvent.cpp -@@ -27,7 +27,9 @@ +@@ -27,9 +27,11 @@ #include "PointerEvent.h" #include "EventNames.h" +#include "MouseEvent.h" #include "Node.h" + #include "PlatformMouseEvent.h" + #include "PointerEventTypeNames.h" +#include "PlatformTouchEvent.h" #include namespace WebCore { -@@ -116,4 +118,63 @@ EventInterface PointerEvent::eventInterface() const +@@ -124,4 +126,51 @@ EventInterface PointerEvent::eventInterface() const return PointerEventInterfaceType; } @@ -2836,18 +2833,6 @@ index 37ef194cdc90fecbf8733161ff7b538fc566dece..835e442a762d570b48037c1c9190e6d2 + return nullAtom(); +} + -+static short buttonForType(const AtomString& type) -+{ -+ return type == eventNames().pointermoveEvent ? -1 : 0; -+} -+ -+static unsigned short buttonsForType(const AtomString& type) -+{ -+ // We have contact with the touch surface for most events except when we've released the touch or canceled it. -+ auto& eventNames = WebCore::eventNames(); -+ return (type == eventNames.pointerupEvent || type == eventNames.pointeroutEvent || type == eventNames.pointerleaveEvent || type == eventNames.pointercancelEvent) ? 0 : 1; -+} -+ +Ref PointerEvent::create(const PlatformTouchEvent& event, unsigned index, bool isPrimary, Ref&& view, const IntPoint& touchDelta) +{ + const auto& type = pointerEventType(event.touchPoints().at(index).state()); @@ -2875,7 +2860,7 @@ index 37ef194cdc90fecbf8733161ff7b538fc566dece..835e442a762d570b48037c1c9190e6d2 + } // namespace WebCore diff --git a/Source/WebCore/dom/PointerEvent.h b/Source/WebCore/dom/PointerEvent.h -index 17784dfc482c3033ea0e3ce7fdcf75b2eb4fc9d7..956c05246b5c1368e2c5257b347848d1dab4b969 100644 +index e56223a885097ff60f8db1eef5cca4aa4ad0511d..60c5ad6b8795b2985249833a8d1143ebab64b700 100644 --- a/Source/WebCore/dom/PointerEvent.h +++ b/Source/WebCore/dom/PointerEvent.h @@ -33,6 +33,8 @@ @@ -2888,7 +2873,7 @@ index 17784dfc482c3033ea0e3ce7fdcf75b2eb4fc9d7..956c05246b5c1368e2c5257b347848d1 #if ENABLE(TOUCH_EVENTS) && PLATFORM(WPE) @@ -85,7 +87,7 @@ public: - static Ref create(const AtomString& type, short button, const MouseEvent&, PointerID, const String& pointerType); + static Ref create(const AtomString& type, MouseButton, const MouseEvent&, PointerID, const String& pointerType); static Ref create(const AtomString& type, PointerID, const String& pointerType, IsPrimary = IsPrimary::No); -#if ENABLE(TOUCH_EVENTS) && (PLATFORM(IOS_FAMILY) || PLATFORM(WPE)) @@ -2896,9 +2881,9 @@ index 17784dfc482c3033ea0e3ce7fdcf75b2eb4fc9d7..956c05246b5c1368e2c5257b347848d1 static Ref create(const PlatformTouchEvent&, unsigned touchIndex, bool isPrimary, Ref&&, const IntPoint& touchDelta = { }); static Ref create(const AtomString& type, const PlatformTouchEvent&, unsigned touchIndex, bool isPrimary, Ref&&, const IntPoint& touchDelta = { }); #endif -@@ -134,7 +136,7 @@ private: +@@ -140,7 +142,7 @@ private: PointerEvent(const AtomString&, Init&&); - PointerEvent(const AtomString& type, short button, const MouseEvent&, PointerID, const String& pointerType); + PointerEvent(const AtomString& type, MouseButton, const MouseEvent&, PointerID, const String& pointerType); PointerEvent(const AtomString& type, PointerID, const String& pointerType, IsPrimary); -#if ENABLE(TOUCH_EVENTS) && (PLATFORM(IOS_FAMILY) || PLATFORM(WPE)) +#if ENABLE(TOUCH_EVENTS) @@ -2906,7 +2891,7 @@ index 17784dfc482c3033ea0e3ce7fdcf75b2eb4fc9d7..956c05246b5c1368e2c5257b347848d1 #endif diff --git a/Source/WebCore/editing/libwpe/EditorLibWPE.cpp b/Source/WebCore/editing/libwpe/EditorLibWPE.cpp -index 52ee00ea5df1b7eabedc3e9c344bd6d715d462fa..e311d17d5f18c9bf239e9fab3c1090c3578fc96c 100644 +index 7813532cc52d582c42aebc979a1ecd1137765f08..125b3aa4cb35eff2b117a46c1e126de129b60c04 100644 --- a/Source/WebCore/editing/libwpe/EditorLibWPE.cpp +++ b/Source/WebCore/editing/libwpe/EditorLibWPE.cpp @@ -34,6 +34,7 @@ @@ -2923,7 +2908,7 @@ index 52ee00ea5df1b7eabedc3e9c344bd6d715d462fa..e311d17d5f18c9bf239e9fab3c1090c3 +RefPtr Editor::webContentFromPasteboard(Pasteboard& pasteboard, const SimpleRange& context, bool allowPlainText, bool& chosePlainText) +{ -+ WebContentReader reader(*m_document.frame(), context, allowPlainText); ++ WebContentReader reader(*document().frame(), context, allowPlainText); + pasteboard.read(reader); + chosePlainText = reader.madeFragmentFromPlainText; + return WTFMove(reader.fragment); @@ -2968,7 +2953,7 @@ index 161a668d55aa0d3de7d7acb9ea752119c06130de..80d9a99b4346cbbbc23d7647fe6ebef0 break; } diff --git a/Source/WebCore/inspector/InspectorController.cpp b/Source/WebCore/inspector/InspectorController.cpp -index c2dabfbc86e46d14048134545031159757e7c2e4..d3833a407a9e47c8809620ef5cf97346891dd29c 100644 +index 284787f7db1051968ccaf7852bf0e71228ef2e99..d1977ec28b264e964264fe26155d84005e8099aa 100644 --- a/Source/WebCore/inspector/InspectorController.cpp +++ b/Source/WebCore/inspector/InspectorController.cpp @@ -288,6 +288,8 @@ void InspectorController::disconnectFrontend(FrontendChannel& frontendChannel) @@ -3049,10 +3034,10 @@ index 3a981b5bf5ca0bbf4d1c9f0b125564742cd8cad9..f8fc2ca6700461627933f149c5837075 } // namespace WebCore diff --git a/Source/WebCore/inspector/InspectorInstrumentation.cpp b/Source/WebCore/inspector/InspectorInstrumentation.cpp -index 55e1d3bbe88f38a730fae0ab6e4c513508b1c8a6..727f8143f1a07c6e7e605a2c0147ee707d074d93 100644 +index 51cc7097811a72232a9415e64efda2b103e392cf..c622d7e4baf7c0c30b667fdef6a3c05445e6e8ad 100644 --- a/Source/WebCore/inspector/InspectorInstrumentation.cpp +++ b/Source/WebCore/inspector/InspectorInstrumentation.cpp -@@ -598,6 +598,12 @@ void InspectorInstrumentation::applyUserAgentOverrideImpl(InstrumentingAgents& i +@@ -599,6 +599,12 @@ void InspectorInstrumentation::applyUserAgentOverrideImpl(InstrumentingAgents& i pageAgent->applyUserAgentOverride(userAgent); } @@ -3065,7 +3050,7 @@ index 55e1d3bbe88f38a730fae0ab6e4c513508b1c8a6..727f8143f1a07c6e7e605a2c0147ee70 void InspectorInstrumentation::applyEmulatedMediaImpl(InstrumentingAgents& instrumentingAgents, AtomString& media) { if (auto* pageAgent = instrumentingAgents.enabledPageAgent()) -@@ -681,6 +687,12 @@ void InspectorInstrumentation::didFailLoadingImpl(InstrumentingAgents& instrumen +@@ -682,6 +688,12 @@ void InspectorInstrumentation::didFailLoadingImpl(InstrumentingAgents& instrumen consoleAgent->didFailLoading(identifier, error); // This should come AFTER resource notification, front-end relies on this. } @@ -3078,7 +3063,7 @@ index 55e1d3bbe88f38a730fae0ab6e4c513508b1c8a6..727f8143f1a07c6e7e605a2c0147ee70 void InspectorInstrumentation::willLoadXHRSynchronouslyImpl(InstrumentingAgents& instrumentingAgents) { if (auto* networkAgent = instrumentingAgents.enabledNetworkAgent()) -@@ -713,20 +725,17 @@ void InspectorInstrumentation::didReceiveScriptResponseImpl(InstrumentingAgents& +@@ -714,20 +726,17 @@ void InspectorInstrumentation::didReceiveScriptResponseImpl(InstrumentingAgents& void InspectorInstrumentation::domContentLoadedEventFiredImpl(InstrumentingAgents& instrumentingAgents, LocalFrame& frame) { @@ -3102,7 +3087,7 @@ index 55e1d3bbe88f38a730fae0ab6e4c513508b1c8a6..727f8143f1a07c6e7e605a2c0147ee70 } void InspectorInstrumentation::frameDetachedFromParentImpl(InstrumentingAgents& instrumentingAgents, LocalFrame& frame) -@@ -809,12 +818,6 @@ void InspectorInstrumentation::frameDocumentUpdatedImpl(InstrumentingAgents& ins +@@ -810,12 +819,6 @@ void InspectorInstrumentation::frameDocumentUpdatedImpl(InstrumentingAgents& ins pageDOMDebuggerAgent->frameDocumentUpdated(frame); } @@ -3115,7 +3100,7 @@ index 55e1d3bbe88f38a730fae0ab6e4c513508b1c8a6..727f8143f1a07c6e7e605a2c0147ee70 void InspectorInstrumentation::frameStartedLoadingImpl(InstrumentingAgents& instrumentingAgents, LocalFrame& frame) { if (frame.isMainFrame()) { -@@ -845,10 +848,10 @@ void InspectorInstrumentation::frameStoppedLoadingImpl(InstrumentingAgents& inst +@@ -846,10 +849,10 @@ void InspectorInstrumentation::frameStoppedLoadingImpl(InstrumentingAgents& inst inspectorPageAgent->frameStoppedLoading(frame); } @@ -3128,7 +3113,7 @@ index 55e1d3bbe88f38a730fae0ab6e4c513508b1c8a6..727f8143f1a07c6e7e605a2c0147ee70 } void InspectorInstrumentation::frameClearedScheduledNavigationImpl(InstrumentingAgents& instrumentingAgents, Frame& frame) -@@ -863,6 +866,12 @@ void InspectorInstrumentation::accessibilitySettingsDidChangeImpl(InstrumentingA +@@ -864,6 +867,12 @@ void InspectorInstrumentation::accessibilitySettingsDidChangeImpl(InstrumentingA inspectorPageAgent->accessibilitySettingsDidChange(); } @@ -3141,7 +3126,7 @@ index 55e1d3bbe88f38a730fae0ab6e4c513508b1c8a6..727f8143f1a07c6e7e605a2c0147ee70 #if ENABLE(DARK_MODE_CSS) || HAVE(OS_DARK_MODE_SUPPORT) void InspectorInstrumentation::defaultAppearanceDidChangeImpl(InstrumentingAgents& instrumentingAgents) { -@@ -1051,6 +1060,12 @@ void InspectorInstrumentation::consoleStopRecordingCanvasImpl(InstrumentingAgent +@@ -1052,6 +1061,12 @@ void InspectorInstrumentation::consoleStopRecordingCanvasImpl(InstrumentingAgent canvasAgent->consoleStopRecordingCanvas(context); } @@ -3154,7 +3139,7 @@ index 55e1d3bbe88f38a730fae0ab6e4c513508b1c8a6..727f8143f1a07c6e7e605a2c0147ee70 void InspectorInstrumentation::didOpenDatabaseImpl(InstrumentingAgents& instrumentingAgents, Database& database) { if (auto* databaseAgent = instrumentingAgents.enabledDatabaseAgent()) -@@ -1351,6 +1366,36 @@ void InspectorInstrumentation::renderLayerDestroyedImpl(InstrumentingAgents& ins +@@ -1358,6 +1373,36 @@ void InspectorInstrumentation::renderLayerDestroyedImpl(InstrumentingAgents& ins layerTreeAgent->renderLayerDestroyed(renderLayer); } @@ -3191,7 +3176,7 @@ index 55e1d3bbe88f38a730fae0ab6e4c513508b1c8a6..727f8143f1a07c6e7e605a2c0147ee70 InstrumentingAgents& InspectorInstrumentation::instrumentingAgents(WorkerOrWorkletGlobalScope& globalScope) { return globalScope.inspectorController().m_instrumentingAgents; -@@ -1362,6 +1407,13 @@ InstrumentingAgents& InspectorInstrumentation::instrumentingAgents(Page& page) +@@ -1369,6 +1414,13 @@ InstrumentingAgents& InspectorInstrumentation::instrumentingAgents(Page& page) return page.inspectorController().m_instrumentingAgents.get(); } @@ -3206,14 +3191,14 @@ index 55e1d3bbe88f38a730fae0ab6e4c513508b1c8a6..727f8143f1a07c6e7e605a2c0147ee70 { if (is(context)) diff --git a/Source/WebCore/inspector/InspectorInstrumentation.h b/Source/WebCore/inspector/InspectorInstrumentation.h -index e1c01081c9f6ad3683b1510bda02b9c27d8d0db3..fadc5a9f33aa1aeabe6fe88ad7bff7c866956df9 100644 +index f032b90453a44b40c03786f660f105d94ccfab65..51aebd842948754785c25e99980594d45658df48 100644 --- a/Source/WebCore/inspector/InspectorInstrumentation.h +++ b/Source/WebCore/inspector/InspectorInstrumentation.h @@ -31,6 +31,7 @@ #pragma once -+#include "AccessibilityObjectInterface.h" ++#include "AXCoreObject.h" #include "CSSSelector.h" #include "CanvasBase.h" #include "CanvasRenderingContext.h" @@ -3273,7 +3258,7 @@ index e1c01081c9f6ad3683b1510bda02b9c27d8d0db3..fadc5a9f33aa1aeabe6fe88ad7bff7c8 static void performanceMark(ScriptExecutionContext&, const String&, std::optional, LocalFrame*); -@@ -325,6 +331,12 @@ public: +@@ -326,6 +332,12 @@ public: static void layerTreeDidChange(Page*); static void renderLayerDestroyed(Page*, const RenderLayer&); @@ -3286,7 +3271,7 @@ index e1c01081c9f6ad3683b1510bda02b9c27d8d0db3..fadc5a9f33aa1aeabe6fe88ad7bff7c8 static void frontendCreated(); static void frontendDeleted(); static bool hasFrontends() { return InspectorInstrumentationPublic::hasFrontends(); } -@@ -341,6 +353,8 @@ public: +@@ -342,6 +354,8 @@ public: static void registerInstrumentingAgents(InstrumentingAgents&); static void unregisterInstrumentingAgents(InstrumentingAgents&); @@ -3295,7 +3280,7 @@ index e1c01081c9f6ad3683b1510bda02b9c27d8d0db3..fadc5a9f33aa1aeabe6fe88ad7bff7c8 private: static void didClearWindowObjectInWorldImpl(InstrumentingAgents&, LocalFrame&, DOMWrapperWorld&); static bool isDebuggerPausedImpl(InstrumentingAgents&); -@@ -420,6 +434,7 @@ private: +@@ -421,6 +435,7 @@ private: static void didRecalculateStyleImpl(InstrumentingAgents&); static void didScheduleStyleRecalculationImpl(InstrumentingAgents&, Document&); static void applyUserAgentOverrideImpl(InstrumentingAgents&, String&); @@ -3303,7 +3288,7 @@ index e1c01081c9f6ad3683b1510bda02b9c27d8d0db3..fadc5a9f33aa1aeabe6fe88ad7bff7c8 static void applyEmulatedMediaImpl(InstrumentingAgents&, AtomString&); static void flexibleBoxRendererBeganLayoutImpl(InstrumentingAgents&, const RenderObject&); -@@ -434,6 +449,7 @@ private: +@@ -435,6 +450,7 @@ private: static void didReceiveDataImpl(InstrumentingAgents&, ResourceLoaderIdentifier, const SharedBuffer*, int encodedDataLength); static void didFinishLoadingImpl(InstrumentingAgents&, ResourceLoaderIdentifier, DocumentLoader*, const NetworkLoadMetrics&, ResourceLoader*); static void didFailLoadingImpl(InstrumentingAgents&, ResourceLoaderIdentifier, DocumentLoader*, const ResourceError&); @@ -3311,7 +3296,7 @@ index e1c01081c9f6ad3683b1510bda02b9c27d8d0db3..fadc5a9f33aa1aeabe6fe88ad7bff7c8 static void willLoadXHRSynchronouslyImpl(InstrumentingAgents&); static void didLoadXHRSynchronouslyImpl(InstrumentingAgents&); static void scriptImportedImpl(InstrumentingAgents&, ResourceLoaderIdentifier, const String& sourceString); -@@ -444,13 +460,13 @@ private: +@@ -445,13 +461,13 @@ private: static void frameDetachedFromParentImpl(InstrumentingAgents&, LocalFrame&); static void didCommitLoadImpl(InstrumentingAgents&, LocalFrame&, DocumentLoader*); static void frameDocumentUpdatedImpl(InstrumentingAgents&, LocalFrame&); @@ -3327,7 +3312,7 @@ index e1c01081c9f6ad3683b1510bda02b9c27d8d0db3..fadc5a9f33aa1aeabe6fe88ad7bff7c8 #if ENABLE(DARK_MODE_CSS) || HAVE(OS_DARK_MODE_SUPPORT) static void defaultAppearanceDidChangeImpl(InstrumentingAgents&); #endif -@@ -477,6 +493,7 @@ private: +@@ -478,6 +494,7 @@ private: static void stopProfilingImpl(InstrumentingAgents&, JSC::JSGlobalObject*, const String& title); static void consoleStartRecordingCanvasImpl(InstrumentingAgents&, CanvasRenderingContext&, JSC::JSGlobalObject&, JSC::JSObject* options); static void consoleStopRecordingCanvasImpl(InstrumentingAgents&, CanvasRenderingContext&); @@ -3335,7 +3320,7 @@ index e1c01081c9f6ad3683b1510bda02b9c27d8d0db3..fadc5a9f33aa1aeabe6fe88ad7bff7c8 static void performanceMarkImpl(InstrumentingAgents&, const String& label, std::optional, LocalFrame*); -@@ -534,6 +551,12 @@ private: +@@ -536,6 +553,12 @@ private: static void layerTreeDidChangeImpl(InstrumentingAgents&); static void renderLayerDestroyedImpl(InstrumentingAgents&, const RenderLayer&); @@ -3348,7 +3333,7 @@ index e1c01081c9f6ad3683b1510bda02b9c27d8d0db3..fadc5a9f33aa1aeabe6fe88ad7bff7c8 static InstrumentingAgents& instrumentingAgents(Page&); static InstrumentingAgents& instrumentingAgents(WorkerOrWorkletGlobalScope&); -@@ -1066,6 +1089,13 @@ inline void InspectorInstrumentation::applyUserAgentOverride(LocalFrame& frame, +@@ -1068,6 +1091,13 @@ inline void InspectorInstrumentation::applyUserAgentOverride(LocalFrame& frame, applyUserAgentOverrideImpl(*agents, userAgent); } @@ -3362,7 +3347,7 @@ index e1c01081c9f6ad3683b1510bda02b9c27d8d0db3..fadc5a9f33aa1aeabe6fe88ad7bff7c8 inline void InspectorInstrumentation::applyEmulatedMedia(LocalFrame& frame, AtomString& media) { FAST_RETURN_IF_NO_FRONTENDS(void()); -@@ -1168,6 +1198,13 @@ inline void InspectorInstrumentation::didFailLoading(WorkerOrWorkletGlobalScope& +@@ -1170,6 +1200,13 @@ inline void InspectorInstrumentation::didFailLoading(WorkerOrWorkletGlobalScope& didFailLoadingImpl(instrumentingAgents(globalScope), identifier, nullptr, error); } @@ -3376,7 +3361,7 @@ index e1c01081c9f6ad3683b1510bda02b9c27d8d0db3..fadc5a9f33aa1aeabe6fe88ad7bff7c8 inline void InspectorInstrumentation::continueAfterXFrameOptionsDenied(LocalFrame& frame, ResourceLoaderIdentifier identifier, DocumentLoader& loader, const ResourceResponse& response) { // Treat the same as didReceiveResponse. -@@ -1258,13 +1295,6 @@ inline void InspectorInstrumentation::frameDocumentUpdated(LocalFrame& frame) +@@ -1260,13 +1297,6 @@ inline void InspectorInstrumentation::frameDocumentUpdated(LocalFrame& frame) frameDocumentUpdatedImpl(*agents, frame); } @@ -3390,7 +3375,7 @@ index e1c01081c9f6ad3683b1510bda02b9c27d8d0db3..fadc5a9f33aa1aeabe6fe88ad7bff7c8 inline void InspectorInstrumentation::frameStartedLoading(LocalFrame& frame) { FAST_RETURN_IF_NO_FRONTENDS(void()); -@@ -1286,11 +1316,11 @@ inline void InspectorInstrumentation::frameStoppedLoading(LocalFrame& frame) +@@ -1288,11 +1318,11 @@ inline void InspectorInstrumentation::frameStoppedLoading(LocalFrame& frame) frameStoppedLoadingImpl(*agents, frame); } @@ -3404,7 +3389,7 @@ index e1c01081c9f6ad3683b1510bda02b9c27d8d0db3..fadc5a9f33aa1aeabe6fe88ad7bff7c8 } inline void InspectorInstrumentation::frameClearedScheduledNavigation(Frame& frame) -@@ -1306,6 +1336,13 @@ inline void InspectorInstrumentation::accessibilitySettingsDidChange(Page& page) +@@ -1308,6 +1338,13 @@ inline void InspectorInstrumentation::accessibilitySettingsDidChange(Page& page) accessibilitySettingsDidChangeImpl(instrumentingAgents(page)); } @@ -3418,7 +3403,7 @@ index e1c01081c9f6ad3683b1510bda02b9c27d8d0db3..fadc5a9f33aa1aeabe6fe88ad7bff7c8 #if ENABLE(DARK_MODE_CSS) || HAVE(OS_DARK_MODE_SUPPORT) inline void InspectorInstrumentation::defaultAppearanceDidChange(Page& page) { -@@ -1687,6 +1724,11 @@ inline void InspectorInstrumentation::performanceMark(ScriptExecutionContext& co +@@ -1696,6 +1733,11 @@ inline void InspectorInstrumentation::performanceMark(ScriptExecutionContext& co performanceMarkImpl(*agents, label, WTFMove(startTime), frame); } @@ -3430,7 +3415,7 @@ index e1c01081c9f6ad3683b1510bda02b9c27d8d0db3..fadc5a9f33aa1aeabe6fe88ad7bff7c8 inline void InspectorInstrumentation::didRequestAnimationFrame(Document& document, int callbackId) { FAST_RETURN_IF_NO_FRONTENDS(void()); -@@ -1743,6 +1785,42 @@ inline void InspectorInstrumentation::renderLayerDestroyed(Page* page, const Ren +@@ -1752,6 +1794,42 @@ inline void InspectorInstrumentation::renderLayerDestroyed(Page* page, const Ren renderLayerDestroyedImpl(*agents, renderLayer); } @@ -3908,7 +3893,7 @@ index 1a37f64d732d700f38a5d5b51c2ca2645ad30eeb..ab544156dc0d711074b86f51e3c7e63a void discardBindings(); diff --git a/Source/WebCore/inspector/agents/InspectorNetworkAgent.cpp b/Source/WebCore/inspector/agents/InspectorNetworkAgent.cpp -index 8b5f6a837b71d877d5c6e0c0e128169859716c63..ef1d8212902787f8933ae8a8a34578521431e591 100644 +index 30e72aa5670d45e97332f647ebddc1677ffc6b9e..18986563d46d1261c2684ffac320f70276cc9d6b 100644 --- a/Source/WebCore/inspector/agents/InspectorNetworkAgent.cpp +++ b/Source/WebCore/inspector/agents/InspectorNetworkAgent.cpp @@ -59,6 +59,7 @@ @@ -3997,7 +3982,7 @@ index 8b5f6a837b71d877d5c6e0c0e128169859716c63..ef1d8212902787f8933ae8a8a3457852 { return startsWithLettersIgnoringASCIICase(mimeType, "text/"_s) diff --git a/Source/WebCore/inspector/agents/InspectorNetworkAgent.h b/Source/WebCore/inspector/agents/InspectorNetworkAgent.h -index c6ebcc9d7e399a35f71350c9374df0f2107c518b..3bfa03ae7f27d9128fe207c1de1bfea9dbf5e44f 100644 +index dc7e574ee6e9256a1f75ea838d20ca7f5e9190de..5dd4464256e0f5d652fa51fd611286ddc1da6f5c 100644 --- a/Source/WebCore/inspector/agents/InspectorNetworkAgent.h +++ b/Source/WebCore/inspector/agents/InspectorNetworkAgent.h @@ -34,6 +34,8 @@ @@ -4018,7 +4003,7 @@ index c6ebcc9d7e399a35f71350c9374df0f2107c518b..3bfa03ae7f27d9128fe207c1de1bfea9 // InspectorInstrumentation void willRecalculateStyle(); diff --git a/Source/WebCore/inspector/agents/InspectorPageAgent.cpp b/Source/WebCore/inspector/agents/InspectorPageAgent.cpp -index 0a015134788986e4b829b887377c976ee2673b6b..e2924cf0d8863281f2a5102e7998e2814361393c 100644 +index cbf8e17e8faec3b6687483006d0a85b7341185a7..f734ba6ffed5cacc892ae027296cf76bef66caca 100644 --- a/Source/WebCore/inspector/agents/InspectorPageAgent.cpp +++ b/Source/WebCore/inspector/agents/InspectorPageAgent.cpp @@ -32,19 +32,27 @@ @@ -4361,7 +4346,7 @@ index 0a015134788986e4b829b887377c976ee2673b6b..e2924cf0d8863281f2a5102e7998e281 #if ENABLE(DARK_MODE_CSS) || HAVE(OS_DARK_MODE_SUPPORT) void InspectorPageAgent::defaultAppearanceDidChange() { -@@ -1032,13 +1145,22 @@ void InspectorPageAgent::defaultAppearanceDidChange() +@@ -1032,13 +1145,25 @@ void InspectorPageAgent::defaultAppearanceDidChange() void InspectorPageAgent::didClearWindowObjectInWorld(LocalFrame& frame, DOMWrapperWorld& world) { @@ -4381,13 +4366,16 @@ index 0a015134788986e4b829b887377c976ee2673b6b..e2924cf0d8863281f2a5102e7998e281 + if (!m_worldNameToBootstrapScript.contains(worldName)) return; -- frame.script().evaluateIgnoringException(ScriptSourceCode(m_bootstrapScript, URL { "web-inspector://bootstrap.js"_str })); +- frame.script().evaluateIgnoringException(ScriptSourceCode(m_bootstrapScript, JSC::SourceTaintedOrigin::Untainted, URL { "web-inspector://bootstrap.js"_str })); ++ if (m_ignoreDidClearWindowObject) ++ return; ++ + String bootstrapScript = m_worldNameToBootstrapScript.get(worldName); -+ frame.script().evaluateInWorldIgnoringException(ScriptSourceCode(bootstrapScript, URL { "web-inspector://bootstrap.js"_str }), world); ++ frame.script().evaluateInWorldIgnoringException(ScriptSourceCode(bootstrapScript, JSC::SourceTaintedOrigin::Untainted, URL { "web-inspector://bootstrap.js"_str }), world); } void InspectorPageAgent::didPaint(RenderObject& renderer, const LayoutRect& rect) -@@ -1086,6 +1208,51 @@ void InspectorPageAgent::didRecalculateStyle() +@@ -1086,6 +1211,51 @@ void InspectorPageAgent::didRecalculateStyle() m_overlay->update(); } @@ -4439,7 +4427,7 @@ index 0a015134788986e4b829b887377c976ee2673b6b..e2924cf0d8863281f2a5102e7998e281 Ref InspectorPageAgent::buildObjectForFrame(LocalFrame* frame) { ASSERT_ARG(frame, frame); -@@ -1183,6 +1350,12 @@ void InspectorPageAgent::applyUserAgentOverride(String& userAgent) +@@ -1183,6 +1353,12 @@ void InspectorPageAgent::applyUserAgentOverride(String& userAgent) userAgent = m_userAgentOverride; } @@ -4452,7 +4440,7 @@ index 0a015134788986e4b829b887377c976ee2673b6b..e2924cf0d8863281f2a5102e7998e281 void InspectorPageAgent::applyEmulatedMedia(AtomString& media) { if (!m_emulatedMedia.isEmpty()) -@@ -1210,11 +1383,13 @@ Protocol::ErrorStringOr InspectorPageAgent::snapshotNode(Protocol::DOM:: +@@ -1210,11 +1386,13 @@ Protocol::ErrorStringOr InspectorPageAgent::snapshotNode(Protocol::DOM:: return snapshot->toDataURL("image/png"_s, std::nullopt, PreserveResolution::Yes); } @@ -4467,7 +4455,7 @@ index 0a015134788986e4b829b887377c976ee2673b6b..e2924cf0d8863281f2a5102e7998e281 IntRect rectangle(x, y, width, height); auto* localMainFrame = dynamicDowncast(m_inspectedPage.mainFrame()); -@@ -1228,6 +1403,43 @@ Protocol::ErrorStringOr InspectorPageAgent::snapshotRect(int x, int y, i +@@ -1228,6 +1406,43 @@ Protocol::ErrorStringOr InspectorPageAgent::snapshotRect(int x, int y, i return snapshot->toDataURL("image/png"_s, std::nullopt, PreserveResolution::Yes); } @@ -4511,7 +4499,7 @@ index 0a015134788986e4b829b887377c976ee2673b6b..e2924cf0d8863281f2a5102e7998e281 #if ENABLE(WEB_ARCHIVE) && USE(CF) Protocol::ErrorStringOr InspectorPageAgent::archive() { -@@ -1244,7 +1456,6 @@ Protocol::ErrorStringOr InspectorPageAgent::archive() +@@ -1244,7 +1459,6 @@ Protocol::ErrorStringOr InspectorPageAgent::archive() } #endif @@ -4519,7 +4507,7 @@ index 0a015134788986e4b829b887377c976ee2673b6b..e2924cf0d8863281f2a5102e7998e281 Protocol::ErrorStringOr InspectorPageAgent::setScreenSizeOverride(std::optional&& width, std::optional&& height) { if (width.has_value() != height.has_value()) -@@ -1262,6 +1473,629 @@ Protocol::ErrorStringOr InspectorPageAgent::setScreenSizeOverride(std::opt +@@ -1262,6 +1476,626 @@ Protocol::ErrorStringOr InspectorPageAgent::setScreenSizeOverride(std::opt localMainFrame->setOverrideScreenSize(FloatSize(width.value_or(0), height.value_or(0))); return { }; } @@ -4902,9 +4890,6 @@ index 0a015134788986e4b829b887377c976ee2673b6b..e2924cf0d8863281f2a5102e7998e281 + axNode->setFocused(axObject->isFocused()); + if (axObject->isModalNode()) + axNode->setModal(axObject->isModalNode()); -+ bool multiline = axObject->ariaIsMultiline() || axObject->roleValue() == AccessibilityRole::TextArea; -+ if (multiline) -+ axNode->setMultiline(multiline); + if (axObject->isMultiSelectable()) + axNode->setMultiselectable(axObject->isMultiSelectable()); + if (liveObject && liveObject->supportsReadOnly() && !axObject->canSetValueAttribute() && axObject->isEnabled()) @@ -5150,7 +5135,7 @@ index 0a015134788986e4b829b887377c976ee2673b6b..e2924cf0d8863281f2a5102e7998e281 } // namespace WebCore diff --git a/Source/WebCore/inspector/agents/InspectorPageAgent.h b/Source/WebCore/inspector/agents/InspectorPageAgent.h -index f270cb7c3bcc1b5d7d646d9c6e6bda7063cf82e3..5f80d34d6476250a2495ccd4871aed1c2ef13ccc 100644 +index f270cb7c3bcc1b5d7d646d9c6e6bda7063cf82e3..e85f09290c40f3c9a203e1ffd69dea3ef8fce289 100644 --- a/Source/WebCore/inspector/agents/InspectorPageAgent.h +++ b/Source/WebCore/inspector/agents/InspectorPageAgent.h @@ -32,8 +32,10 @@ @@ -5210,7 +5195,7 @@ index f270cb7c3bcc1b5d7d646d9c6e6bda7063cf82e3..5f80d34d6476250a2495ccd4871aed1c Inspector::Protocol::ErrorStringOr>> searchInResource(const Inspector::Protocol::Network::FrameId&, const String& url, const String& query, std::optional&& caseSensitive, std::optional&& isRegex, const Inspector::Protocol::Network::RequestId&); Inspector::Protocol::ErrorStringOr>> searchInResources(const String&, std::optional&& caseSensitive, std::optional&& isRegex); #if !PLATFORM(IOS_FAMILY) -@@ -115,37 +126,57 @@ public: +@@ -115,45 +126,68 @@ public: #endif Inspector::Protocol::ErrorStringOr setShowPaintRects(bool); Inspector::Protocol::ErrorStringOr setEmulatedMedia(const String&); @@ -5275,7 +5260,10 @@ index f270cb7c3bcc1b5d7d646d9c6e6bda7063cf82e3..5f80d34d6476250a2495ccd4871aed1c Frame* frameForId(const Inspector::Protocol::Network::FrameId&); WEBCORE_EXPORT String frameId(Frame*); -@@ -154,6 +185,7 @@ public: + String loaderId(DocumentLoader*); + LocalFrame* assertFrame(Inspector::Protocol::ErrorString&, const Inspector::Protocol::Network::FrameId&); ++ void setIgnoreDidClearWindowObject(bool ignore) { m_ignoreDidClearWindowObject = ignore; } ++ bool ignoreDidClearWindowObject() const { return m_ignoreDidClearWindowObject; } private: double timestamp(); @@ -5283,7 +5271,7 @@ index f270cb7c3bcc1b5d7d646d9c6e6bda7063cf82e3..5f80d34d6476250a2495ccd4871aed1c static bool mainResourceContent(LocalFrame*, bool withBase64Encode, String* result); static bool dataContent(const uint8_t* data, unsigned size, const String& textEncodingName, bool withBase64Encode, String* result); -@@ -169,17 +201,21 @@ private: +@@ -169,17 +203,22 @@ private: RefPtr m_backendDispatcher; Page& m_inspectedPage; @@ -5304,6 +5292,7 @@ index f270cb7c3bcc1b5d7d646d9c6e6bda7063cf82e3..5f80d34d6476250a2495ccd4871aed1c + bool m_interceptFileChooserDialog { false }; + bool m_bypassCSP { false }; + bool m_doingAccessibilitySnapshot { false }; ++ bool m_ignoreDidClearWindowObject { false }; }; } // namespace WebCore @@ -5358,7 +5347,7 @@ index 242aea89da38d22a2c2b314f337e6aa4f3becdb8..51f978caae1a6a76fb1833fbfef76837 } diff --git a/Source/WebCore/inspector/agents/page/PageRuntimeAgent.cpp b/Source/WebCore/inspector/agents/page/PageRuntimeAgent.cpp -index 77b093ba149993094dc059d6cf8348095500bcb7..dc0a611a9da1ae4ffb54f1b57bba6ea290a57f48 100644 +index 77b093ba149993094dc059d6cf8348095500bcb7..fe0a9a641dfb497dcd95aa4c9fc962784157a964 100644 --- a/Source/WebCore/inspector/agents/page/PageRuntimeAgent.cpp +++ b/Source/WebCore/inspector/agents/page/PageRuntimeAgent.cpp @@ -34,6 +34,7 @@ @@ -5377,7 +5366,7 @@ index 77b093ba149993094dc059d6cf8348095500bcb7..dc0a611a9da1ae4ffb54f1b57bba6ea2 #include "SecurityOrigin.h" #include "UserGestureEmulationScope.h" #include -@@ -85,6 +87,8 @@ Protocol::ErrorStringOr PageRuntimeAgent::disable() +@@ -85,13 +87,73 @@ Protocol::ErrorStringOr PageRuntimeAgent::disable() { m_instrumentingAgents.setEnabledPageRuntimeAgent(nullptr); @@ -5386,10 +5375,17 @@ index 77b093ba149993094dc059d6cf8348095500bcb7..dc0a611a9da1ae4ffb54f1b57bba6ea2 return InspectorRuntimeAgent::disable(); } -@@ -94,8 +98,66 @@ void PageRuntimeAgent::frameNavigated(LocalFrame& frame) + void PageRuntimeAgent::frameNavigated(LocalFrame& frame) + { ++ auto* pageAgent = m_instrumentingAgents.enabledPageAgent(); ++ if (pageAgent) ++ pageAgent->setIgnoreDidClearWindowObject(true); + // Ensure execution context is created for the frame even if it doesn't have scripts. mainWorldGlobalObject(frame); - } - ++ if (pageAgent) ++ pageAgent->setIgnoreDidClearWindowObject(false); ++} ++ +static JSC_DECLARE_HOST_FUNCTION(bindingCallback); + +JSC_DEFINE_HOST_FUNCTION(bindingCallback, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) @@ -5441,22 +5437,26 @@ index 77b093ba149993094dc059d6cf8348095500bcb7..dc0a611a9da1ae4ffb54f1b57bba6ea2 + if (injectedScript.hasNoValue()) + return; + m_frontendDispatcher->bindingCalled(injectedScriptManager().injectedScriptIdFor(globalObject), name, arg); -+} -+ + } + void PageRuntimeAgent::didClearWindowObjectInWorld(LocalFrame& frame, DOMWrapperWorld& world) - { +@@ -100,7 +162,26 @@ void PageRuntimeAgent::didClearWindowObjectInWorld(LocalFrame& frame, DOMWrapper + if (!pageAgent) + return; + ++ if (pageAgent->ignoreDidClearWindowObject()) ++ return; ++ + if (world.isNormal()) { + for (const auto& name : m_bindingNames) + addBindingToFrame(frame, name); + } + - auto* pageAgent = m_instrumentingAgents.enabledPageAgent(); - if (!pageAgent) - return; -@@ -103,6 +165,15 @@ void PageRuntimeAgent::didClearWindowObjectInWorld(LocalFrame& frame, DOMWrapper ++ pageAgent->setIgnoreDidClearWindowObject(true); notifyContextCreated(pageAgent->frameId(&frame), frame.script().globalObject(world), world); - } - ++ pageAgent->setIgnoreDidClearWindowObject(false); ++} ++ +void PageRuntimeAgent::didReceiveMainResourceError(LocalFrame& frame) +{ + if (frame.loader().stateMachine().isDisplayingInitialEmptyDocument()) { @@ -5464,12 +5464,10 @@ index 77b093ba149993094dc059d6cf8348095500bcb7..dc0a611a9da1ae4ffb54f1b57bba6ea2 + // it usable in case loading failed. + mainWorldGlobalObject(frame); + } -+} -+ + } + InjectedScript PageRuntimeAgent::injectedScriptForEval(Protocol::ErrorString& errorString, std::optional&& executionContextId) - { - if (!executionContextId) { -@@ -139,9 +210,6 @@ void PageRuntimeAgent::reportExecutionContextCreation() +@@ -139,9 +220,6 @@ void PageRuntimeAgent::reportExecutionContextCreation() return; m_inspectedPage.forEachFrame([&](LocalFrame& frame) { @@ -5479,7 +5477,7 @@ index 77b093ba149993094dc059d6cf8348095500bcb7..dc0a611a9da1ae4ffb54f1b57bba6ea2 auto frameId = pageAgent->frameId(&frame); // Always send the main world first. -@@ -200,18 +268,24 @@ Protocol::ErrorStringOr, std::op +@@ -200,18 +278,24 @@ Protocol::ErrorStringOr, std::op if (injectedScript.hasNoValue()) return makeUnexpected(errorString); @@ -5585,10 +5583,10 @@ index 7efc7c39d4ea689063c3371c9d9f5d25e433b3ae..c18f0b38ef9a22b594b60287d6c205b1 protected: static SameSiteInfo sameSiteInfo(const Document&, IsForDOMCookieAccess = IsForDOMCookieAccess::No); diff --git a/Source/WebCore/loader/DocumentLoader.cpp b/Source/WebCore/loader/DocumentLoader.cpp -index 5442846cef667a81cf7c08b1d653ee46d66812bb..d4390a1518dae85d067f7808ef0b06a54451c4c5 100644 +index c4cb8010b9f631f8d03f2251726c31b8d7eec2e5..fef684a8c4b63de4415db9ce156ee62acaefb303 100644 --- a/Source/WebCore/loader/DocumentLoader.cpp +++ b/Source/WebCore/loader/DocumentLoader.cpp -@@ -765,8 +765,10 @@ void DocumentLoader::willSendRequest(ResourceRequest&& newRequest, const Resourc +@@ -767,8 +767,10 @@ void DocumentLoader::willSendRequest(ResourceRequest&& newRequest, const Resourc if (!didReceiveRedirectResponse) return completionHandler(WTFMove(newRequest)); @@ -5599,7 +5597,7 @@ index 5442846cef667a81cf7c08b1d653ee46d66812bb..d4390a1518dae85d067f7808ef0b06a5 switch (navigationPolicyDecision) { case NavigationPolicyDecision::IgnoreLoad: case NavigationPolicyDecision::LoadWillContinueInAnotherProcess: -@@ -1549,8 +1551,6 @@ void DocumentLoader::detachFromFrame() +@@ -1551,8 +1553,6 @@ void DocumentLoader::detachFromFrame() if (!m_frame) return; @@ -5627,10 +5625,10 @@ index 4c9176db0a582a075fb3d68c921dcd960c271e86..3363380982c54cea74bd853e2682068b DocumentWriter& writer() const { return m_writer; } diff --git a/Source/WebCore/loader/FrameLoader.cpp b/Source/WebCore/loader/FrameLoader.cpp -index e97907da3295916577044fc2d4503bc39e5359b3..22d43a8a2814486dea3e7ac3af4348d3d812bfe9 100644 +index 9328921ba26573b39baad48e2aa711a6319d2a67..380ffe9398057e8d0de5c700d8af6264a7f6a1df 100644 --- a/Source/WebCore/loader/FrameLoader.cpp +++ b/Source/WebCore/loader/FrameLoader.cpp -@@ -1219,6 +1219,7 @@ void FrameLoader::loadInSameDocument(URL url, RefPtr stat +@@ -1210,6 +1210,7 @@ void FrameLoader::loadInSameDocument(URL url, RefPtr stat } m_client->dispatchDidNavigateWithinPage(); @@ -5638,7 +5636,7 @@ index e97907da3295916577044fc2d4503bc39e5359b3..22d43a8a2814486dea3e7ac3af4348d3 m_frame.document()->statePopped(stateObject ? stateObject.releaseNonNull() : SerializedScriptValue::nullValue()); m_client->dispatchDidPopStateWithinPage(); -@@ -1675,6 +1676,8 @@ void FrameLoader::loadWithDocumentLoader(DocumentLoader* loader, FrameLoadType t +@@ -1666,6 +1667,8 @@ void FrameLoader::loadWithDocumentLoader(DocumentLoader* loader, FrameLoadType t const String& httpMethod = loader->request().httpMethod(); if (shouldPerformFragmentNavigation(isFormSubmission, httpMethod, policyChecker().loadType(), newURL)) { @@ -5647,7 +5645,7 @@ index e97907da3295916577044fc2d4503bc39e5359b3..22d43a8a2814486dea3e7ac3af4348d3 RefPtr oldDocumentLoader = m_documentLoader; NavigationAction action { *m_frame.document(), loader->request(), InitiatedByMainFrame::Unknown, policyChecker().loadType(), isFormSubmission }; action.setIsRequestFromClientOrUserInput(loader->isRequestFromClientOrUserInput()); -@@ -1707,7 +1710,9 @@ void FrameLoader::loadWithDocumentLoader(DocumentLoader* loader, FrameLoadType t +@@ -1698,7 +1701,9 @@ void FrameLoader::loadWithDocumentLoader(DocumentLoader* loader, FrameLoadType t } RELEASE_ASSERT(!isBackForwardLoadType(policyChecker().loadType()) || history().provisionalItem()); @@ -5657,7 +5655,7 @@ index e97907da3295916577044fc2d4503bc39e5359b3..22d43a8a2814486dea3e7ac3af4348d3 continueLoadAfterNavigationPolicy(request, formState.get(), navigationPolicyDecision, allowNavigationToInvalidURL); completionHandler(); }, PolicyDecisionMode::Asynchronous); -@@ -2967,14 +2972,19 @@ String FrameLoader::userAgent(const URL& url) const +@@ -2951,14 +2956,19 @@ String FrameLoader::userAgent(const URL& url) const String FrameLoader::navigatorPlatform() const { @@ -5679,7 +5677,7 @@ index e97907da3295916577044fc2d4503bc39e5359b3..22d43a8a2814486dea3e7ac3af4348d3 } void FrameLoader::dispatchOnloadEvents() -@@ -3401,6 +3411,8 @@ void FrameLoader::receivedMainResourceError(const ResourceError& error) +@@ -3386,6 +3396,8 @@ void FrameLoader::receivedMainResourceError(const ResourceError& error) checkCompleted(); if (m_frame.page()) checkLoadComplete(); @@ -5688,7 +5686,7 @@ index e97907da3295916577044fc2d4503bc39e5359b3..22d43a8a2814486dea3e7ac3af4348d3 } void FrameLoader::continueFragmentScrollAfterNavigationPolicy(const ResourceRequest& request, const SecurityOrigin* requesterOrigin, bool shouldContinue) -@@ -4236,9 +4248,6 @@ String FrameLoader::referrer() const +@@ -4221,9 +4233,6 @@ String FrameLoader::referrer() const void FrameLoader::dispatchDidClearWindowObjectsInAllWorlds() { @@ -5698,7 +5696,7 @@ index e97907da3295916577044fc2d4503bc39e5359b3..22d43a8a2814486dea3e7ac3af4348d3 Vector> worlds; ScriptController::getAllWorlds(worlds); for (auto& world : worlds) -@@ -4247,13 +4256,13 @@ void FrameLoader::dispatchDidClearWindowObjectsInAllWorlds() +@@ -4232,13 +4241,13 @@ void FrameLoader::dispatchDidClearWindowObjectsInAllWorlds() void FrameLoader::dispatchDidClearWindowObjectInWorld(DOMWrapperWorld& world) { @@ -5808,7 +5806,7 @@ index cc2916050d7bff5290dd8079d5b77c68c6dd4649..94622e922f4624a4d07896a9983901e4 ASSERT(m_document); if (request.charset().isEmpty() && m_document && (type == CachedResource::Type::Script || type == CachedResource::Type::CSSStyleSheet)) diff --git a/Source/WebCore/page/ChromeClient.h b/Source/WebCore/page/ChromeClient.h -index de70dc20b757150c82c5327fc151742331f1f305..e7eba51632049fe0264faf10837491873bd78286 100644 +index 33293f37310d019dbd755d067797a5bb4b3a56cf..c28b5ccb4bd4f179f850efc5c1a1eae18528da64 100644 --- a/Source/WebCore/page/ChromeClient.h +++ b/Source/WebCore/page/ChromeClient.h @@ -325,7 +325,7 @@ public: @@ -5821,10 +5819,10 @@ index de70dc20b757150c82c5327fc151742331f1f305..e7eba51632049fe0264faf1083749187 #if ENABLE(INPUT_TYPE_COLOR) diff --git a/Source/WebCore/page/EventHandler.cpp b/Source/WebCore/page/EventHandler.cpp -index f34a453825002e7e176ab07f83d38cee4fe312d9..5bf4e02dfe2f77593199615afb5ff4777d44fb73 100644 +index 2428fc773b39011a7d642d4036a7e78386ebeb68..4af98fa0726d89e32135e7b99343897e3eb31d8f 100644 --- a/Source/WebCore/page/EventHandler.cpp +++ b/Source/WebCore/page/EventHandler.cpp -@@ -145,6 +145,7 @@ +@@ -147,6 +147,7 @@ #if ENABLE(TOUCH_EVENTS) && !ENABLE(IOS_TOUCH_EVENTS) #include "PlatformTouchEvent.h" @@ -5832,7 +5830,7 @@ index f34a453825002e7e176ab07f83d38cee4fe312d9..5bf4e02dfe2f77593199615afb5ff477 #endif #if ENABLE(MAC_GESTURE_EVENTS) -@@ -856,9 +857,7 @@ bool EventHandler::handleMousePressEvent(const MouseEventWithHitTestResults& eve +@@ -858,9 +859,7 @@ bool EventHandler::handleMousePressEvent(const MouseEventWithHitTestResults& eve m_mousePressNode = event.targetNode(); m_frame.document()->setFocusNavigationStartingNode(event.targetNode()); @@ -5842,7 +5840,7 @@ index f34a453825002e7e176ab07f83d38cee4fe312d9..5bf4e02dfe2f77593199615afb5ff477 m_mousePressed = true; m_selectionInitiationState = HaveNotStartedSelection; -@@ -898,8 +897,6 @@ VisiblePosition EventHandler::selectionExtentRespectingEditingBoundary(const Vis +@@ -900,8 +899,6 @@ VisiblePosition EventHandler::selectionExtentRespectingEditingBoundary(const Vis return adjustedTarget->renderer()->positionForPoint(LayoutPoint(selectionEndPoint), nullptr); } @@ -5851,7 +5849,7 @@ index f34a453825002e7e176ab07f83d38cee4fe312d9..5bf4e02dfe2f77593199615afb5ff477 #if !PLATFORM(IOS_FAMILY) bool EventHandler::supportsSelectionUpdatesOnMouseDrag() const -@@ -921,8 +918,10 @@ bool EventHandler::handleMouseDraggedEvent(const MouseEventWithHitTestResults& e +@@ -923,8 +920,10 @@ bool EventHandler::handleMouseDraggedEvent(const MouseEventWithHitTestResults& e Ref protectedFrame(m_frame); @@ -5861,8 +5859,8 @@ index f34a453825002e7e176ab07f83d38cee4fe312d9..5bf4e02dfe2f77593199615afb5ff477 +#endif RefPtr targetNode = event.targetNode(); - if (event.event().button() != LeftButton || !targetNode) -@@ -943,7 +942,9 @@ bool EventHandler::handleMouseDraggedEvent(const MouseEventWithHitTestResults& e + if (event.event().button() != MouseButton::Left || !targetNode) +@@ -945,7 +944,9 @@ bool EventHandler::handleMouseDraggedEvent(const MouseEventWithHitTestResults& e ASSERT(mouseDownMayStartSelect() || m_mouseDownMayStartAutoscroll); #endif @@ -5872,7 +5870,7 @@ index f34a453825002e7e176ab07f83d38cee4fe312d9..5bf4e02dfe2f77593199615afb5ff477 if (m_mouseDownMayStartAutoscroll && !panScrollInProgress()) { m_autoscrollController->startAutoscrollForSelection(renderer); -@@ -960,6 +961,8 @@ bool EventHandler::handleMouseDraggedEvent(const MouseEventWithHitTestResults& e +@@ -962,6 +963,8 @@ bool EventHandler::handleMouseDraggedEvent(const MouseEventWithHitTestResults& e return true; } @@ -5881,7 +5879,7 @@ index f34a453825002e7e176ab07f83d38cee4fe312d9..5bf4e02dfe2f77593199615afb5ff477 bool EventHandler::eventMayStartDrag(const PlatformMouseEvent& event) const { // This is a pre-flight check of whether the event might lead to a drag being started. Be careful -@@ -991,6 +994,8 @@ bool EventHandler::eventMayStartDrag(const PlatformMouseEvent& event) const +@@ -993,6 +996,8 @@ bool EventHandler::eventMayStartDrag(const PlatformMouseEvent& event) const return targetElement && page->dragController().draggableElement(&m_frame, targetElement.get(), result.roundedPointInInnerNodeFrame(), state); } @@ -5890,7 +5888,7 @@ index f34a453825002e7e176ab07f83d38cee4fe312d9..5bf4e02dfe2f77593199615afb5ff477 void EventHandler::updateSelectionForMouseDrag() { if (!supportsSelectionUpdatesOnMouseDrag()) -@@ -1099,7 +1104,6 @@ void EventHandler::updateSelectionForMouseDrag(const HitTestResult& hitTestResul +@@ -1101,7 +1106,6 @@ void EventHandler::updateSelectionForMouseDrag(const HitTestResult& hitTestResul if (oldSelection != newSelection && ImageOverlay::isOverlayText(newSelection.start().containerNode()) && ImageOverlay::isOverlayText(newSelection.end().containerNode())) invalidateClick(); } @@ -5898,7 +5896,7 @@ index f34a453825002e7e176ab07f83d38cee4fe312d9..5bf4e02dfe2f77593199615afb5ff477 void EventHandler::lostMouseCapture() { -@@ -1147,9 +1151,7 @@ bool EventHandler::handleMouseReleaseEvent(const MouseEventWithHitTestResults& e +@@ -1149,9 +1153,7 @@ bool EventHandler::handleMouseReleaseEvent(const MouseEventWithHitTestResults& e // on the selection, the selection goes away. However, if we are // editing, place the caret. if (m_mouseDownWasSingleClickInSelection && m_selectionInitiationState != ExtendedSelection @@ -5906,9 +5904,9 @@ index f34a453825002e7e176ab07f83d38cee4fe312d9..5bf4e02dfe2f77593199615afb5ff477 && m_dragStartPosition == event.event().position() -#endif && m_frame.selection().isRange() - && event.event().button() != RightButton) { + && event.event().button() != MouseButton::Right) { VisibleSelection newSelection; -@@ -2148,10 +2150,8 @@ bool EventHandler::handleMouseMoveEvent(const PlatformMouseEvent& platformMouseE +@@ -2168,10 +2170,8 @@ HandleMouseEventResult EventHandler::handleMouseMoveEvent(const PlatformMouseEve swallowEvent = !dispatchMouseEvent(eventNames().mousemoveEvent, mouseEvent.targetNode(), 0, platformMouseEvent, FireMouseOverOut::Yes); @@ -5919,7 +5917,7 @@ index f34a453825002e7e176ab07f83d38cee4fe312d9..5bf4e02dfe2f77593199615afb5ff477 return swallowEvent; } -@@ -4246,7 +4246,14 @@ bool EventHandler::handleDrag(const MouseEventWithHitTestResults& event, CheckDr +@@ -4273,7 +4273,14 @@ bool EventHandler::handleDrag(const MouseEventWithHitTestResults& event, CheckDr if (!m_frame.document()) return false; @@ -5935,7 +5933,7 @@ index f34a453825002e7e176ab07f83d38cee4fe312d9..5bf4e02dfe2f77593199615afb5ff477 auto hasNonDefaultPasteboardData = HasNonDefaultPasteboardData::No; if (dragState().shouldDispatchEvents) { -@@ -4931,11 +4938,6 @@ bool EventHandler::handleTouchEvent(const PlatformTouchEvent& event) +@@ -4958,11 +4965,6 @@ bool EventHandler::handleTouchEvent(const PlatformTouchEvent& event) document.page()->pointerCaptureController().dispatchEventForTouchAtIndex( *touchTarget, cancelEvent, index, !index, *document.windowProxy(), { 0, 0 }); } @@ -5947,7 +5945,7 @@ index f34a453825002e7e176ab07f83d38cee4fe312d9..5bf4e02dfe2f77593199615afb5ff477 #endif if (&m_frame != targetFrame) { -@@ -4977,6 +4979,9 @@ bool EventHandler::handleTouchEvent(const PlatformTouchEvent& event) +@@ -5004,6 +5006,9 @@ bool EventHandler::handleTouchEvent(const PlatformTouchEvent& event) changedTouches[pointState].m_touches->append(WTFMove(touch)); changedTouches[pointState].m_targets.add(touchTarget); } @@ -5958,10 +5956,10 @@ index f34a453825002e7e176ab07f83d38cee4fe312d9..5bf4e02dfe2f77593199615afb5ff477 m_touchPressed = touches->length() > 0; if (allTouchReleased) diff --git a/Source/WebCore/page/EventHandler.h b/Source/WebCore/page/EventHandler.h -index 13dd8153250bb5c4a26fe8ed5411b6ff8b8694b2..46ae6da891196750275b2eff404117712a190903 100644 +index c42f7523be4fc59b3cee076b620735c8693fbc08..cc1ae3be93e72cea09e0a5795a6b18df301034ab 100644 --- a/Source/WebCore/page/EventHandler.h +++ b/Source/WebCore/page/EventHandler.h -@@ -138,9 +138,7 @@ public: +@@ -139,9 +139,7 @@ public: WEBCORE_EXPORT VisiblePosition selectionExtentRespectingEditingBoundary(const VisibleSelection&, const LayoutPoint&, Node*); @@ -5971,7 +5969,7 @@ index 13dd8153250bb5c4a26fe8ed5411b6ff8b8694b2..46ae6da891196750275b2eff40411771 #if ENABLE(PAN_SCROLLING) void didPanScrollStart(); -@@ -405,10 +403,8 @@ private: +@@ -406,10 +404,8 @@ private: bool startKeyboardScrollAnimationOnRenderBoxAndItsAncestors(ScrollDirection, ScrollGranularity, RenderBox*, bool isKeyRepeat); bool startKeyboardScrollAnimationOnEnclosingScrollableContainer(ScrollDirection, ScrollGranularity, Node*, bool isKeyRepeat); @@ -5982,7 +5980,7 @@ index 13dd8153250bb5c4a26fe8ed5411b6ff8b8694b2..46ae6da891196750275b2eff40411771 WEBCORE_EXPORT bool handleMouseReleaseEvent(const MouseEventWithHitTestResults&); -@@ -510,10 +506,8 @@ private: +@@ -511,10 +507,8 @@ private: void defaultTabEventHandler(KeyboardEvent&); void defaultArrowEventHandler(FocusDirection, KeyboardEvent&); @@ -5993,7 +5991,7 @@ index 13dd8153250bb5c4a26fe8ed5411b6ff8b8694b2..46ae6da891196750275b2eff40411771 // The following are called at the beginning of handleMouseUp and handleDrag. // If they return true it indicates that they have consumed the event. -@@ -521,9 +515,10 @@ private: +@@ -522,9 +516,10 @@ private: #if ENABLE(DRAG_SUPPORT) bool eventLoopHandleMouseDragged(const MouseEventWithHitTestResults&); @@ -6005,7 +6003,7 @@ index 13dd8153250bb5c4a26fe8ed5411b6ff8b8694b2..46ae6da891196750275b2eff40411771 enum class SetOrClearLastScrollbar { Clear, Set }; void updateLastScrollbarUnderMouse(Scrollbar*, SetOrClearLastScrollbar); -@@ -616,8 +611,8 @@ private: +@@ -617,8 +612,8 @@ private: Timer m_autoHideCursorTimer; #endif @@ -6016,7 +6014,7 @@ index 13dd8153250bb5c4a26fe8ed5411b6ff8b8694b2..46ae6da891196750275b2eff40411771 RefPtr m_dragTarget; bool m_mouseDownMayStartDrag { false }; diff --git a/Source/WebCore/page/FrameSnapshotting.cpp b/Source/WebCore/page/FrameSnapshotting.cpp -index d62d41b79f148cee977447f740bd6a768b159b0c..2d16d7243788ef876487d741caddc4a71cb5425f 100644 +index 7ee2659df6aca0f84b61cb0de78ba9518b65b61e..0f09634cc4e679742b64a29506e2bd65e73c6fe9 100644 --- a/Source/WebCore/page/FrameSnapshotting.cpp +++ b/Source/WebCore/page/FrameSnapshotting.cpp @@ -109,7 +109,7 @@ RefPtr snapshotFrameRectWithClip(LocalFrame& frame, const IntRect& @@ -6096,7 +6094,7 @@ index a6537109eeecdeb6d541fff3469c3c8f3a061ca2..d8fa86589e6d0ddcb3f84f2d6afed722 if (stateObjectType == StateObjectType::Push) { frame->loader().history().pushState(WTFMove(data), title, fullURL.string()); diff --git a/Source/WebCore/page/LocalFrame.cpp b/Source/WebCore/page/LocalFrame.cpp -index 0efefdcb28dd9083c99b16e719ea100fb1352dcf..8400b6cf505342727cca716330f87807d5be9e30 100644 +index e5452be0cfa1e035befbca4d1d2595688220f700..ab0fe3458df4270681f69facf39fd33a5144c892 100644 --- a/Source/WebCore/page/LocalFrame.cpp +++ b/Source/WebCore/page/LocalFrame.cpp @@ -40,6 +40,7 @@ @@ -6504,7 +6502,7 @@ index 0efefdcb28dd9083c99b16e719ea100fb1352dcf..8400b6cf505342727cca716330f87807 #undef FRAME_RELEASE_LOG_ERROR diff --git a/Source/WebCore/page/LocalFrame.h b/Source/WebCore/page/LocalFrame.h -index 95458a16c7d0fd5e30ef8db0b84f8ff951a95e0b..292763c72ebf7f41b0447ba09117c28e54f621bc 100644 +index dec423046a398d9ff9581182339202ab234d1b86..1fa73649aa87c39dc2e113bc9e16e27507c6f910 100644 --- a/Source/WebCore/page/LocalFrame.h +++ b/Source/WebCore/page/LocalFrame.h @@ -28,8 +28,10 @@ @@ -6583,10 +6581,10 @@ index 95458a16c7d0fd5e30ef8db0b84f8ff951a95e0b..292763c72ebf7f41b0447ba09117c28e ViewportArguments m_viewportArguments; diff --git a/Source/WebCore/page/Page.cpp b/Source/WebCore/page/Page.cpp -index 39ffd9497dc9ef93f770f3eb8d2a492b59f76af1..fce232374cc127be90a273563f4838a56f8e85c7 100644 +index eeca345e3f10fbe452408539a33264d5dce43ec5..23b03d2caae1eef9cd6594a26a78fac4f035956d 100644 --- a/Source/WebCore/page/Page.cpp +++ b/Source/WebCore/page/Page.cpp -@@ -526,6 +526,45 @@ void Page::setOverrideViewportArguments(const std::optional& +@@ -527,6 +527,45 @@ void Page::setOverrideViewportArguments(const std::optional& document->updateViewportArguments(); } @@ -6632,7 +6630,7 @@ index 39ffd9497dc9ef93f770f3eb8d2a492b59f76af1..fce232374cc127be90a273563f4838a5 ScrollingCoordinator* Page::scrollingCoordinator() { if (!m_scrollingCoordinator && m_settings->scrollingCoordinatorEnabled()) { -@@ -3753,6 +3792,26 @@ void Page::setUseDarkAppearanceOverride(std::optional valueOverride) +@@ -3751,6 +3790,26 @@ void Page::setUseDarkAppearanceOverride(std::optional valueOverride) #endif } @@ -6660,10 +6658,10 @@ index 39ffd9497dc9ef93f770f3eb8d2a492b59f76af1..fce232374cc127be90a273563f4838a5 { if (insets == m_fullscreenInsets) diff --git a/Source/WebCore/page/Page.h b/Source/WebCore/page/Page.h -index 0941d8324e74a84b140441863c13bcabdc411c0e..eb91d294bacdb9e9078f8c5d53ff76db6165e9d4 100644 +index 332905b95e257e19a1c4928da5d6b0598497a63b..7a09f36a9a52edbc89ed224c319333d9b7878525 100644 --- a/Source/WebCore/page/Page.h +++ b/Source/WebCore/page/Page.h -@@ -309,6 +309,9 @@ public: +@@ -310,6 +310,9 @@ public: const std::optional& overrideViewportArguments() const { return m_overrideViewportArguments; } WEBCORE_EXPORT void setOverrideViewportArguments(const std::optional&); @@ -6673,7 +6671,7 @@ index 0941d8324e74a84b140441863c13bcabdc411c0e..eb91d294bacdb9e9078f8c5d53ff76db static void refreshPlugins(bool reload); WEBCORE_EXPORT PluginData& pluginData(); void clearPluginData(); -@@ -370,6 +373,10 @@ public: +@@ -371,6 +374,10 @@ public: #if ENABLE(DRAG_SUPPORT) DragController& dragController() { return m_dragController.get(); } const DragController& dragController() const { return m_dragController.get(); } @@ -6684,7 +6682,7 @@ index 0941d8324e74a84b140441863c13bcabdc411c0e..eb91d294bacdb9e9078f8c5d53ff76db #endif FocusController& focusController() const { return *m_focusController; } #if ENABLE(CONTEXT_MENUS) -@@ -539,6 +546,10 @@ public: +@@ -541,6 +548,10 @@ public: WEBCORE_EXPORT void effectiveAppearanceDidChange(bool useDarkAppearance, bool useElevatedUserInterfaceLevel); bool defaultUseDarkAppearance() const { return m_useDarkAppearance; } void setUseDarkAppearanceOverride(std::optional); @@ -6695,7 +6693,7 @@ index 0941d8324e74a84b140441863c13bcabdc411c0e..eb91d294bacdb9e9078f8c5d53ff76db #if ENABLE(TEXT_AUTOSIZING) float textAutosizingWidth() const { return m_textAutosizingWidth; } -@@ -976,6 +987,11 @@ public: +@@ -978,6 +989,11 @@ public: WEBCORE_EXPORT void setInteractionRegionsEnabled(bool); #endif @@ -6707,7 +6705,7 @@ index 0941d8324e74a84b140441863c13bcabdc411c0e..eb91d294bacdb9e9078f8c5d53ff76db #if ENABLE(DEVICE_ORIENTATION) && PLATFORM(IOS_FAMILY) DeviceOrientationUpdateProvider* deviceOrientationUpdateProvider() const { return m_deviceOrientationUpdateProvider.get(); } #endif -@@ -1115,6 +1131,9 @@ private: +@@ -1126,6 +1142,9 @@ private: #if ENABLE(DRAG_SUPPORT) UniqueRef m_dragController; @@ -6717,7 +6715,7 @@ index 0941d8324e74a84b140441863c13bcabdc411c0e..eb91d294bacdb9e9078f8c5d53ff76db #endif std::unique_ptr m_focusController; #if ENABLE(CONTEXT_MENUS) -@@ -1195,6 +1214,8 @@ private: +@@ -1206,6 +1225,8 @@ private: bool m_useElevatedUserInterfaceLevel { false }; bool m_useDarkAppearance { false }; std::optional m_useDarkAppearanceOverride; @@ -6726,7 +6724,7 @@ index 0941d8324e74a84b140441863c13bcabdc411c0e..eb91d294bacdb9e9078f8c5d53ff76db #if ENABLE(TEXT_AUTOSIZING) float m_textAutosizingWidth { 0 }; -@@ -1370,6 +1391,11 @@ private: +@@ -1381,6 +1402,11 @@ private: #endif std::optional m_overrideViewportArguments; @@ -6766,7 +6764,7 @@ index 9a6549a792bf95f6d5671289bc58be259ec73732..03a6ceb14a18b3b74e8544a98fb85841 Page& m_page; }; diff --git a/Source/WebCore/page/PointerCaptureController.cpp b/Source/WebCore/page/PointerCaptureController.cpp -index bd147a1fee46d2f228c115230f8d071dc303756c..b06dce53e787d95c6b89c01511084f95175764b7 100644 +index 4fcf544270085cd95bbab81297e68fe6701f5f49..94cf0aecebe5abe95cf308e8696df170c5ddc8d9 100644 --- a/Source/WebCore/page/PointerCaptureController.cpp +++ b/Source/WebCore/page/PointerCaptureController.cpp @@ -195,7 +195,7 @@ bool PointerCaptureController::preventsCompatibilityMouseEventsForIdentifier(Poi @@ -6778,7 +6776,7 @@ index bd147a1fee46d2f228c115230f8d071dc303756c..b06dce53e787d95c6b89c01511084f95 static bool hierarchyHasCapturingEventListeners(Element* target, const AtomString& eventName) { for (RefPtr currentNode = target; currentNode; currentNode = currentNode->parentInComposedTree()) { -@@ -474,7 +474,7 @@ void PointerCaptureController::cancelPointer(PointerID pointerId, const IntPoint +@@ -499,7 +499,7 @@ void PointerCaptureController::cancelPointer(PointerID pointerId, const IntPoint capturingData->pendingTargetOverride = nullptr; capturingData->state = CapturingData::State::Cancelled; @@ -6788,10 +6786,10 @@ index bd147a1fee46d2f228c115230f8d071dc303756c..b06dce53e787d95c6b89c01511084f95 #endif diff --git a/Source/WebCore/page/PointerCaptureController.h b/Source/WebCore/page/PointerCaptureController.h -index 9ec307bf796452e21c695116d1f678e1d9916709..b47ed65e85cf9cbb0d32d6199a9b1c1c0681dfcb 100644 +index 91a9847a4083393e225f42e71c2dd590a88c0289..b0838c84e4ba24378db42f40a68856afe95fb9a7 100644 --- a/Source/WebCore/page/PointerCaptureController.h +++ b/Source/WebCore/page/PointerCaptureController.h -@@ -57,7 +57,7 @@ public: +@@ -58,7 +58,7 @@ public: RefPtr pointerEventForMouseEvent(const MouseEvent&, PointerID, const String& pointerType); @@ -6800,7 +6798,7 @@ index 9ec307bf796452e21c695116d1f678e1d9916709..b47ed65e85cf9cbb0d32d6199a9b1c1c void dispatchEventForTouchAtIndex(EventTarget&, const PlatformTouchEvent&, unsigned, bool isPrimary, WindowProxy&, const IntPoint&); #endif -@@ -77,12 +77,12 @@ private: +@@ -78,12 +78,12 @@ private: RefPtr pendingTargetOverride; RefPtr targetOverride; @@ -6860,7 +6858,7 @@ index 11af362d3018851cb8258419f3e0d3e01ea369c0..9ec64d3c88bf37ad423aa613bae557d7 } diff --git a/Source/WebCore/page/csp/ContentSecurityPolicy.cpp b/Source/WebCore/page/csp/ContentSecurityPolicy.cpp -index cce7a25b8c69999670a38a05a7ce4fbf94132634..0ad60e920e5bdb6c5de7518bfd4d3076d5a70e38 100644 +index 58012d74ce6972f3fd2c7e7d054cf36ad08f05ed..37e981adab6620b750bbf82a2e10d2a6058712e6 100644 --- a/Source/WebCore/page/csp/ContentSecurityPolicy.cpp +++ b/Source/WebCore/page/csp/ContentSecurityPolicy.cpp @@ -336,6 +336,8 @@ bool ContentSecurityPolicy::allowContentSecurityPolicySourceStarToMatchAnyProtoc @@ -7022,10 +7020,10 @@ index c77ddd17643628a87c0245ee2ea3ba417d40bc23..c5179ff48e57cdcfbe709a10bae517ff bool m_disallowFileAccess { false }; }; diff --git a/Source/WebCore/platform/DragImage.cpp b/Source/WebCore/platform/DragImage.cpp -index 550600c6f370af1b71a896d4fa3ef5a871a48c19..35d125361b7fa892cf863352bf0bd7131325ff38 100644 +index 9b613ca69af779a1e52b6a66216b0905809262dc..cd48a3551ae8da7702154798fcc6ab4df00f7f7f 100644 --- a/Source/WebCore/platform/DragImage.cpp +++ b/Source/WebCore/platform/DragImage.cpp -@@ -279,7 +279,7 @@ DragImage::~DragImage() +@@ -280,7 +280,7 @@ DragImage::~DragImage() deleteDragImage(m_dragImageRef); } @@ -7129,7 +7127,7 @@ index 4aa768e1c2a8da63004f34ccbf0d347b2484c37b..515c99ed21cde6c157bb9a1378a78372 #endif diff --git a/Source/WebCore/platform/PlatformScreen.cpp b/Source/WebCore/platform/PlatformScreen.cpp -index 8c8bc5f6060e10b59cacc92765542c85407f3c1b..fad09775881af6d15e16256a90e3c89d2b968d16 100644 +index 39fbdb1be924da33344ac10293798b2abde2e468..caa6421a1c1b616e2eac7cc8645cffd9f27d54bb 100644 --- a/Source/WebCore/platform/PlatformScreen.cpp +++ b/Source/WebCore/platform/PlatformScreen.cpp @@ -25,6 +25,7 @@ @@ -7140,7 +7138,7 @@ index 8c8bc5f6060e10b59cacc92765542c85407f3c1b..fad09775881af6d15e16256a90e3c89d #if PLATFORM(COCOA) || PLATFORM(GTK) -@@ -72,3 +73,25 @@ const ScreenData* screenData(PlatformDisplayID screenDisplayID) +@@ -73,3 +74,25 @@ const ScreenData* screenData(PlatformDisplayID screenDisplayID) } // namespace WebCore #endif // PLATFORM(COCOA) || PLATFORM(GTK) @@ -7193,7 +7191,7 @@ index efa9c30953573d88c18e28110e5f86be339656fb..d6cc2afe8026ceebf80d2b0acac478cb + } // namespace WebCore diff --git a/Source/WebCore/platform/adwaita/ScrollbarThemeAdwaita.cpp b/Source/WebCore/platform/adwaita/ScrollbarThemeAdwaita.cpp -index 62df6fb981bd1543adff14632408979a804217f3..b78a9effdcf473d7af71684a2039ef3e7141b9b9 100644 +index a9d29336decf605282e17416699c4e89a8f9eb22..8d09cf5c3650db7574c20fbf64ba3530f982c32a 100644 --- a/Source/WebCore/platform/adwaita/ScrollbarThemeAdwaita.cpp +++ b/Source/WebCore/platform/adwaita/ScrollbarThemeAdwaita.cpp @@ -43,7 +43,7 @@ @@ -7326,7 +7324,7 @@ index b60f9a64bacc8282860da6de299b75aeb295b9b5..55bd017c03c6478ca334bd5ef164160f namespace WebCore { diff --git a/Source/WebCore/platform/graphics/win/ComplexTextControllerUniscribe.cpp b/Source/WebCore/platform/graphics/win/ComplexTextControllerUniscribe.cpp -index b6849ae2de8f035240920a3575d79b03d23f5ea9..6b62177a2789bc0e9cdb0f8421e0fb28e17e9a6f 100644 +index 17ad42448d476fa7fa40c309a7e930454b6e21d2..b4888dc22db2b67e6e95dc0831e8b6103924bc95 100644 --- a/Source/WebCore/platform/graphics/win/ComplexTextControllerUniscribe.cpp +++ b/Source/WebCore/platform/graphics/win/ComplexTextControllerUniscribe.cpp @@ -169,6 +169,33 @@ static Vector stringIndicesFromClusters(const Vector& clusters, @@ -8259,10 +8257,10 @@ index 35ade40b37f0c476815535541118f9246ed199cd..2bd1444f9a5e9a14ab3d6acbc020434e m_commonHeaders.append(CommonHeader { name, value }); } diff --git a/Source/WebCore/platform/network/NetworkStorageSession.h b/Source/WebCore/platform/network/NetworkStorageSession.h -index ba0203910f4fa63f5424f121d5ed9b77d5788565..3d9fb4367c482f3d15e18dfb70c3f477bb2b9232 100644 +index 0aaa786af751fea1c46de73deab2ba09596191a1..a3a087e33bc70f2a83b996638225cb4bc4d94d68 100644 --- a/Source/WebCore/platform/network/NetworkStorageSession.h +++ b/Source/WebCore/platform/network/NetworkStorageSession.h -@@ -158,6 +158,8 @@ public: +@@ -159,6 +159,8 @@ public: NetworkingContext* context() const; #endif @@ -8272,7 +8270,7 @@ index ba0203910f4fa63f5424f121d5ed9b77d5788565..3d9fb4367c482f3d15e18dfb70c3f477 WEBCORE_EXPORT void setCookie(const Cookie&); WEBCORE_EXPORT void setCookies(const Vector&, const URL&, const URL& mainDocumentURL); diff --git a/Source/WebCore/platform/network/ResourceResponseBase.cpp b/Source/WebCore/platform/network/ResourceResponseBase.cpp -index c5e76b6cbb06657e033af97f2d96cf80731bb499..564dd39db77cf22624996c46ea6ae96c67670e7f 100644 +index 226bf4b1c6b84bfe9e0e8632388798c0a7f4f5ab..104041824ca34790914a66b09135104280277bf5 100644 --- a/Source/WebCore/platform/network/ResourceResponseBase.cpp +++ b/Source/WebCore/platform/network/ResourceResponseBase.cpp @@ -74,6 +74,7 @@ ResourceResponseBase::ResourceResponseBase(std::optional m_networkLoadMetrics; short m_httpStatusCode; -@@ -320,6 +321,11 @@ protected: +@@ -279,6 +280,11 @@ protected: AtomString m_httpStatusText; AtomString m_httpVersion; HTTPHeaderMap m_httpHeaderFields; @@ -8315,7 +8313,7 @@ index a2a0e57218fc14c7ead18bfe93330e9ddc260f36..ecfbd8538223ad08e64bb99fe976d2a1 Box m_networkLoadMetrics; mutable std::optional m_certificateInfo; -@@ -368,6 +374,7 @@ void ResourceResponseBase::encode(Encoder& encoder) const +@@ -375,6 +381,7 @@ void ResourceResponseBase::encode(Encoder& encoder) const encoder << m_httpStatusText; encoder << m_httpVersion; encoder << m_httpHeaderFields; @@ -8323,7 +8321,7 @@ index a2a0e57218fc14c7ead18bfe93330e9ddc260f36..ecfbd8538223ad08e64bb99fe976d2a1 encoder << m_httpStatusCode; encoder << m_certificateInfo; -@@ -437,6 +444,12 @@ bool ResourceResponseBase::decode(Decoder& decoder, ResourceResponseBase& respon +@@ -444,6 +451,12 @@ bool ResourceResponseBase::decode(Decoder& decoder, ResourceResponseBase& respon return false; response.m_httpHeaderFields = WTFMove(*httpHeaderFields); @@ -8337,7 +8335,7 @@ index a2a0e57218fc14c7ead18bfe93330e9ddc260f36..ecfbd8538223ad08e64bb99fe976d2a1 decoder >> httpStatusCode; if (!httpStatusCode) diff --git a/Source/WebCore/platform/network/cocoa/NetworkStorageSessionCocoa.mm b/Source/WebCore/platform/network/cocoa/NetworkStorageSessionCocoa.mm -index fa98150c2de166d8637a931dba34218ea40fe582..dcfc225a2b224ca873e2ee4fcf334c0b3dc59e2c 100644 +index 3aa7ce0dbf02a78ab3b7931101d72d283276cdaf..e9ffb44462271d793c0dcc460e15c308e54c4f9d 100644 --- a/Source/WebCore/platform/network/cocoa/NetworkStorageSessionCocoa.mm +++ b/Source/WebCore/platform/network/cocoa/NetworkStorageSessionCocoa.mm @@ -539,6 +539,22 @@ bool NetworkStorageSession::setCookieFromDOM(const URL& firstParty, const SameSi @@ -8599,7 +8597,7 @@ index f997a64f68deca632435450869c1abc9c3c35ac2..79da1d419799e359c60f2346e1d3ac8a OptionSet PlatformKeyboardEvent::currentStateOfModifierKeys() diff --git a/Source/WebCore/platform/win/PasteboardWin.cpp b/Source/WebCore/platform/win/PasteboardWin.cpp -index 9cfe7fe6a51ab15834082822f523fb92204531a5..dd1c2153b865a9dd558df20462884422fe2ad84e 100644 +index eed85c96b3d8292856e030691ce9c29199dbd3f0..0c2642562d5fec67977b2896433bf75331796616 100644 --- a/Source/WebCore/platform/win/PasteboardWin.cpp +++ b/Source/WebCore/platform/win/PasteboardWin.cpp @@ -1129,7 +1129,21 @@ void Pasteboard::writeCustomData(const Vector& data) @@ -9070,7 +9068,7 @@ index 0000000000000000000000000000000000000000..cf2b51f6f02837a1106f4d999f2f130e + +} // namespace WebCore diff --git a/Source/WebCore/rendering/RenderTextControl.cpp b/Source/WebCore/rendering/RenderTextControl.cpp -index 9928b9733fb7152b3fdcb357b086950f15bd39e2..8a54795f463a9f3c746bcf5f2d7966f40f37f48f 100644 +index 4c511db8dda43552564e17b389bb438d9c44da12..530cc0b6c0bb26cc4f53614019dd6a02e4d73e6a 100644 --- a/Source/WebCore/rendering/RenderTextControl.cpp +++ b/Source/WebCore/rendering/RenderTextControl.cpp @@ -209,13 +209,13 @@ void RenderTextControl::layoutExcludedChildren(bool relayoutChildren) @@ -9089,7 +9087,7 @@ index 9928b9733fb7152b3fdcb357b086950f15bd39e2..8a54795f463a9f3c746bcf5f2d7966f4 { auto innerText = innerTextElement(); diff --git a/Source/WebCore/rendering/RenderTextControl.h b/Source/WebCore/rendering/RenderTextControl.h -index 1497fa9cf6222fe02f84b9b13ce60cc51f301206..a4095a4d6f0b49e0a3a105597d69184380df9e42 100644 +index e064a7a15a416e593b68de15133c5528b1ce2ae0..2860c13fad04ea32e28142848e0583701263f45d 100644 --- a/Source/WebCore/rendering/RenderTextControl.h +++ b/Source/WebCore/rendering/RenderTextControl.h @@ -36,9 +36,9 @@ public: @@ -9104,11 +9102,11 @@ index 1497fa9cf6222fe02f84b9b13ce60cc51f301206..a4095a4d6f0b49e0a3a105597d691843 int innerLineHeight() const override; #endif diff --git a/Source/WebCore/workers/WorkerConsoleClient.cpp b/Source/WebCore/workers/WorkerConsoleClient.cpp -index 7b429300affcb05189d4816059f405d6686c4f51..a2a160ea608c719ce2b714cd32510248ef404390 100644 +index 3a0f87d60e42971d841b9eb5c8f1b36b4cb9649e..f2da6be1c58b89adc458c9e2c86c9ab6d613ff2a 100644 --- a/Source/WebCore/workers/WorkerConsoleClient.cpp +++ b/Source/WebCore/workers/WorkerConsoleClient.cpp -@@ -99,4 +99,6 @@ void WorkerConsoleClient::recordEnd(JSC::JSGlobalObject*, Ref&& - +@@ -101,4 +101,6 @@ void WorkerConsoleClient::recordEnd(JSC::JSGlobalObject*, Ref&& + // FIXME: Web Inspector: support console screenshots in a Worker void WorkerConsoleClient::screenshot(JSC::JSGlobalObject*, Ref&&) { } +void WorkerConsoleClient::bindingCalled(JSC::JSGlobalObject*, const String&, const String&) { } @@ -9127,10 +9125,10 @@ index 1d8488e0d36288e09cd5662bd7f770ade95dfee3..dee07f87b47d62d4ef8ede45824bdb2f WorkerOrWorkletGlobalScope& m_globalScope; }; diff --git a/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.cpp b/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.cpp -index 4206bd668f41c059cf20ec5513274418a573740b..cc6b604eff4c3b73b6b8ff9075014b8ca2fc6b30 100644 +index 824d31987aa0a64cf709d54bbe43ae3d7f1c532e..9b8617411bd90b566b4283b49ae7557d6ff3b205 100644 --- a/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.cpp +++ b/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.cpp -@@ -95,6 +95,8 @@ +@@ -97,6 +97,8 @@ #if PLATFORM(COCOA) #include @@ -9139,7 +9137,7 @@ index 4206bd668f41c059cf20ec5513274418a573740b..cc6b604eff4c3b73b6b8ff9075014b8c #endif #if ENABLE(APPLE_PAY_REMOTE_UI) -@@ -1076,6 +1078,14 @@ void NetworkConnectionToWebProcess::clearPageSpecificData(PageIdentifier pageID) +@@ -1065,6 +1067,14 @@ void NetworkConnectionToWebProcess::clearPageSpecificData(PageIdentifier pageID) #endif } @@ -9155,10 +9153,10 @@ index 4206bd668f41c059cf20ec5513274418a573740b..cc6b604eff4c3b73b6b8ff9075014b8c void NetworkConnectionToWebProcess::removeStorageAccessForFrame(FrameIdentifier frameID, PageIdentifier pageID) { diff --git a/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.h b/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.h -index 2bf91f53c3775f8c961cf7f14e000681231cbe5e..2f5ed58455deda2d55a198eb7d7bc204c3857153 100644 +index 3772b447b4557cf3a5d1ed26adf36f93caaa83d6..c86332696a764efe91dbd47e33b6388c58504ce3 100644 --- a/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.h +++ b/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.h -@@ -328,6 +328,8 @@ private: +@@ -331,6 +331,8 @@ private: void clearPageSpecificData(WebCore::PageIdentifier); @@ -9168,7 +9166,7 @@ index 2bf91f53c3775f8c961cf7f14e000681231cbe5e..2f5ed58455deda2d55a198eb7d7bc204 void removeStorageAccessForFrame(WebCore::FrameIdentifier, WebCore::PageIdentifier); diff --git a/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.messages.in b/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.messages.in -index 2367ada0de191766c89ef2855a4f30a66ebe328a..b0e7951d30cc087c3bfb307c707779a5bb550e32 100644 +index ace7031a520e2daeec7cb0ef328ba0514fec0108..687119f766a738e9477108d7e6de8ee925ab11ad 100644 --- a/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.messages.in +++ b/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.messages.in @@ -71,6 +71,8 @@ messages -> NetworkConnectionToWebProcess LegacyReceiver { @@ -9181,7 +9179,7 @@ index 2367ada0de191766c89ef2855a4f30a66ebe328a..b0e7951d30cc087c3bfb307c707779a5 RemoveStorageAccessForFrame(WebCore::FrameIdentifier frameID, WebCore::PageIdentifier pageID); LogUserInteraction(WebCore::RegistrableDomain domain) diff --git a/Source/WebKit/NetworkProcess/NetworkProcess.cpp b/Source/WebKit/NetworkProcess/NetworkProcess.cpp -index 172177e7cd3749a446a9143c1566363f932b7a49..a632fc51d7e28e2f9bd8545f4e26f0a06eb8d035 100644 +index 85d30e6299b237fd3148210f4452b454aaae72e3..7dbc44bc31819244f1ae50e72e51d17ff9e9babb 100644 --- a/Source/WebKit/NetworkProcess/NetworkProcess.cpp +++ b/Source/WebKit/NetworkProcess/NetworkProcess.cpp @@ -625,6 +625,12 @@ void NetworkProcess::registrableDomainsExemptFromWebsiteDataDeletion(PAL::Sessio @@ -9198,7 +9196,7 @@ index 172177e7cd3749a446a9143c1566363f932b7a49..a632fc51d7e28e2f9bd8545f4e26f0a0 void NetworkProcess::dumpResourceLoadStatistics(PAL::SessionID sessionID, CompletionHandler&& completionHandler) { diff --git a/Source/WebKit/NetworkProcess/NetworkProcess.h b/Source/WebKit/NetworkProcess/NetworkProcess.h -index a072b511e5ce431d443a423a509497558ef26b18..bc5f4f3e9a5fff415ff89272907d4111205e9e0c 100644 +index 982ae2d8d112b351ca6ef47390e2a9abac9b40dc..740b62ad313335efe4a39a29626d6b3e079673be 100644 --- a/Source/WebKit/NetworkProcess/NetworkProcess.h +++ b/Source/WebKit/NetworkProcess/NetworkProcess.h @@ -33,6 +33,7 @@ @@ -9228,7 +9226,7 @@ index a072b511e5ce431d443a423a509497558ef26b18..bc5f4f3e9a5fff415ff89272907d4111 void clearPrevalentResource(PAL::SessionID, RegistrableDomain&&, CompletionHandler&&); void clearUserInteraction(PAL::SessionID, RegistrableDomain&&, CompletionHandler&&); diff --git a/Source/WebKit/NetworkProcess/NetworkProcess.messages.in b/Source/WebKit/NetworkProcess/NetworkProcess.messages.in -index 20d7d32baf524e43a11a86ef208c404696d8ace7..2e6fa5ca2266d9868b6683540a5ba999819005ba 100644 +index 2e602dec7f870f03528023d26df86b47a7bb5a9c..319e65680c3615d9ee8c9fbefd5b56dba631c646 100644 --- a/Source/WebKit/NetworkProcess/NetworkProcess.messages.in +++ b/Source/WebKit/NetworkProcess/NetworkProcess.messages.in @@ -85,6 +85,8 @@ messages -> NetworkProcess LegacyReceiver { @@ -9241,7 +9239,7 @@ index 20d7d32baf524e43a11a86ef208c404696d8ace7..2e6fa5ca2266d9868b6683540a5ba999 ClearPrevalentResource(PAL::SessionID sessionID, WebCore::RegistrableDomain resourceDomain) -> () ClearUserInteraction(PAL::SessionID sessionID, WebCore::RegistrableDomain resourceDomain) -> () diff --git a/Source/WebKit/NetworkProcess/NetworkSession.h b/Source/WebKit/NetworkProcess/NetworkSession.h -index 986309ce9567147c5b0b0d047de82d26ee230ba3..36f67b7bf663d7398de33d7e361b0dd4d4574cc4 100644 +index c8e0c534db8fee2a0f3c6b2b507bf7bdace88aa1..b75b615efdaea496190a8c88192701183630af83 100644 --- a/Source/WebKit/NetworkProcess/NetworkSession.h +++ b/Source/WebKit/NetworkProcess/NetworkSession.h @@ -205,6 +205,9 @@ public: @@ -9254,7 +9252,7 @@ index 986309ce9567147c5b0b0d047de82d26ee230ba3..36f67b7bf663d7398de33d7e361b0dd4 #if ENABLE(SERVICE_WORKER) void removeSoftUpdateLoader(ServiceWorkerSoftUpdateLoader* loader) { m_softUpdateLoaders.remove(loader); } void addNavigationPreloaderTask(ServiceWorkerFetchTask&); -@@ -327,6 +330,7 @@ protected: +@@ -326,6 +329,7 @@ protected: bool m_privateClickMeasurementDebugModeEnabled { false }; std::optional m_ephemeralMeasurement; bool m_isRunningEphemeralMeasurementTest { false }; @@ -9263,7 +9261,7 @@ index 986309ce9567147c5b0b0d047de82d26ee230ba3..36f67b7bf663d7398de33d7e361b0dd4 HashSet> m_keptAliveLoads; diff --git a/Source/WebKit/NetworkProcess/cocoa/NetworkSessionCocoa.mm b/Source/WebKit/NetworkProcess/cocoa/NetworkSessionCocoa.mm -index 6fab2cc458410ed58d263c3162014fca9cb9501c..f51327e9691d4f716891848ca9f06314d8572b21 100644 +index 51f26de4a6aa90dc7406e3e4e4d2b461276f7cc9..da5dadd438075c8aceed417285c33d1b31737e69 100644 --- a/Source/WebKit/NetworkProcess/cocoa/NetworkSessionCocoa.mm +++ b/Source/WebKit/NetworkProcess/cocoa/NetworkSessionCocoa.mm @@ -766,6 +766,8 @@ - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didRece @@ -9290,7 +9288,7 @@ index 6fab2cc458410ed58d263c3162014fca9cb9501c..f51327e9691d4f716891848ca9f06314 #if !LOG_DISABLED LOG(NetworkSession, "%llu didReceiveResponse completionHandler (%d)", taskIdentifier, policyAction); diff --git a/Source/WebKit/NetworkProcess/curl/NetworkDataTaskCurl.cpp b/Source/WebKit/NetworkProcess/curl/NetworkDataTaskCurl.cpp -index 8ef8c83a4b1cc6bca6167214c3a1ee9cec8fa235..e0b40605da9adb8b81240838189d9559fb882c3e 100644 +index 6307e164caa300d25db023b3b8ebcdebe8377df2..9d4f0a20d40e3fc0ca62c62d94796936972729b3 100644 --- a/Source/WebKit/NetworkProcess/curl/NetworkDataTaskCurl.cpp +++ b/Source/WebKit/NetworkProcess/curl/NetworkDataTaskCurl.cpp @@ -82,10 +82,18 @@ NetworkDataTaskCurl::NetworkDataTaskCurl(NetworkSession& session, NetworkDataTas @@ -9302,7 +9300,7 @@ index 8ef8c83a4b1cc6bca6167214c3a1ee9cec8fa235..e0b40605da9adb8b81240838189d9559 - m_curlRequest->setUserPass(m_initialCredential.user(), m_initialCredential.password()); - m_curlRequest->setAuthenticationScheme(ProtectionSpace::AuthenticationScheme::HTTPBasic); + if (request.url().protocolIsData()) { -+ DataURLDecoder::decode(request.url(), { }, [this, protectedThis = Ref { *this }](auto decodeResult) mutable { ++ DataURLDecoder::decode(request.url(), { }, DataURLDecoder::ShouldValidatePadding::Yes, [this, protectedThis = Ref { *this }](auto decodeResult) mutable { + didReadDataURL(WTFMove(decodeResult)); + }); + } else { @@ -9552,10 +9550,10 @@ index 13e557214edc7f060dd01ef10c2ab97fe0e9c41e..6ec6ea5328eab0cc9f5247ee14e15c56 } diff --git a/Source/WebKit/PlatformGTK.cmake b/Source/WebKit/PlatformGTK.cmake -index 229843e90a0d3e66762221e60c9e25b77ceccd6e..c39b8b8b5675cf03704ad88b9bc1c9fbc20fc4ad 100644 +index 2ba3f0bfd3e479df2b5c298b1bf83100ccc78c09..4445671e17bba56998300a746ec2e0735e3faaac 100644 --- a/Source/WebKit/PlatformGTK.cmake +++ b/Source/WebKit/PlatformGTK.cmake -@@ -306,6 +306,9 @@ list(APPEND WebKit_SYSTEM_INCLUDE_DIRECTORIES +@@ -308,6 +308,9 @@ list(APPEND WebKit_SYSTEM_INCLUDE_DIRECTORIES ${GSTREAMER_PBUTILS_INCLUDE_DIRS} ${GTK_INCLUDE_DIRS} ${LIBSOUP_INCLUDE_DIRS} @@ -9565,7 +9563,7 @@ index 229843e90a0d3e66762221e60c9e25b77ceccd6e..c39b8b8b5675cf03704ad88b9bc1c9fb ) list(APPEND WebKit_INTERFACE_INCLUDE_DIRECTORIES -@@ -352,6 +355,9 @@ if (USE_LIBWEBRTC) +@@ -363,6 +366,9 @@ if (USE_LIBWEBRTC) list(APPEND WebKit_SYSTEM_INCLUDE_DIRECTORIES "${THIRDPARTY_DIR}/libwebrtc/Source/" "${THIRDPARTY_DIR}/libwebrtc/Source/webrtc" @@ -9575,7 +9573,7 @@ index 229843e90a0d3e66762221e60c9e25b77ceccd6e..c39b8b8b5675cf03704ad88b9bc1c9fb ) endif () -@@ -395,6 +401,12 @@ else () +@@ -406,6 +412,12 @@ else () set(WebKitGTK_ENUM_HEADER_TEMPLATE ${WEBKIT_DIR}/UIProcess/API/gtk/WebKitEnumTypesGtk3.h.in) endif () @@ -9589,10 +9587,10 @@ index 229843e90a0d3e66762221e60c9e25b77ceccd6e..c39b8b8b5675cf03704ad88b9bc1c9fb set(WebKitGTK_ENUM_GENERATION_HEADERS ${WebKitGTK_INSTALLED_HEADERS}) list(REMOVE_ITEM WebKitGTK_ENUM_GENERATION_HEADERS ${WebKitGTK_DERIVED_SOURCES_DIR}/webkit/WebKitEnumTypes.h) diff --git a/Source/WebKit/PlatformWPE.cmake b/Source/WebKit/PlatformWPE.cmake -index 9a80143b3e079290c646340b932b7029845a2e05..3b31af45d209851d568eab5868b6d80ef1cf9723 100644 +index 0c3e4b7cf41fe811fcc5117200b75a8beaec8590..d425d830c7f3d0e9a591d02ac8974b64a90f4221 100644 --- a/Source/WebKit/PlatformWPE.cmake +++ b/Source/WebKit/PlatformWPE.cmake -@@ -191,6 +191,7 @@ set(WPE_API_HEADER_TEMPLATES +@@ -193,6 +193,7 @@ set(WPE_API_HEADER_TEMPLATES ${WEBKIT_DIR}/UIProcess/API/glib/WebKitWindowProperties.h.in ${WEBKIT_DIR}/UIProcess/API/glib/WebKitWebsitePolicies.h.in ${WEBKIT_DIR}/UIProcess/API/glib/webkit.h.in @@ -9600,7 +9598,7 @@ index 9a80143b3e079290c646340b932b7029845a2e05..3b31af45d209851d568eab5868b6d80e ) if (ENABLE_2022_GLIB_API) -@@ -368,6 +369,7 @@ list(APPEND WebKit_PRIVATE_INCLUDE_DIRECTORIES +@@ -370,6 +371,7 @@ list(APPEND WebKit_PRIVATE_INCLUDE_DIRECTORIES "${WEBKIT_DIR}/UIProcess/Launcher/libwpe" "${WEBKIT_DIR}/UIProcess/Notifications/glib/" "${WEBKIT_DIR}/UIProcess/geoclue" @@ -9608,7 +9606,7 @@ index 9a80143b3e079290c646340b932b7029845a2e05..3b31af45d209851d568eab5868b6d80e "${WEBKIT_DIR}/UIProcess/gstreamer" "${WEBKIT_DIR}/UIProcess/linux" "${WEBKIT_DIR}/UIProcess/soup" -@@ -391,8 +393,17 @@ list(APPEND WebKit_SYSTEM_INCLUDE_DIRECTORIES +@@ -393,8 +395,17 @@ list(APPEND WebKit_SYSTEM_INCLUDE_DIRECTORIES ${GIO_UNIX_INCLUDE_DIRS} ${GLIB_INCLUDE_DIRS} ${LIBSOUP_INCLUDE_DIRS} @@ -9797,10 +9795,18 @@ index 7164af8d347828ba80bcb52fd6ca814401157f2b..9ba0e32717d9b4cb01f3f43898aa402a #if USE(APPKIT) diff --git a/Source/WebKit/Shared/NativeWebMouseEvent.h b/Source/WebKit/Shared/NativeWebMouseEvent.h -index c586d2775021a9e164dc36b1732e1a2dadc986a0..ffb79428fe2de5af626bf3a9ad49545380a8e85d 100644 +index c586d2775021a9e164dc36b1732e1a2dadc986a0..189a9bec7af066fee5d84fa3ce1ed145de01080f 100644 --- a/Source/WebKit/Shared/NativeWebMouseEvent.h +++ b/Source/WebKit/Shared/NativeWebMouseEvent.h -@@ -79,6 +79,11 @@ public: +@@ -31,6 +31,7 @@ + #if USE(APPKIT) + #include + OBJC_CLASS NSView; ++OBJC_CLASS NSEvent; + #endif + + #if PLATFORM(GTK) +@@ -79,6 +80,11 @@ public: NativeWebMouseEvent(HWND, UINT message, WPARAM, LPARAM, bool); #endif @@ -9840,10 +9846,10 @@ index 72ad2880160a374e8fa663e561d59becf9d2f36d..372ae6953199245fe4fc55a49813c7ca #endif }; diff --git a/Source/WebKit/Shared/WebCoreArgumentCoders.cpp b/Source/WebKit/Shared/WebCoreArgumentCoders.cpp -index 33d83803d0284b3471cfe848aa8b0ae86735ce28..cf76a4b330469bba86fb2c72ee82c2ead0c34b77 100644 +index 8da28fdec08100d09523701e34a5259771d3a88b..6efd40a3d0ea091bc47f5f9b8838abb38abc8192 100644 --- a/Source/WebKit/Shared/WebCoreArgumentCoders.cpp +++ b/Source/WebKit/Shared/WebCoreArgumentCoders.cpp -@@ -193,6 +193,10 @@ +@@ -190,6 +190,10 @@ #include #endif @@ -9855,10 +9861,10 @@ index 33d83803d0284b3471cfe848aa8b0ae86735ce28..cf76a4b330469bba86fb2c72ee82c2ea namespace IPC { diff --git a/Source/WebKit/Shared/WebCoreArgumentCoders.serialization.in b/Source/WebKit/Shared/WebCoreArgumentCoders.serialization.in -index b106303da85a8226327a6f9e986c4646d39b4bbc..56c69d19af821e0ebdfa77c6873ca0c5d165dc7f 100644 +index a6776eaf01794b6c3fdba691e1d8ef7019988906..e6a185b040a776cab3de6791978ad2743c1456ca 100644 --- a/Source/WebKit/Shared/WebCoreArgumentCoders.serialization.in +++ b/Source/WebKit/Shared/WebCoreArgumentCoders.serialization.in -@@ -2338,6 +2338,9 @@ class WebCore::AuthenticationChallenge { +@@ -2400,6 +2400,9 @@ class WebCore::AuthenticationChallenge { class WebCore::DragData { #if PLATFORM(COCOA) String pasteboardName(); @@ -9868,7 +9874,7 @@ index b106303da85a8226327a6f9e986c4646d39b4bbc..56c69d19af821e0ebdfa77c6873ca0c5 #endif WebCore::IntPoint clientPosition(); WebCore::IntPoint globalPosition(); -@@ -2860,6 +2863,7 @@ enum class WebCore::ResourceLoadPriority : uint8_t { +@@ -2927,6 +2930,7 @@ using WebCore::ResourceResponseBase::Source = WebCore::ResourceResponseBaseSourc AtomString m_httpStatusText; AtomString m_httpVersion; WebCore::HTTPHeaderMap m_httpHeaderFields; @@ -9877,18 +9883,10 @@ index b106303da85a8226327a6f9e986c4646d39b4bbc..56c69d19af821e0ebdfa77c6873ca0c5 short m_httpStatusCode; diff --git a/Source/WebKit/Shared/WebKeyboardEvent.cpp b/Source/WebKit/Shared/WebKeyboardEvent.cpp -index a80efd294086ea1bd3b1e9dc805491ff5230962f..17d9d5461ae47a122160f10b7fdcb662f06bea7a 100644 +index b80bcb39473ecec86be5671f38698130bd9acbf3..d886cbac5f4c073e14e12f257fa920419ea0cf39 100644 --- a/Source/WebKit/Shared/WebKeyboardEvent.cpp +++ b/Source/WebKit/Shared/WebKeyboardEvent.cpp -@@ -35,6 +35,7 @@ WebKeyboardEvent::WebKeyboardEvent() - { - } - -+ - #if USE(APPKIT) - - WebKeyboardEvent::WebKeyboardEvent(WebEvent&& event, const String& text, const String& unmodifiedText, const String& key, const String& code, const String& keyIdentifier, int windowsVirtualKeyCode, int nativeVirtualKeyCode, int macCharCode, bool handledByInputMethod, const Vector& commands, bool isAutoRepeat, bool isKeypad, bool isSystemKey) -@@ -56,6 +57,24 @@ WebKeyboardEvent::WebKeyboardEvent(WebEvent&& event, const String& text, const S +@@ -52,6 +52,24 @@ WebKeyboardEvent::WebKeyboardEvent(WebEvent&& event, const String& text, const S ASSERT(isKeyboardEventType(type())); } @@ -9913,7 +9911,7 @@ index a80efd294086ea1bd3b1e9dc805491ff5230962f..17d9d5461ae47a122160f10b7fdcb662 #elif PLATFORM(GTK) WebKeyboardEvent::WebKeyboardEvent(WebEvent&& event, const String& text, const String& key, const String& code, const String& keyIdentifier, int windowsVirtualKeyCode, int nativeVirtualKeyCode, bool handledByInputMethod, std::optional>&& preeditUnderlines, std::optional&& preeditSelectionRange, Vector&& commands, bool isAutoRepeat, bool isKeypad) -@@ -79,6 +98,24 @@ WebKeyboardEvent::WebKeyboardEvent(WebEvent&& event, const String& text, const S +@@ -75,6 +93,24 @@ WebKeyboardEvent::WebKeyboardEvent(WebEvent&& event, const String& text, const S ASSERT(isKeyboardEventType(type())); } @@ -9938,7 +9936,7 @@ index a80efd294086ea1bd3b1e9dc805491ff5230962f..17d9d5461ae47a122160f10b7fdcb662 #elif PLATFORM(IOS_FAMILY) WebKeyboardEvent::WebKeyboardEvent(WebEvent&& event, const String& text, const String& unmodifiedText, const String& key, const String& code, const String& keyIdentifier, int windowsVirtualKeyCode, int nativeVirtualKeyCode, int macCharCode, bool handledByInputMethod, bool isAutoRepeat, bool isKeypad, bool isSystemKey) -@@ -142,6 +179,27 @@ WebKeyboardEvent::WebKeyboardEvent(WebEvent&& event, const String& text, const S +@@ -138,6 +174,27 @@ WebKeyboardEvent::WebKeyboardEvent(WebEvent&& event, const String& text, const S #endif @@ -9967,10 +9965,10 @@ index a80efd294086ea1bd3b1e9dc805491ff5230962f..17d9d5461ae47a122160f10b7fdcb662 { } diff --git a/Source/WebKit/Shared/WebKeyboardEvent.h b/Source/WebKit/Shared/WebKeyboardEvent.h -index 7840e34e303873e771150f242f57e621ebbb78a3..c3cea2e0506bcf5c25815b06d35dbdb5dc58edb7 100644 +index 976edc95bef9fde10d1e875fce2e00d3732c0456..f2f49d9badd67317c5c1f9aa248e8a1cb6a2f66d 100644 --- a/Source/WebKit/Shared/WebKeyboardEvent.h +++ b/Source/WebKit/Shared/WebKeyboardEvent.h -@@ -43,14 +43,18 @@ public: +@@ -42,14 +42,18 @@ public: #if USE(APPKIT) WebKeyboardEvent(WebEvent&&, const String& text, const String& unmodifiedText, const String& key, const String& code, const String& keyIdentifier, int windowsVirtualKeyCode, int nativeVirtualKeyCode, int macCharCode, bool handledByInputMethod, const Vector&, bool isAutoRepeat, bool isKeypad, bool isSystemKey); @@ -9990,17 +9988,17 @@ index 7840e34e303873e771150f242f57e621ebbb78a3..c3cea2e0506bcf5c25815b06d35dbdb5 const String& text() const { return m_text; } diff --git a/Source/WebKit/Shared/WebMouseEvent.h b/Source/WebKit/Shared/WebMouseEvent.h -index d5f411e74ca6db9cdd9fcb6712f9ef7294dfde0e..a002b61520e52afb88bc3d2340e2010196860406 100644 +index a38fc7fde1d5f1a1fd04ae1f84eb59c1501deec5..d3669c3d3bad91468fbbeeaa328c361082ecd408 100644 --- a/Source/WebKit/Shared/WebMouseEvent.h +++ b/Source/WebKit/Shared/WebMouseEvent.h -@@ -73,6 +73,7 @@ public: +@@ -70,6 +70,7 @@ public: - WebMouseEventButton button() const { return static_cast(m_button); } + WebMouseEventButton button() const { return m_button; } unsigned short buttons() const { return m_buttons; } + void playwrightSetButtons(unsigned short buttons) { m_buttons = buttons; } const WebCore::IntPoint& position() const { return m_position; } // Relative to the view. + void setPosition(const WebCore::IntPoint& position) { m_position = position; } const WebCore::IntPoint& globalPosition() const { return m_globalPosition; } - float deltaX() const { return m_deltaX; } diff --git a/Source/WebKit/Shared/WebPageCreationParameters.h b/Source/WebKit/Shared/WebPageCreationParameters.h index 843ab2240d8d6718e9d542854f82e85eac528594..fed43c932c5d87ce60eb010f2498ba4d25fd4f8d 100644 --- a/Source/WebKit/Shared/WebPageCreationParameters.h @@ -10283,17 +10281,32 @@ index 0000000000000000000000000000000000000000..789a0d7cf69704c8f665a9ed79348fbc +}; + +} // namespace IPC +diff --git a/Source/WebKit/Shared/win/WebEventFactory.cpp b/Source/WebKit/Shared/win/WebEventFactory.cpp +index 665b9d6a9de903ee9ad6dc53e15ab421b6cb769f..2b129963074d2ceec1c05f3a637c5e1c9e652008 100644 +--- a/Source/WebKit/Shared/win/WebEventFactory.cpp ++++ b/Source/WebKit/Shared/win/WebEventFactory.cpp +@@ -476,7 +476,7 @@ WebKeyboardEvent WebEventFactory::createWebKeyboardEvent(HWND hwnd, UINT message + #if ENABLE(TOUCH_EVENTS) + WebTouchEvent WebEventFactory::createWebTouchEvent() + { +- return WebTouchEvent(); ++ return WebTouchEvent({ WebEventType::TouchMove, OptionSet { }, WallTime::now()}, { }); + } + #endif // ENABLE(TOUCH_EVENTS) + diff --git a/Source/WebKit/Sources.txt b/Source/WebKit/Sources.txt -index d38b9735d6f7730b4fc97863274805d44081516a..43f9cd8ceae182313451753507ad15c2ee7701ff 100644 +index 54ba22b9092ef5db4b89cab56ca7c23bff325b74..93f49bd8b4bd7f3f367433931eb923c6dd49bef4 100644 --- a/Source/WebKit/Sources.txt +++ b/Source/WebKit/Sources.txt -@@ -384,21 +384,26 @@ Shared/XR/XRDeviceProxy.cpp - +@@ -386,6 +386,7 @@ Shared/XR/XRDeviceProxy.cpp UIProcess/AuxiliaryProcessProxy.cpp UIProcess/BackgroundProcessResponsivenessTimer.cpp + UIProcess/BrowsingContextGroup.cpp +UIProcess/BrowserInspectorPipe.cpp UIProcess/DeviceIdHashSaltStorage.cpp - UIProcess/DrawingAreaProxy.cpp + UIProcess/DisplayLink.cpp + UIProcess/DisplayLinkProcessProxyClient.cpp +@@ -393,16 +394,20 @@ UIProcess/DrawingAreaProxy.cpp UIProcess/FrameLoadState.cpp UIProcess/GeolocationPermissionRequestManagerProxy.cpp UIProcess/GeolocationPermissionRequestProxy.cpp @@ -10314,7 +10327,7 @@ index d38b9735d6f7730b4fc97863274805d44081516a..43f9cd8ceae182313451753507ad15c2 UIProcess/RemotePageDrawingAreaProxy.cpp UIProcess/RemotePageProxy.cpp UIProcess/ResponsivenessTimer.cpp -@@ -442,6 +447,8 @@ UIProcess/WebOpenPanelResultListenerProxy.cpp +@@ -446,6 +451,8 @@ UIProcess/WebOpenPanelResultListenerProxy.cpp UIProcess/WebPageDiagnosticLoggingClient.cpp UIProcess/WebPageGroup.cpp UIProcess/WebPageInjectedBundleClient.cpp @@ -10323,7 +10336,7 @@ index d38b9735d6f7730b4fc97863274805d44081516a..43f9cd8ceae182313451753507ad15c2 UIProcess/WebPageProxy.cpp UIProcess/WebPageProxyMessageReceiverRegistration.cpp UIProcess/WebPasteboardProxy.cpp -@@ -573,7 +580,11 @@ UIProcess/Inspector/WebInspectorUtilities.cpp +@@ -577,7 +584,11 @@ UIProcess/Inspector/WebInspectorUtilities.cpp UIProcess/Inspector/WebPageDebuggable.cpp UIProcess/Inspector/WebPageInspectorController.cpp @@ -10336,7 +10349,7 @@ index d38b9735d6f7730b4fc97863274805d44081516a..43f9cd8ceae182313451753507ad15c2 UIProcess/Media/AudioSessionRoutingArbitratorProxy.cpp UIProcess/Media/MediaUsageManager.cpp diff --git a/Source/WebKit/SourcesCocoa.txt b/Source/WebKit/SourcesCocoa.txt -index 58047624cd374a6e074e1e6bee1d891829d8ff43..f8670e9474dff752e03dab613b62c42f191262df 100644 +index 86462cd6ccdea1e2e0d880522c0b45c89a0ae95e..074368b88c78b253c800907a8a2c18fa3a1f60ba 100644 --- a/Source/WebKit/SourcesCocoa.txt +++ b/Source/WebKit/SourcesCocoa.txt @@ -258,6 +258,7 @@ UIProcess/API/Cocoa/_WKApplicationManifest.mm @@ -10356,10 +10369,10 @@ index 58047624cd374a6e074e1e6bee1d891829d8ff43..f8670e9474dff752e03dab613b62c42f UIProcess/Inspector/mac/WKInspectorResourceURLSchemeHandler.mm UIProcess/Inspector/mac/WKInspectorViewController.mm diff --git a/Source/WebKit/SourcesGTK.txt b/Source/WebKit/SourcesGTK.txt -index 2a57e2ade1be21cc7603fd246702db002f5f1d9c..d552d6fc58a650ff17283ba2661b69a8ef7ddf93 100644 +index 9dbd29a59aecfbbf84d41117eccb3ee7d7b5f68d..e871cb46c70193613d91a8367acbf9e791f26eea 100644 --- a/Source/WebKit/SourcesGTK.txt +++ b/Source/WebKit/SourcesGTK.txt -@@ -137,6 +137,7 @@ UIProcess/API/glib/WebKitAutomationSession.cpp @no-unify +@@ -136,6 +136,7 @@ UIProcess/API/glib/WebKitAutomationSession.cpp @no-unify UIProcess/API/glib/WebKitBackForwardList.cpp @no-unify UIProcess/API/glib/WebKitBackForwardListItem.cpp @no-unify UIProcess/API/glib/WebKitClipboardPermissionRequest.cpp @no-unify @@ -10367,15 +10380,15 @@ index 2a57e2ade1be21cc7603fd246702db002f5f1d9c..d552d6fc58a650ff17283ba2661b69a8 UIProcess/API/glib/WebKitContextMenuClient.cpp @no-unify UIProcess/API/glib/WebKitCookieManager.cpp @no-unify UIProcess/API/glib/WebKitCredential.cpp @no-unify -@@ -260,6 +261,7 @@ UIProcess/WebsiteData/soup/WebsiteDataStoreSoup.cpp - - UIProcess/cairo/BackingStoreCairo.cpp @no-unify - +@@ -263,6 +264,7 @@ UIProcess/glib/DisplayLinkGLib.cpp + UIProcess/glib/DisplayVBlankMonitor.cpp + UIProcess/glib/DisplayVBlankMonitorDRM.cpp + UIProcess/glib/DisplayVBlankMonitorTimer.cpp +UIProcess/glib/InspectorPlaywrightAgentClientGLib.cpp UIProcess/glib/WebPageProxyGLib.cpp UIProcess/glib/WebProcessPoolGLib.cpp UIProcess/glib/WebProcessProxyGLib.cpp -@@ -276,6 +278,7 @@ UIProcess/gtk/ClipboardGtk4.cpp @no-unify +@@ -279,6 +281,7 @@ UIProcess/gtk/ClipboardGtk4.cpp @no-unify UIProcess/gtk/WebDateTimePickerGtk.cpp UIProcess/gtk/GtkSettingsManager.cpp UIProcess/gtk/HardwareAccelerationManager.cpp @@ -10383,7 +10396,7 @@ index 2a57e2ade1be21cc7603fd246702db002f5f1d9c..d552d6fc58a650ff17283ba2661b69a8 UIProcess/gtk/KeyBindingTranslator.cpp UIProcess/gtk/PointerLockManager.cpp @no-unify UIProcess/gtk/PointerLockManagerWayland.cpp @no-unify -@@ -288,6 +291,8 @@ UIProcess/gtk/ViewGestureControllerGtk.cpp +@@ -291,6 +294,8 @@ UIProcess/gtk/ViewGestureControllerGtk.cpp UIProcess/gtk/WebColorPickerGtk.cpp UIProcess/gtk/WebContextMenuProxyGtk.cpp UIProcess/gtk/WebDataListSuggestionsDropdownGtk.cpp @@ -10517,7 +10530,7 @@ index f3e3b7ef340958b5a46c9252ff63a140afc24e2c..4b516ac0840608638209705250d8bf7e virtual void setStatusText(WebKit::WebPageProxy*, const WTF::String&) { } virtual void mouseDidMoveOverElement(WebKit::WebPageProxy&, const WebKit::WebHitTestResultData&, OptionSet, Object*) { } diff --git a/Source/WebKit/UIProcess/API/C/WKInspector.cpp b/Source/WebKit/UIProcess/API/C/WKInspector.cpp -index 988637e3347397ae751691f6356d090e1e5a9dbf..474c27a0849324c915e304d61b73ba4f98fdf365 100644 +index 990b0e5ebad19fdaf1b0036585be2ed88bc125d2..9f1931dbdd8c70d3637a2d71406b5968896037f2 100644 --- a/Source/WebKit/UIProcess/API/C/WKInspector.cpp +++ b/Source/WebKit/UIProcess/API/C/WKInspector.cpp @@ -28,6 +28,11 @@ @@ -10560,7 +10573,7 @@ index 026121d114c5fcad84c1396be8d692625beaa3bd..edd6e5cae033124c589959a42522fde0 } #endif diff --git a/Source/WebKit/UIProcess/API/C/WKPage.cpp b/Source/WebKit/UIProcess/API/C/WKPage.cpp -index f008969cf4d83c58d2c2b94642dc7f5d7a08bc4a..ec827835cfdb8c9fb4b872c41d0b9e5d6197a363 100644 +index 6c6e8f0ae2d133056f40caf6253377f751b48283..379dd3bea9dec5bd2e15031cfa6d89a407974c6f 100644 --- a/Source/WebKit/UIProcess/API/C/WKPage.cpp +++ b/Source/WebKit/UIProcess/API/C/WKPage.cpp @@ -1784,6 +1784,13 @@ void WKPageSetPageUIClient(WKPageRef pageRef, const WKPageUIClientBase* wkClient @@ -10647,10 +10660,10 @@ index 65d8ab73430840b02682ce879d1db18b9597d6b0..ea89752f4b90018ea1f008e0d89f9a2a // Version 15. WKPageDecidePolicyForSpeechRecognitionPermissionRequestCallback decidePolicyForSpeechRecognitionPermissionRequest; diff --git a/Source/WebKit/UIProcess/API/Cocoa/WKPreferences.mm b/Source/WebKit/UIProcess/API/Cocoa/WKPreferences.mm -index fd1619cf0f9c3bea676e90ae81a84fa023f4b209..ad224273f275e697676ab4586da96dcda0807a68 100644 +index a3467a0ef399235b9f8e689b67887b38eba184df..c12f153c3b5d5a38674076a714503126566f3ede 100644 --- a/Source/WebKit/UIProcess/API/Cocoa/WKPreferences.mm +++ b/Source/WebKit/UIProcess/API/Cocoa/WKPreferences.mm -@@ -716,6 +716,16 @@ - (void)_setMediaCaptureRequiresSecureConnection:(BOOL)requiresSecureConnection +@@ -706,6 +706,16 @@ - (void)_setMediaCaptureRequiresSecureConnection:(BOOL)requiresSecureConnection _preferences->setMediaCaptureRequiresSecureConnection(requiresSecureConnection); } @@ -10668,10 +10681,10 @@ index fd1619cf0f9c3bea676e90ae81a84fa023f4b209..ad224273f275e697676ab4586da96dcd { return _preferences->inactiveMediaCaptureSteamRepromptIntervalInMinutes(); diff --git a/Source/WebKit/UIProcess/API/Cocoa/WKPreferencesPrivate.h b/Source/WebKit/UIProcess/API/Cocoa/WKPreferencesPrivate.h -index 6b22ff70ebf73b69fb2ad02eed21d0dc016fb5e1..1df25b413da2e5b358597b8b500bc2ea921e9c6f 100644 +index 3b399415a391a7c32f822c6bc61f5ef5e8cae8ce..9940139edc9fc7425ca0e6c8907460ebbcc38b5d 100644 --- a/Source/WebKit/UIProcess/API/Cocoa/WKPreferencesPrivate.h +++ b/Source/WebKit/UIProcess/API/Cocoa/WKPreferencesPrivate.h -@@ -122,6 +122,7 @@ typedef NS_ENUM(NSInteger, _WKPitchCorrectionAlgorithm) { +@@ -121,6 +121,7 @@ typedef NS_ENUM(NSInteger, _WKPitchCorrectionAlgorithm) { @property (nonatomic, setter=_setMockCaptureDevicesEnabled:) BOOL _mockCaptureDevicesEnabled WK_API_AVAILABLE(macos(10.13), ios(11.0)); @property (nonatomic, setter=_setMockCaptureDevicesPromptEnabled:) BOOL _mockCaptureDevicesPromptEnabled WK_API_AVAILABLE(macos(10.13.4), ios(11.3)); @property (nonatomic, setter=_setMediaCaptureRequiresSecureConnection:) BOOL _mediaCaptureRequiresSecureConnection WK_API_AVAILABLE(macos(10.13), ios(11.0)); @@ -10718,7 +10731,7 @@ index fe711453fd4ef346ff7d88bfec878642b6eb32d6..c3e4fcf0b51e827b6945c89613f22b17 NS_ASSUME_NONNULL_END diff --git a/Source/WebKit/UIProcess/API/Cocoa/WKWebsiteDataStore.mm b/Source/WebKit/UIProcess/API/Cocoa/WKWebsiteDataStore.mm -index d19ba0dec9f449f10ca00d7aa86e4aef760c6044..19f6b7c82cc9fc5b3db708f428563ac5a888955d 100644 +index 830340886c8cd3d3699ad8f9daa1be7a03edf113..8aee71985c4924ff384b1491d81f874831c31044 100644 --- a/Source/WebKit/UIProcess/API/Cocoa/WKWebsiteDataStore.mm +++ b/Source/WebKit/UIProcess/API/Cocoa/WKWebsiteDataStore.mm @@ -50,6 +50,7 @@ @@ -10729,7 +10742,7 @@ index d19ba0dec9f449f10ca00d7aa86e4aef760c6044..19f6b7c82cc9fc5b3db708f428563ac5 #import #import #import -@@ -371,6 +372,11 @@ - (void)removeDataOfTypes:(NSSet *)dataTypes modifiedSince:(NSDate *)date comple +@@ -382,6 +383,11 @@ - (void)removeDataOfTypes:(NSSet *)dataTypes modifiedSince:(NSDate *)date comple }); } @@ -10902,7 +10915,7 @@ index 5dbdde074da5226c4a76ec5a4fcf54b3fb717849..f2aa93846c6d22c20e0584d90afc46e9 { _processPoolConfiguration->setIsAutomaticProcessWarmingEnabled(prewarms); diff --git a/Source/WebKit/UIProcess/API/Cocoa/_WKRemoteWebInspectorViewController.mm b/Source/WebKit/UIProcess/API/Cocoa/_WKRemoteWebInspectorViewController.mm -index 96d3ce10e76c3d24a452d6d22c7c7dc5bc141958..38ef5226464059584db57f8497eb7a4f470ccea2 100644 +index e5209134d490c226db6a122f4f0f3f38d26e20eb..47a41b11999d7927fbfbe033e3630eac86d61e99 100644 --- a/Source/WebKit/UIProcess/API/Cocoa/_WKRemoteWebInspectorViewController.mm +++ b/Source/WebKit/UIProcess/API/Cocoa/_WKRemoteWebInspectorViewController.mm @@ -24,6 +24,7 @@ @@ -11224,7 +11237,7 @@ index e994309b097c1b140abfa4373fd2fafee46c05ec..6e0cc677a3bf33683ae8c89d12a48191 #endif +int webkitWebContextExistingCount(); diff --git a/Source/WebKit/UIProcess/API/glib/WebKitWebView.cpp b/Source/WebKit/UIProcess/API/glib/WebKitWebView.cpp -index f7b6b9edc676320c8e557e20551411eaa07aad0f..1881ca72d1b6adf15d3c7e9148615c0fa47f6e4f 100644 +index 26dee4a6bedbbed1148b22d288fe115ed6ac3a59..da2f18fb5e0a9448a672cba7ea526dce5a748fdc 100644 --- a/Source/WebKit/UIProcess/API/glib/WebKitWebView.cpp +++ b/Source/WebKit/UIProcess/API/glib/WebKitWebView.cpp @@ -34,6 +34,7 @@ @@ -11331,7 +11344,7 @@ index f7b6b9edc676320c8e557e20551411eaa07aad0f..1881ca72d1b6adf15d3c7e9148615c0f { if (!webView->priv->currentScriptDialog) diff --git a/Source/WebKit/UIProcess/API/glib/WebKitWebViewPrivate.h b/Source/WebKit/UIProcess/API/glib/WebKitWebViewPrivate.h -index c2c3aaa89344742b3e53e2e7afc004828e02b46e..7863882532a6cf4cb592a475ff165a264c3c1913 100644 +index 8b5eb1ce720c9ad09b9de9e9f3d6b15564a86b54..89642048bad844aabdf1eec8486679bfc75e20c0 100644 --- a/Source/WebKit/UIProcess/API/glib/WebKitWebViewPrivate.h +++ b/Source/WebKit/UIProcess/API/glib/WebKitWebViewPrivate.h @@ -65,6 +65,7 @@ void webkitWebViewRunJavaScriptAlert(WebKitWebView*, const CString& message, Fun @@ -11355,7 +11368,7 @@ index 805f9f638c1630b5e9310494ae2970262de001cc..add3e80896c2e82bdd12cee15c8014bf #include <@API_INCLUDE_PREFIX@/WebKitClipboardPermissionRequest.h> #include <@API_INCLUDE_PREFIX@/WebKitColorChooserRequest.h> diff --git a/Source/WebKit/UIProcess/API/gtk/PageClientImpl.cpp b/Source/WebKit/UIProcess/API/gtk/PageClientImpl.cpp -index 8abaceabaa129b7d2ae369c8840bdfc41699f580..51f8e259b424a7416f1613f4d7e40a5f4cd8b8e8 100644 +index fe0d38ff507855bed8ac300e118812420b383e34..1e355db49a389b3b85d0a32d4b3110aae556eec8 100644 --- a/Source/WebKit/UIProcess/API/gtk/PageClientImpl.cpp +++ b/Source/WebKit/UIProcess/API/gtk/PageClientImpl.cpp @@ -257,6 +257,8 @@ void PageClientImpl::doneWithKeyEvent(const NativeWebKeyboardEvent& event, bool @@ -11493,10 +11506,10 @@ index 496079da90993ac37689b060b69ecd4a67c2b6a8..af30181ca922f16c0f6e245c70e5ce7d G_BEGIN_DECLS diff --git a/Source/WebKit/UIProcess/API/gtk/WebKitWebViewBase.cpp b/Source/WebKit/UIProcess/API/gtk/WebKitWebViewBase.cpp -index 6eb11ae3ae2cd7667d10d165de33342713306cd7..41c420b6e395a67218b9942e7ef879c7958515fb 100644 +index 6147a00162bbbde87746db7ce16b474a77aaa343..96600573d75e0bdc779071632517c11d020a2b51 100644 --- a/Source/WebKit/UIProcess/API/gtk/WebKitWebViewBase.cpp +++ b/Source/WebKit/UIProcess/API/gtk/WebKitWebViewBase.cpp -@@ -2823,6 +2823,11 @@ void webkitWebViewBaseResetClickCounter(WebKitWebViewBase* webkitWebViewBase) +@@ -2997,6 +2997,11 @@ void webkitWebViewBaseResetClickCounter(WebKitWebViewBase* webkitWebViewBase) #endif } @@ -11508,7 +11521,7 @@ index 6eb11ae3ae2cd7667d10d165de33342713306cd7..41c420b6e395a67218b9942e7ef879c7 void webkitWebViewBaseEnterAcceleratedCompositingMode(WebKitWebViewBase* webkitWebViewBase, const LayerTreeContext& layerTreeContext) { ASSERT(webkitWebViewBase->priv->acceleratedBackingStore); -@@ -2883,12 +2888,12 @@ void webkitWebViewBasePageClosed(WebKitWebViewBase* webkitWebViewBase) +@@ -3053,12 +3058,12 @@ void webkitWebViewBasePageClosed(WebKitWebViewBase* webkitWebViewBase) webkitWebViewBase->priv->acceleratedBackingStore->update({ }); } @@ -11524,7 +11537,7 @@ index 6eb11ae3ae2cd7667d10d165de33342713306cd7..41c420b6e395a67218b9942e7ef879c7 #if !USE(GTK4) diff --git a/Source/WebKit/UIProcess/API/gtk/WebKitWebViewBasePrivate.h b/Source/WebKit/UIProcess/API/gtk/WebKitWebViewBasePrivate.h -index 1204b4342c1cf8d38d215c1c8628bd7eb1fbd415..43265c737ecaa3ee24513ad50ca3c3650ebeb698 100644 +index a003c198a6ec179454b010fda093a303250da7b0..125b1960ba73b1dd26daff90eb415e9735b13cfa 100644 --- a/Source/WebKit/UIProcess/API/gtk/WebKitWebViewBasePrivate.h +++ b/Source/WebKit/UIProcess/API/gtk/WebKitWebViewBasePrivate.h @@ -27,6 +27,7 @@ @@ -11535,7 +11548,7 @@ index 1204b4342c1cf8d38d215c1c8628bd7eb1fbd415..43265c737ecaa3ee24513ad50ca3c365 #include "APIPageConfiguration.h" #include "InputMethodState.h" #include "SameDocumentNavigationType.h" -@@ -97,7 +98,7 @@ void webkitWebViewBaseStartDrag(WebKitWebViewBase*, WebCore::SelectionData&&, Op +@@ -100,7 +101,7 @@ void webkitWebViewBaseStartDrag(WebKitWebViewBase*, WebCore::SelectionData&&, Op void webkitWebViewBaseDidPerformDragControllerAction(WebKitWebViewBase*); #endif @@ -11544,7 +11557,7 @@ index 1204b4342c1cf8d38d215c1c8628bd7eb1fbd415..43265c737ecaa3ee24513ad50ca3c365 void webkitWebViewBaseSetEnableBackForwardNavigationGesture(WebKitWebViewBase*, bool enabled); WebKit::ViewGestureController* webkitWebViewBaseViewGestureController(WebKitWebViewBase*); -@@ -130,3 +131,5 @@ void webkitWebViewBaseSynthesizeWheelEvent(WebKitWebViewBase*, const GdkEvent*, +@@ -133,3 +134,5 @@ void webkitWebViewBaseSynthesizeWheelEvent(WebKitWebViewBase*, const GdkEvent*, void webkitWebViewBaseMakeBlank(WebKitWebViewBase*, bool); void webkitWebViewBasePageGrabbedTouch(WebKitWebViewBase*); void webkitWebViewBaseSetShouldNotifyFocusEvents(WebKitWebViewBase*, bool); @@ -11988,10 +12001,10 @@ index 8a8356b94e0632118a24bb9adf5a1fe72f10fb8d..f332ffe5e633dce8e8a7f0f2a411ca29 void didChangePageID(WKWPE::View&) override; void didReceiveUserMessage(WKWPE::View&, WebKit::UserMessage&&, CompletionHandler&&) override; diff --git a/Source/WebKit/UIProcess/AuxiliaryProcessProxy.cpp b/Source/WebKit/UIProcess/AuxiliaryProcessProxy.cpp -index f297d86285bcc3f975638be9b7750ae7ce11f05f..85b07321c8b155bcf6433f0d7c7807224039e342 100644 +index b3f4ddba8d65cdb8f67f4f3370f5fa63da84c735..011446c18963da3fa3341f550ee58a09c8b59aa1 100644 --- a/Source/WebKit/UIProcess/AuxiliaryProcessProxy.cpp +++ b/Source/WebKit/UIProcess/AuxiliaryProcessProxy.cpp -@@ -127,7 +127,11 @@ void AuxiliaryProcessProxy::getLaunchOptions(ProcessLauncher::LaunchOptions& lau +@@ -140,7 +140,11 @@ void AuxiliaryProcessProxy::getLaunchOptions(ProcessLauncher::LaunchOptions& lau launchOptions.processCmdPrefix = String::fromUTF8(processCmdPrefix); #endif // ENABLE(DEVELOPER_MODE) && (PLATFORM(GTK) || PLATFORM(WPE)) @@ -12004,10 +12017,10 @@ index f297d86285bcc3f975638be9b7750ae7ce11f05f..85b07321c8b155bcf6433f0d7c780722 platformGetLaunchOptions(launchOptions); } diff --git a/Source/WebKit/UIProcess/AuxiliaryProcessProxy.h b/Source/WebKit/UIProcess/AuxiliaryProcessProxy.h -index 90e6a8f4292c8fbcbd0cc36fa3618f8b7d2d62cc..14ac561735f2de4ccd0b2ef74e7e7a1f41c1d983 100644 +index 9aff8c5be29dee4751dfd98615d4b6dfbba2c010..ab3841c8017dee65bdacdb0f9487a6955de2cba0 100644 --- a/Source/WebKit/UIProcess/AuxiliaryProcessProxy.h +++ b/Source/WebKit/UIProcess/AuxiliaryProcessProxy.h -@@ -208,13 +208,16 @@ protected: +@@ -207,13 +207,16 @@ protected: static RefPtr fetchAudioComponentServerRegistrations(); #endif @@ -12190,7 +12203,7 @@ index 7b4841ee8a15252d24fa7378a2005efb2db96f6f..d3eb7886316c7c1b45fb4673df15b743 bool webViewRunBeforeUnloadConfirmPanelWithMessageInitiatedByFrameCompletionHandler : 1; bool webViewRequestGeolocationPermissionForFrameDecisionHandler : 1; diff --git a/Source/WebKit/UIProcess/Cocoa/UIDelegate.mm b/Source/WebKit/UIProcess/Cocoa/UIDelegate.mm -index 17e89992472d58c617b5c9c0bd0d478b7c1e9123..aea3be250928924fd68910e0309c835a0dac2446 100644 +index 2b48d6de50f8dd8831e4b368105c032902c23188..459588046d140d21544d9f8838e66c6d9e0f0bbb 100644 --- a/Source/WebKit/UIProcess/Cocoa/UIDelegate.mm +++ b/Source/WebKit/UIProcess/Cocoa/UIDelegate.mm @@ -117,6 +117,7 @@ void UIDelegate::setDelegate(id delegate) @@ -12201,7 +12214,7 @@ index 17e89992472d58c617b5c9c0bd0d478b7c1e9123..aea3be250928924fd68910e0309c835a m_delegateMethods.webViewRequestStorageAccessPanelUnderFirstPartyCompletionHandler = [delegate respondsToSelector:@selector(_webView:requestStorageAccessPanelForDomain:underCurrentDomain:completionHandler:)]; m_delegateMethods.webViewRunBeforeUnloadConfirmPanelWithMessageInitiatedByFrameCompletionHandler = [delegate respondsToSelector:@selector(_webView:runBeforeUnloadConfirmPanelWithMessage:initiatedByFrame:completionHandler:)]; m_delegateMethods.webViewRequestGeolocationPermissionForOriginDecisionHandler = [delegate respondsToSelector:@selector(_webView:requestGeolocationPermissionForOrigin:initiatedByFrame:decisionHandler:)]; -@@ -427,6 +428,15 @@ void UIDelegate::UIClient::runJavaScriptPrompt(WebPageProxy& page, const WTF::St +@@ -429,6 +430,15 @@ void UIDelegate::UIClient::runJavaScriptPrompt(WebPageProxy& page, const WTF::St }).get()]; } @@ -12218,7 +12231,7 @@ index 17e89992472d58c617b5c9c0bd0d478b7c1e9123..aea3be250928924fd68910e0309c835a { if (!m_uiDelegate) diff --git a/Source/WebKit/UIProcess/Cocoa/WebPageProxyCocoa.mm b/Source/WebKit/UIProcess/Cocoa/WebPageProxyCocoa.mm -index 361bf358f4a63482d7cc1a2358ce93e6965b5289..c9545d1937f5526741bba0d7558c8cde2dc083c0 100644 +index 3e2492348a0aa797686552bcd75ca3ed5a26295a..72222350d4019d07ea20f32ad7d92b8efff7e463 100644 --- a/Source/WebKit/UIProcess/Cocoa/WebPageProxyCocoa.mm +++ b/Source/WebKit/UIProcess/Cocoa/WebPageProxyCocoa.mm @@ -37,6 +37,7 @@ @@ -12343,18 +12356,20 @@ index f064a1cfe684ce89caa2c45ccae4cd673b0d526d..65e866b3a6b9ab535c0cddcfef19c9db m_activationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:NSApplicationDidBecomeActiveNotification object:NSApp queue:[NSOperationQueue currentQueue] usingBlock:^(NSNotification *notification) { diff --git a/Source/WebKit/UIProcess/CoordinatedGraphics/DrawingAreaProxyCoordinatedGraphics.cpp b/Source/WebKit/UIProcess/CoordinatedGraphics/DrawingAreaProxyCoordinatedGraphics.cpp -index bf1ede749837580db8c5e487e5bacaf916d9c270..c1d38acabec8c59bc460dab264088abc377976d0 100644 +index be4de15c34e3baa6d4a7db749c705c549f5dd207..39e78fae0c6481de28ef800dc621b268ac02b62b 100644 --- a/Source/WebKit/UIProcess/CoordinatedGraphics/DrawingAreaProxyCoordinatedGraphics.cpp +++ b/Source/WebKit/UIProcess/CoordinatedGraphics/DrawingAreaProxyCoordinatedGraphics.cpp -@@ -33,12 +33,15 @@ +@@ -33,14 +33,17 @@ #include "LayerTreeContext.h" #include "MessageSenderInlines.h" #include "UpdateInfo.h" +#include "WebPageInspectorController.h" #include "WebPageProxy.h" #include "WebPreferences.h" + #include "WebProcessPool.h" #include "WebProcessProxy.h" #include + #include +#include #if PLATFORM(GTK) @@ -12362,7 +12377,7 @@ index bf1ede749837580db8c5e487e5bacaf916d9c270..c1d38acabec8c59bc460dab264088abc #include #endif -@@ -46,6 +49,13 @@ +@@ -48,6 +51,13 @@ #include #endif @@ -12376,8 +12391,8 @@ index bf1ede749837580db8c5e487e5bacaf916d9c270..c1d38acabec8c59bc460dab264088abc namespace WebKit { using namespace WebCore; -@@ -149,6 +159,11 @@ void DrawingAreaProxyCoordinatedGraphics::deviceScaleFactorDidChange() - m_webPageProxy.send(Messages::DrawingArea::SetDeviceScaleFactor(m_webPageProxy.deviceScaleFactor()), m_identifier); +@@ -161,6 +171,11 @@ void DrawingAreaProxyCoordinatedGraphics::deviceScaleFactorDidChange() + protectedWebPageProxy()->send(Messages::DrawingArea::SetDeviceScaleFactor(m_webPageProxy->deviceScaleFactor()), m_identifier); } +void DrawingAreaProxyCoordinatedGraphics::waitForSizeUpdate(Function&& callback) @@ -12388,9 +12403,9 @@ index bf1ede749837580db8c5e487e5bacaf916d9c270..c1d38acabec8c59bc460dab264088abc void DrawingAreaProxyCoordinatedGraphics::setBackingStoreIsDiscardable(bool isBackingStoreDiscardable) { #if !PLATFORM(WPE) -@@ -215,6 +230,45 @@ void DrawingAreaProxyCoordinatedGraphics::targetRefreshRateDidChange(unsigned ra - m_webPageProxy.send(Messages::DrawingArea::TargetRefreshRateDidChange(rate), m_identifier); +@@ -229,6 +244,45 @@ void DrawingAreaProxyCoordinatedGraphics::targetRefreshRateDidChange(unsigned ra } + #endif +#if PLATFORM(WIN) +void DrawingAreaProxyCoordinatedGraphics::didChangeAcceleratedCompositingMode(bool enabled) @@ -12406,12 +12421,12 @@ index bf1ede749837580db8c5e487e5bacaf916d9c270..c1d38acabec8c59bc460dab264088abc +#if PLATFORM(WIN) + HWndDC dc; + if (m_isInAcceleratedCompositingMode) { -+ dc.setHWnd(reinterpret_cast(m_webPageProxy.viewWidget())); ++ dc.setHWnd(reinterpret_cast(protectedWebPageProxy()->viewWidget())); + surface = adoptRef(cairo_win32_surface_create(dc)); +#else + if (isInAcceleratedCompositingMode()) { +# if PLATFORM(GTK) -+ AcceleratedBackingStore* backingStore = webkitWebViewBaseGetAcceleratedBackingStore(WEBKIT_WEB_VIEW_BASE(m_webPageProxy.viewWidget())); ++ AcceleratedBackingStore* backingStore = webkitWebViewBaseGetAcceleratedBackingStore(WEBKIT_WEB_VIEW_BASE(protectedWebPageProxy()->viewWidget())); + if (!backingStore) + return; + @@ -12427,14 +12442,14 @@ index bf1ede749837580db8c5e487e5bacaf916d9c270..c1d38acabec8c59bc460dab264088abc + if (!surface) + return; + -+ m_webPageProxy.inspectorController().didPaint(surface.get()); ++ protectedWebPageProxy()->inspectorController().didPaint(surface.get()); +} +#endif + bool DrawingAreaProxyCoordinatedGraphics::alwaysUseCompositing() const { - return m_webPageProxy.preferences().acceleratedCompositingEnabled() && m_webPageProxy.preferences().forceCompositingMode(); -@@ -269,6 +323,11 @@ void DrawingAreaProxyCoordinatedGraphics::didUpdateGeometry() + return m_webPageProxy->preferences().acceleratedCompositingEnabled() && m_webPageProxy->preferences().forceCompositingMode(); +@@ -283,6 +337,11 @@ void DrawingAreaProxyCoordinatedGraphics::didUpdateGeometry() // we need to resend the new size here. if (m_lastSentSize != m_size) sendUpdateGeometry(); @@ -12447,7 +12462,7 @@ index bf1ede749837580db8c5e487e5bacaf916d9c270..c1d38acabec8c59bc460dab264088abc #if !PLATFORM(WPE) diff --git a/Source/WebKit/UIProcess/CoordinatedGraphics/DrawingAreaProxyCoordinatedGraphics.h b/Source/WebKit/UIProcess/CoordinatedGraphics/DrawingAreaProxyCoordinatedGraphics.h -index 482795eb9d23e54d3e0dca1f7fb38aaab58818db..3c20d84970d6304392a760db1726baa3acf62e7f 100644 +index d5d61ca02c9acba5b359fcfcb7bbae3632ef8ce4..ca9b4cdb1276b4e53b3c256bc1459fa40d549de5 100644 --- a/Source/WebKit/UIProcess/CoordinatedGraphics/DrawingAreaProxyCoordinatedGraphics.h +++ b/Source/WebKit/UIProcess/CoordinatedGraphics/DrawingAreaProxyCoordinatedGraphics.h @@ -30,6 +30,7 @@ @@ -12469,17 +12484,17 @@ index 482795eb9d23e54d3e0dca1f7fb38aaab58818db..3c20d84970d6304392a760db1726baa3 void dispatchAfterEnsuringDrawing(CompletionHandler&&); -@@ -69,6 +74,9 @@ private: - void exitAcceleratedCompositingMode(uint64_t backingStoreStateID, UpdateInfo&&) override; - void updateAcceleratedCompositingMode(uint64_t backingStoreStateID, const LayerTreeContext&) override; +@@ -75,6 +80,9 @@ private: + #if !PLATFORM(GTK) void targetRefreshRateDidChange(unsigned) override; + #endif +#if PLATFORM(WIN) + void didChangeAcceleratedCompositingMode(bool enabled) override; +#endif bool shouldSendWheelEventsToEventDispatcher() const override { return true; } -@@ -118,6 +126,7 @@ private: +@@ -124,6 +132,7 @@ private: // The last size we sent to the web process. WebCore::IntSize m_lastSentSize; @@ -12487,7 +12502,7 @@ index 482795eb9d23e54d3e0dca1f7fb38aaab58818db..3c20d84970d6304392a760db1726baa3 #if !PLATFORM(WPE) bool m_isBackingStoreDiscardable { true }; -@@ -126,6 +135,10 @@ private: +@@ -132,6 +141,10 @@ private: RunLoop::Timer m_discardBackingStoreTimer; #endif std::unique_ptr m_drawingMonitor; @@ -12591,18 +12606,18 @@ index 9b69cad753b5b2e3844caac57b44c067507e68e7..1e898d7311a2cb8cb6d9a4042f91f41c } // namespace WebKit diff --git a/Source/WebKit/UIProcess/DrawingAreaProxy.h b/Source/WebKit/UIProcess/DrawingAreaProxy.h -index 6df5f84736de1e30958171ee39cbc91ff786798d..4c084b4c223580487a4f90f59e416a6c1c0f4854 100644 +index 3e9751ffb6e3069227dc0c92f0c9b9c18f7111f8..b66739b19aba8be7b69c8a7600d2b1b4c28844ff 100644 --- a/Source/WebKit/UIProcess/DrawingAreaProxy.h +++ b/Source/WebKit/UIProcess/DrawingAreaProxy.h -@@ -86,6 +86,7 @@ public: +@@ -87,6 +87,7 @@ public: const WebCore::IntSize& size() const { return m_size; } bool setSize(const WebCore::IntSize&, const WebCore::IntSize& scrollOffset = { }); + void waitForSizeUpdate(Function&&); - #if USE(COORDINATED_GRAPHICS) || USE(TEXTURE_MAPPER) + #if !PLATFORM(GTK) && (USE(COORDINATED_GRAPHICS) || USE(TEXTURE_MAPPER)) virtual void targetRefreshRateDidChange(unsigned) { } -@@ -163,6 +164,10 @@ private: +@@ -166,6 +167,10 @@ private: virtual void update(uint64_t /* backingStoreStateID */, UpdateInfo&&) { } virtual void exitAcceleratedCompositingMode(uint64_t /* backingStoreStateID */, UpdateInfo&&) { } #endif @@ -13943,7 +13958,7 @@ index 0000000000000000000000000000000000000000..e2ce910f3fd7f587add552275b7e7176 + +} // namespace WebKit diff --git a/Source/WebKit/UIProcess/Inspector/InspectorTargetProxy.cpp b/Source/WebKit/UIProcess/Inspector/InspectorTargetProxy.cpp -index 4a945dd8063cd7cd90174f6d4cd4197bcb98a101..16e73d6a724dd80487b7b140aaaccfec253eb7aa 100644 +index fedef553cec70feb6c6475a5f752fbc6833dbb38..16e73d6a724dd80487b7b140aaaccfec253eb7aa 100644 --- a/Source/WebKit/UIProcess/Inspector/InspectorTargetProxy.cpp +++ b/Source/WebKit/UIProcess/Inspector/InspectorTargetProxy.cpp @@ -28,11 +28,10 @@ @@ -13959,7 +13974,7 @@ index 4a945dd8063cd7cd90174f6d4cd4197bcb98a101..16e73d6a724dd80487b7b140aaaccfec namespace WebKit { -@@ -40,18 +39,17 @@ using namespace Inspector; +@@ -40,19 +39,17 @@ using namespace Inspector; std::unique_ptr InspectorTargetProxy::create(WebPageProxy& page, const String& targetId, Inspector::InspectorTargetType type) { @@ -13970,7 +13985,8 @@ index 4a945dd8063cd7cd90174f6d4cd4197bcb98a101..16e73d6a724dd80487b7b140aaaccfec -std::unique_ptr InspectorTargetProxy::create(ProvisionalPageProxy& provisionalPage, const String& targetId, Inspector::InspectorTargetType type) +std::unique_ptr InspectorTargetProxy::create(ProvisionalPageProxy& provisionalPage, const String& targetId) { -- auto target = InspectorTargetProxy::create(provisionalPage.page(), targetId, type); +- Ref page = provisionalPage.page(); +- auto target = InspectorTargetProxy::create(page, targetId, type); - target->m_provisionalPage = provisionalPage; - return target; + return makeUnique(provisionalPage.page(), &provisionalPage, targetId, Inspector::InspectorTargetType::Page); @@ -13983,7 +13999,7 @@ index 4a945dd8063cd7cd90174f6d4cd4197bcb98a101..16e73d6a724dd80487b7b140aaaccfec , m_identifier(targetId) , m_type(type) { -@@ -98,6 +96,31 @@ void InspectorTargetProxy::didCommitProvisionalTarget() +@@ -99,6 +96,31 @@ void InspectorTargetProxy::didCommitProvisionalTarget() m_provisionalPage = nullptr; } @@ -14056,7 +14072,7 @@ index a2239cec8e18850f35f7f88a9c4ebadc62bf4023..79f3ff84327dc075ec96983e04db4b10 } // namespace WebKit diff --git a/Source/WebKit/UIProcess/Inspector/WebPageInspectorController.cpp b/Source/WebKit/UIProcess/Inspector/WebPageInspectorController.cpp -index ed4e6f30b8c35966075573dccee801daceec865e..81e10124a8a745727fc25316345cda01201a5dba 100644 +index 6d229b943fe69cd258b32b1e38c5716715a4cd4b..77a7bacae6c09488b2c44dd1fd758679532c1a24 100644 --- a/Source/WebKit/UIProcess/Inspector/WebPageInspectorController.cpp +++ b/Source/WebKit/UIProcess/Inspector/WebPageInspectorController.cpp @@ -26,13 +26,21 @@ @@ -14081,7 +14097,7 @@ index ed4e6f30b8c35966075573dccee801daceec865e..81e10124a8a745727fc25316345cda01 #include #include #include -@@ -49,27 +57,114 @@ static String getTargetID(const ProvisionalPageProxy& provisionalPage) +@@ -49,32 +57,119 @@ static String getTargetID(const ProvisionalPageProxy& provisionalPage) return WebPageInspectorTarget::toTargetID(provisionalPage.webPageID()); } @@ -14106,6 +14122,13 @@ index ed4e6f30b8c35966075573dccee801daceec865e..81e10124a8a745727fc25316345cda01 - m_agents.append(WTFMove(targetAgent)); } +-Ref WebPageInspectorController::protectedInspectedPage() ++CheckedRef WebPageInspectorController::protectedInspectedPage() + { +- return m_inspectedPage.get(); ++ return m_inspectedPage; + } + void WebPageInspectorController::init() { + auto targetAgent = makeUnique(m_frontendRouter.get(), m_backendDispatcher.get()); @@ -14125,15 +14148,15 @@ index ed4e6f30b8c35966075573dccee801daceec865e..81e10124a8a745727fc25316345cda01 + s_observer->didCreateInspectorController(m_inspectedPage); + + // window.open will create page with already running process. -+ if (!m_inspectedPage.hasRunningProcess()) ++ if (!m_inspectedPage->hasRunningProcess()) + return; - String pageTargetId = WebPageInspectorTarget::toTargetID(m_inspectedPage.webPageID()); + String pageTargetId = WebPageInspectorTarget::toTargetID(m_inspectedPage->webPageID()); createInspectorTarget(pageTargetId, Inspector::InspectorTargetType::Page); } +void WebPageInspectorController::didFinishAttachingToWebProcess() +{ -+ String pageTargetID = WebPageInspectorTarget::toTargetID(m_inspectedPage.webPageID()); ++ String pageTargetID = WebPageInspectorTarget::toTargetID(m_inspectedPage->webPageID()); + // Create target only after attaching to a Web Process first time. Before that + // we cannot event establish frontend connection. + if (m_targets.contains(pageTargetID)) @@ -14143,7 +14166,7 @@ index ed4e6f30b8c35966075573dccee801daceec865e..81e10124a8a745727fc25316345cda01 + void WebPageInspectorController::pageClosed() { -+ String pageTargetId = WebPageInspectorTarget::toTargetID(m_inspectedPage.webPageID()); ++ String pageTargetId = WebPageInspectorTarget::toTargetID(m_inspectedPage->webPageID()); + destroyInspectorTarget(pageTargetId); + disconnectAllFrontends(); @@ -14158,7 +14181,7 @@ index ed4e6f30b8c35966075573dccee801daceec865e..81e10124a8a745727fc25316345cda01 +{ + if (reason != ProcessTerminationReason::Crash) + return false; -+ String targetId = WebPageInspectorTarget::toTargetID(m_inspectedPage.webPageID()); ++ String targetId = WebPageInspectorTarget::toTargetID(m_inspectedPage->webPageID()); + auto it = m_targets.find(targetId); + if (it == m_targets.end()) + return false; @@ -14199,7 +14222,7 @@ index ed4e6f30b8c35966075573dccee801daceec865e..81e10124a8a745727fc25316345cda01 } bool WebPageInspectorController::hasLocalFrontend() const -@@ -83,6 +178,17 @@ void WebPageInspectorController::connectFrontend(Inspector::FrontendChannel& fro +@@ -88,6 +183,17 @@ void WebPageInspectorController::connectFrontend(Inspector::FrontendChannel& fro bool connectingFirstFrontend = !m_frontendRouter->hasFrontends(); @@ -14217,7 +14240,7 @@ index ed4e6f30b8c35966075573dccee801daceec865e..81e10124a8a745727fc25316345cda01 m_frontendRouter->connectFrontend(frontendChannel); if (connectingFirstFrontend) -@@ -101,8 +207,10 @@ void WebPageInspectorController::disconnectFrontend(FrontendChannel& frontendCha +@@ -107,8 +213,10 @@ void WebPageInspectorController::disconnectFrontend(FrontendChannel& frontendCha m_frontendRouter->disconnectFrontend(frontendChannel); bool disconnectingLastFrontend = !m_frontendRouter->hasFrontends(); @@ -14227,18 +14250,18 @@ index ed4e6f30b8c35966075573dccee801daceec865e..81e10124a8a745727fc25316345cda01 + m_pendingNavigations.clear(); + } - m_inspectedPage.didChangeInspectorFrontendCount(m_frontendRouter->frontendCount()); - -@@ -125,6 +233,8 @@ void WebPageInspectorController::disconnectAllFrontends() + auto inspectedPage = protectedInspectedPage(); + inspectedPage->didChangeInspectorFrontendCount(m_frontendRouter->frontendCount()); +@@ -132,6 +240,8 @@ void WebPageInspectorController::disconnectAllFrontends() // Disconnect any remaining remote frontends. m_frontendRouter->disconnectAllFrontends(); + m_pendingNavigations.clear(); + - m_inspectedPage.didChangeInspectorFrontendCount(m_frontendRouter->frontendCount()); + auto inspectedPage = protectedInspectedPage(); + inspectedPage->didChangeInspectorFrontendCount(m_frontendRouter->frontendCount()); - #if ENABLE(REMOTE_INSPECTOR) -@@ -151,6 +261,66 @@ void WebPageInspectorController::setIndicating(bool indicating) +@@ -160,6 +270,66 @@ void WebPageInspectorController::setIndicating(bool indicating) } #endif @@ -14255,7 +14278,7 @@ index ed4e6f30b8c35966075573dccee801daceec865e..81e10124a8a745727fc25316345cda01 + +void WebPageInspectorController::navigate(WebCore::ResourceRequest&& request, WebFrameProxy* frame, NavigationHandler&& completionHandler) +{ -+ auto navigation = m_inspectedPage.loadRequestForInspector(WTFMove(request), frame); ++ auto navigation = m_inspectedPage->loadRequestForInspector(WTFMove(request), frame); + if (!navigation) { + completionHandler("Failed to navigate"_s, 0); + return; @@ -14304,8 +14327,8 @@ index ed4e6f30b8c35966075573dccee801daceec865e..81e10124a8a745727fc25316345cda01 + void WebPageInspectorController::createInspectorTarget(const String& targetId, Inspector::InspectorTargetType type) { - addTarget(InspectorTargetProxy::create(m_inspectedPage, targetId, type)); -@@ -170,6 +340,32 @@ void WebPageInspectorController::sendMessageToInspectorFrontend(const String& ta + addTarget(InspectorTargetProxy::create(protectedInspectedPage(), targetId, type)); +@@ -179,6 +349,32 @@ void WebPageInspectorController::sendMessageToInspectorFrontend(const String& ta m_targetAgent->sendMessageFromTargetToFrontend(targetId, message); } @@ -14320,17 +14343,17 @@ index ed4e6f30b8c35966075573dccee801daceec865e..81e10124a8a745727fc25316345cda01 + if (!m_frontendRouter->hasFrontends()) + return false; + -+ if (!m_inspectedPage.isPageOpenedByDOMShowingInitialEmptyDocument()) ++ if (!m_inspectedPage->isPageOpenedByDOMShowingInitialEmptyDocument()) + return false; + -+ auto* target = m_targets.get(WebPageInspectorTarget::toTargetID(m_inspectedPage.webPageID())); ++ auto* target = m_targets.get(WebPageInspectorTarget::toTargetID(m_inspectedPage->webPageID())); + ASSERT(target); + return target->isPaused(); +} + +void WebPageInspectorController::setContinueLoadingCallback(WTF::Function&& callback) +{ -+ auto* target = m_targets.get(WebPageInspectorTarget::toTargetID(m_inspectedPage.webPageID())); ++ auto* target = m_targets.get(WebPageInspectorTarget::toTargetID(m_inspectedPage->webPageID())); + ASSERT(target); + target->setResumeCallback(WTFMove(callback)); +} @@ -14338,7 +14361,7 @@ index ed4e6f30b8c35966075573dccee801daceec865e..81e10124a8a745727fc25316345cda01 bool WebPageInspectorController::shouldPauseLoading(const ProvisionalPageProxy& provisionalPage) const { if (!m_frontendRouter->hasFrontends()) -@@ -189,7 +385,7 @@ void WebPageInspectorController::setContinueLoadingCallback(const ProvisionalPag +@@ -198,7 +394,7 @@ void WebPageInspectorController::setContinueLoadingCallback(const ProvisionalPag void WebPageInspectorController::didCreateProvisionalPage(ProvisionalPageProxy& provisionalPage) { @@ -14347,7 +14370,7 @@ index ed4e6f30b8c35966075573dccee801daceec865e..81e10124a8a745727fc25316345cda01 } void WebPageInspectorController::willDestroyProvisionalPage(const ProvisionalPageProxy& provisionalPage) -@@ -267,4 +463,27 @@ void WebPageInspectorController::browserExtensionsDisabled(HashSet&& ext +@@ -277,4 +473,27 @@ void WebPageInspectorController::browserExtensionsDisabled(HashSet&& ext m_enabledBrowserAgent->extensionsDisabled(WTFMove(extensionIDs)); } @@ -14356,30 +14379,30 @@ index ed4e6f30b8c35966075573dccee801daceec865e..81e10124a8a745727fc25316345cda01 + // Set this to true as otherwise updating any preferences will override its + // value in the Web Process to false (and InspectorController sets it locally + // to true when frontend is connected). -+ m_inspectedPage.preferences().setDeveloperExtrasEnabled(true); ++ m_inspectedPage->preferences().setDeveloperExtrasEnabled(true); + + // Navigation to cached pages doesn't fire some of the events (e.g. execution context created) + // that inspector depends on. So we disable the cache when front-end connects. -+ m_inspectedPage.preferences().setUsesBackForwardCache(false); ++ m_inspectedPage->preferences().setUsesBackForwardCache(false); + + // Enable popup debugging. + // TODO: allow to set preferences over the inspector protocol or find a better place for this. -+ m_inspectedPage.preferences().setJavaScriptCanOpenWindowsAutomatically(true); ++ m_inspectedPage->preferences().setJavaScriptCanOpenWindowsAutomatically(true); + + // Enable media stream. -+ if (!m_inspectedPage.preferences().mediaStreamEnabled()) { -+ m_inspectedPage.preferences().setMediaDevicesEnabled(true); -+ m_inspectedPage.preferences().setMediaStreamEnabled(true); -+ m_inspectedPage.preferences().setPeerConnectionEnabled(true); ++ if (!m_inspectedPage->preferences().mediaStreamEnabled()) { ++ m_inspectedPage->preferences().setMediaDevicesEnabled(true); ++ m_inspectedPage->preferences().setMediaStreamEnabled(true); ++ m_inspectedPage->preferences().setPeerConnectionEnabled(true); + } +} + } // namespace WebKit diff --git a/Source/WebKit/UIProcess/Inspector/WebPageInspectorController.h b/Source/WebKit/UIProcess/Inspector/WebPageInspectorController.h -index 90619b09c986a6791eec834668ac38ba1581ba93..5ef7f3e25ae95cfbe5564d3456953b9abcd23db4 100644 +index 0a44e45c3d208942ba1bbf0624ae241ca7bbd973..7e891a1fe3e2588e0aa286c4cbb2064967379fcb 100644 --- a/Source/WebKit/UIProcess/Inspector/WebPageInspectorController.h +++ b/Source/WebKit/UIProcess/Inspector/WebPageInspectorController.h -@@ -26,17 +26,35 @@ +@@ -26,6 +26,7 @@ #pragma once #include "InspectorTargetProxy.h" @@ -14387,6 +14410,7 @@ index 90619b09c986a6791eec834668ac38ba1581ba93..5ef7f3e25ae95cfbe5564d3456953b9a #include #include #include +@@ -33,11 +34,28 @@ #include #include #include @@ -14415,7 +14439,7 @@ index 90619b09c986a6791eec834668ac38ba1581ba93..5ef7f3e25ae95cfbe5564d3456953b9a } namespace WebKit { -@@ -44,6 +62,23 @@ namespace WebKit { +@@ -45,6 +63,23 @@ namespace WebKit { class InspectorBrowserAgent; struct WebPageAgentContext; @@ -14439,7 +14463,7 @@ index 90619b09c986a6791eec834668ac38ba1581ba93..5ef7f3e25ae95cfbe5564d3456953b9a class WebPageInspectorController { WTF_MAKE_NONCOPYABLE(WebPageInspectorController); WTF_MAKE_FAST_ALLOCATED; -@@ -51,7 +86,21 @@ public: +@@ -52,7 +87,21 @@ public: WebPageInspectorController(WebPageProxy&); void init(); @@ -14461,7 +14485,7 @@ index 90619b09c986a6791eec834668ac38ba1581ba93..5ef7f3e25ae95cfbe5564d3456953b9a bool hasLocalFrontend() const; -@@ -64,11 +113,25 @@ public: +@@ -65,11 +114,25 @@ public: #if ENABLE(REMOTE_INSPECTOR) void setIndicating(bool); #endif @@ -14487,7 +14511,13 @@ index 90619b09c986a6791eec834668ac38ba1581ba93..5ef7f3e25ae95cfbe5564d3456953b9a bool shouldPauseLoading(const ProvisionalPageProxy&) const; void setContinueLoadingCallback(const ProvisionalPageProxy&, WTF::Function&&); -@@ -87,6 +150,7 @@ private: +@@ -84,11 +147,12 @@ public: + void browserExtensionsDisabled(HashSet&&); + + private: +- Ref protectedInspectedPage(); ++ CheckedRef protectedInspectedPage(); + WebPageAgentContext webPageAgentContext(); void createLazyAgents(); void addTarget(std::unique_ptr&&); @@ -14495,8 +14525,8 @@ index 90619b09c986a6791eec834668ac38ba1581ba93..5ef7f3e25ae95cfbe5564d3456953b9a Ref m_frontendRouter; Ref m_backendDispatcher; -@@ -95,11 +159,17 @@ private: - WebPageProxy& m_inspectedPage; +@@ -97,11 +161,17 @@ private: + CheckedRef m_inspectedPage; Inspector::InspectorTargetAgent* m_targetAgent { nullptr }; + WebPageInspectorEmulationAgent* m_emulationAgent { nullptr }; @@ -14746,10 +14776,10 @@ index 0000000000000000000000000000000000000000..d0e11ed81a6257c011df23d5870da740 +} // namespace WebKit diff --git a/Source/WebKit/UIProcess/InspectorPlaywrightAgent.cpp b/Source/WebKit/UIProcess/InspectorPlaywrightAgent.cpp new file mode 100644 -index 0000000000000000000000000000000000000000..5a3010759c8ed1934e105ebda0a7c9b508a3c824 +index 0000000000000000000000000000000000000000..24dd8c24c555475ae707cb2abb22f8ffd669846c --- /dev/null +++ b/Source/WebKit/UIProcess/InspectorPlaywrightAgent.cpp -@@ -0,0 +1,1003 @@ +@@ -0,0 +1,1019 @@ +/* + * Copyright (C) 2019 Microsoft Corporation. + * @@ -15269,6 +15299,19 @@ index 0000000000000000000000000000000000000000..5a3010759c8ed1934e105ebda0a7c9b5 + return { }; +} + ++Inspector::Protocol::ErrorStringOr InspectorPlaywrightAgent::getInfo() ++{ ++#if PLATFORM(MAC) ++ return { "macOS"_s }; ++#elif PLATFORM(GTK) || PLATFORM(WPE) ++ return { "Linux"_s }; ++#elif PLATFORM(WIN) ++ return { "Windows"_s }; ++#else ++#error "Unsupported platform." ++#endif ++} ++ +void InspectorPlaywrightAgent::close(Ref&& callback) +{ + closeImpl([callback = WTFMove(callback)] (String error) { @@ -15475,22 +15518,25 @@ index 0000000000000000000000000000000000000000..5a3010759c8ed1934e105ebda0a7c9b5 + return { }; +} + -+Inspector::Protocol::ErrorStringOr InspectorPlaywrightAgent::takePageScreenshot(const String& pageProxyID, int x, int y, int width, int height, std::optional&& omitDeviceScaleFactor) ++void InspectorPlaywrightAgent::takePageScreenshot(const String& pageProxyID, int x, int y, int width, int height, std::optional&& omitDeviceScaleFactor, Ref&& callback) +{ -+#if PLATFORM(COCOA) || PLATFORM(GTK) || PLATFORM(WPE) ++#if PLATFORM(MAC) || PLATFORM(GTK) || PLATFORM(WPE) + auto* pageProxyChannel = m_pageProxyChannels.get(pageProxyID); -+ if (!pageProxyChannel) -+ return makeUnexpected("Unknown pageProxyID"_s); ++ if (!pageProxyChannel) { ++ callback->sendFailure("Unknown pageProxyID"_s); ++ return; ++ } + + bool nominalResolution = omitDeviceScaleFactor.has_value() && *omitDeviceScaleFactor; + WebCore::IntRect clip(x, y, width, height); -+ String error; -+ String screenshot = m_client->takePageScreenshot(error, pageProxyChannel->page(), WTFMove(clip), nominalResolution); -+ if (!error.isEmpty()) -+ return makeUnexpected(error); -+ return screenshot; ++ m_client->takePageScreenshot(pageProxyChannel->page(), WTFMove(clip), nominalResolution, [callback = WTFMove(callback)](const String& error, const String& data) { ++ if (error.isEmpty()) ++ callback->sendSuccess(data); ++ else ++ callback->sendFailure(error); ++ }); +#else -+ return makeUnexpected("This method is only supported on macOS."_s); ++ return callback->sendFailure("This method is not supported on this platform."_s); +#endif +} + @@ -15755,10 +15801,10 @@ index 0000000000000000000000000000000000000000..5a3010759c8ed1934e105ebda0a7c9b5 +#endif // ENABLE(REMOTE_INSPECTOR) diff --git a/Source/WebKit/UIProcess/InspectorPlaywrightAgent.h b/Source/WebKit/UIProcess/InspectorPlaywrightAgent.h new file mode 100644 -index 0000000000000000000000000000000000000000..5e370940b08031fb552bc8b419874deb1ce59233 +index 0000000000000000000000000000000000000000..36b7f12b5d9fce000715b42ee2980c20e0e91ef7 --- /dev/null +++ b/Source/WebKit/UIProcess/InspectorPlaywrightAgent.h -@@ -0,0 +1,129 @@ +@@ -0,0 +1,130 @@ +/* + * Copyright (C) 2019 Microsoft Corporation. + * @@ -15842,13 +15888,14 @@ index 0000000000000000000000000000000000000000..5e370940b08031fb552bc8b419874deb + // PlaywrightDispatcherHandler + Inspector::Protocol::ErrorStringOr enable() override; + Inspector::Protocol::ErrorStringOr disable() override; ++ Inspector::Protocol::ErrorStringOr getInfo() override; + void close(Ref&&) override; + Inspector::Protocol::ErrorStringOr createContext(const String& proxyServer, const String& proxyBypassList) override; + void deleteContext(const String& browserContextID, Ref&& callback) override; + Inspector::Protocol::ErrorStringOr createPage(const String& browserContextID) override; + void navigate(const String& url, const String& pageProxyID, const String& frameId, const String& referrer, Ref&&) override; + Inspector::Protocol::ErrorStringOr grantFileReadAccess(const String& pageProxyID, Ref&& paths) override; -+ Inspector::Protocol::ErrorStringOr takePageScreenshot(const String& pageProxyID, int x, int y, int width, int height, std::optional&& omitDeviceScaleFactor) override; ++ void takePageScreenshot(const String& pageProxyID, int x, int y, int width, int height, std::optional&& omitDeviceScaleFactor, Ref&&) override; + Inspector::Protocol::ErrorStringOr setIgnoreCertificateErrors(const String& browserContextID, bool ignore) override; + + void getAllCookies(const String& browserContextID, Ref&&) override; @@ -15890,7 +15937,7 @@ index 0000000000000000000000000000000000000000..5e370940b08031fb552bc8b419874deb +#endif // ENABLE(REMOTE_INSPECTOR) diff --git a/Source/WebKit/UIProcess/InspectorPlaywrightAgentClient.h b/Source/WebKit/UIProcess/InspectorPlaywrightAgentClient.h new file mode 100644 -index 0000000000000000000000000000000000000000..6815001186506b5926ef089d03f6fce46b9f249d +index 0000000000000000000000000000000000000000..e7a3dcc533294bb6e12f65d79b5b716bd3c12236 --- /dev/null +++ b/Source/WebKit/UIProcess/InspectorPlaywrightAgentClient.h @@ -0,0 +1,73 @@ @@ -15960,7 +16007,7 @@ index 0000000000000000000000000000000000000000..6815001186506b5926ef089d03f6fce4 + virtual std::unique_ptr createBrowserContext(WTF::String& error, const WTF::String& proxyServer, const WTF::String& proxyBypassList) = 0; + virtual void deleteBrowserContext(WTF::String& error, PAL::SessionID) = 0; +#if PLATFORM(COCOA) || PLATFORM(GTK) || PLATFORM(WPE) -+ virtual String takePageScreenshot(WTF::String& error, WebPageProxy&, WebCore::IntRect&& clip, bool nominalResolution) = 0; ++ virtual void takePageScreenshot(WebPageProxy&, WebCore::IntRect&& clip, bool nominalResolution, CompletionHandler&& completionHandler) = 0; +#endif +}; + @@ -15997,10 +16044,10 @@ index 3fe0abcfe36bef7ca45bed5661a737ed2bfe56d0..510656948af01ec65d4543c805e9667a #include "RemoteMediaSessionCoordinatorProxyMessages.h" #include "WebPageProxy.h" diff --git a/Source/WebKit/UIProcess/PageClient.h b/Source/WebKit/UIProcess/PageClient.h -index 290c016a047960d38fe60f78d4a0fc1c64ac5d38..82e01ed83f9159ba7f2af3f5bd5082f162a778b9 100644 +index ed7fea3c0ae4b0567f592226ca34a53f6a453c9a..6eed8f4a0f69f3d45e09f68a33d76d49908ab0d6 100644 --- a/Source/WebKit/UIProcess/PageClient.h +++ b/Source/WebKit/UIProcess/PageClient.h -@@ -84,6 +84,10 @@ OBJC_CLASS WKView; +@@ -85,6 +85,10 @@ OBJC_CLASS WKView; #endif #endif @@ -16011,7 +16058,7 @@ index 290c016a047960d38fe60f78d4a0fc1c64ac5d38..82e01ed83f9159ba7f2af3f5bd5082f1 namespace API { class Attachment; class HitTestResult; -@@ -332,7 +336,16 @@ public: +@@ -333,7 +337,16 @@ public: virtual void selectionDidChange() = 0; #endif @@ -16155,7 +16202,7 @@ index 0000000000000000000000000000000000000000..3c8fd0549f1847515d35092f0f49b060 + +#endif // ENABLE(FULLSCREEN_API) diff --git a/Source/WebKit/UIProcess/ProvisionalFrameProxy.cpp b/Source/WebKit/UIProcess/ProvisionalFrameProxy.cpp -index d37c0fdab3545ea166e1f062e7bd40107180c056..9e3f20f9f709786ff7093c746c0ae20022ee134c 100644 +index 529513b85bdad92abf0f72021545780b4c03f0d9..cacc725db7660d956222a85a164f88d477bbc659 100644 --- a/Source/WebKit/UIProcess/ProvisionalFrameProxy.cpp +++ b/Source/WebKit/UIProcess/ProvisionalFrameProxy.cpp @@ -25,6 +25,7 @@ @@ -16485,17 +16532,17 @@ index f8c06bc779b6be310018840147a044b7c34fed3c..0c35907c6d0e0d2c3bba1f40b2ef59fb namespace WebCore { class PlatformWheelEvent; diff --git a/Source/WebKit/UIProcess/RemotePageProxy.cpp b/Source/WebKit/UIProcess/RemotePageProxy.cpp -index 962ee6cae7dc76a19243f77f4c01b90163a2e8ba..2aebb41f50dcd87f738daafe0b5aa244844df227 100644 +index 6714d262e7db42fe3341e29c6d06150749117b9e..f6a74d735df19a3376c2f386489326bc4a3571c8 100644 --- a/Source/WebKit/UIProcess/RemotePageProxy.cpp +++ b/Source/WebKit/UIProcess/RemotePageProxy.cpp -@@ -39,6 +39,7 @@ +@@ -41,6 +41,7 @@ #include "WebPageProxyMessages.h" #include "WebProcessMessages.h" #include "WebProcessProxy.h" +#include "WebProcessMessages.h" + #include namespace WebKit { - diff --git a/Source/WebKit/UIProcess/WebContextMenuProxy.cpp b/Source/WebKit/UIProcess/WebContextMenuProxy.cpp index 2c36e80c79519b09f1969701dbd8c712b50089de..b6a2e3dd03496a57238e7381880ad2f78d8f4b72 100644 --- a/Source/WebKit/UIProcess/WebContextMenuProxy.cpp @@ -16521,7 +16568,7 @@ index 2071f653d6fd7413dd5336b85d02c6a92cab68b2..af9409c0adfc97a60d4ed789999310a7 WebPageProxy* page() const { return m_page.get(); } diff --git a/Source/WebKit/UIProcess/WebFrameProxy.cpp b/Source/WebKit/UIProcess/WebFrameProxy.cpp -index 0f2875d0465b3267f210e065a9e5ce0830353abb..837b65eeacd48b75fef7d3bb274eddb3a0393639 100644 +index e838f905d68e3a0450c8923a5ba0fcbd34e01cbf..a99b6e7f5b3fadf30229da3c740e1119fd1c53cf 100644 --- a/Source/WebKit/UIProcess/WebFrameProxy.cpp +++ b/Source/WebKit/UIProcess/WebFrameProxy.cpp @@ -30,6 +30,7 @@ @@ -16796,7 +16843,7 @@ index 0000000000000000000000000000000000000000..12657f8e7c4b2596707680d299dc8084 +} // namespace WebKit diff --git a/Source/WebKit/UIProcess/WebPageInspectorInputAgent.cpp b/Source/WebKit/UIProcess/WebPageInspectorInputAgent.cpp new file mode 100644 -index 0000000000000000000000000000000000000000..0b33e978d46982cee8ed5b39c331502770694478 +index 0000000000000000000000000000000000000000..d21c7462293142fbc8b05fd17f030a20f3ab7bde --- /dev/null +++ b/Source/WebKit/UIProcess/WebPageInspectorInputAgent.cpp @@ -0,0 +1,334 @@ @@ -17018,7 +17065,7 @@ index 0000000000000000000000000000000000000000..0b33e978d46982cee8ed5b39c3315027 + +void WebPageInspectorInputAgent::dispatchMouseEvent(const String& type, int x, int y, std::optional&& modifiers, const String& button, std::optional&& buttons, std::optional&& clickCount, std::optional&& deltaX, std::optional&& deltaY, Ref&& callback) +{ -+ WebEventType eventType = WebEventType::NoType; ++ WebEventType eventType = WebEventType::MouseMove; + if (type == "down"_s) + eventType = WebEventType::MouseDown; + else if (type == "up"_s) @@ -17034,16 +17081,16 @@ index 0000000000000000000000000000000000000000..0b33e978d46982cee8ed5b39c3315027 + if (modifiers) + eventModifiers = eventModifiers.fromRaw(*modifiers); + -+ WebMouseEventButton eventButton = WebMouseEventButton::NoButton; ++ WebMouseEventButton eventButton = WebMouseEventButton::None; + if (!!button) { + if (button == "left"_s) -+ eventButton = WebMouseEventButton::LeftButton; ++ eventButton = WebMouseEventButton::Left; + else if (button == "middle"_s) -+ eventButton = WebMouseEventButton::MiddleButton; ++ eventButton = WebMouseEventButton::Middle; + else if (button == "right"_s) -+ eventButton = WebMouseEventButton::RightButton; ++ eventButton = WebMouseEventButton::Right; + else if (button == "none"_s) -+ eventButton = WebMouseEventButton::NoButton; ++ eventButton = WebMouseEventButton::None; + else { + callback->sendFailure("Unsupported eventButton"_s); + return; @@ -17227,12 +17274,12 @@ index 0000000000000000000000000000000000000000..3e87bf40ced2301f4fb145c6cb31f2cf + +} // namespace WebKit diff --git a/Source/WebKit/UIProcess/WebPageProxy.cpp b/Source/WebKit/UIProcess/WebPageProxy.cpp -index 279ab83cbd781b921d30055998baad70faa8156a..186553172243117291f9773dc8db54bbd24752a6 100644 +index 198ba50873059dc6dc9afb8d6bc97ed0538ed4ee..8ca9b6b657d70c1996c84c7b19aaf74323676418 100644 --- a/Source/WebKit/UIProcess/WebPageProxy.cpp +++ b/Source/WebKit/UIProcess/WebPageProxy.cpp -@@ -171,12 +171,14 @@ - #include +@@ -174,12 +174,14 @@ #include + #include #include +#include #include @@ -17245,7 +17292,7 @@ index 279ab83cbd781b921d30055998baad70faa8156a..186553172243117291f9773dc8db54bb #include #include #include -@@ -195,16 +197,19 @@ +@@ -198,17 +200,20 @@ #include #include #include @@ -17257,6 +17304,7 @@ index 279ab83cbd781b921d30055998baad70faa8156a..186553172243117291f9773dc8db54bb #include #include +#include + #include #include #include #include @@ -17265,7 +17313,7 @@ index 279ab83cbd781b921d30055998baad70faa8156a..186553172243117291f9773dc8db54bb #include #include #include -@@ -272,6 +277,9 @@ +@@ -277,6 +282,9 @@ #if PLATFORM(GTK) #include "GtkSettingsManager.h" @@ -17275,7 +17323,7 @@ index 279ab83cbd781b921d30055998baad70faa8156a..186553172243117291f9773dc8db54bb #include #endif -@@ -381,6 +389,8 @@ static constexpr Seconds tryCloseTimeoutDelay = 50_ms; +@@ -386,6 +394,8 @@ static constexpr Seconds tryCloseTimeoutDelay = 50_ms; static constexpr Seconds audibleActivityClearDelay = 10_s; #endif @@ -17283,8 +17331,8 @@ index 279ab83cbd781b921d30055998baad70faa8156a..186553172243117291f9773dc8db54bb + DEFINE_DEBUG_ONLY_GLOBAL(WTF::RefCountedLeakCounter, webPageProxyCounter, ("WebPageProxy")); - class StorageRequests { -@@ -747,6 +757,10 @@ WebPageProxy::~WebPageProxy() + #if PLATFORM(COCOA) +@@ -759,6 +769,10 @@ WebPageProxy::~WebPageProxy() internals().remotePageProxyInOpenerProcess = nullptr; internals().openedRemotePageProxies.clear(); @@ -17295,7 +17343,7 @@ index 279ab83cbd781b921d30055998baad70faa8156a..186553172243117291f9773dc8db54bb } void WebPageProxy::addAllMessageReceivers() -@@ -1204,6 +1218,7 @@ void WebPageProxy::finishAttachingToWebProcess(ProcessLaunchReason reason) +@@ -1225,6 +1239,7 @@ void WebPageProxy::finishAttachingToWebProcess(ProcessLaunchReason reason) pageClient().didRelaunchProcess(); internals().pageLoadState.didSwapWebProcesses(); @@ -17303,7 +17351,7 @@ index 279ab83cbd781b921d30055998baad70faa8156a..186553172243117291f9773dc8db54bb } void WebPageProxy::didAttachToRunningProcess() -@@ -1212,7 +1227,7 @@ void WebPageProxy::didAttachToRunningProcess() +@@ -1233,7 +1248,7 @@ void WebPageProxy::didAttachToRunningProcess() #if ENABLE(FULLSCREEN_API) ASSERT(!m_fullScreenManager); @@ -17312,7 +17360,7 @@ index 279ab83cbd781b921d30055998baad70faa8156a..186553172243117291f9773dc8db54bb #endif #if ENABLE(VIDEO_PRESENTATION_MODE) ASSERT(!m_playbackSessionManager); -@@ -1600,6 +1615,21 @@ WebProcessProxy& WebPageProxy::ensureRunningProcess() +@@ -1622,6 +1637,21 @@ WebProcessProxy& WebPageProxy::ensureRunningProcess() return m_process; } @@ -17334,7 +17382,7 @@ index 279ab83cbd781b921d30055998baad70faa8156a..186553172243117291f9773dc8db54bb RefPtr WebPageProxy::loadRequest(ResourceRequest&& request, ShouldOpenExternalURLsPolicy shouldOpenExternalURLsPolicy, API::Object* userData) { if (m_isClosed) -@@ -2160,6 +2190,32 @@ void WebPageProxy::setControlledByAutomation(bool controlled) +@@ -2179,6 +2209,32 @@ void WebPageProxy::setControlledByAutomation(bool controlled) websiteDataStore().networkProcess().send(Messages::NetworkProcess::SetSessionIsControlledByAutomation(m_websiteDataStore->sessionID(), m_controlledByAutomation), 0); } @@ -17367,7 +17415,7 @@ index 279ab83cbd781b921d30055998baad70faa8156a..186553172243117291f9773dc8db54bb void WebPageProxy::createInspectorTarget(const String& targetId, Inspector::InspectorTargetType type) { MESSAGE_CHECK(m_process, !targetId.isEmpty()); -@@ -2402,6 +2458,25 @@ void WebPageProxy::updateActivityState(OptionSet flagsToUpdate) +@@ -2421,6 +2477,25 @@ void WebPageProxy::updateActivityState(OptionSet flagsToUpdate) { bool wasVisible = isViewVisible(); internals().activityState.remove(flagsToUpdate); @@ -17393,7 +17441,7 @@ index 279ab83cbd781b921d30055998baad70faa8156a..186553172243117291f9773dc8db54bb if (flagsToUpdate & ActivityState::IsFocused && pageClient().isViewFocused()) internals().activityState.add(ActivityState::IsFocused); if (flagsToUpdate & ActivityState::WindowIsActive && pageClient().isViewWindowActive()) -@@ -3087,6 +3162,8 @@ void WebPageProxy::performDragControllerAction(DragControllerAction action, Drag +@@ -3106,6 +3181,8 @@ void WebPageProxy::performDragControllerAction(DragControllerAction action, Drag { if (!hasRunningProcess()) return; @@ -17402,7 +17450,7 @@ index 279ab83cbd781b921d30055998baad70faa8156a..186553172243117291f9773dc8db54bb #if PLATFORM(GTK) UNUSED_PARAM(dragStorageName); UNUSED_PARAM(sandboxExtensionHandle); -@@ -3097,6 +3174,8 @@ void WebPageProxy::performDragControllerAction(DragControllerAction action, Drag +@@ -3116,6 +3193,8 @@ void WebPageProxy::performDragControllerAction(DragControllerAction action, Drag m_process->assumeReadAccessToBaseURL(*this, url); ASSERT(dragData.platformData()); @@ -17411,7 +17459,7 @@ index 279ab83cbd781b921d30055998baad70faa8156a..186553172243117291f9773dc8db54bb send(Messages::WebPage::PerformDragControllerAction(action, dragData.clientPosition(), dragData.globalPosition(), dragData.draggingSourceOperationMask(), *dragData.platformData(), dragData.flags())); #else send(Messages::WebPage::PerformDragControllerAction(action, dragData, sandboxExtensionHandle, sandboxExtensionsForUpload)); -@@ -3112,18 +3191,41 @@ void WebPageProxy::didPerformDragControllerAction(std::optional dragOperationMask, ShareableBitmap::Handle&& dragImageHandle, IntPoint&& dragImageHotspot) + void WebPageProxy::startDrag(SelectionData&& selectionData, OptionSet dragOperationMask, std::optional&& dragImageHandle, IntPoint&& dragImageHotspot) { -- RefPtr dragImage = !dragImageHandle.isNull() ? ShareableBitmap::create(WTFMove(dragImageHandle)) : nullptr; +- RefPtr dragImage = dragImageHandle ? ShareableBitmap::create(WTFMove(*dragImageHandle)) : nullptr; - pageClient().startDrag(WTFMove(selectionData), dragOperationMask, WTFMove(dragImage), WTFMove(dragImageHotspot)); + if (m_interceptDrags) { + m_dragSelectionData = WTFMove(selectionData); + m_dragSourceOperationMask = dragOperationMask; + } else { +#if PLATFORM(GTK) -+ RefPtr dragImage = !dragImageHandle.isNull() ? ShareableBitmap::create(WTFMove(dragImageHandle)) : nullptr; ++ RefPtr dragImage = dragImageHandle ? ShareableBitmap::create(WTFMove(*dragImageHandle)) : nullptr; + pageClient().startDrag(WTFMove(selectionData), dragOperationMask, WTFMove(dragImage), WTFMove(dragImageHotspot)); +#endif + } @@ -17456,7 +17504,7 @@ index 279ab83cbd781b921d30055998baad70faa8156a..186553172243117291f9773dc8db54bb void WebPageProxy::dragEnded(const IntPoint& clientPosition, const IntPoint& globalPosition, OptionSet dragOperationMask) { if (!hasRunningProcess()) -@@ -3132,6 +3234,24 @@ void WebPageProxy::dragEnded(const IntPoint& clientPosition, const IntPoint& glo +@@ -3151,6 +3253,24 @@ void WebPageProxy::dragEnded(const IntPoint& clientPosition, const IntPoint& glo setDragCaretRect({ }); } @@ -17481,7 +17529,7 @@ index 279ab83cbd781b921d30055998baad70faa8156a..186553172243117291f9773dc8db54bb void WebPageProxy::didPerformDragOperation(bool handled) { pageClient().didPerformDragOperation(handled); -@@ -3144,6 +3264,16 @@ void WebPageProxy::didStartDrag() +@@ -3163,6 +3283,16 @@ void WebPageProxy::didStartDrag() discardQueuedMouseEvents(); send(Messages::WebPage::DidStartDrag()); @@ -17498,7 +17546,7 @@ index 279ab83cbd781b921d30055998baad70faa8156a..186553172243117291f9773dc8db54bb } void WebPageProxy::dragCancelled() -@@ -3267,17 +3397,37 @@ void WebPageProxy::processNextQueuedMouseEvent() +@@ -3319,17 +3449,38 @@ void WebPageProxy::processNextQueuedMouseEvent() m_process->startResponsivenessTimer(); } @@ -17508,20 +17556,20 @@ index 279ab83cbd781b921d30055998baad70faa8156a..186553172243117291f9773dc8db54bb + std::optional> sandboxExtensions; #if PLATFORM(MAC) -- bool eventMayStartDrag = !m_currentDragOperation && eventType == WebEventType::MouseMove && event.button() != WebMouseEventButton::NoButton; +- bool eventMayStartDrag = !m_currentDragOperation && eventType == WebEventType::MouseMove && event.button() != WebMouseEventButton::None; - if (eventMayStartDrag) - sandboxExtensions = SandboxExtension::createHandlesForMachLookup({ "com.apple.iconservices"_s, "com.apple.iconservices.store"_s }, process().auditToken(), SandboxExtension::MachBootstrapOptions::EnableMachBootstrap); -+ bool eventMayStartDrag = !m_currentDragOperation && eventType == WebEventType::MouseMove && event.button() != WebMouseEventButton::NoButton; ++ bool eventMayStartDrag = !m_currentDragOperation && eventType == WebEventType::MouseMove && event.button() != WebMouseEventButton::None; + if (eventMayStartDrag) + sandboxExtensions = SandboxExtension::createHandlesForMachLookup({ "com.apple.iconservices"_s, "com.apple.iconservices.store"_s }, process().auditToken(), SandboxExtension::MachBootstrapOptions::EnableMachBootstrap); #endif -- + - LOG_WITH_STREAM(MouseHandling, stream << "UIProcess: sent mouse event " << eventType << " (queue size " << internals().mouseEventQueue.size() << ")"); - m_process->recordUserGestureAuthorizationToken(event.authorizationToken()); -- send(Messages::WebPage::MouseEvent(m_mainFrame->frameID(), event, sandboxExtensions)); +- sendMouseEvent(m_mainFrame->frameID(), event, WTFMove(sandboxExtensions)); + LOG_WITH_STREAM(MouseHandling, stream << "UIProcess: sent mouse event " << eventType << " (queue size " << internals().mouseEventQueue.size() << ")"); + m_process->recordUserGestureAuthorizationToken(event.authorizationToken()); -+ send(Messages::WebPage::MouseEvent(m_mainFrame->frameID(), event, sandboxExtensions)); ++ sendMouseEvent(m_mainFrame->frameID(), event, WTFMove(sandboxExtensions)); + } else { +#if PLATFORM(WIN) || PLATFORM(COCOA) + DragData dragData(*m_dragSelectionData, event.position(), event.globalPosition(), m_dragSourceOperationMask); @@ -17544,7 +17592,7 @@ index 279ab83cbd781b921d30055998baad70faa8156a..186553172243117291f9773dc8db54bb } void WebPageProxy::doAfterProcessingAllPendingMouseEvents(WTF::Function&& action) -@@ -3437,6 +3587,8 @@ void WebPageProxy::wheelEventHandlingCompleted(bool wasHandled) +@@ -3489,6 +3640,8 @@ void WebPageProxy::wheelEventHandlingCompleted(bool wasHandled) if (auto* automationSession = process().processPool().automationSession()) automationSession->wheelEventsFlushedForPage(*this); @@ -17553,7 +17601,7 @@ index 279ab83cbd781b921d30055998baad70faa8156a..186553172243117291f9773dc8db54bb } void WebPageProxy::cacheWheelEventScrollingAccelerationCurve(const NativeWebWheelEvent& nativeWheelEvent) -@@ -3565,7 +3717,7 @@ static TrackingType mergeTrackingTypes(TrackingType a, TrackingType b) +@@ -3643,7 +3796,7 @@ static TrackingType mergeTrackingTypes(TrackingType a, TrackingType b) void WebPageProxy::updateTouchEventTracking(const WebTouchEvent& touchStartEvent) { @@ -17562,7 +17610,7 @@ index 279ab83cbd781b921d30055998baad70faa8156a..186553172243117291f9773dc8db54bb for (auto& touchPoint : touchStartEvent.touchPoints()) { auto location = touchPoint.location(); auto update = [this, location](TrackingType& trackingType, EventTrackingRegions::EventType eventType) { -@@ -3981,6 +4133,8 @@ void WebPageProxy::receivedNavigationPolicyDecision(WebProcessProxy& processNavi +@@ -4071,6 +4224,8 @@ void WebPageProxy::receivedNavigationPolicyDecision(WebProcessProxy& processNavi if (policyAction != PolicyAction::Use || (!preferences().siteIsolationEnabled() && !frame.isMainFrame()) || !navigation) { @@ -17571,7 +17619,7 @@ index 279ab83cbd781b921d30055998baad70faa8156a..186553172243117291f9773dc8db54bb auto previousPendingNavigationID = internals().pageLoadState.pendingAPIRequest().navigationID; receivedPolicyDecision(policyAction, navigation, navigation->websitePolicies(), WTFMove(navigationAction), WTFMove(sender), WillContinueLoadInNewProcess::No, std::nullopt); #if HAVE(APP_SSO) -@@ -4087,6 +4241,7 @@ void WebPageProxy::receivedNavigationPolicyDecision(WebProcessProxy& processNavi +@@ -4178,6 +4333,7 @@ void WebPageProxy::receivedNavigationPolicyDecision(WebProcessProxy& processNavi void WebPageProxy::receivedPolicyDecision(PolicyAction action, API::Navigation* navigation, RefPtr&& websitePolicies, std::variant, Ref>&& navigationActionOrResponse, Ref&& sender, WillContinueLoadInNewProcess willContinueLoadInNewProcess, std::optional sandboxExtensionHandle) { @@ -17579,7 +17627,7 @@ index 279ab83cbd781b921d30055998baad70faa8156a..186553172243117291f9773dc8db54bb if (!hasRunningProcess()) { sender->send(PolicyDecision { isNavigatingToAppBoundDomain() }); return; -@@ -4939,6 +5094,11 @@ void WebPageProxy::pageScaleFactorDidChange(double scaleFactor) +@@ -5031,6 +5187,11 @@ void WebPageProxy::pageScaleFactorDidChange(double scaleFactor) m_pageScaleFactor = scaleFactor; } @@ -17591,7 +17639,7 @@ index 279ab83cbd781b921d30055998baad70faa8156a..186553172243117291f9773dc8db54bb void WebPageProxy::pluginScaleFactorDidChange(double pluginScaleFactor) { MESSAGE_CHECK(m_process, scaleFactorIsValid(pluginScaleFactor)); -@@ -5419,6 +5579,7 @@ void WebPageProxy::didDestroyNavigationShared(Ref&& process, ui +@@ -5548,6 +5709,7 @@ void WebPageProxy::didDestroyNavigationShared(Ref&& process, ui PageClientProtector protector(pageClient()); m_navigationState->didDestroyNavigation(process->coreProcessIdentifier(), navigationID); @@ -17599,7 +17647,7 @@ index 279ab83cbd781b921d30055998baad70faa8156a..186553172243117291f9773dc8db54bb } void WebPageProxy::didStartProvisionalLoadForFrame(FrameIdentifier frameID, FrameInfoData&& frameInfo, ResourceRequest&& request, uint64_t navigationID, URL&& url, URL&& unreachableURL, const UserData& userData) -@@ -5665,6 +5826,8 @@ void WebPageProxy::didFailProvisionalLoadForFrameShared(Ref&& p +@@ -5794,6 +5956,8 @@ void WebPageProxy::didFailProvisionalLoadForFrameShared(Ref&& p m_failingProvisionalLoadURL = { }; @@ -17608,7 +17656,7 @@ index 279ab83cbd781b921d30055998baad70faa8156a..186553172243117291f9773dc8db54bb // If the provisional page's load fails then we destroy the provisional page. if (m_provisionalPage && m_provisionalPage->mainFrame() == &frame && willContinueLoading == WillContinueLoading::No) m_provisionalPage = nullptr; -@@ -6274,7 +6437,15 @@ void WebPageProxy::beginSafeBrowsingCheck(const URL&, bool, WebFramePolicyListen +@@ -6402,7 +6566,15 @@ void WebPageProxy::beginSafeBrowsingCheck(const URL&, bool, WebFramePolicyListen void WebPageProxy::decidePolicyForNavigationActionAsync(FrameInfoData&& frameInfo, uint64_t navigationID, NavigationActionData&& navigationActionData, FrameInfoData&& originatingFrameInfo, std::optional originatingPageID, const WebCore::ResourceRequest& originalRequest, WebCore::ResourceRequest&& request, IPC::FormDataReference&& requestBody, CompletionHandler&& completionHandler) { @@ -17625,7 +17673,7 @@ index 279ab83cbd781b921d30055998baad70faa8156a..186553172243117291f9773dc8db54bb } void WebPageProxy::decidePolicyForNavigationActionAsyncShared(Ref&& process, PageIdentifier webPageID, FrameInfoData&& frameInfo, uint64_t navigationID, NavigationActionData&& navigationActionData, FrameInfoData&& originatingFrameInfo, std::optional originatingPageID, const WebCore::ResourceRequest& originalRequest, WebCore::ResourceRequest&& request, IPC::FormDataReference&& requestBody, CompletionHandler&& completionHandler) -@@ -6856,6 +7027,7 @@ void WebPageProxy::createNewPage(FrameInfoData&& originatingFrameInfoData, WebPa +@@ -6984,6 +7156,7 @@ void WebPageProxy::createNewPage(FrameInfoData&& originatingFrameInfoData, WebPa if (auto* page = originatingFrameInfo->page()) openerAppInitiatedState = page->lastNavigationWasAppInitiated(); @@ -17633,7 +17681,7 @@ index 279ab83cbd781b921d30055998baad70faa8156a..186553172243117291f9773dc8db54bb auto completionHandler = [this, protectedThis = Ref { *this }, mainFrameURL, request, reply = WTFMove(reply), privateClickMeasurement = navigationActionData.privateClickMeasurement, openerAppInitiatedState = WTFMove(openerAppInitiatedState), openerFrameID = originatingFrameInfoData.frameID] (RefPtr newPage) mutable { if (!newPage) { reply(std::nullopt, std::nullopt); -@@ -6903,6 +7075,7 @@ void WebPageProxy::createNewPage(FrameInfoData&& originatingFrameInfoData, WebPa +@@ -7031,6 +7204,7 @@ void WebPageProxy::createNewPage(FrameInfoData&& originatingFrameInfoData, WebPa void WebPageProxy::showPage() { m_uiClient->showPage(this); @@ -17641,7 +17689,7 @@ index 279ab83cbd781b921d30055998baad70faa8156a..186553172243117291f9773dc8db54bb } bool WebPageProxy::hasOpenedPage() const -@@ -6982,6 +7155,10 @@ void WebPageProxy::closePage() +@@ -7110,6 +7284,10 @@ void WebPageProxy::closePage() if (isClosed()) return; @@ -17652,7 +17700,7 @@ index 279ab83cbd781b921d30055998baad70faa8156a..186553172243117291f9773dc8db54bb WEBPAGEPROXY_RELEASE_LOG(Process, "closePage:"); pageClient().clearAllEditCommands(); m_uiClient->close(this); -@@ -7018,6 +7195,8 @@ void WebPageProxy::runJavaScriptAlert(FrameIdentifier frameID, FrameInfoData&& f +@@ -7146,6 +7324,8 @@ void WebPageProxy::runJavaScriptAlert(FrameIdentifier frameID, FrameInfoData&& f } runModalJavaScriptDialog(WTFMove(frame), WTFMove(frameInfo), message, [reply = WTFMove(reply)](WebPageProxy& page, WebFrameProxy* frame, FrameInfoData&& frameInfo, const String& message, CompletionHandler&& completion) mutable { @@ -17661,7 +17709,7 @@ index 279ab83cbd781b921d30055998baad70faa8156a..186553172243117291f9773dc8db54bb page.m_uiClient->runJavaScriptAlert(page, message, frame, WTFMove(frameInfo), [reply = WTFMove(reply), completion = WTFMove(completion)]() mutable { reply(); completion(); -@@ -7039,6 +7218,8 @@ void WebPageProxy::runJavaScriptConfirm(FrameIdentifier frameID, FrameInfoData&& +@@ -7167,6 +7347,8 @@ void WebPageProxy::runJavaScriptConfirm(FrameIdentifier frameID, FrameInfoData&& if (auto* automationSession = process().processPool().automationSession()) automationSession->willShowJavaScriptDialog(*this); } @@ -17670,7 +17718,7 @@ index 279ab83cbd781b921d30055998baad70faa8156a..186553172243117291f9773dc8db54bb runModalJavaScriptDialog(WTFMove(frame), WTFMove(frameInfo), message, [reply = WTFMove(reply)](WebPageProxy& page, WebFrameProxy* frame, FrameInfoData&& frameInfo, const String& message, CompletionHandler&& completion) mutable { page.m_uiClient->runJavaScriptConfirm(page, message, frame, WTFMove(frameInfo), [reply = WTFMove(reply), completion = WTFMove(completion)](bool result) mutable { -@@ -7062,6 +7243,8 @@ void WebPageProxy::runJavaScriptPrompt(FrameIdentifier frameID, FrameInfoData&& +@@ -7190,6 +7372,8 @@ void WebPageProxy::runJavaScriptPrompt(FrameIdentifier frameID, FrameInfoData&& if (auto* automationSession = process().processPool().automationSession()) automationSession->willShowJavaScriptDialog(*this); } @@ -17679,7 +17727,7 @@ index 279ab83cbd781b921d30055998baad70faa8156a..186553172243117291f9773dc8db54bb runModalJavaScriptDialog(WTFMove(frame), WTFMove(frameInfo), message, [reply = WTFMove(reply), defaultValue](WebPageProxy& page, WebFrameProxy* frame, FrameInfoData&& frameInfo, const String& message, CompletionHandler&& completion) mutable { page.m_uiClient->runJavaScriptPrompt(page, message, defaultValue, frame, WTFMove(frameInfo), [reply = WTFMove(reply), completion = WTFMove(completion)](auto& result) mutable { -@@ -7176,6 +7359,8 @@ void WebPageProxy::runBeforeUnloadConfirmPanel(FrameIdentifier frameID, FrameInf +@@ -7304,6 +7488,8 @@ void WebPageProxy::runBeforeUnloadConfirmPanel(FrameIdentifier frameID, FrameInf return; } } @@ -17688,7 +17736,7 @@ index 279ab83cbd781b921d30055998baad70faa8156a..186553172243117291f9773dc8db54bb // Since runBeforeUnloadConfirmPanel() can spin a nested run loop we need to turn off the responsiveness timer and the tryClose timer. m_process->stopResponsivenessTimer(); -@@ -7626,6 +7811,11 @@ void WebPageProxy::resourceLoadDidCompleteWithError(ResourceLoadInfo&& loadInfo, +@@ -7754,6 +7940,11 @@ void WebPageProxy::resourceLoadDidCompleteWithError(ResourceLoadInfo&& loadInfo, } #if ENABLE(FULLSCREEN_API) @@ -17700,7 +17748,7 @@ index 279ab83cbd781b921d30055998baad70faa8156a..186553172243117291f9773dc8db54bb WebFullScreenManagerProxy* WebPageProxy::fullScreenManager() { return m_fullScreenManager.get(); -@@ -8546,6 +8736,8 @@ void WebPageProxy::didReceiveEvent(WebEventType eventType, bool handled) +@@ -8671,6 +8862,8 @@ void WebPageProxy::didReceiveEvent(WebEventType eventType, bool handled) if (auto* automationSession = process().processPool().automationSession()) automationSession->mouseEventsFlushedForPage(*this); didFinishProcessingAllPendingMouseEvents(); @@ -17709,7 +17757,7 @@ index 279ab83cbd781b921d30055998baad70faa8156a..186553172243117291f9773dc8db54bb } break; } -@@ -8584,7 +8776,6 @@ void WebPageProxy::didReceiveEvent(WebEventType eventType, bool handled) +@@ -8709,7 +8902,6 @@ void WebPageProxy::didReceiveEvent(WebEventType eventType, bool handled) // The call to doneWithKeyEvent may close this WebPage. // Protect against this being destroyed. Ref protect(*this); @@ -17717,7 +17765,7 @@ index 279ab83cbd781b921d30055998baad70faa8156a..186553172243117291f9773dc8db54bb pageClient().doneWithKeyEvent(event, handled); if (!handled) m_uiClient->didNotHandleKeyEvent(this, event); -@@ -8593,6 +8784,7 @@ void WebPageProxy::didReceiveEvent(WebEventType eventType, bool handled) +@@ -8718,6 +8910,7 @@ void WebPageProxy::didReceiveEvent(WebEventType eventType, bool handled) if (!canProcessMoreKeyEvents) { if (auto* automationSession = process().processPool().automationSession()) automationSession->keyboardEventsFlushedForPage(*this); @@ -17725,7 +17773,7 @@ index 279ab83cbd781b921d30055998baad70faa8156a..186553172243117291f9773dc8db54bb } break; } -@@ -8946,7 +9138,10 @@ void WebPageProxy::dispatchProcessDidTerminate(ProcessTerminationReason reason) +@@ -9072,7 +9265,10 @@ void WebPageProxy::dispatchProcessDidTerminate(ProcessTerminationReason reason) { WEBPAGEPROXY_RELEASE_LOG_ERROR(Loading, "dispatchProcessDidTerminate: reason=%" PUBLIC_LOG_STRING, processTerminationReasonToString(reason)); @@ -17737,7 +17785,7 @@ index 279ab83cbd781b921d30055998baad70faa8156a..186553172243117291f9773dc8db54bb if (m_loaderClient) handledByClient = reason != ProcessTerminationReason::RequestedByClient && m_loaderClient->processDidCrash(*this); else -@@ -9319,6 +9514,7 @@ bool WebPageProxy::useGPUProcessForDOMRenderingEnabled() const +@@ -9445,6 +9641,7 @@ bool WebPageProxy::useGPUProcessForDOMRenderingEnabled() const WebPageCreationParameters WebPageProxy::creationParameters(WebProcessProxy& process, DrawingAreaProxy& drawingArea, RefPtr&& websitePolicies) { @@ -17745,7 +17793,7 @@ index 279ab83cbd781b921d30055998baad70faa8156a..186553172243117291f9773dc8db54bb WebPageCreationParameters parameters; parameters.processDisplayName = configuration().processDisplayName(); -@@ -9520,6 +9716,8 @@ WebPageCreationParameters WebPageProxy::creationParameters(WebProcessProxy& proc +@@ -9646,6 +9843,8 @@ WebPageCreationParameters WebPageProxy::creationParameters(WebProcessProxy& proc parameters.httpsUpgradeEnabled = preferences().upgradeKnownHostsToHTTPSEnabled() ? m_configuration->httpsUpgradeEnabled() : false; @@ -17754,7 +17802,7 @@ index 279ab83cbd781b921d30055998baad70faa8156a..186553172243117291f9773dc8db54bb #if PLATFORM(IOS) || PLATFORM(VISION) // FIXME: This is also being passed over the to WebProcess via the PreferencesStore. parameters.allowsDeprecatedSynchronousXMLHttpRequestDuringUnload = allowsDeprecatedSynchronousXMLHttpRequestDuringUnload(); -@@ -9604,8 +9802,42 @@ void WebPageProxy::gamepadActivity(const Vector>& gam +@@ -9730,8 +9929,42 @@ void WebPageProxy::gamepadActivity(const Vector>& gam #endif @@ -17797,7 +17845,7 @@ index 279ab83cbd781b921d30055998baad70faa8156a..186553172243117291f9773dc8db54bb if (negotiatedLegacyTLS == NegotiatedLegacyTLS::Yes) { m_navigationClient->shouldAllowLegacyTLS(*this, authenticationChallenge.get(), [this, protectedThis = Ref { *this }, authenticationChallenge] (bool shouldAllowLegacyTLS) { if (shouldAllowLegacyTLS) -@@ -9709,6 +9941,15 @@ void WebPageProxy::requestGeolocationPermissionForFrame(GeolocationIdentifier ge +@@ -9835,6 +10068,15 @@ void WebPageProxy::requestGeolocationPermissionForFrame(GeolocationIdentifier ge request->deny(); }; @@ -17814,7 +17862,7 @@ index 279ab83cbd781b921d30055998baad70faa8156a..186553172243117291f9773dc8db54bb // and make it one UIClient call that calls the completionHandler with false // if there is no delegate instead of returning the completionHandler diff --git a/Source/WebKit/UIProcess/WebPageProxy.h b/Source/WebKit/UIProcess/WebPageProxy.h -index 41b18adc02933918be11cbff5569b13f9f8f8cc1..6769f624d2f67e1442722b759a10693e476dad44 100644 +index 09dc8e03ab68b452cfae3b93f20565ff0d80f5a0..0a8f50078a2f7bc965ed34e067a6d5d4a2ba5b31 100644 --- a/Source/WebKit/UIProcess/WebPageProxy.h +++ b/Source/WebKit/UIProcess/WebPageProxy.h @@ -26,6 +26,7 @@ @@ -17825,10 +17873,10 @@ index 41b18adc02933918be11cbff5569b13f9f8f8cc1..6769f624d2f67e1442722b759a10693e #include "MessageReceiver.h" #include "MessageSender.h" #include -@@ -33,6 +34,24 @@ - #include +@@ -34,6 +35,24 @@ #include #include + #include +#include "InspectorDialogAgent.h" +#include "WebProtectionSpace.h" +#include @@ -17850,7 +17898,7 @@ index 41b18adc02933918be11cbff5569b13f9f8f8cc1..6769f624d2f67e1442722b759a10693e namespace API { class Attachment; -@@ -93,6 +112,7 @@ class DestinationColorSpace; +@@ -94,6 +113,7 @@ class DestinationColorSpace; class DragData; class FloatPoint; class FloatQuad; @@ -17858,7 +17906,7 @@ index 41b18adc02933918be11cbff5569b13f9f8f8cc1..6769f624d2f67e1442722b759a10693e class FloatRect; class FloatSize; class FontAttributeChanges; -@@ -381,6 +401,7 @@ class WebExtensionController; +@@ -385,6 +405,7 @@ class WebExtensionController; class WebFramePolicyListenerProxy; class WebFrameProxy; class WebFullScreenManagerProxy; @@ -17866,7 +17914,7 @@ index 41b18adc02933918be11cbff5569b13f9f8f8cc1..6769f624d2f67e1442722b759a10693e class WebInspectorUIProxy; class WebKeyboardEvent; class WebMouseEvent; -@@ -576,6 +597,8 @@ public: +@@ -580,6 +601,8 @@ public: void setControlledByAutomation(bool); WebPageInspectorController& inspectorController() { return *m_inspectorController; } @@ -17875,7 +17923,7 @@ index 41b18adc02933918be11cbff5569b13f9f8f8cc1..6769f624d2f67e1442722b759a10693e #if PLATFORM(IOS_FAMILY) void showInspectorIndication(); -@@ -606,6 +629,7 @@ public: +@@ -614,6 +637,7 @@ public: bool hasSleepDisabler() const; #if ENABLE(FULLSCREEN_API) @@ -17883,7 +17931,7 @@ index 41b18adc02933918be11cbff5569b13f9f8f8cc1..6769f624d2f67e1442722b759a10693e WebFullScreenManagerProxy* fullScreenManager(); API::FullscreenClient& fullscreenClient() const { return *m_fullscreenClient; } -@@ -694,6 +718,11 @@ public: +@@ -702,6 +726,11 @@ public: void setPageLoadStateObserver(std::unique_ptr&&); @@ -17895,7 +17943,7 @@ index 41b18adc02933918be11cbff5569b13f9f8f8cc1..6769f624d2f67e1442722b759a10693e void initializeWebPage(); void setDrawingArea(std::unique_ptr&&); -@@ -720,6 +749,7 @@ public: +@@ -728,6 +757,7 @@ public: void addPlatformLoadParameters(WebProcessProxy&, LoadParameters&); RefPtr loadRequest(WebCore::ResourceRequest&&); RefPtr loadRequest(WebCore::ResourceRequest&&, WebCore::ShouldOpenExternalURLsPolicy, API::Object* userData = nullptr); @@ -17903,7 +17951,7 @@ index 41b18adc02933918be11cbff5569b13f9f8f8cc1..6769f624d2f67e1442722b759a10693e RefPtr loadFile(const String& fileURL, const String& resourceDirectoryURL, bool isAppInitiated = true, API::Object* userData = nullptr); RefPtr loadData(const IPC::DataReference&, const String& MIMEType, const String& encoding, const String& baseURL, API::Object* userData = nullptr); RefPtr loadData(const IPC::DataReference&, const String& MIMEType, const String& encoding, const String& baseURL, API::Object* userData, WebCore::ShouldOpenExternalURLsPolicy); -@@ -1288,6 +1318,7 @@ public: +@@ -1299,6 +1329,7 @@ public: #endif void pageScaleFactorDidChange(double); @@ -17911,7 +17959,7 @@ index 41b18adc02933918be11cbff5569b13f9f8f8cc1..6769f624d2f67e1442722b759a10693e void pluginScaleFactorDidChange(double); void pluginZoomFactorDidChange(double); -@@ -1377,14 +1408,20 @@ public: +@@ -1389,14 +1420,20 @@ public: void didStartDrag(); void dragCancelled(); void setDragCaretRect(const WebCore::IntRect&); @@ -17925,7 +17973,7 @@ index 41b18adc02933918be11cbff5569b13f9f8f8cc1..6769f624d2f67e1442722b759a10693e #endif -#if PLATFORM(GTK) +#if PLATFORM(GTK) || PLATFORM(WPE) - void startDrag(WebCore::SelectionData&&, OptionSet, ShareableBitmapHandle&& dragImage, WebCore::IntPoint&& dragImageHotspot); + void startDrag(WebCore::SelectionData&&, OptionSet, std::optional&& dragImage, WebCore::IntPoint&& dragImageHotspot); #endif +#if PLATFORM(WIN) + void startDrag(WebCore::DragDataMap&& dragDataMap); @@ -17933,7 +17981,7 @@ index 41b18adc02933918be11cbff5569b13f9f8f8cc1..6769f624d2f67e1442722b759a10693e #endif void processDidBecomeUnresponsive(); -@@ -1601,6 +1638,7 @@ public: +@@ -1614,6 +1651,7 @@ public: void setViewportSizeForCSSViewportUnits(const WebCore::FloatSize&); WebCore::FloatSize viewportSizeForCSSViewportUnits() const; @@ -17941,7 +17989,7 @@ index 41b18adc02933918be11cbff5569b13f9f8f8cc1..6769f624d2f67e1442722b759a10693e void didReceiveAuthenticationChallengeProxy(Ref&&, NegotiatedLegacyTLS); void negotiatedLegacyTLS(); void didNegotiateModernTLS(const URL&); -@@ -1635,6 +1673,8 @@ public: +@@ -1648,6 +1686,8 @@ public: #if PLATFORM(COCOA) || PLATFORM(GTK) RefPtr takeViewSnapshot(std::optional&&); @@ -17950,7 +17998,7 @@ index 41b18adc02933918be11cbff5569b13f9f8f8cc1..6769f624d2f67e1442722b759a10693e #endif #if ENABLE(WEB_CRYPTO) -@@ -2907,8 +2947,10 @@ private: +@@ -2923,8 +2963,10 @@ private: String m_overrideContentSecurityPolicy; RefPtr m_inspector; @@ -17961,7 +18009,7 @@ index 41b18adc02933918be11cbff5569b13f9f8f8cc1..6769f624d2f67e1442722b759a10693e std::unique_ptr m_fullScreenManager; std::unique_ptr m_fullscreenClient; #endif -@@ -3090,6 +3132,22 @@ private: +@@ -3106,6 +3148,22 @@ private: std::optional m_currentDragOperation; bool m_currentDragIsOverFileInput { false }; unsigned m_currentDragNumberOfFilesToBeAccepted { 0 }; @@ -17984,7 +18032,7 @@ index 41b18adc02933918be11cbff5569b13f9f8f8cc1..6769f624d2f67e1442722b759a10693e #endif bool m_mainFrameHasHorizontalScrollbar { false }; -@@ -3257,6 +3315,10 @@ private: +@@ -3273,6 +3331,10 @@ private: RefPtr messageBody; }; Vector m_pendingInjectedBundleMessages; @@ -17996,7 +18044,7 @@ index 41b18adc02933918be11cbff5569b13f9f8f8cc1..6769f624d2f67e1442722b759a10693e #if PLATFORM(IOS_FAMILY) && ENABLE(DEVICE_ORIENTATION) std::unique_ptr m_webDeviceOrientationUpdateProviderProxy; diff --git a/Source/WebKit/UIProcess/WebPageProxy.messages.in b/Source/WebKit/UIProcess/WebPageProxy.messages.in -index 3501a297012db004b25d91c8a92d149b47a5c98d..845a929a0aa4b1af1047a0c7234feedecdaf4403 100644 +index 2531bdf69d387b180de1e9e917e493c20ad64cbd..56042234d6c5a31792c4bf7b398f13345024bf99 100644 --- a/Source/WebKit/UIProcess/WebPageProxy.messages.in +++ b/Source/WebKit/UIProcess/WebPageProxy.messages.in @@ -29,6 +29,7 @@ messages -> WebPageProxy { @@ -18006,7 +18054,7 @@ index 3501a297012db004b25d91c8a92d149b47a5c98d..845a929a0aa4b1af1047a0c7234feede + LogToStderr(String text) DidChangeViewportProperties(struct WebCore::ViewportAttributes attributes) - DidReceiveEvent(enum:int8_t WebKit::WebEventType eventType, bool handled) + DidReceiveEvent(enum:uint8_t WebKit::WebEventType eventType, bool handled) @@ -179,6 +180,7 @@ messages -> WebPageProxy { #endif @@ -18021,7 +18069,7 @@ index 3501a297012db004b25d91c8a92d149b47a5c98d..845a929a0aa4b1af1047a0c7234feede #endif -#if PLATFORM(GTK) && ENABLE(DRAG_SUPPORT) +#if (PLATFORM(GTK) || PLATFORM(WPE)) && ENABLE(DRAG_SUPPORT) - StartDrag(WebCore::SelectionData selectionData, OptionSet dragOperationMask, WebKit::ShareableBitmap::Handle dragImage, WebCore::IntPoint dragImageHotspot) + StartDrag(WebCore::SelectionData selectionData, OptionSet dragOperationMask, std::optional dragImage, WebCore::IntPoint dragImageHotspot) #endif - +#if PLATFORM(WIN) && ENABLE(DRAG_SUPPORT) @@ -18031,7 +18079,7 @@ index 3501a297012db004b25d91c8a92d149b47a5c98d..845a929a0aa4b1af1047a0c7234feede DidPerformDragOperation(bool handled) #endif diff --git a/Source/WebKit/UIProcess/WebProcessCache.cpp b/Source/WebKit/UIProcess/WebProcessCache.cpp -index 58e39fd6df029efb3801ad914dfc96f4e6a8d8dc..7b47e3f1e08d6f974889def778fcee35b3b8ee09 100644 +index b94b04e7b3558585f49927e1e3bd370e6619bad6..c0cf985d704368bc5c1d4b8e4d556671b3d386e7 100644 --- a/Source/WebKit/UIProcess/WebProcessCache.cpp +++ b/Source/WebKit/UIProcess/WebProcessCache.cpp @@ -81,6 +81,10 @@ bool WebProcessCache::canCacheProcess(WebProcessProxy& process) const @@ -18046,7 +18094,7 @@ index 58e39fd6df029efb3801ad914dfc96f4e6a8d8dc..7b47e3f1e08d6f974889def778fcee35 } diff --git a/Source/WebKit/UIProcess/WebProcessPool.cpp b/Source/WebKit/UIProcess/WebProcessPool.cpp -index b5b404aeacee13c1d1f08cf2be9ccd19eeae8175..ba574e9613171e0591dd6ec70ee310965a33e2c5 100644 +index 278394445e636fc68adb47ae178274e2a5dced14..0de9f2a8fb4a3327a3b53b9cf01526c0eeef1c87 100644 --- a/Source/WebKit/UIProcess/WebProcessPool.cpp +++ b/Source/WebKit/UIProcess/WebProcessPool.cpp @@ -384,10 +384,10 @@ void WebProcessPool::setAutomationClient(std::unique_ptr& @@ -18074,7 +18122,7 @@ index b5b404aeacee13c1d1f08cf2be9ccd19eeae8175..ba574e9613171e0591dd6ec70ee31096 void WebProcessPool::fullKeyboardAccessModeChanged(bool fullKeyboardAccessEnabled) { -@@ -542,6 +543,14 @@ void WebProcessPool::establishRemoteWorkerContextConnectionToNetworkProcess(Remo +@@ -533,6 +534,14 @@ void WebProcessPool::establishRemoteWorkerContextConnectionToNetworkProcess(Remo RefPtr requestingProcess = requestingProcessIdentifier ? WebProcessProxy::processForIdentifier(*requestingProcessIdentifier) : nullptr; WebProcessPool* processPool = requestingProcess ? &requestingProcess->processPool() : processPools()[0]; @@ -18089,7 +18137,7 @@ index b5b404aeacee13c1d1f08cf2be9ccd19eeae8175..ba574e9613171e0591dd6ec70ee31096 ASSERT(processPool); WebProcessProxy* remoteWorkerProcessProxy { nullptr }; -@@ -850,7 +859,7 @@ void WebProcessPool::initializeNewWebProcess(WebProcessProxy& process, WebsiteDa +@@ -841,7 +850,7 @@ void WebProcessPool::initializeNewWebProcess(WebProcessProxy& process, WebsiteDa #endif parameters.cacheModel = LegacyGlobalSettings::singleton().cacheModel(); @@ -18099,7 +18147,7 @@ index b5b404aeacee13c1d1f08cf2be9ccd19eeae8175..ba574e9613171e0591dd6ec70ee31096 parameters.urlSchemesRegisteredAsEmptyDocument = copyToVector(m_schemesToRegisterAsEmptyDocument); diff --git a/Source/WebKit/UIProcess/WebProcessProxy.cpp b/Source/WebKit/UIProcess/WebProcessProxy.cpp -index e25e43bc48460a221afc2594dcda553942def734..05033d41747ea2def5b591e156c1d98e086e8d29 100644 +index e515c6978a3b9e1d408541afa4e8b82875c7f376..b36cab14e2989a692a3f7976b0bef9d5b621f0c9 100644 --- a/Source/WebKit/UIProcess/WebProcessProxy.cpp +++ b/Source/WebKit/UIProcess/WebProcessProxy.cpp @@ -170,6 +170,11 @@ Vector> WebProcessProxy::allProcesses() @@ -18114,7 +18162,7 @@ index e25e43bc48460a221afc2594dcda553942def734..05033d41747ea2def5b591e156c1d98e RefPtr WebProcessProxy::processForIdentifier(ProcessIdentifier identifier) { return allProcessMap().get(identifier).get(); -@@ -502,6 +507,26 @@ void WebProcessProxy::getLaunchOptions(ProcessLauncher::LaunchOptions& launchOpt +@@ -486,6 +491,26 @@ void WebProcessProxy::getLaunchOptions(ProcessLauncher::LaunchOptions& launchOpt if (WebKit::isInspectorProcessPool(processPool())) launchOptions.extraInitializationData.add("inspector-process"_s, "1"_s); @@ -18142,10 +18190,10 @@ index e25e43bc48460a221afc2594dcda553942def734..05033d41747ea2def5b591e156c1d98e if (isPrewarmed()) diff --git a/Source/WebKit/UIProcess/WebProcessProxy.h b/Source/WebKit/UIProcess/WebProcessProxy.h -index f88872f19589ef5c40b4b6c40937ff280a580503..949588f6f8cf7c7f010e1f8f811bbbf6cb6c1fea 100644 +index a80bc87a48f681e0a2ab3078839ac0cf8e3b9d85..e59c287057661d594862533bf97212f2120d9e65 100644 --- a/Source/WebKit/UIProcess/WebProcessProxy.h +++ b/Source/WebKit/UIProcess/WebProcessProxy.h -@@ -163,6 +163,7 @@ public: +@@ -164,6 +164,7 @@ public: static void forWebPagesWithOrigin(PAL::SessionID, const WebCore::SecurityOriginData&, const Function&); static Vector> allowedFirstPartiesForCookies(); @@ -18154,10 +18202,10 @@ index f88872f19589ef5c40b4b6c40937ff280a580503..949588f6f8cf7c7f010e1f8f811bbbf6 WebConnection* webConnection() const { return m_webConnection.get(); } diff --git a/Source/WebKit/UIProcess/WebsiteData/WebsiteDataStore.cpp b/Source/WebKit/UIProcess/WebsiteData/WebsiteDataStore.cpp -index 3fc97e96b47acf364b0fcb17bf9150567cf4f1dc..2ab6808401a9b975d48f7bd30a5215ff2b0f0551 100644 +index ff22a4dd064d2eecd549e6c44ebd34ca15492ebc..4001117d975aeeeaed5536f0c5afa36077e72a75 100644 --- a/Source/WebKit/UIProcess/WebsiteData/WebsiteDataStore.cpp +++ b/Source/WebKit/UIProcess/WebsiteData/WebsiteDataStore.cpp -@@ -293,7 +293,8 @@ SOAuthorizationCoordinator& WebsiteDataStore::soAuthorizationCoordinator(const W +@@ -294,7 +294,8 @@ SOAuthorizationCoordinator& WebsiteDataStore::soAuthorizationCoordinator(const W static Ref networkProcessForSession(PAL::SessionID sessionID) { @@ -18166,8 +18214,8 @@ index 3fc97e96b47acf364b0fcb17bf9150567cf4f1dc..2ab6808401a9b975d48f7bd30a5215ff +#if ((PLATFORM(GTK) || PLATFORM(WPE))) if (sessionID.isEphemeral()) { // Reuse a previous persistent session network process for ephemeral sessions. - for (auto* dataStore : allDataStores().values()) { -@@ -2211,6 +2212,12 @@ void WebsiteDataStore::originDirectoryForTesting(WebCore::ClientOrigin&& origin, + for (auto& dataStore : allDataStores().values()) { +@@ -2216,6 +2217,12 @@ void WebsiteDataStore::originDirectoryForTesting(WebCore::ClientOrigin&& origin, protectedNetworkProcess()->websiteDataOriginDirectoryForTesting(m_sessionID, WTFMove(origin), type, WTFMove(completionHandler)); } @@ -18181,10 +18229,10 @@ index 3fc97e96b47acf364b0fcb17bf9150567cf4f1dc..2ab6808401a9b975d48f7bd30a5215ff void WebsiteDataStore::hasAppBoundSession(CompletionHandler&& completionHandler) const { diff --git a/Source/WebKit/UIProcess/WebsiteData/WebsiteDataStore.h b/Source/WebKit/UIProcess/WebsiteData/WebsiteDataStore.h -index 8b2b390229d48baaadf725b6e4cf3e1313e2d2d8..7bbb2d6ad89d8dfcef6fb48c03fb951dee98f8f6 100644 +index 9fb3ac0e6abcd6732b774c7adac72c91088c4f37..1bce09f386c5541e9932f6a26cbb374d8114567c 100644 --- a/Source/WebKit/UIProcess/WebsiteData/WebsiteDataStore.h +++ b/Source/WebKit/UIProcess/WebsiteData/WebsiteDataStore.h -@@ -95,6 +95,7 @@ class DeviceIdHashSaltStorage; +@@ -96,6 +96,7 @@ class DeviceIdHashSaltStorage; class DownloadProxy; class NetworkProcessProxy; class SOAuthorizationCoordinator; @@ -18192,15 +18240,15 @@ index 8b2b390229d48baaadf725b6e4cf3e1313e2d2d8..7bbb2d6ad89d8dfcef6fb48c03fb951d class VirtualAuthenticatorManager; class WebPageProxy; class WebProcessPool; -@@ -106,6 +107,7 @@ enum class UnifiedOriginStorageLevel : uint8_t; +@@ -107,6 +108,7 @@ enum class UnifiedOriginStorageLevel : uint8_t; enum class WebsiteDataFetchOption : uint8_t; enum class WebsiteDataType : uint32_t; +struct FrameInfoData; struct NetworkProcessConnectionInfo; + struct WebPushMessage; struct WebsiteDataRecord; - struct WebsiteDataStoreParameters; -@@ -116,6 +118,14 @@ enum class StorageAccessStatus : uint8_t; +@@ -118,6 +120,14 @@ enum class StorageAccessStatus : uint8_t; enum class StorageAccessPromptStatus; #endif @@ -18212,10 +18260,10 @@ index 8b2b390229d48baaadf725b6e4cf3e1313e2d2d8..7bbb2d6ad89d8dfcef6fb48c03fb951d + virtual ~DownloadInstrumentation() = default; +}; + - class WebsiteDataStore : public API::ObjectImpl, public Identified, public CanMakeWeakPtr { + class WebsiteDataStore : public API::ObjectImpl, public Identified, public CanMakeWeakPtr, public CanMakeCheckedPtr { public: static Ref defaultDataStore(); -@@ -316,11 +326,13 @@ public: +@@ -318,11 +328,13 @@ public: const WebCore::CurlProxySettings& networkProxySettings() const { return m_proxySettings; } #endif @@ -18230,7 +18278,7 @@ index 8b2b390229d48baaadf725b6e4cf3e1313e2d2d8..7bbb2d6ad89d8dfcef6fb48c03fb951d void setNetworkProxySettings(WebCore::SoupNetworkProxySettings&&); const WebCore::SoupNetworkProxySettings& networkProxySettings() const { return m_networkProxySettings; } void setCookiePersistentStorage(const String&, SoupCookiePersistentStorageType); -@@ -405,6 +417,12 @@ public: +@@ -407,6 +419,12 @@ public: static const String& defaultBaseDataDirectory(); #endif @@ -18243,7 +18291,7 @@ index 8b2b390229d48baaadf725b6e4cf3e1313e2d2d8..7bbb2d6ad89d8dfcef6fb48c03fb951d void resetQuota(CompletionHandler&&); void resetStoragePersistedState(CompletionHandler&&); #if PLATFORM(IOS_FAMILY) -@@ -560,9 +578,11 @@ private: +@@ -566,9 +584,11 @@ private: WebCore::CurlProxySettings m_proxySettings; #endif @@ -18256,7 +18304,7 @@ index 8b2b390229d48baaadf725b6e4cf3e1313e2d2d8..7bbb2d6ad89d8dfcef6fb48c03fb951d WebCore::SoupNetworkProxySettings m_networkProxySettings; String m_cookiePersistentStoragePath; SoupCookiePersistentStorageType m_cookiePersistentStorageType { SoupCookiePersistentStorageType::SQLite }; -@@ -591,6 +611,10 @@ private: +@@ -597,6 +617,10 @@ private: RefPtr m_cookieStore; RefPtr m_networkProcess; @@ -18348,10 +18396,10 @@ index db15435ed4581388a631547cdc92c092362ff84a..de45cfe0b737a0450455bafbda91887d }; diff --git a/Source/WebKit/UIProcess/glib/InspectorPlaywrightAgentClientGLib.cpp b/Source/WebKit/UIProcess/glib/InspectorPlaywrightAgentClientGLib.cpp new file mode 100644 -index 0000000000000000000000000000000000000000..da579b964f8ff0c4b4f79283a6661d7a8672c4d9 +index 0000000000000000000000000000000000000000..93de9be341045a616b0219a965af2447ecbfc926 --- /dev/null +++ b/Source/WebKit/UIProcess/glib/InspectorPlaywrightAgentClientGLib.cpp -@@ -0,0 +1,181 @@ +@@ -0,0 +1,184 @@ +/* + * Copyright (C) 2019 Microsoft Corporation. + * @@ -18511,23 +18559,26 @@ index 0000000000000000000000000000000000000000..da579b964f8ff0c4b4f79283a6661d7a + m_idToContext.remove(sessionID); +} + -+String InspectorPlaywrightAgentClientGlib::takePageScreenshot(WTF::String& error, WebPageProxy& page, WebCore::IntRect&& clip, bool nominalResolution) ++void InspectorPlaywrightAgentClientGlib::takePageScreenshot(WebPageProxy& page, WebCore::IntRect&& clip, bool nominalResolution, CompletionHandler&& completionHandler) +{ -+ cairo_surface_t* surface = nullptr; ++ page.callAfterNextPresentationUpdate([protectedPage = Ref{ page }, clip = WTFMove(clip), nominalResolution, completionHandler = WTFMove(completionHandler)]() mutable { ++ cairo_surface_t* surface = nullptr; +#if PLATFORM(GTK) -+ RefPtr viewSnapshot = page.pageClient().takeViewSnapshot(WTFMove(clip), nominalResolution); -+ if (viewSnapshot) -+ surface = viewSnapshot->surface(); ++ RefPtr viewSnapshot = protectedPage->pageClient().takeViewSnapshot(WTFMove(clip), nominalResolution); ++ if (viewSnapshot) ++ surface = viewSnapshot->surface(); +#elif PLATFORM(WPE) -+ RefPtr protectPtr = page.pageClient().takeViewSnapshot(WTFMove(clip), nominalResolution); -+ surface = protectPtr.get(); ++ RefPtr protectPtr = protectedPage->pageClient().takeViewSnapshot(WTFMove(clip), nominalResolution); ++ surface = protectPtr.get(); +#endif -+ if (surface) { -+ Vector encodeData = WebCore::encodeData(surface, "image/png"_s, std::nullopt); -+ return makeString("data:image/png;base64,"_s, base64Encoded(encodeData)); -+ } -+ error = "Failed to take screenshot"_s; -+ return String(); ++ if (surface) { ++ Vector encodeData = WebCore::encodeData(surface, "image/png"_s, std::nullopt); ++ completionHandler(emptyString(), makeString("data:image/png;base64,"_s, base64Encoded(encodeData))); ++ return; ++ } ++ ++ completionHandler("Failed to take screenshot"_s, emptyString()); ++ }); +} + +} // namespace WebKit @@ -18535,7 +18586,7 @@ index 0000000000000000000000000000000000000000..da579b964f8ff0c4b4f79283a6661d7a +#endif // ENABLE(REMOTE_INSPECTOR) diff --git a/Source/WebKit/UIProcess/glib/InspectorPlaywrightAgentClientGLib.h b/Source/WebKit/UIProcess/glib/InspectorPlaywrightAgentClientGLib.h new file mode 100644 -index 0000000000000000000000000000000000000000..7280da5ef80c76bb34fc0a905efe70bbe5dcc3c8 +index 0000000000000000000000000000000000000000..394f07e1754be52b7d503d5720cba5d3724c5f4d --- /dev/null +++ b/Source/WebKit/UIProcess/glib/InspectorPlaywrightAgentClientGLib.h @@ -0,0 +1,61 @@ @@ -18588,7 +18639,7 @@ index 0000000000000000000000000000000000000000..7280da5ef80c76bb34fc0a905efe70bb + void closeBrowser() override; + std::unique_ptr createBrowserContext(WTF::String& error, const WTF::String& proxyServer, const WTF::String& proxyBypassList) override; + void deleteBrowserContext(WTF::String& error, PAL::SessionID) override; -+ String takePageScreenshot(WTF::String& error, WebPageProxy&, WebCore::IntRect&& clip, bool nominalResolution) override; ++ void takePageScreenshot(WebPageProxy&, WebCore::IntRect&& clip, bool nominalResolution, CompletionHandler&& completionHandler) override; + +private: + WebKitWebContext* findContext(WTF::String& error, PAL::SessionID); @@ -18601,7 +18652,7 @@ index 0000000000000000000000000000000000000000..7280da5ef80c76bb34fc0a905efe70bb + +#endif // ENABLE(REMOTE_INSPECTOR) diff --git a/Source/WebKit/UIProcess/gtk/AcceleratedBackingStore.h b/Source/WebKit/UIProcess/gtk/AcceleratedBackingStore.h -index 39aeff71fe05354cf63d3b3701d363642d63aca4..32e96cdd0bdbd8c5dcde43fdf60052ac13a226f7 100644 +index 71fb4cbd4338bcbda3a61019cbf4a914bf74953e..6f56e4afb345c23b4d8b91ee77f1991c72e58383 100644 --- a/Source/WebKit/UIProcess/gtk/AcceleratedBackingStore.h +++ b/Source/WebKit/UIProcess/gtk/AcceleratedBackingStore.h @@ -28,6 +28,7 @@ @@ -18620,16 +18671,16 @@ index 39aeff71fe05354cf63d3b3701d363642d63aca4..32e96cdd0bdbd8c5dcde43fdf60052ac + virtual void realize() { }; virtual void unrealize() { }; - virtual bool makeContextCurrent() { return false; } + virtual int renderHostFileDescriptor() { return -1; } diff --git a/Source/WebKit/UIProcess/gtk/AcceleratedBackingStoreDMABuf.cpp b/Source/WebKit/UIProcess/gtk/AcceleratedBackingStoreDMABuf.cpp -index 63b699e8944ee87f8546496aca281cf4efd78869..a1ecfb5f4467555ad3a370d23f4005678fb2f443 100644 +index 9c0f5efdfb0e3ea188fdb48e9953a79c00671df6..8bf763775887898ff160dd42813735475cb7149d 100644 --- a/Source/WebKit/UIProcess/gtk/AcceleratedBackingStoreDMABuf.cpp +++ b/Source/WebKit/UIProcess/gtk/AcceleratedBackingStoreDMABuf.cpp -@@ -386,6 +386,25 @@ void AcceleratedBackingStoreDMABuf::Surface::paint(GtkWidget*, cairo_t* cr, cons +@@ -358,6 +358,25 @@ void AcceleratedBackingStoreDMABuf::RendererCairo::paint(GtkWidget* widget, cair } #endif -+cairo_surface_t* AcceleratedBackingStoreDMABuf::Surface::surfaceForScreencast() ++cairo_surface_t* AcceleratedBackingStoreDMABuf::RendererCairo::surfaceForScreencast() +{ + if (!m_surface) + return nullptr; @@ -18648,67 +18699,64 @@ index 63b699e8944ee87f8546496aca281cf4efd78869..a1ecfb5f4467555ad3a370d23f400567 + return m_flippedSurface.get(); +} + - void AcceleratedBackingStoreDMABuf::configure(UnixFileDescriptor&& backFD, UnixFileDescriptor&& frontFD, UnixFileDescriptor&& displayFD, const WebCore::IntSize& size, uint32_t format, uint32_t offset, uint32_t stride, uint64_t modifier) + void AcceleratedBackingStoreDMABuf::didCreateBuffer(uint64_t id, WTF::UnixFileDescriptor&& fd, const WebCore::IntSize& size, uint32_t format, uint32_t offset, uint32_t stride, uint64_t modifier) { - m_isSoftwareRast = false; -@@ -571,4 +590,13 @@ bool AcceleratedBackingStoreDMABuf::paint(cairo_t* cr, const WebCore::IntRect& c + #if USE(GBM) +@@ -520,6 +539,15 @@ bool AcceleratedBackingStoreDMABuf::paint(cairo_t* cr, const WebCore::IntRect& c } #endif +cairo_surface_t* AcceleratedBackingStoreDMABuf::surface() +{ -+ if (m_committedSource) -+ return m_committedSource->surfaceForScreencast(); ++ if (!m_renderer) ++ return nullptr; + -+ return nullptr; ++ return m_renderer->surfaceForScreencast(); +} + + } // namespace WebKit + + #endif // USE(EGL) diff --git a/Source/WebKit/UIProcess/gtk/AcceleratedBackingStoreDMABuf.h b/Source/WebKit/UIProcess/gtk/AcceleratedBackingStoreDMABuf.h -index 1bc769ada3135a19f13851c8099ad008fcac8a04..1604833f958746b993dea0b50f16758b867ed071 100644 +index 66f42a891c7672604c0163d0b3157cbc9af7fc44..bebaa3f0bd732fbba75a158ce2b02bbd3f6d9c77 100644 --- a/Source/WebKit/UIProcess/gtk/AcceleratedBackingStoreDMABuf.h +++ b/Source/WebKit/UIProcess/gtk/AcceleratedBackingStoreDMABuf.h -@@ -82,6 +82,7 @@ private: +@@ -86,6 +86,7 @@ private: #else bool paint(cairo_t*, const WebCore::IntRect&) override; #endif + cairo_surface_t* surface() override; - void realize() override; void unrealize() override; - bool makeContextCurrent() override; -@@ -99,6 +100,7 @@ private: + void update(const LayerTreeContext&) override; + +@@ -175,6 +176,7 @@ private: #else virtual void paint(GtkWidget*, cairo_t*, const WebCore::IntRect&) const = 0; #endif + virtual cairo_surface_t* surfaceForScreencast() = 0; - const WebCore::IntSize size() const { return m_size; } + Buffer* buffer() const { return m_buffer.get(); } -@@ -125,6 +127,7 @@ private: +@@ -199,6 +201,7 @@ private: #else void paint(GtkWidget*, cairo_t*, const WebCore::IntRect&) const override; #endif + cairo_surface_t* surfaceForScreencast() override { return nullptr; } - GRefPtr m_context; unsigned m_textureID { 0 }; -@@ -156,6 +159,7 @@ private: + #if USE(GTK4) +@@ -220,8 +223,10 @@ private: #else void paint(GtkWidget*, cairo_t*, const WebCore::IntRect&) const override; #endif + cairo_surface_t* surfaceForScreencast() override; - #if USE(GBM) - RefPtr map(struct gbm_bo*) const; -@@ -173,6 +177,7 @@ private: RefPtr m_surface; - RefPtr m_backSurface; - RefPtr m_displaySurface; + RefPtr m_flippedSurface; }; - std::unique_ptr createSource(); + GRefPtr m_gdkGLContext; diff --git a/Source/WebKit/UIProcess/gtk/AcceleratedBackingStoreX11.h b/Source/WebKit/UIProcess/gtk/AcceleratedBackingStoreX11.h index 054e80bd900cf16d69801e8102ca989ff0563e1d..8245d7ed58008dbb6152e55e619e4331d30ae674 100644 --- a/Source/WebKit/UIProcess/gtk/AcceleratedBackingStoreX11.h @@ -18971,7 +19019,7 @@ index 0000000000000000000000000000000000000000..6a204c5bba8fb95ddb2d1c14cae7a3a7 + +} // namespace WebKit diff --git a/Source/WebKit/UIProcess/ios/PageClientImplIOS.mm b/Source/WebKit/UIProcess/ios/PageClientImplIOS.mm -index a5d22fb2ded1af0b22d3633e2c0f27390883679f..3a5672f959161ed6c8c6e159bb0373791d6963a2 100644 +index a36702e99ff8212d5a4154b0880e63ad457d231b..e5efeee83c835551ed034c480061416479f1ef5d 100644 --- a/Source/WebKit/UIProcess/ios/PageClientImplIOS.mm +++ b/Source/WebKit/UIProcess/ios/PageClientImplIOS.mm @@ -465,6 +465,8 @@ IntRect PageClientImpl::rootViewToAccessibilityScreen(const IntRect& rect) @@ -18983,22 +19031,9 @@ index a5d22fb2ded1af0b22d3633e2c0f27390883679f..3a5672f959161ed6c8c6e159bb037379 [contentView() _didHandleKeyEvent:event.nativeEvent() eventWasHandled:eventWasHandled]; } -diff --git a/Source/WebKit/UIProcess/mac/DisplayCaptureSessionManager.mm b/Source/WebKit/UIProcess/mac/DisplayCaptureSessionManager.mm -index b966690dded104d2d3debd5a25fa4a7ba3e5539b..ec12c90073fc3498a1fe8d78e943c57441e1c607 100644 ---- a/Source/WebKit/UIProcess/mac/DisplayCaptureSessionManager.mm -+++ b/Source/WebKit/UIProcess/mac/DisplayCaptureSessionManager.mm -@@ -245,7 +245,7 @@ void DisplayCaptureSessionManager::promptForGetDisplayMedia(UserMediaPermissionR - #elif PLATFORM(MAC) - - // There is no picker on systems without ScreenCaptureKit, so share the main screen. -- completionHandler(CGDisplayStreamScreenCaptureSource::screenCaptureDeviceForMainDisplay()); -+ completionHandler(WebCore::CGDisplayStreamScreenCaptureSource::screenCaptureDeviceForMainDisplay()); - - #endif - } diff --git a/Source/WebKit/UIProcess/mac/InspectorPlaywrightAgentClientMac.h b/Source/WebKit/UIProcess/mac/InspectorPlaywrightAgentClientMac.h new file mode 100644 -index 0000000000000000000000000000000000000000..a56c14a89afef09890174e72895733a6499543a1 +index 0000000000000000000000000000000000000000..2aabc02a4b5432f68a6e85fd9689775608f05a67 --- /dev/null +++ b/Source/WebKit/UIProcess/mac/InspectorPlaywrightAgentClientMac.h @@ -0,0 +1,53 @@ @@ -19046,7 +19081,7 @@ index 0000000000000000000000000000000000000000..a56c14a89afef09890174e72895733a6 + void closeBrowser() override; + std::unique_ptr createBrowserContext(WTF::String& error, const WTF::String& proxyServer, const WTF::String& proxyBypassList) override; + void deleteBrowserContext(WTF::String& error, PAL::SessionID) override; -+ String takePageScreenshot(WTF::String& error, WebPageProxy&, WebCore::IntRect&& clip, bool nominalResolution) override; ++ void takePageScreenshot(WebPageProxy&, WebCore::IntRect&& clip, bool nominalResolution, CompletionHandler&& completionHandler) override; + +private: + _WKBrowserInspectorDelegate* delegate_; @@ -19057,10 +19092,10 @@ index 0000000000000000000000000000000000000000..a56c14a89afef09890174e72895733a6 +} // namespace API diff --git a/Source/WebKit/UIProcess/mac/InspectorPlaywrightAgentClientMac.mm b/Source/WebKit/UIProcess/mac/InspectorPlaywrightAgentClientMac.mm new file mode 100644 -index 0000000000000000000000000000000000000000..a30d408d97c383929036626691020923f9dbfbd7 +index 0000000000000000000000000000000000000000..0de68ad69c87f9d5b0a5f0d24fb358a50b59b4a2 --- /dev/null +++ b/Source/WebKit/UIProcess/mac/InspectorPlaywrightAgentClientMac.mm -@@ -0,0 +1,94 @@ +@@ -0,0 +1,96 @@ +/* + * Copyright (C) 2019 Microsoft Corporation. + * @@ -19140,18 +19175,20 @@ index 0000000000000000000000000000000000000000..a30d408d97c383929036626691020923 + [delegate_ deleteBrowserContext:sessionID.toUInt64()]; +} + -+String InspectorPlaywrightAgentClientMac::takePageScreenshot(WTF::String& error, WebPageProxy& page, WebCore::IntRect&& clipRect, bool) ++void InspectorPlaywrightAgentClientMac::takePageScreenshot(WebPageProxy& page, WebCore::IntRect&& clipRect, bool, CompletionHandler&& completionHandler) +{ -+ RetainPtr imageRef = page.pageClient().takeSnapshotForAutomation(); -+ if (!imageRef) { -+ error = "Could not take view snapshot"_s; -+ return String(); -+ } -+ + int toolbarHeight = headless_ ? 0 : 59; -+ clipRect.move(0, toolbarHeight); -+ RetainPtr transformedImageRef = adoptCF(CGImageCreateWithImageInRect(imageRef.get(), clipRect)); -+ return WebCore::dataURL(transformedImageRef.get(), "image/png"_s, std::nullopt); ++ page.callAfterNextPresentationUpdate([protectedPage = Ref { page }, toolbarHeight, clipRect = WTFMove(clipRect), completionHandler = WTFMove(completionHandler)]() mutable { ++ RetainPtr imageRef = protectedPage->pageClient().takeSnapshotForAutomation(); ++ if (!imageRef) { ++ completionHandler("Could not take view snapshot"_s, emptyString()); ++ return; ++ } ++ ++ clipRect.move(0, toolbarHeight); ++ RetainPtr transformedImageRef = adoptCF(CGImageCreateWithImageInRect(imageRef.get(), clipRect)); ++ completionHandler(emptyString(), WebCore::dataURL(transformedImageRef.get(), "image/png"_s, std::nullopt)); ++ }); +} + +} // namespace WebKit @@ -19358,18 +19395,6 @@ index f57fbfcb950259723f5d7672198ec811491003a2..30eb73f44f4b8d2dc8888f86128b2215 return m_impl->windowIsFrontWindowUnderMouse(event.nativeEvent()); } -diff --git a/Source/WebKit/UIProcess/mac/WKImmediateActionController.h b/Source/WebKit/UIProcess/mac/WKImmediateActionController.h -index 74db97569f5179f3065f0a48dbb228777ddc6925..853d6ed84648ccb60f63ed464dddfeedfe374178 100644 ---- a/Source/WebKit/UIProcess/mac/WKImmediateActionController.h -+++ b/Source/WebKit/UIProcess/mac/WKImmediateActionController.h -@@ -31,6 +31,7 @@ - #import "WKImmediateActionTypes.h" - #import "WebHitTestResultData.h" - #import -+#import - #import - #import - #import diff --git a/Source/WebKit/UIProcess/mac/WebContextMenuProxyMac.h b/Source/WebKit/UIProcess/mac/WebContextMenuProxyMac.h index 46aaf12accb35a2e7685d61887e76f0fe14d76c5..37f418197dcc3f6c74fac258ba77d00df0effeb2 100644 --- a/Source/WebKit/UIProcess/mac/WebContextMenuProxyMac.h @@ -19600,26 +19625,10 @@ index a8284990329d56c79fb17debafe17a39a6a60301..b8ce65526d3eb35816d9c3bae913983c void saveBackForwardSnapshotForCurrentItem(); void saveBackForwardSnapshotForItem(WebBackForwardListItem&); diff --git a/Source/WebKit/UIProcess/mac/WebViewImpl.mm b/Source/WebKit/UIProcess/mac/WebViewImpl.mm -index b2ae8d81e2f7a92ffb317614a31052a119888b22..b0fcdf6b5cb584c6746c40cc2733b6b70c6dcdcf 100644 +index bddd6c9c1fb39f22b6ff733135de4a2df425a4bd..7f7b2f825c67eb50666c913f9a45db12880928ca 100644 --- a/Source/WebKit/UIProcess/mac/WebViewImpl.mm +++ b/Source/WebKit/UIProcess/mac/WebViewImpl.mm -@@ -87,6 +87,7 @@ - #import - #import - #import -+#import - #import - #import - #import -@@ -104,6 +105,7 @@ - #import - #import - #import -+#import - #import - #import - #import -@@ -2340,6 +2342,11 @@ WebCore::DestinationColorSpace WebViewImpl::colorSpace() +@@ -2342,6 +2342,11 @@ WebCore::DestinationColorSpace WebViewImpl::colorSpace() if (!m_colorSpace) m_colorSpace = [NSColorSpace sRGBColorSpace]; } @@ -19631,7 +19640,7 @@ index b2ae8d81e2f7a92ffb317614a31052a119888b22..b0fcdf6b5cb584c6746c40cc2733b6b7 ASSERT(m_colorSpace); return WebCore::DestinationColorSpace { [m_colorSpace CGColorSpace] }; -@@ -4406,6 +4413,18 @@ ALLOW_DEPRECATED_DECLARATIONS_BEGIN +@@ -4408,6 +4413,18 @@ ALLOW_DEPRECATED_DECLARATIONS_BEGIN ALLOW_DEPRECATED_DECLARATIONS_END } @@ -19650,65 +19659,6 @@ index b2ae8d81e2f7a92ffb317614a31052a119888b22..b0fcdf6b5cb584c6746c40cc2733b6b7 RefPtr WebViewImpl::takeViewSnapshot() { NSWindow *window = [m_view window]; -@@ -5002,11 +5021,11 @@ static Vector compositionHighlights(NSAttributedS - Vector highlights; - [string enumerateAttributesInRange:NSMakeRange(0, string.length) options:0 usingBlock:[&highlights](NSDictionary *attributes, NSRange range, BOOL *) { - std::optional backgroundHighlightColor; -- if (CocoaColor *backgroundColor = attributes[NSBackgroundColorAttributeName]) -+ if (WebCore::CocoaColor *backgroundColor = attributes[NSBackgroundColorAttributeName]) - backgroundHighlightColor = WebCore::colorFromCocoaColor(backgroundColor); - - std::optional foregroundHighlightColor; -- if (CocoaColor *foregroundColor = attributes[NSForegroundColorAttributeName]) -+ if (WebCore::CocoaColor *foregroundColor = attributes[NSForegroundColorAttributeName]) - foregroundHighlightColor = WebCore::colorFromCocoaColor(foregroundColor); - - highlights.append({ static_cast(range.location), static_cast(NSMaxRange(range)), backgroundHighlightColor, foregroundHighlightColor }); -@@ -5098,7 +5117,7 @@ static Vector compositionUnderlines(NSAttributedS - return mergedUnderlines; - } - --static HashMap> compositionAnnotations(NSAttributedString *string) -+static HashMap> compositionAnnotations(NSAttributedString *string) - { - if (!string.length) - return { }; -@@ -5111,7 +5130,7 @@ static HashMap> compositionAnnotations(NSAttribut - }); - #endif - -- HashMap> annotations; -+ HashMap> annotations; - [string enumerateAttributesInRange:NSMakeRange(0, string.length) options:0 usingBlock:[&annotations](NSDictionary *attributes, NSRange range, BOOL *) { - [attributes enumerateKeysAndObjectsUsingBlock:[&annotations, &range](NSAttributedStringKey key, id value, BOOL *) { - -@@ -5120,7 +5139,7 @@ static HashMap> compositionAnnotations(NSAttribut - - auto it = annotations.find(key); - if (it == annotations.end()) -- it = annotations.add(key, Vector { }).iterator; -+ it = annotations.add(key, Vector { }).iterator; - auto& vector = it->value; - - // Coalesce this range into the previous one if possible -@@ -5147,7 +5166,7 @@ void WebViewImpl::setMarkedText(id string, NSRange selectedRange, NSRange replac - - Vector underlines; - Vector highlights; -- HashMap> annotations; -+ HashMap> annotations; - NSString *text; - - if (isAttributedString) { -@@ -6011,7 +6030,7 @@ void WebViewImpl::updateMediaPlaybackControlsManager() - [m_playbackControlsManager setCanTogglePictureInPicture:NO]; - } - -- if (PlatformPlaybackSessionInterface* interface = m_page->playbackSessionManager()->controlsManagerInterface()) { -+ if (WebCore::PlatformPlaybackSessionInterface* interface = m_page->playbackSessionManager()->controlsManagerInterface()) { - [m_playbackControlsManager setPlaybackSessionInterfaceMac:interface]; - interface->updatePlaybackControlsManagerCanTogglePictureInPicture(); - } diff --git a/Source/WebKit/UIProcess/win/InspectorPlaywrightAgentClientWin.cpp b/Source/WebKit/UIProcess/win/InspectorPlaywrightAgentClientWin.cpp new file mode 100644 index 0000000000000000000000000000000000000000..dd7fe0604188bb025f361f1c44685e38bbf935ca @@ -20469,10 +20419,10 @@ index 0000000000000000000000000000000000000000..a7d88f8c745f95af21db71dcfce368ba + +} // namespace WebKit diff --git a/Source/WebKit/WebKit.xcodeproj/project.pbxproj b/Source/WebKit/WebKit.xcodeproj/project.pbxproj -index 4d2b0f95ee9155c2609dde955b557a9ab470ff24..b577c3453e36f94e9b7919db453b7852cf7f5457 100644 +index 3a3aaeb4f7cdef3eb62623c7c47442dfc0a01a73..bc1c7a054afa8db531f617c8db1a6ad170621743 100644 --- a/Source/WebKit/WebKit.xcodeproj/project.pbxproj +++ b/Source/WebKit/WebKit.xcodeproj/project.pbxproj -@@ -1350,6 +1350,7 @@ +@@ -1384,6 +1384,7 @@ 5CABDC8722C40FED001EDE8E /* APIMessageListener.h in Headers */ = {isa = PBXBuildFile; fileRef = 5CABDC8322C40FA7001EDE8E /* APIMessageListener.h */; }; 5CADDE05215046BD0067D309 /* WKWebProcess.h in Headers */ = {isa = PBXBuildFile; fileRef = 5C74300E21500492004BFA17 /* WKWebProcess.h */; settings = {ATTRIBUTES = (Private, ); }; }; 5CAECB6627465AE400AB78D0 /* UnifiedSource115.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5CAECB5E27465AE300AB78D0 /* UnifiedSource115.cpp */; }; @@ -20480,7 +20430,7 @@ index 4d2b0f95ee9155c2609dde955b557a9ab470ff24..b577c3453e36f94e9b7919db453b7852 5CAF7AA726F93AB00003F19E /* adattributiond.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5CAF7AA526F93A950003F19E /* adattributiond.cpp */; }; 5CAFDE452130846300B1F7E1 /* _WKInspector.h in Headers */ = {isa = PBXBuildFile; fileRef = 5CAFDE422130843500B1F7E1 /* _WKInspector.h */; settings = {ATTRIBUTES = (Private, ); }; }; 5CAFDE472130846A00B1F7E1 /* _WKInspectorInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 5CAFDE442130843600B1F7E1 /* _WKInspectorInternal.h */; }; -@@ -2109,6 +2110,18 @@ +@@ -2162,6 +2163,18 @@ DF0C5F28252ECB8E00D921DB /* WKDownload.h in Headers */ = {isa = PBXBuildFile; fileRef = DF0C5F24252ECB8D00D921DB /* WKDownload.h */; settings = {ATTRIBUTES = (Public, ); }; }; DF0C5F2A252ECB8E00D921DB /* WKDownloadDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = DF0C5F26252ECB8E00D921DB /* WKDownloadDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; DF0C5F2B252ED44000D921DB /* WKDownloadInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = DF0C5F25252ECB8E00D921DB /* WKDownloadInternal.h */; }; @@ -20499,7 +20449,7 @@ index 4d2b0f95ee9155c2609dde955b557a9ab470ff24..b577c3453e36f94e9b7919db453b7852 DF462E0F23F22F5500EFF35F /* WKHTTPCookieStorePrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = DF462E0E23F22F5300EFF35F /* WKHTTPCookieStorePrivate.h */; settings = {ATTRIBUTES = (Private, ); }; }; DF462E1223F338BE00EFF35F /* WKContentWorldPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = DF462E1123F338AD00EFF35F /* WKContentWorldPrivate.h */; settings = {ATTRIBUTES = (Private, ); }; }; DF7A231C291B088D00B98DF3 /* WKSnapshotConfigurationPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = DF7A231B291B088D00B98DF3 /* WKSnapshotConfigurationPrivate.h */; settings = {ATTRIBUTES = (Private, ); }; }; -@@ -2174,6 +2187,8 @@ +@@ -2227,6 +2240,8 @@ E5BEF6822130C48000F31111 /* WebDataListSuggestionsDropdownIOS.h in Headers */ = {isa = PBXBuildFile; fileRef = E5BEF6802130C47F00F31111 /* WebDataListSuggestionsDropdownIOS.h */; }; E5CB07DC20E1678F0022C183 /* WKFormColorControl.h in Headers */ = {isa = PBXBuildFile; fileRef = E5CB07DA20E1678F0022C183 /* WKFormColorControl.h */; }; E5CBA76427A318E100DF7858 /* UnifiedSource120.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E5CBA75F27A3187800DF7858 /* UnifiedSource120.cpp */; }; @@ -20508,7 +20458,7 @@ index 4d2b0f95ee9155c2609dde955b557a9ab470ff24..b577c3453e36f94e9b7919db453b7852 E5CBA76527A318E100DF7858 /* UnifiedSource118.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E5CBA76127A3187900DF7858 /* UnifiedSource118.cpp */; }; E5CBA76627A318E100DF7858 /* UnifiedSource116.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E5CBA76327A3187B00DF7858 /* UnifiedSource116.cpp */; }; E5CBA76727A318E100DF7858 /* UnifiedSource119.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E5CBA76027A3187900DF7858 /* UnifiedSource119.cpp */; }; -@@ -2192,6 +2207,9 @@ +@@ -2245,6 +2260,9 @@ EBA8D3B627A5E33F00CB7900 /* MockPushServiceConnection.mm in Sources */ = {isa = PBXBuildFile; fileRef = EBA8D3B027A5E33F00CB7900 /* MockPushServiceConnection.mm */; }; EBA8D3B727A5E33F00CB7900 /* PushServiceConnection.mm in Sources */ = {isa = PBXBuildFile; fileRef = EBA8D3B127A5E33F00CB7900 /* PushServiceConnection.mm */; }; ED82A7F2128C6FAF004477B3 /* WKBundlePageOverlay.h in Headers */ = {isa = PBXBuildFile; fileRef = 1A22F0FF1289FCD90085E74F /* WKBundlePageOverlay.h */; settings = {ATTRIBUTES = (Private, ); }; }; @@ -20516,9 +20466,9 @@ index 4d2b0f95ee9155c2609dde955b557a9ab470ff24..b577c3453e36f94e9b7919db453b7852 + F33C7AC7249AD79C0018BE41 /* libwebrtc.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = F33C7AC6249AD79C0018BE41 /* libwebrtc.dylib */; }; + F3867F0A24607D4E008F0F31 /* InspectorScreencastAgent.h in Headers */ = {isa = PBXBuildFile; fileRef = F3867F0424607D2B008F0F31 /* InspectorScreencastAgent.h */; }; F409BA181E6E64BC009DA28E /* WKDragDestinationAction.h in Headers */ = {isa = PBXBuildFile; fileRef = F409BA171E6E64B3009DA28E /* WKDragDestinationAction.h */; settings = {ATTRIBUTES = (Private, ); }; }; - F4299507270E234D0032298B /* StreamMessageReceiver.h in Headers */ = {isa = PBXBuildFile; fileRef = F4299506270E234C0032298B /* StreamMessageReceiver.h */; }; - F42D634122A0EFDF00D2FB3A /* WebAutocorrectionData.h in Headers */ = {isa = PBXBuildFile; fileRef = F42D633F22A0EFD300D2FB3A /* WebAutocorrectionData.h */; }; -@@ -5459,6 +5477,7 @@ + F40C3B712AB401C5007A3567 /* WKDatePickerPopoverController.h in Headers */ = {isa = PBXBuildFile; fileRef = F40C3B6F2AB40167007A3567 /* WKDatePickerPopoverController.h */; }; + F41795A62AC61B78007F5F12 /* CompactContextMenuPresenter.h in Headers */ = {isa = PBXBuildFile; fileRef = F41795A42AC619A2007F5F12 /* CompactContextMenuPresenter.h */; }; +@@ -5641,6 +5659,7 @@ 5CABDC8522C40FCC001EDE8E /* WKMessageListener.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WKMessageListener.h; sourceTree = ""; }; 5CABE07A28F60E8A00D83FD9 /* WebPushMessage.serialization.in */ = {isa = PBXFileReference; lastKnownFileType = text; path = WebPushMessage.serialization.in; sourceTree = ""; }; 5CADDE0D2151AA010067D309 /* AuthenticationChallengeDisposition.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AuthenticationChallengeDisposition.h; sourceTree = ""; }; @@ -20526,7 +20476,7 @@ index 4d2b0f95ee9155c2609dde955b557a9ab470ff24..b577c3453e36f94e9b7919db453b7852 5CAECB5E27465AE300AB78D0 /* UnifiedSource115.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = UnifiedSource115.cpp; sourceTree = ""; }; 5CAF7AA426F93A750003F19E /* adattributiond */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = adattributiond; sourceTree = BUILT_PRODUCTS_DIR; }; 5CAF7AA526F93A950003F19E /* adattributiond.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = adattributiond.cpp; sourceTree = ""; }; -@@ -7058,6 +7077,19 @@ +@@ -7255,6 +7274,19 @@ DF0C5F24252ECB8D00D921DB /* WKDownload.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WKDownload.h; sourceTree = ""; }; DF0C5F25252ECB8E00D921DB /* WKDownloadInternal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WKDownloadInternal.h; sourceTree = ""; }; DF0C5F26252ECB8E00D921DB /* WKDownloadDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WKDownloadDelegate.h; sourceTree = ""; }; @@ -20546,7 +20496,7 @@ index 4d2b0f95ee9155c2609dde955b557a9ab470ff24..b577c3453e36f94e9b7919db453b7852 DF462E0E23F22F5300EFF35F /* WKHTTPCookieStorePrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WKHTTPCookieStorePrivate.h; sourceTree = ""; }; DF462E1123F338AD00EFF35F /* WKContentWorldPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WKContentWorldPrivate.h; sourceTree = ""; }; DF58C6311371AC5800F9A37C /* NativeWebWheelEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NativeWebWheelEvent.h; sourceTree = ""; }; -@@ -7193,6 +7225,8 @@ +@@ -7390,6 +7422,8 @@ E5CBA76127A3187900DF7858 /* UnifiedSource118.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = UnifiedSource118.cpp; sourceTree = ""; }; E5CBA76227A3187900DF7858 /* UnifiedSource117.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = UnifiedSource117.cpp; sourceTree = ""; }; E5CBA76327A3187B00DF7858 /* UnifiedSource116.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = UnifiedSource116.cpp; sourceTree = ""; }; @@ -20555,7 +20505,7 @@ index 4d2b0f95ee9155c2609dde955b557a9ab470ff24..b577c3453e36f94e9b7919db453b7852 E5DEFA6726F8F42600AB68DB /* PhotosUISPI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PhotosUISPI.h; sourceTree = ""; }; EB0D312D275AE13300863D8F /* com.apple.webkit.webpushd.mac.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = com.apple.webkit.webpushd.mac.plist; sourceTree = ""; }; EB0D312E275AE13300863D8F /* com.apple.webkit.webpushd.ios.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = com.apple.webkit.webpushd.ios.plist; sourceTree = ""; }; -@@ -7216,6 +7250,14 @@ +@@ -7412,6 +7446,14 @@ ECA680D31E6904B500731D20 /* ExtraPrivateSymbolsForTAPI.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ExtraPrivateSymbolsForTAPI.h; sourceTree = ""; }; ECBFC1DB1E6A4D66000300C7 /* ExtraPublicSymbolsForTAPI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ExtraPublicSymbolsForTAPI.h; sourceTree = ""; }; F036978715F4BF0500C3A80E /* WebColorPicker.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = WebColorPicker.cpp; sourceTree = ""; }; @@ -20568,9 +20518,9 @@ index 4d2b0f95ee9155c2609dde955b557a9ab470ff24..b577c3453e36f94e9b7919db453b7852 + F3867F0424607D2B008F0F31 /* InspectorScreencastAgent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = InspectorScreencastAgent.h; sourceTree = ""; }; + F3970344249BD4CE003E1A22 /* ScreencastEncoderMac.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ScreencastEncoderMac.mm; sourceTree = ""; }; F409BA171E6E64B3009DA28E /* WKDragDestinationAction.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WKDragDestinationAction.h; sourceTree = ""; }; - F40D1B68220BDC0F00B49A01 /* WebAutocorrectionContext.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = WebAutocorrectionContext.h; path = ios/WebAutocorrectionContext.h; sourceTree = ""; }; - F41056612130699A0092281D /* APIAttachmentCocoa.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = APIAttachmentCocoa.mm; sourceTree = ""; }; -@@ -7390,6 +7432,7 @@ + F40C3B6F2AB40167007A3567 /* WKDatePickerPopoverController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = WKDatePickerPopoverController.h; path = ios/forms/WKDatePickerPopoverController.h; sourceTree = ""; }; + F40C3B702AB40167007A3567 /* WKDatePickerPopoverController.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = WKDatePickerPopoverController.mm; path = ios/forms/WKDatePickerPopoverController.mm; sourceTree = ""; }; +@@ -7653,6 +7695,7 @@ 3766F9EE189A1241003CF19B /* JavaScriptCore.framework in Frameworks */, 3766F9F1189A1254003CF19B /* libicucore.dylib in Frameworks */, 7B9FC5BB28A5233B007570E7 /* libWebKitPlatform.a in Frameworks */, @@ -20578,7 +20528,7 @@ index 4d2b0f95ee9155c2609dde955b557a9ab470ff24..b577c3453e36f94e9b7919db453b7852 3766F9EF189A1244003CF19B /* QuartzCore.framework in Frameworks */, 37694525184FC6B600CDE21F /* Security.framework in Frameworks */, 37BEC4DD1948FC6A008B4286 /* WebCore.framework in Frameworks */, -@@ -9911,6 +9954,7 @@ +@@ -10221,6 +10264,7 @@ 99788ACA1F421DCA00C08000 /* _WKAutomationSessionConfiguration.mm */, 990D28A81C6404B000986977 /* _WKAutomationSessionDelegate.h */, 990D28AF1C65203900986977 /* _WKAutomationSessionInternal.h */, @@ -20586,7 +20536,7 @@ index 4d2b0f95ee9155c2609dde955b557a9ab470ff24..b577c3453e36f94e9b7919db453b7852 5C4609E222430E4C009943C2 /* _WKContentRuleListAction.h */, 5C4609E322430E4D009943C2 /* _WKContentRuleListAction.mm */, 5C4609E422430E4D009943C2 /* _WKContentRuleListActionInternal.h */, -@@ -11062,6 +11106,7 @@ +@@ -11382,6 +11426,7 @@ E34B110C27C46BC6006D2F2E /* libWebCoreTestShim.dylib */, E34B110F27C46D09006D2F2E /* libWebCoreTestSupport.dylib */, DDE992F4278D06D900F60D26 /* libWebKitAdditions.a */, @@ -20594,7 +20544,7 @@ index 4d2b0f95ee9155c2609dde955b557a9ab470ff24..b577c3453e36f94e9b7919db453b7852 57A9FF15252C6AEF006A2040 /* libWTF.a */, 5750F32A2032D4E500389347 /* LocalAuthentication.framework */, 570DAAB0230273D200E8FC04 /* NearField.framework */, -@@ -11601,6 +11646,12 @@ +@@ -11941,6 +11986,12 @@ children = ( 9197940423DBC4BB00257892 /* InspectorBrowserAgent.cpp */, 9197940323DBC4BB00257892 /* InspectorBrowserAgent.h */, @@ -20607,7 +20557,7 @@ index 4d2b0f95ee9155c2609dde955b557a9ab470ff24..b577c3453e36f94e9b7919db453b7852 ); path = Agents; sourceTree = ""; -@@ -11609,6 +11660,7 @@ +@@ -11949,6 +12000,7 @@ isa = PBXGroup; children = ( A5D3504D1D78F0D2005124A9 /* RemoteWebInspectorUIProxyMac.mm */, @@ -20615,15 +20565,15 @@ index 4d2b0f95ee9155c2609dde955b557a9ab470ff24..b577c3453e36f94e9b7919db453b7852 1CA8B935127C774E00576C2B /* WebInspectorUIProxyMac.mm */, 99A7ACE326012919006D57FD /* WKInspectorResourceURLSchemeHandler.h */, 99A7ACE42601291A006D57FD /* WKInspectorResourceURLSchemeHandler.mm */, -@@ -12214,6 +12266,7 @@ +@@ -12569,6 +12621,7 @@ E1513C65166EABB200149FCB /* AuxiliaryProcessProxy.h */, 46A2B6061E5675A200C3DEDA /* BackgroundProcessResponsivenessTimer.cpp */, 46A2B6071E5675A200C3DEDA /* BackgroundProcessResponsivenessTimer.h */, + D71A944B237239FB002C4D9E /* BrowserInspectorPipe.h */, + 5C6D69352AC3935D0099BDAF /* BrowsingContextGroup.cpp */, + 5C6D69362AC3935D0099BDAF /* BrowsingContextGroup.h */, 07297F9C1C1711EA003F0735 /* DeviceIdHashSaltStorage.cpp */, - 07297F9D1C17BBEA223F0735 /* DeviceIdHashSaltStorage.h */, - BC2652121182608100243E12 /* DrawingAreaProxy.cpp */, -@@ -12229,6 +12282,8 @@ +@@ -12590,6 +12643,8 @@ 2DD5A72A1EBF09A7009BA597 /* HiddenPageThrottlingAutoIncreasesCounter.h */, 839A2F2F1E2067390039057E /* HighPerformanceGraphicsUsageSampler.cpp */, 839A2F301E2067390039057E /* HighPerformanceGraphicsUsageSampler.h */, @@ -20632,7 +20582,7 @@ index 4d2b0f95ee9155c2609dde955b557a9ab470ff24..b577c3453e36f94e9b7919db453b7852 5CEABA2B2333251400797797 /* LegacyGlobalSettings.cpp */, 5CEABA2A2333247700797797 /* LegacyGlobalSettings.h */, 31607F3819627002009B87DA /* LegacySessionStateCoding.h */, -@@ -12263,6 +12318,7 @@ +@@ -12624,6 +12679,7 @@ 1A0C227D2451130A00ED614D /* QuickLookThumbnailingSoftLink.mm */, 1AEE57232409F142002005D6 /* QuickLookThumbnailLoader.h */, 1AEE57242409F142002005D6 /* QuickLookThumbnailLoader.mm */, @@ -20640,7 +20590,7 @@ index 4d2b0f95ee9155c2609dde955b557a9ab470ff24..b577c3453e36f94e9b7919db453b7852 5CCB54DC2A4FEA6A0005FAA8 /* RemotePageDrawingAreaProxy.cpp */, 5CCB54DB2A4FEA6A0005FAA8 /* RemotePageDrawingAreaProxy.h */, 5C907E9A294D507100B3402D /* RemotePageProxy.cpp */, -@@ -12368,6 +12424,8 @@ +@@ -12729,6 +12785,8 @@ BC7B6204129A0A6700D174A4 /* WebPageGroup.h */, 2D9EA3101A96D9EB002D2807 /* WebPageInjectedBundleClient.cpp */, 2D9EA30E1A96CBFF002D2807 /* WebPageInjectedBundleClient.h */, @@ -20649,7 +20599,7 @@ index 4d2b0f95ee9155c2609dde955b557a9ab470ff24..b577c3453e36f94e9b7919db453b7852 BC111B0B112F5E4F00337BAB /* WebPageProxy.cpp */, BC032DCB10F4389F0058C15A /* WebPageProxy.h */, BCBD38FA125BAB9A00D2C29F /* WebPageProxy.messages.in */, -@@ -12530,6 +12588,7 @@ +@@ -12891,6 +12949,7 @@ BC646C1911DD399F006455B0 /* WKBackForwardListItemRef.h */, BC646C1611DD399F006455B0 /* WKBackForwardListRef.cpp */, BC646C1711DD399F006455B0 /* WKBackForwardListRef.h */, @@ -20657,8 +20607,8 @@ index 4d2b0f95ee9155c2609dde955b557a9ab470ff24..b577c3453e36f94e9b7919db453b7852 BCB9E24A1120E15C00A137E0 /* WKContext.cpp */, BCB9E2491120E15C00A137E0 /* WKContext.h */, 1AE52F9319201F6B00A1FA37 /* WKContextConfigurationRef.cpp */, -@@ -13109,6 +13168,9 @@ - 0F49294628FF0F4B00AF8509 /* DisplayLinkProcessProxyClient.h */, +@@ -13465,6 +13524,9 @@ + 7AFA6F682A9F57C50055322A /* DisplayLinkMac.cpp */, 31ABA79C215AF9E000C90E31 /* HighPerformanceGPUManager.h */, 31ABA79D215AF9E000C90E31 /* HighPerformanceGPUManager.mm */, + D71A94302370E025002C4D9E /* InspectorPlaywrightAgentClientMac.h */, @@ -20667,7 +20617,7 @@ index 4d2b0f95ee9155c2609dde955b557a9ab470ff24..b577c3453e36f94e9b7919db453b7852 1AFDE65B1954E8D500C48FFA /* LegacySessionStateCoding.cpp */, 0FCB4E5818BBE3D9000FCFC9 /* PageClientImplMac.h */, 0FCB4E5918BBE3D9000FCFC9 /* PageClientImplMac.mm */, -@@ -13132,6 +13194,8 @@ +@@ -13488,6 +13550,8 @@ E568B92120A3AC6A00E3C856 /* WebDataListSuggestionsDropdownMac.mm */, E55CD20124D09F1F0042DB9C /* WebDateTimePickerMac.h */, E55CD20224D09F1F0042DB9C /* WebDateTimePickerMac.mm */, @@ -20676,7 +20626,7 @@ index 4d2b0f95ee9155c2609dde955b557a9ab470ff24..b577c3453e36f94e9b7919db453b7852 BC857E8512B71EBB00EDEB2E /* WebPageProxyMac.mm */, BC5750951268F3C6006F0F12 /* WebPopupMenuProxyMac.h */, BC5750961268F3C6006F0F12 /* WebPopupMenuProxyMac.mm */, -@@ -14055,6 +14119,7 @@ +@@ -14431,6 +14495,7 @@ 99788ACB1F421DDA00C08000 /* _WKAutomationSessionConfiguration.h in Headers */, 990D28AC1C6420CF00986977 /* _WKAutomationSessionDelegate.h in Headers */, 990D28B11C65208D00986977 /* _WKAutomationSessionInternal.h in Headers */, @@ -20684,7 +20634,7 @@ index 4d2b0f95ee9155c2609dde955b557a9ab470ff24..b577c3453e36f94e9b7919db453b7852 5C4609E7224317B4009943C2 /* _WKContentRuleListAction.h in Headers */, 5C4609E8224317BB009943C2 /* _WKContentRuleListActionInternal.h in Headers */, 1A5704F81BE01FF400874AF1 /* _WKContextMenuElementInfo.h in Headers */, -@@ -14330,6 +14395,7 @@ +@@ -14711,6 +14776,7 @@ E170876C16D6CA6900F99226 /* BlobRegistryProxy.h in Headers */, 4F601432155C5AA2001FBDE0 /* BlockingResponseMap.h in Headers */, 1A5705111BE410E600874AF1 /* BlockSPI.h in Headers */, @@ -20692,7 +20642,7 @@ index 4d2b0f95ee9155c2609dde955b557a9ab470ff24..b577c3453e36f94e9b7919db453b7852 BC3065FA1259344E00E71278 /* CacheModel.h in Headers */, 935BF7FC2936BF1A00B41326 /* CacheStorageCache.h in Headers */, 934CF817294B884C00304F7D /* CacheStorageDiskStore.h in Headers */, -@@ -14465,7 +14531,11 @@ +@@ -14846,7 +14912,11 @@ BC14DF77120B5B7900826C0C /* InjectedBundleScriptWorld.h in Headers */, CE550E152283752200D28791 /* InsertTextOptions.h in Headers */, 9197940523DBC4BB00257892 /* InspectorBrowserAgent.h in Headers */, @@ -20704,7 +20654,7 @@ index 4d2b0f95ee9155c2609dde955b557a9ab470ff24..b577c3453e36f94e9b7919db453b7852 A5E391FD2183C1F800C8FB31 /* InspectorTargetProxy.h in Headers */, C5BCE5DF1C50766A00CDE3FA /* InteractionInformationAtPosition.h in Headers */, 2D4D2C811DF60BF3002EB10C /* InteractionInformationRequest.h in Headers */, -@@ -14693,6 +14763,7 @@ +@@ -15078,6 +15148,7 @@ CDAC20CA23FC2F750021DEE3 /* RemoteCDMInstanceSession.h in Headers */, CDAC20C923FC2F750021DEE3 /* RemoteCDMInstanceSessionIdentifier.h in Headers */, F451C0FE2703B263002BA03B /* RemoteDisplayListRecorderProxy.h in Headers */, @@ -20712,7 +20662,7 @@ index 4d2b0f95ee9155c2609dde955b557a9ab470ff24..b577c3453e36f94e9b7919db453b7852 2D47B56D1810714E003A3AEE /* RemoteLayerBackingStore.h in Headers */, 2DDF731518E95060004F5A66 /* RemoteLayerBackingStoreCollection.h in Headers */, 1AB16AEA164B3A8800290D62 /* RemoteLayerTreeContext.h in Headers */, -@@ -14744,6 +14815,7 @@ +@@ -15129,6 +15200,7 @@ E1E552C516AE065F004ED653 /* SandboxInitializationParameters.h in Headers */, E36FF00327F36FBD004BE21A /* SandboxStateVariables.h in Headers */, 7BAB111025DD02B3008FC479 /* ScopedActiveMessageReceiveQueue.h in Headers */, @@ -20720,7 +20670,7 @@ index 4d2b0f95ee9155c2609dde955b557a9ab470ff24..b577c3453e36f94e9b7919db453b7852 E4D54D0421F1D72D007E3C36 /* ScrollingTreeFrameScrollingNodeRemoteIOS.h in Headers */, 0F931C1C18C5711900DBA7C3 /* ScrollingTreeOverflowScrollingNodeIOS.h in Headers */, 0F931C1C18C5711900DBB8D4 /* ScrollingTreeScrollingNodeDelegateIOS.h in Headers */, -@@ -15041,6 +15113,8 @@ +@@ -15439,6 +15511,8 @@ 939EF87029D112EE00F23AEE /* WebPageInlines.h in Headers */, 9197940823DBC4CB00257892 /* WebPageInspectorAgentBase.h in Headers */, A513F5402154A5D700662841 /* WebPageInspectorController.h in Headers */, @@ -20729,7 +20679,7 @@ index 4d2b0f95ee9155c2609dde955b557a9ab470ff24..b577c3453e36f94e9b7919db453b7852 A543E30C215C8A8D00279CD9 /* WebPageInspectorTarget.h in Headers */, A543E30D215C8A9000279CD9 /* WebPageInspectorTargetController.h in Headers */, A543E307215AD13700279CD9 /* WebPageInspectorTargetFrontendChannel.h in Headers */, -@@ -17095,6 +17169,8 @@ +@@ -17647,6 +17721,8 @@ 522F792928D50EBB0069B45B /* HidService.mm in Sources */, 2749F6442146561B008380BF /* InjectedBundleNodeHandle.cpp in Sources */, 2749F6452146561E008380BF /* InjectedBundleRangeHandle.cpp in Sources */, @@ -20738,7 +20688,7 @@ index 4d2b0f95ee9155c2609dde955b557a9ab470ff24..b577c3453e36f94e9b7919db453b7852 1C2B4D4B2A819D0D00C528A1 /* JSWebExtensionAPIAlarms.mm in Sources */, B6114A7F29394A1600380B1B /* JSWebExtensionAPIEvent.mm in Sources */, 1C5DC471290B33A20061EC62 /* JSWebExtensionAPIExtension.mm in Sources */, -@@ -17473,6 +17549,8 @@ +@@ -18036,6 +18112,8 @@ E3816B3D27E2463A005EAFC0 /* WebMockContentFilterManager.cpp in Sources */, 31BA924D148831260062EDB5 /* WebNotificationManagerMessageReceiver.cpp in Sources */, 2DF6FE52212E110900469030 /* WebPage.cpp in Sources */, @@ -20748,7 +20698,7 @@ index 4d2b0f95ee9155c2609dde955b557a9ab470ff24..b577c3453e36f94e9b7919db453b7852 BCBD3914125BB1A800D2C29F /* WebPageProxyMessageReceiver.cpp in Sources */, 7CE9CE101FA0767A000177DE /* WebPageUpdatePreferences.cpp in Sources */, diff --git a/Source/WebKit/WebProcess/Network/WebLoaderStrategy.cpp b/Source/WebKit/WebProcess/Network/WebLoaderStrategy.cpp -index 96ca9aa77753f7ca5684185cfe0c6c61b4fa778d..93a415117af807fa7048a7a6e2e602839176af3b 100644 +index dead79df15381da7b12e6195b3617dc470bb6209..325fe0755e3cb1c31e6e4bfad54a4e2b3ee8daaa 100644 --- a/Source/WebKit/WebProcess/Network/WebLoaderStrategy.cpp +++ b/Source/WebKit/WebProcess/Network/WebLoaderStrategy.cpp @@ -237,6 +237,11 @@ void WebLoaderStrategy::scheduleLoad(ResourceLoader& resourceLoader, CachedResou @@ -20877,10 +20827,10 @@ index 96ca9aa77753f7ca5684185cfe0c6c61b4fa778d..93a415117af807fa7048a7a6e2e60283 { WebProcess::singleton().ensureNetworkProcessConnection().connection().send(Messages::NetworkConnectionToWebProcess::SetCaptureExtraNetworkLoadMetricsEnabled(enabled), 0); diff --git a/Source/WebKit/WebProcess/Network/WebLoaderStrategy.h b/Source/WebKit/WebProcess/Network/WebLoaderStrategy.h -index 3a3e596fa167fadaf36cfafdf8fd91dd0d7c8a8e..a6a305585706f9a407375fd2e365c52bcb1b8a43 100644 +index 9c7b0d517fe931f248039a6084b517c224823fd4..bb74ca5c7479a84aeb83ebfe9eefe65ddcfc81cb 100644 --- a/Source/WebKit/WebProcess/Network/WebLoaderStrategy.h +++ b/Source/WebKit/WebProcess/Network/WebLoaderStrategy.h -@@ -41,6 +41,7 @@ struct FetchOptions; +@@ -42,6 +42,7 @@ struct FetchOptions; namespace WebKit { class NetworkProcessConnection; @@ -20888,7 +20838,7 @@ index 3a3e596fa167fadaf36cfafdf8fd91dd0d7c8a8e..a6a305585706f9a407375fd2e365c52b class WebFrame; class WebPage; class WebURLSchemeTaskProxy; -@@ -88,6 +89,9 @@ public: +@@ -89,6 +90,9 @@ public: bool isOnLine() const final; void addOnlineStateChangeListener(Function&&) final; void setOnLineState(bool); @@ -20898,7 +20848,7 @@ index 3a3e596fa167fadaf36cfafdf8fd91dd0d7c8a8e..a6a305585706f9a407375fd2e365c52b void setExistingNetworkResourceLoadIdentifierToResume(std::optional existingNetworkResourceLoadIdentifierToResume) { m_existingNetworkResourceLoadIdentifierToResume = existingNetworkResourceLoadIdentifierToResume; } -@@ -140,6 +144,7 @@ private: +@@ -141,6 +145,7 @@ private: Vector> m_onlineStateChangeListeners; std::optional m_existingNetworkResourceLoadIdentifierToResume; bool m_isOnLine { true }; @@ -20943,18 +20893,18 @@ index ee9c3c4f48c328daaa015e2122235e51349bd999..5b3a4d3e742147195e0ff9e88176759d auto permissionHandlers = m_requestsPerOrigin.take(securityOrigin); diff --git a/Source/WebKit/WebProcess/WebCoreSupport/WebChromeClient.cpp b/Source/WebKit/WebProcess/WebCoreSupport/WebChromeClient.cpp -index 888dc9b6f2edb6347901611bc9cd4622b4b0a08a..3bf71068c4a17cf8fb165f32cb39b6b324a407b2 100644 +index 9514de26d42a2fe4935c392213db2eb13d11ea2b..0300f932059a1b574e60397f4e510de2414dc7fa 100644 --- a/Source/WebKit/WebProcess/WebCoreSupport/WebChromeClient.cpp +++ b/Source/WebKit/WebProcess/WebCoreSupport/WebChromeClient.cpp -@@ -425,6 +425,8 @@ void WebChromeClient::setResizable(bool resizable) - - void WebChromeClient::addMessageToConsole(MessageSource source, MessageLevel level, const String& message, unsigned lineNumber, unsigned columnNumber, const String& sourceID) +@@ -437,6 +437,8 @@ void WebChromeClient::addMessageToConsole(MessageSource source, MessageLevel lev { -+ if (level == MessageLevel::Error) -+ m_page.send(Messages::WebPageProxy::LogToStderr(message)); // Notify the bundle client. - m_page.injectedBundleUIClient().willAddMessageToConsole(&m_page, source, level, message, lineNumber, columnNumber, sourceID); + auto page = protectedPage(); ++ if (level == MessageLevel::Error) ++ page->send(Messages::WebPageProxy::LogToStderr(message)); + page->injectedBundleUIClient().willAddMessageToConsole(page.ptr(), source, level, message, lineNumber, columnNumber, sourceID); } + diff --git a/Source/WebKit/WebProcess/WebCoreSupport/WebDragClient.cpp b/Source/WebKit/WebProcess/WebCoreSupport/WebDragClient.cpp index 87121e8b57e5ad7ef74857685f0db65e164a5bf8..580dca6ca0709a2d620d3999beb69856981a54e8 100644 --- a/Source/WebKit/WebProcess/WebCoreSupport/WebDragClient.cpp @@ -20983,10 +20933,10 @@ index 87121e8b57e5ad7ef74857685f0db65e164a5bf8..580dca6ca0709a2d620d3999beb69856 { } diff --git a/Source/WebKit/WebProcess/WebCoreSupport/WebLocalFrameLoaderClient.cpp b/Source/WebKit/WebProcess/WebCoreSupport/WebLocalFrameLoaderClient.cpp -index 38d6259743caae4f32be376a8f54194d80571885..8a221e35850842113070ebf8da570ba6cdcdea1b 100644 +index 9c27ea6ed43608c07cf17d8f76f10db802404ae0..a1cf6f75abd6f4dc05dbc0f0e3485c305809fd69 100644 --- a/Source/WebKit/WebProcess/WebCoreSupport/WebLocalFrameLoaderClient.cpp +++ b/Source/WebKit/WebProcess/WebCoreSupport/WebLocalFrameLoaderClient.cpp -@@ -1548,14 +1548,6 @@ void WebLocalFrameLoaderClient::transitionToCommittedForNewPage() +@@ -1540,14 +1540,6 @@ void WebLocalFrameLoaderClient::transitionToCommittedForNewPage() if (webPage->scrollPinningBehavior() != ScrollPinningBehavior::DoNotPin) view->setScrollPinningBehavior(webPage->scrollPinningBehavior()); @@ -21082,10 +21032,10 @@ index 0000000000000000000000000000000000000000..ba14bf43794ef03a4b090135631a8f7f +#endif // ENABLE(DRAG_SUPPORT) diff --git a/Source/WebKit/WebProcess/WebCoreSupport/wpe/WebDragClientWPE.cpp b/Source/WebKit/WebProcess/WebCoreSupport/wpe/WebDragClientWPE.cpp new file mode 100644 -index 0000000000000000000000000000000000000000..ddc4b5e736252135a63d5787065c20fa35a764a6 +index 0000000000000000000000000000000000000000..dd5a7c8d64fda2abb888cfad417f6325b7e7bf78 --- /dev/null +++ b/Source/WebKit/WebProcess/WebCoreSupport/wpe/WebDragClientWPE.cpp -@@ -0,0 +1,58 @@ +@@ -0,0 +1,59 @@ +/* + * Copyright (C) 2011 Igalia S.L. + * @@ -21118,6 +21068,7 @@ index 0000000000000000000000000000000000000000..ddc4b5e736252135a63d5787065c20fa + +#include "ArgumentCodersWPE.h" +#include "ShareableBitmap.h" ++#include "ShareableBitmapHandle.h" +#include "WebPage.h" +#include "WebPageProxyMessages.h" +#include @@ -21137,7 +21088,7 @@ index 0000000000000000000000000000000000000000..ddc4b5e736252135a63d5787065c20fa +{ + m_page->willStartDrag(); + -+ ShareableBitmapHandle handle; ++ std::optional handle; + m_page->send(Messages::WebPageProxy::StartDrag(dataTransfer.pasteboard().selectionData(), dataTransfer.sourceOperationMask(), WTFMove(handle), dataTransfer.dragLocation())); +} + @@ -21145,10 +21096,10 @@ index 0000000000000000000000000000000000000000..ddc4b5e736252135a63d5787065c20fa + +#endif // ENABLE(DRAG_SUPPORT) diff --git a/Source/WebKit/WebProcess/WebPage/CoordinatedGraphics/DrawingAreaCoordinatedGraphics.cpp b/Source/WebKit/WebProcess/WebPage/CoordinatedGraphics/DrawingAreaCoordinatedGraphics.cpp -index 703e35bdff1dd1ac1c603fb17ba161b23f258225..eb04eff245fbfab332b6b720feb7e04e43ec53e2 100644 +index 600e8fe1ae52b0c5b1ee1826dc858b1666d9cc07..3b4583e76102070e19b114c8e48bd90cb8339f5a 100644 --- a/Source/WebKit/WebProcess/WebPage/CoordinatedGraphics/DrawingAreaCoordinatedGraphics.cpp +++ b/Source/WebKit/WebProcess/WebPage/CoordinatedGraphics/DrawingAreaCoordinatedGraphics.cpp -@@ -39,6 +39,7 @@ +@@ -40,6 +40,7 @@ #include "WebPreferencesKeys.h" #include "WebProcess.h" #include @@ -21156,7 +21107,7 @@ index 703e35bdff1dd1ac1c603fb17ba161b23f258225..eb04eff245fbfab332b6b720feb7e04e #include #include #include -@@ -114,6 +115,16 @@ void DrawingAreaCoordinatedGraphics::scroll(const IntRect& scrollRect, const Int +@@ -109,6 +110,16 @@ void DrawingAreaCoordinatedGraphics::scroll(const IntRect& scrollRect, const Int ASSERT(m_scrollRect.isEmpty()); ASSERT(m_scrollOffset.isEmpty()); ASSERT(m_dirtyRegion.isEmpty()); @@ -21173,7 +21124,7 @@ index 703e35bdff1dd1ac1c603fb17ba161b23f258225..eb04eff245fbfab332b6b720feb7e04e m_layerTreeHost->scrollNonCompositedContents(scrollRect); return; } -@@ -241,6 +252,7 @@ void DrawingAreaCoordinatedGraphics::updatePreferences(const WebPreferencesStore +@@ -236,6 +247,7 @@ void DrawingAreaCoordinatedGraphics::updatePreferences(const WebPreferencesStore settings.setAcceleratedCompositingEnabled(false); } #endif @@ -21181,7 +21132,7 @@ index 703e35bdff1dd1ac1c603fb17ba161b23f258225..eb04eff245fbfab332b6b720feb7e04e settings.setForceCompositingMode(store.getBoolValueForKey(WebPreferencesKey::forceCompositingModeKey())); // Fixed position elements need to be composited and create stacking contexts // in order to be scrolled by the ScrollingCoordinator. -@@ -618,6 +630,11 @@ void DrawingAreaCoordinatedGraphics::enterAcceleratedCompositingMode(GraphicsLay +@@ -635,6 +647,11 @@ void DrawingAreaCoordinatedGraphics::enterAcceleratedCompositingMode(GraphicsLay m_scrollOffset = IntSize(); m_displayTimer.stop(); m_isWaitingForDidUpdate = false; @@ -21193,7 +21144,7 @@ index 703e35bdff1dd1ac1c603fb17ba161b23f258225..eb04eff245fbfab332b6b720feb7e04e } void DrawingAreaCoordinatedGraphics::sendEnterAcceleratedCompositingModeIfNeeded() -@@ -677,6 +694,11 @@ void DrawingAreaCoordinatedGraphics::exitAcceleratedCompositingMode() +@@ -696,6 +713,11 @@ void DrawingAreaCoordinatedGraphics::exitAcceleratedCompositingMode() // UI process, we still need to let it know about the new contents, so send an Update message. send(Messages::DrawingAreaProxy::Update(0, WTFMove(updateInfo))); } @@ -21206,10 +21157,10 @@ index 703e35bdff1dd1ac1c603fb17ba161b23f258225..eb04eff245fbfab332b6b720feb7e04e void DrawingAreaCoordinatedGraphics::scheduleDisplay() diff --git a/Source/WebKit/WebProcess/WebPage/CoordinatedGraphics/LayerTreeHost.cpp b/Source/WebKit/WebProcess/WebPage/CoordinatedGraphics/LayerTreeHost.cpp -index 063deca3282a25c46dda53769673aeab501390e1..e6c462d6b21340bd7992e168e09f5fdbcea34c88 100644 +index 0b5c370117f2afbbd17132462c4a67738f9751ff..192a19e8d4da8f6b11135f06e147d2a6f5abcebc 100644 --- a/Source/WebKit/WebProcess/WebPage/CoordinatedGraphics/LayerTreeHost.cpp +++ b/Source/WebKit/WebProcess/WebPage/CoordinatedGraphics/LayerTreeHost.cpp -@@ -187,8 +187,16 @@ void LayerTreeHost::setViewOverlayRootLayer(GraphicsLayer* viewOverlayRootLayer) +@@ -209,8 +209,16 @@ void LayerTreeHost::setViewOverlayRootLayer(GraphicsLayer* viewOverlayRootLayer) void LayerTreeHost::scrollNonCompositedContents(const IntRect& rect) { auto* frameView = m_webPage.localMainFrameView(); @@ -21226,7 +21177,7 @@ index 063deca3282a25c46dda53769673aeab501390e1..e6c462d6b21340bd7992e168e09f5fdb m_viewportController.didScroll(rect.location()); if (m_isDiscardable) -@@ -325,6 +333,10 @@ void LayerTreeHost::didChangeViewport() +@@ -349,6 +357,10 @@ void LayerTreeHost::didChangeViewport() if (!view->useFixedLayout()) view->notifyScrollPositionChanged(m_lastScrollPosition); @@ -21238,10 +21189,10 @@ index 063deca3282a25c46dda53769673aeab501390e1..e6c462d6b21340bd7992e168e09f5fdb if (m_lastPageScaleFactor != pageScale) { diff --git a/Source/WebKit/WebProcess/WebPage/CoordinatedGraphics/LayerTreeHost.h b/Source/WebKit/WebProcess/WebPage/CoordinatedGraphics/LayerTreeHost.h -index 6749e66ac5f8cc7968595365beff19cfdee7861f..7a222444342eb1255bd2744d6647993ad3345e5c 100644 +index 507dd0fcac6cf81a355532cc37004d161f03e8a3..a2b2690120f6f8f01f8ce2cbc2a2d7e01b1637fe 100644 --- a/Source/WebKit/WebProcess/WebPage/CoordinatedGraphics/LayerTreeHost.h +++ b/Source/WebKit/WebProcess/WebPage/CoordinatedGraphics/LayerTreeHost.h -@@ -103,6 +103,13 @@ public: +@@ -116,6 +116,13 @@ public: void adjustTransientZoom(double, WebCore::FloatPoint); void commitTransientZoom(double, WebCore::FloatPoint); #endif @@ -21282,12 +21233,12 @@ index 30c1f55828df6bf4e48543cc3347dde1ee41e10f..e71997ca8292530c5c01ce141443ad42 { if (m_hasRemovedMessageReceiver) diff --git a/Source/WebKit/WebProcess/WebPage/DrawingArea.h b/Source/WebKit/WebProcess/WebPage/DrawingArea.h -index 0a7b4194c982a5ce21c1f3e7da2a6c174bbf2954..6c8dc22966ed0d457622698f1c2f4079f183025f 100644 +index 4f80d0ea4ad74216239024a49b1d7f94d7a03371..8f8e6094eba5c8742aa3d6733de56f8dfea53a3e 100644 --- a/Source/WebKit/WebProcess/WebPage/DrawingArea.h +++ b/Source/WebKit/WebProcess/WebPage/DrawingArea.h -@@ -169,6 +169,9 @@ public: - virtual void didChangeViewportAttributes(WebCore::ViewportAttributes&&) = 0; +@@ -167,6 +167,9 @@ public: virtual void deviceOrPageScaleFactorChanged() = 0; + virtual bool enterAcceleratedCompositingModeIfNeeded() = 0; #endif +#if PLATFORM(WIN) + void didChangeAcceleratedCompositingMode(bool enabled); @@ -21296,10 +21247,10 @@ index 0a7b4194c982a5ce21c1f3e7da2a6c174bbf2954..6c8dc22966ed0d457622698f1c2f4079 virtual void adoptLayersFromDrawingArea(DrawingArea&) { } virtual void adoptDisplayRefreshMonitorsFromDrawingArea(DrawingArea&) { } diff --git a/Source/WebKit/WebProcess/WebPage/WebCookieJar.cpp b/Source/WebKit/WebProcess/WebPage/WebCookieJar.cpp -index a920f9ced33557bc3aac077f7c81574f77ced97b..0badffab778609cf5636594caeac4c03e3a446d1 100644 +index 40b7ce31237d9de6a149c1d685c03634e44f8de6..85527188ef7b085d16a526fbed249a17d5c48230 100644 --- a/Source/WebKit/WebProcess/WebPage/WebCookieJar.cpp +++ b/Source/WebKit/WebProcess/WebPage/WebCookieJar.cpp -@@ -41,6 +41,7 @@ +@@ -43,6 +43,7 @@ #include #include #include @@ -21307,7 +21258,7 @@ index a920f9ced33557bc3aac077f7c81574f77ced97b..0badffab778609cf5636594caeac4c03 #include #include #include -@@ -349,4 +350,10 @@ void WebCookieJar::removeChangeListener(const String& host, const WebCore::Cooki +@@ -353,4 +354,10 @@ void WebCookieJar::removeChangeListener(const String& host, const WebCore::Cooki } #endif @@ -21332,7 +21283,7 @@ index 2aebbbd13594712f8cb3cd1de16da6818bbf4706..125d77abfafe41c2a49568f02a7cde6e WebCookieJar(); diff --git a/Source/WebKit/WebProcess/WebPage/WebDocumentLoader.cpp b/Source/WebKit/WebProcess/WebPage/WebDocumentLoader.cpp -index 7fbf65c821f3c4fddea9ca7dc88f5968fdd7a744..ebd73ea59effe391210552c3d5e0e769043e9dc0 100644 +index 005edbd861f90edeb6188ad3a066472469f1d694..2a89e26092a08eaadec35b6ee640484fa989eef2 100644 --- a/Source/WebKit/WebProcess/WebPage/WebDocumentLoader.cpp +++ b/Source/WebKit/WebProcess/WebPage/WebDocumentLoader.cpp @@ -46,6 +46,14 @@ void WebDocumentLoader::detachFromFrame() @@ -21374,10 +21325,10 @@ index 04efd703491f1c32009c066a84647d7dda3d79ce..473fa1b3a7b7ccf375bfe26f528417d4 uint64_t m_navigationID { 0 }; }; diff --git a/Source/WebKit/WebProcess/WebPage/WebPage.cpp b/Source/WebKit/WebProcess/WebPage/WebPage.cpp -index df07dd55ff15dfd26d47b6aa6bc768256cb01038..b9f174a31f93b938a34c13bb35f43a101d65ebc9 100644 +index c724f61d2449b195326cacf8119e9da8133d8772..567a4b06bf858e78a1ade2a17e76dce3b1af615c 100644 --- a/Source/WebKit/WebProcess/WebPage/WebPage.cpp +++ b/Source/WebKit/WebProcess/WebPage/WebPage.cpp -@@ -1039,6 +1039,9 @@ WebPage::WebPage(PageIdentifier pageID, WebPageCreationParameters&& parameters) +@@ -1046,6 +1046,9 @@ WebPage::WebPage(PageIdentifier pageID, WebPageCreationParameters&& parameters) #endif #endif // HAVE(SANDBOX_STATE_FLAGS) @@ -21387,7 +21338,7 @@ index df07dd55ff15dfd26d47b6aa6bc768256cb01038..b9f174a31f93b938a34c13bb35f43a10 updateThrottleState(); #if ENABLE(ACCESSIBILITY_ANIMATION_CONTROL) updateImageAnimationEnabled(); -@@ -1884,6 +1887,22 @@ void WebPage::transitionFrameToLocal(LocalFrameCreationParameters&& creationPara +@@ -1896,6 +1899,22 @@ void WebPage::transitionFrameToLocal(LocalFrameCreationParameters&& creationPara frame->transitionToLocal(creationParameters.layerHostingContextIdentifier); } @@ -21410,7 +21361,7 @@ index df07dd55ff15dfd26d47b6aa6bc768256cb01038..b9f174a31f93b938a34c13bb35f43a10 void WebPage::loadRequest(LoadParameters&& loadParameters) { WEBPAGE_RELEASE_LOG(Loading, "loadRequest: navigationID=%" PRIu64 ", shouldTreatAsContinuingLoad=%u, lastNavigationWasAppInitiated=%d, existingNetworkResourceLoadIdentifierToResume=%" PRIu64, loadParameters.navigationID, static_cast(loadParameters.shouldTreatAsContinuingLoad), loadParameters.request.isAppInitiated(), valueOrDefault(loadParameters.existingNetworkResourceLoadIdentifierToResume).toUInt64()); -@@ -2168,17 +2187,14 @@ void WebPage::setSize(const WebCore::IntSize& viewSize) +@@ -2181,17 +2200,14 @@ void WebPage::setSize(const WebCore::IntSize& viewSize) view->resize(viewSize); m_drawingArea->setNeedsDisplay(); @@ -21428,7 +21379,7 @@ index df07dd55ff15dfd26d47b6aa6bc768256cb01038..b9f174a31f93b938a34c13bb35f43a10 void WebPage::sendViewportAttributesChanged(const ViewportArguments& viewportArguments) { RefPtr localMainFrame = dynamicDowncast(m_page->mainFrame()); -@@ -2203,20 +2219,18 @@ void WebPage::sendViewportAttributesChanged(const ViewportArguments& viewportArg +@@ -2216,20 +2232,18 @@ void WebPage::sendViewportAttributesChanged(const ViewportArguments& viewportArg ViewportAttributes attr = computeViewportAttributes(viewportArguments, minimumLayoutFallbackWidth, deviceWidth, deviceHeight, 1, m_viewSize); @@ -21456,7 +21407,7 @@ index df07dd55ff15dfd26d47b6aa6bc768256cb01038..b9f174a31f93b938a34c13bb35f43a10 #if USE(COORDINATED_GRAPHICS) m_drawingArea->didChangeViewportAttributes(WTFMove(attr)); -@@ -2224,7 +2238,6 @@ void WebPage::sendViewportAttributesChanged(const ViewportArguments& viewportArg +@@ -2237,7 +2251,6 @@ void WebPage::sendViewportAttributesChanged(const ViewportArguments& viewportArg send(Messages::WebPageProxy::DidChangeViewportProperties(attr)); #endif } @@ -21464,7 +21415,7 @@ index df07dd55ff15dfd26d47b6aa6bc768256cb01038..b9f174a31f93b938a34c13bb35f43a10 void WebPage::scrollMainFrameIfNotAtMaxScrollPosition(const IntSize& scrollOffset) { -@@ -2520,6 +2533,7 @@ void WebPage::scaleView(double scale) +@@ -2533,6 +2546,7 @@ void WebPage::scaleView(double scale) } m_page->setViewScaleFactor(scale); @@ -21472,7 +21423,7 @@ index df07dd55ff15dfd26d47b6aa6bc768256cb01038..b9f174a31f93b938a34c13bb35f43a10 scalePage(pageScale, scrollPositionAtNewScale); } -@@ -2699,18 +2713,14 @@ void WebPage::viewportPropertiesDidChange(const ViewportArguments& viewportArgum +@@ -2712,18 +2726,14 @@ void WebPage::viewportPropertiesDidChange(const ViewportArguments& viewportArgum viewportConfigurationChanged(); #endif @@ -21492,9 +21443,9 @@ index df07dd55ff15dfd26d47b6aa6bc768256cb01038..b9f174a31f93b938a34c13bb35f43a10 } #if !PLATFORM(IOS_FAMILY) -@@ -3671,6 +3681,97 @@ void WebPage::touchEvent(const WebTouchEvent& touchEvent) +@@ -3687,6 +3697,97 @@ void WebPage::touchEvent(const WebTouchEvent& touchEvent, CompletionHandler&& completionHandler) @@ -21554,7 +21505,7 @@ index df07dd55ff15dfd26d47b6aa6bc768256cb01038..b9f174a31f93b938a34c13bb35f43a10 + localMainFrame->eventHandler().mouseMoved(PlatformMouseEvent( + adjustedIntPoint, + adjustedIntPoint, -+ MouseButton::NoButton, ++ MouseButton::None, + PlatformEvent::Type::MouseMoved, + 0, + modifiers, @@ -21565,7 +21516,7 @@ index df07dd55ff15dfd26d47b6aa6bc768256cb01038..b9f174a31f93b938a34c13bb35f43a10 + localMainFrame->eventHandler().handleMousePressEvent(PlatformMouseEvent( + adjustedIntPoint, + adjustedIntPoint, -+ MouseButton::LeftButton, ++ MouseButton::Left, + PlatformEvent::Type::MousePressed, + 1, + modifiers, @@ -21576,7 +21527,7 @@ index df07dd55ff15dfd26d47b6aa6bc768256cb01038..b9f174a31f93b938a34c13bb35f43a10 + localMainFrame->eventHandler().handleMouseReleaseEvent(PlatformMouseEvent( + adjustedIntPoint, + adjustedIntPoint, -+ MouseButton::LeftButton, ++ MouseButton::Left, + PlatformEvent::Type::MouseReleased, + 1, + modifiers, @@ -21590,7 +21541,7 @@ index df07dd55ff15dfd26d47b6aa6bc768256cb01038..b9f174a31f93b938a34c13bb35f43a10 #endif void WebPage::cancelPointer(WebCore::PointerID pointerId, const WebCore::IntPoint& documentPoint) -@@ -3748,6 +3849,11 @@ void WebPage::sendMessageToTargetBackend(const String& targetId, const String& m +@@ -3764,6 +3865,11 @@ void WebPage::sendMessageToTargetBackend(const String& targetId, const String& m m_inspectorTargetController->sendMessageToTargetBackend(targetId, message); } @@ -21602,7 +21553,7 @@ index df07dd55ff15dfd26d47b6aa6bc768256cb01038..b9f174a31f93b938a34c13bb35f43a10 void WebPage::insertNewlineInQuotedContent() { Ref frame = CheckedRef(m_page->focusController())->focusedOrMainFrame(); -@@ -3959,6 +4065,7 @@ void WebPage::didCompletePageTransition() +@@ -3975,6 +4081,7 @@ void WebPage::didCompletePageTransition() void WebPage::show() { send(Messages::WebPageProxy::ShowPage()); @@ -21610,7 +21561,7 @@ index df07dd55ff15dfd26d47b6aa6bc768256cb01038..b9f174a31f93b938a34c13bb35f43a10 } void WebPage::setIsTakingSnapshotsForApplicationSuspension(bool isTakingSnapshotsForApplicationSuspension) -@@ -4964,7 +5071,7 @@ NotificationPermissionRequestManager* WebPage::notificationPermissionRequestMana +@@ -4999,7 +5106,7 @@ NotificationPermissionRequestManager* WebPage::notificationPermissionRequestMana #if ENABLE(DRAG_SUPPORT) @@ -21619,7 +21570,18 @@ index df07dd55ff15dfd26d47b6aa6bc768256cb01038..b9f174a31f93b938a34c13bb35f43a10 void WebPage::performDragControllerAction(DragControllerAction action, const IntPoint& clientPosition, const IntPoint& globalPosition, OptionSet draggingSourceOperationMask, SelectionData&& selectionData, OptionSet flags) { if (!m_page) { -@@ -7449,6 +7556,9 @@ Ref WebPage::createDocumentLoader(LocalFrame& frame, const Resou +@@ -7254,6 +7361,10 @@ void WebPage::didCommitLoad(WebFrame* frame) + #endif + + flushDeferredDidReceiveMouseEvent(); ++// Playwright begin ++ if (frame->isMainFrame()) ++ send(Messages::WebPageProxy::ViewScaleFactorDidChange(viewScaleFactor())); ++// Playwright end + } + + void WebPage::didFinishDocumentLoad(WebFrame& frame) +@@ -7483,6 +7594,9 @@ Ref WebPage::createDocumentLoader(LocalFrame& frame, const Resou WebsitePoliciesData::applyToDocumentLoader(WTFMove(*m_pendingWebsitePolicies), documentLoader); m_pendingWebsitePolicies = std::nullopt; } @@ -21630,7 +21592,7 @@ index df07dd55ff15dfd26d47b6aa6bc768256cb01038..b9f174a31f93b938a34c13bb35f43a10 return documentLoader; diff --git a/Source/WebKit/WebProcess/WebPage/WebPage.h b/Source/WebKit/WebProcess/WebPage/WebPage.h -index 8953b900362dba8d3e83c8a61a87bc33d47c4c84..4df44385d11d65778da2090807153577b6e51bc2 100644 +index cdc6e9622d72353a1ecfeaa96d4ae38bbd05ce4d..d3f29a5abe0df32e0903d7e642426716c10bdc52 100644 --- a/Source/WebKit/WebProcess/WebPage/WebPage.h +++ b/Source/WebKit/WebProcess/WebPage/WebPage.h @@ -69,6 +69,7 @@ @@ -21652,7 +21614,7 @@ index 8953b900362dba8d3e83c8a61a87bc33d47c4c84..4df44385d11d65778da2090807153577 #if PLATFORM(GTK) || PLATFORM(WPE) #include "InputMethodState.h" #endif -@@ -1085,11 +1090,11 @@ public: +@@ -1088,11 +1093,11 @@ public: void clearSelection(); void restoreSelectionInFocusedEditableElement(); @@ -21666,7 +21628,7 @@ index 8953b900362dba8d3e83c8a61a87bc33d47c4c84..4df44385d11d65778da2090807153577 void performDragControllerAction(DragControllerAction, WebCore::DragData&&, SandboxExtension::Handle&&, Vector&&); #endif -@@ -1103,6 +1108,9 @@ public: +@@ -1106,6 +1111,9 @@ public: void didStartDrag(); void dragCancelled(); OptionSet allowedDragSourceActions() const { return m_allowedDragSourceActions; } @@ -21676,7 +21638,7 @@ index 8953b900362dba8d3e83c8a61a87bc33d47c4c84..4df44385d11d65778da2090807153577 #endif void beginPrinting(WebCore::FrameIdentifier, const PrintInfo&); -@@ -1337,6 +1345,7 @@ public: +@@ -1333,6 +1341,7 @@ public: void connectInspector(const String& targetId, Inspector::FrontendChannel::ConnectionType); void disconnectInspector(const String& targetId); void sendMessageToTargetBackend(const String& targetId, const String& message); @@ -21684,7 +21646,7 @@ index 8953b900362dba8d3e83c8a61a87bc33d47c4c84..4df44385d11d65778da2090807153577 void insertNewlineInQuotedContent(); -@@ -1766,6 +1775,7 @@ private: +@@ -1763,6 +1772,7 @@ private: void tryClose(CompletionHandler&&); void platformDidReceiveLoadParameters(const LoadParameters&); void transitionFrameToLocal(LocalFrameCreationParameters&&, WebCore::FrameIdentifier); @@ -21692,15 +21654,15 @@ index 8953b900362dba8d3e83c8a61a87bc33d47c4c84..4df44385d11d65778da2090807153577 void loadRequest(LoadParameters&&); [[noreturn]] void loadRequestWaitingForProcessLaunch(LoadParameters&&, URL&&, WebPageProxyIdentifier, bool); void loadData(LoadParameters&&); -@@ -1804,6 +1814,7 @@ private: +@@ -1801,6 +1811,7 @@ private: void updatePotentialTapSecurityOrigin(const WebTouchEvent&, bool wasHandled); #elif ENABLE(TOUCH_EVENTS) - void touchEvent(const WebTouchEvent&); + void touchEvent(const WebTouchEvent&, CompletionHandler, bool)>&&); + void fakeTouchTap(const WebCore::IntPoint& position, uint8_t modifiers, CompletionHandler&& completionHandler); #endif void cancelPointer(WebCore::PointerID, const WebCore::IntPoint&); -@@ -1943,9 +1954,7 @@ private: +@@ -1941,9 +1952,7 @@ private: void addLayerForFindOverlay(CompletionHandler&&); void removeLayerForFindOverlay(CompletionHandler&&); @@ -21710,7 +21672,7 @@ index 8953b900362dba8d3e83c8a61a87bc33d47c4c84..4df44385d11d65778da2090807153577 void didChangeSelectedIndexForActivePopupMenu(int32_t newIndex); void setTextForActivePopupMenu(int32_t index); -@@ -2493,6 +2502,7 @@ private: +@@ -2496,6 +2505,7 @@ private: UserActivity m_userActivity; uint64_t m_pendingNavigationID { 0 }; @@ -21719,10 +21681,10 @@ index 8953b900362dba8d3e83c8a61a87bc33d47c4c84..4df44385d11d65778da2090807153577 bool m_mainFrameProgressCompleted { false }; diff --git a/Source/WebKit/WebProcess/WebPage/WebPage.messages.in b/Source/WebKit/WebProcess/WebPage/WebPage.messages.in -index a5e984aa25e5ba55138267c09d481f859ce0525f..97a548233aea66f34fcb57e4eaffde208383d7b3 100644 +index 315803b14bb755f44f187d8a1dab4b278cdfc4ba..703834ddc9b3a8b8861406fcda1a5b2aabe1db40 100644 --- a/Source/WebKit/WebProcess/WebPage/WebPage.messages.in +++ b/Source/WebKit/WebProcess/WebPage/WebPage.messages.in -@@ -149,6 +149,7 @@ GenerateSyntheticEditingCommand(enum:uint8_t WebKit::SyntheticEditingCommandType +@@ -150,6 +150,7 @@ GenerateSyntheticEditingCommand(enum:uint8_t WebKit::SyntheticEditingCommandType ConnectInspector(String targetId, Inspector::FrontendChannel::ConnectionType connectionType) DisconnectInspector(String targetId) SendMessageToTargetBackend(String targetId, String message) @@ -21730,15 +21692,15 @@ index a5e984aa25e5ba55138267c09d481f859ce0525f..97a548233aea66f34fcb57e4eaffde20 #if ENABLE(REMOTE_INSPECTOR) SetIndicating(bool indicating); -@@ -159,6 +160,7 @@ GenerateSyntheticEditingCommand(enum:uint8_t WebKit::SyntheticEditingCommandType +@@ -160,6 +161,7 @@ GenerateSyntheticEditingCommand(enum:uint8_t WebKit::SyntheticEditingCommandType #endif #if !ENABLE(IOS_TOUCH_EVENTS) && ENABLE(TOUCH_EVENTS) - TouchEvent(WebKit::WebTouchEvent event) + TouchEvent(WebKit::WebTouchEvent event) -> (std::optional eventType, bool handled) + FakeTouchTap(WebCore::IntPoint position, uint8_t modifiers) -> () Async #endif CancelPointer(WebCore::PointerID pointerId, WebCore::IntPoint documentPoint) -@@ -190,6 +192,7 @@ GenerateSyntheticEditingCommand(enum:uint8_t WebKit::SyntheticEditingCommandType +@@ -191,6 +193,7 @@ GenerateSyntheticEditingCommand(enum:uint8_t WebKit::SyntheticEditingCommandType LoadDataInFrame(IPC::DataReference data, String MIMEType, String encodingName, URL baseURL, WebCore::FrameIdentifier frameID) LoadRequest(struct WebKit::LoadParameters loadParameters) TransitionFrameToLocal(struct WebKit::LocalFrameCreationParameters creationParameters, WebCore::FrameIdentifier frameID) @@ -21746,7 +21708,7 @@ index a5e984aa25e5ba55138267c09d481f859ce0525f..97a548233aea66f34fcb57e4eaffde20 LoadRequestWaitingForProcessLaunch(struct WebKit::LoadParameters loadParameters, URL resourceDirectoryURL, WebKit::WebPageProxyIdentifier pageID, bool checkAssumedReadAccessToResourceURL) LoadData(struct WebKit::LoadParameters loadParameters) LoadSimulatedRequestAndResponse(struct WebKit::LoadParameters loadParameters, WebCore::ResourceResponse simulatedResponse) -@@ -351,10 +354,10 @@ GenerateSyntheticEditingCommand(enum:uint8_t WebKit::SyntheticEditingCommandType +@@ -353,10 +356,10 @@ GenerateSyntheticEditingCommand(enum:uint8_t WebKit::SyntheticEditingCommandType AddMIMETypeWithCustomContentProvider(String mimeType) # Drag and drop. @@ -21759,7 +21721,7 @@ index a5e984aa25e5ba55138267c09d481f859ce0525f..97a548233aea66f34fcb57e4eaffde20 PerformDragControllerAction(enum:uint8_t WebKit::DragControllerAction action, WebCore::DragData dragData, WebKit::SandboxExtension::Handle sandboxExtensionHandle, Vector sandboxExtensionsForUpload) #endif #if ENABLE(DRAG_SUPPORT) -@@ -363,6 +366,10 @@ GenerateSyntheticEditingCommand(enum:uint8_t WebKit::SyntheticEditingCommandType +@@ -365,6 +368,10 @@ GenerateSyntheticEditingCommand(enum:uint8_t WebKit::SyntheticEditingCommandType DragCancelled() #endif @@ -21771,7 +21733,7 @@ index a5e984aa25e5ba55138267c09d481f859ce0525f..97a548233aea66f34fcb57e4eaffde20 RequestDragStart(WebCore::IntPoint clientPosition, WebCore::IntPoint globalPosition, OptionSet allowedActionsMask) RequestAdditionalItemsForDragSession(WebCore::IntPoint clientPosition, WebCore::IntPoint globalPosition, OptionSet allowedActionsMask) diff --git a/Source/WebKit/WebProcess/WebPage/mac/WebPageMac.mm b/Source/WebKit/WebProcess/WebPage/mac/WebPageMac.mm -index c751d39c0abb0640be6428cb7d943b9a7441d430..ad8d59e8ab375e45116620c2499064481528c234 100644 +index b757269682383ff9d9cd423ce6309e3340bddc0d..4fc702660c715fec8f678cdada99f18586b5d4e8 100644 --- a/Source/WebKit/WebProcess/WebPage/mac/WebPageMac.mm +++ b/Source/WebKit/WebProcess/WebPage/mac/WebPageMac.mm @@ -825,21 +825,37 @@ String WebPage::platformUserAgent(const URL&) const @@ -21863,7 +21825,7 @@ index f17f5d719d892309ed9c7093384945866b5117b9..1dba47bbf0dbd0362548423a74b38034 } diff --git a/Source/WebKit/WebProcess/WebProcess.cpp b/Source/WebKit/WebProcess/WebProcess.cpp -index 63302a57fe4dbf666bbf79333df6f3a046ea2005..acdf07c37d6fd1d9641c6f9ee59ceb5ef646d8be 100644 +index ede84d5c3f81e53f51b81dc308212a9081193545..48ee261dec83ba8296cc86f7703286164f39339e 100644 --- a/Source/WebKit/WebProcess/WebProcess.cpp +++ b/Source/WebKit/WebProcess/WebProcess.cpp @@ -94,6 +94,7 @@ @@ -21884,10 +21846,10 @@ index 63302a57fe4dbf666bbf79333df6f3a046ea2005..acdf07c37d6fd1d9641c6f9ee59ceb5e void WebProcess::initializeConnection(IPC::Connection* connection) diff --git a/Source/WebKit/WebProcess/WebProcess.h b/Source/WebKit/WebProcess/WebProcess.h -index 4d1c197aaa4d77e763674d6b02f5e35f5613dc1e..54d8ed73743dfd46aff1efaf35302d4202c54f94 100644 +index 5dbd3733234f4ee42fcf6f6a0e6181ee0e1b8a22..5b44ea308dbff133e98dc889b1f9434ab30ad61d 100644 --- a/Source/WebKit/WebProcess/WebProcess.h +++ b/Source/WebKit/WebProcess/WebProcess.h -@@ -745,7 +745,7 @@ private: +@@ -753,7 +753,7 @@ private: WeakHashMap m_userGestureTokens; @@ -21912,7 +21874,7 @@ index 8987c3964a9308f2454759de7f8972215a3ae416..bcac0afeb94ed8123d1f9fb0b932c849 SetProcessDPIAware(); return true; diff --git a/Source/WebKitLegacy/mac/WebView/WebHTMLView.mm b/Source/WebKitLegacy/mac/WebView/WebHTMLView.mm -index 22c698f09790446fb2c80400b55ba6b4ead49658..885104f89f84c7d82490c7c1ca646e74d55b4ee3 100644 +index 2a111c33c93e477538c5fca177bd1ae258322605..52e07e5a7ff9f0f6758bb613dd7e6057940540d4 100644 --- a/Source/WebKitLegacy/mac/WebView/WebHTMLView.mm +++ b/Source/WebKitLegacy/mac/WebView/WebHTMLView.mm @@ -4210,7 +4210,7 @@ ALLOW_DEPRECATED_DECLARATIONS_END @@ -21925,10 +21887,10 @@ index 22c698f09790446fb2c80400b55ba6b4ead49658..885104f89f84c7d82490c7c1ca646e74 - (void)touch:(WebEvent *)event { diff --git a/Source/WebKitLegacy/mac/WebView/WebView.mm b/Source/WebKitLegacy/mac/WebView/WebView.mm -index b2da18013c630a85d5739361c7e2a946bd50d36c..65884693855ccb25e4e0ca191e3b43055202931a 100644 +index a4e615892112df50314ee59680000e5e322ccd5a..8f0050e522e0cb081460fc11a145f66a80a81476 100644 --- a/Source/WebKitLegacy/mac/WebView/WebView.mm +++ b/Source/WebKitLegacy/mac/WebView/WebView.mm -@@ -3962,7 +3962,7 @@ + (void)_doNotStartObservingNetworkReachability +@@ -3963,7 +3963,7 @@ + (void)_doNotStartObservingNetworkReachability } #endif // PLATFORM(IOS_FAMILY) @@ -21937,7 +21899,7 @@ index b2da18013c630a85d5739361c7e2a946bd50d36c..65884693855ccb25e4e0ca191e3b4305 - (NSArray *)_touchEventRegions { -@@ -4004,7 +4004,7 @@ - (NSArray *)_touchEventRegions +@@ -4005,7 +4005,7 @@ - (NSArray *)_touchEventRegions }).autorelease(); } @@ -21978,7 +21940,7 @@ index 0000000000000000000000000000000000000000..dd6a53e2d57318489b7e49dd7373706d + LIBVPX_LIBRARIES +) diff --git a/Source/cmake/OptionsGTK.cmake b/Source/cmake/OptionsGTK.cmake -index ffd106fe4ad44ef1610c5341e7d16c18bdbd102e..73732f416b5f25a2c79816fb33e5526d9d6be976 100644 +index abad3a9cd4842a176e62225e06bbcf7d766f26f6..43d43416fb79848f2d629ca13248359414f6e54b 100644 --- a/Source/cmake/OptionsGTK.cmake +++ b/Source/cmake/OptionsGTK.cmake @@ -11,8 +11,13 @@ if (${CMAKE_VERSION} VERSION_LESS "3.20" AND NOT ${CMAKE_GENERATOR} STREQUAL "Ni @@ -22006,16 +21968,7 @@ index ffd106fe4ad44ef1610c5341e7d16c18bdbd102e..73732f416b5f25a2c79816fb33e5526d include(GStreamerDefinitions) SET_AND_EXPOSE_TO_BUILD(USE_CAIRO TRUE) -@@ -59,7 +68,7 @@ WEBKIT_OPTION_DEFINE(USE_LIBHYPHEN "Whether to enable the default automatic hyph - WEBKIT_OPTION_DEFINE(USE_LIBSECRET "Whether to enable the persistent credential storage using libsecret." PUBLIC ON) - WEBKIT_OPTION_DEFINE(USE_OPENGL_OR_ES "Whether to use OpenGL or ES." PUBLIC ON) - WEBKIT_OPTION_DEFINE(USE_OPENJPEG "Whether to enable support for JPEG2000 images." PUBLIC ON) --WEBKIT_OPTION_DEFINE(USE_SOUP2 "Whether to enable usage of Soup 2 instead of Soup 3." PUBLIC OFF) -+WEBKIT_OPTION_DEFINE(USE_SOUP2 "Whether to enable usage of Soup 2 instead of Soup 3." PUBLIC ON) - WEBKIT_OPTION_DEFINE(USE_WOFF2 "Whether to enable support for WOFF2 Web Fonts." PUBLIC ON) - - WEBKIT_OPTION_DEPEND(ENABLE_DOCUMENTATION ENABLE_INTROSPECTION) -@@ -97,15 +106,15 @@ endif () +@@ -98,15 +107,15 @@ endif () # without approval from a GTK reviewer. There must be strong reason to support # changing the value of the option. WEBKIT_OPTION_DEFAULT_PORT_VALUE(ENABLE_DRAG_SUPPORT PUBLIC ON) @@ -22034,7 +21987,7 @@ index ffd106fe4ad44ef1610c5341e7d16c18bdbd102e..73732f416b5f25a2c79816fb33e5526d WEBKIT_OPTION_DEFAULT_PORT_VALUE(USE_JPEGXL PUBLIC ON) # Private options shared with other WebKit ports. Add options here when -@@ -132,7 +141,7 @@ WEBKIT_OPTION_DEFAULT_PORT_VALUE(ENABLE_INPUT_TYPE_TIME PRIVATE ON) +@@ -133,7 +142,7 @@ WEBKIT_OPTION_DEFAULT_PORT_VALUE(ENABLE_INPUT_TYPE_TIME PRIVATE ON) WEBKIT_OPTION_DEFAULT_PORT_VALUE(ENABLE_INPUT_TYPE_WEEK PRIVATE ON) WEBKIT_OPTION_DEFAULT_PORT_VALUE(ENABLE_TRACKING_PREVENTION PRIVATE ON) WEBKIT_OPTION_DEFAULT_PORT_VALUE(ENABLE_LAYER_BASED_SVG_ENGINE PRIVATE ON) @@ -22043,7 +21996,7 @@ index ffd106fe4ad44ef1610c5341e7d16c18bdbd102e..73732f416b5f25a2c79816fb33e5526d WEBKIT_OPTION_DEFAULT_PORT_VALUE(ENABLE_MEDIA_SESSION PRIVATE ${ENABLE_EXPERIMENTAL_FEATURES}) WEBKIT_OPTION_DEFAULT_PORT_VALUE(ENABLE_MEDIA_SESSION_PLAYLIST PRIVATE OFF) WEBKIT_OPTION_DEFAULT_PORT_VALUE(ENABLE_MEDIA_STREAM PRIVATE ON) -@@ -145,7 +154,7 @@ WEBKIT_OPTION_DEFAULT_PORT_VALUE(ENABLE_NETWORK_CACHE_SPECULATIVE_REVALIDATION P +@@ -146,7 +155,7 @@ WEBKIT_OPTION_DEFAULT_PORT_VALUE(ENABLE_NETWORK_CACHE_SPECULATIVE_REVALIDATION P WEBKIT_OPTION_DEFAULT_PORT_VALUE(ENABLE_NETWORK_CACHE_STALE_WHILE_REVALIDATE PRIVATE ON) WEBKIT_OPTION_DEFAULT_PORT_VALUE(ENABLE_OFFSCREEN_CANVAS PRIVATE ${ENABLE_EXPERIMENTAL_FEATURES}) WEBKIT_OPTION_DEFAULT_PORT_VALUE(ENABLE_OFFSCREEN_CANVAS_IN_WORKERS PRIVATE ${ENABLE_EXPERIMENTAL_FEATURES}) @@ -22052,7 +22005,7 @@ index ffd106fe4ad44ef1610c5341e7d16c18bdbd102e..73732f416b5f25a2c79816fb33e5526d WEBKIT_OPTION_DEFAULT_PORT_VALUE(ENABLE_PERIODIC_MEMORY_MONITOR PRIVATE ON) WEBKIT_OPTION_DEFAULT_PORT_VALUE(ENABLE_POINTER_LOCK PRIVATE ON) WEBKIT_OPTION_DEFAULT_PORT_VALUE(ENABLE_SERVICE_WORKER PRIVATE ON) -@@ -156,6 +165,15 @@ WEBKIT_OPTION_DEFAULT_PORT_VALUE(ENABLE_WEB_API_STATISTICS PRIVATE ON) +@@ -157,6 +166,15 @@ WEBKIT_OPTION_DEFAULT_PORT_VALUE(ENABLE_WEB_API_STATISTICS PRIVATE ON) WEBKIT_OPTION_DEFAULT_PORT_VALUE(ENABLE_WEB_CODECS PRIVATE ${ENABLE_EXPERIMENTAL_FEATURES}) WEBKIT_OPTION_DEFAULT_PORT_VALUE(ENABLE_WEB_RTC PRIVATE ${ENABLE_EXPERIMENTAL_FEATURES}) @@ -22068,7 +22021,7 @@ index ffd106fe4ad44ef1610c5341e7d16c18bdbd102e..73732f416b5f25a2c79816fb33e5526d include(GStreamerDependencies) # Finalize the value for all options. Do not attempt to use an option before -@@ -271,6 +289,7 @@ if (NOT EXISTS "${TOOLS_DIR}/glib/apply-build-revision-to-files.py") +@@ -272,6 +290,7 @@ if (NOT EXISTS "${TOOLS_DIR}/glib/apply-build-revision-to-files.py") endif () SET_AND_EXPOSE_TO_BUILD(USE_ATSPI ${ENABLE_ACCESSIBILITY}) @@ -22077,7 +22030,7 @@ index ffd106fe4ad44ef1610c5341e7d16c18bdbd102e..73732f416b5f25a2c79816fb33e5526d SET_AND_EXPOSE_TO_BUILD(HAVE_OS_DARK_MODE_SUPPORT 1) diff --git a/Source/cmake/OptionsWPE.cmake b/Source/cmake/OptionsWPE.cmake -index 003934385b37e49bb34f5767f6cfd11d5a0abafb..e97930ad9eab516cb7ee06e51cff16969c72cb25 100644 +index db463c118d6e6006b9d8e324c6b215e006f9734e..45295ec365715a933dc40bb087e245ce21cd6330 100644 --- a/Source/cmake/OptionsWPE.cmake +++ b/Source/cmake/OptionsWPE.cmake @@ -9,8 +9,13 @@ if (${CMAKE_VERSION} VERSION_LESS "3.20" AND NOT ${CMAKE_GENERATOR} STREQUAL "Ni @@ -22127,7 +22080,7 @@ index 003934385b37e49bb34f5767f6cfd11d5a0abafb..e97930ad9eab516cb7ee06e51cff1696 WEBKIT_OPTION_DEFAULT_PORT_VALUE(ENABLE_TOUCH_EVENTS PRIVATE ON) WEBKIT_OPTION_DEFAULT_PORT_VALUE(ENABLE_VARIATION_FONTS PRIVATE ON) WEBKIT_OPTION_DEFAULT_PORT_VALUE(ENABLE_WEB_CODECS PRIVATE ${ENABLE_EXPERIMENTAL_FEATURES}) -@@ -88,19 +93,36 @@ if (WPE_VERSION VERSION_GREATER_EQUAL 1.13.90) +@@ -88,13 +93,30 @@ if (WPE_VERSION VERSION_GREATER_EQUAL 1.13.90) WEBKIT_OPTION_DEFAULT_PORT_VALUE(ENABLE_GAMEPAD PUBLIC ON) endif () @@ -22159,13 +22112,6 @@ index 003934385b37e49bb34f5767f6cfd11d5a0abafb..e97930ad9eab516cb7ee06e51cff1696 WEBKIT_OPTION_DEFINE(ENABLE_WPE_1_1_API "Whether to build WPE 1.1 instead of WPE 2.0" PUBLIC OFF) WEBKIT_OPTION_DEFINE(USE_GBM "Whether to enable usage of GBM." PUBLIC ON) WEBKIT_OPTION_DEFINE(USE_LCMS "Whether to enable support for image color management using libcms2." PUBLIC ON) - WEBKIT_OPTION_DEFINE(USE_LIBDRM "Whether to enable usage of libdrm." PUBLIC ON) - WEBKIT_OPTION_DEFINE(USE_OPENJPEG "Whether to enable support for JPEG2000 images." PUBLIC ON) --WEBKIT_OPTION_DEFINE(USE_SOUP2 "Whether to enable usage of Soup 2 instead of Soup 3." PUBLIC OFF) -+WEBKIT_OPTION_DEFINE(USE_SOUP2 "Whether to enable usage of Soup 2 instead of Soup 3." PUBLIC ON) - WEBKIT_OPTION_DEFINE(USE_WOFF2 "Whether to enable support for WOFF2 Web Fonts." PUBLIC ON) - - # Private options specific to the WPE port. diff --git a/Source/cmake/OptionsWin.cmake b/Source/cmake/OptionsWin.cmake index 1d358332016df580af0c125bdc6c909967acb11d..649afb06efc04b17c9333ced2308820b1f1ae2e7 100644 --- a/Source/cmake/OptionsWin.cmake @@ -22231,7 +22177,7 @@ index 1d358332016df580af0c125bdc6c909967acb11d..649afb06efc04b17c9333ced2308820b WEBKIT_OPTION_DEFAULT_PORT_VALUE(ENABLE_NETSCAPE_PLUGIN_API PRIVATE OFF) diff --git a/Source/cmake/WebKitCompilerFlags.cmake b/Source/cmake/WebKitCompilerFlags.cmake -index 9b2fecf9a0d367baf910bd241eca9e010f09a0a1..eaa55cd39113ada3d4a7589dac0199797da89123 100644 +index 82d26ca244a46427f3fdc81ec418943c3d0c675f..15dcaf559b1a5ff1e447e92c8ec9aad4ef8e1915 100644 --- a/Source/cmake/WebKitCompilerFlags.cmake +++ b/Source/cmake/WebKitCompilerFlags.cmake @@ -87,7 +87,7 @@ macro(WEBKIT_ADD_TARGET_CXX_FLAGS _target) @@ -22243,6 +22189,20 @@ index 9b2fecf9a0d367baf910bd241eca9e010f09a0a1..eaa55cd39113ada3d4a7589dac019979 if (DEVELOPER_MODE AND DEVELOPER_MODE_FATAL_WARNINGS) if (MSVC) set(FATAL_WARNINGS_FLAG /WX) +diff --git a/Tools/DumpRenderTree/DerivedSources.make b/Tools/DumpRenderTree/DerivedSources.make +index 57aae19f1a16d08ba1579562eeff264c8768af4d..6ce36f40cc954bd02b86f84bd9a0bd2928459938 100644 +--- a/Tools/DumpRenderTree/DerivedSources.make ++++ b/Tools/DumpRenderTree/DerivedSources.make +@@ -73,8 +73,8 @@ $(IDL_FILE_NAMES_LIST) : $(UICONTEXT_INTERFACES:%=%.idl) + JS%.h JS%.cpp : %.idl $(SCRIPTS) $(IDL_ATTRIBUTES_FILE) $(IDL_FILE_NAMES_LIST) $(FEATURE_AND_PLATFORM_DEFINE_DEPENDENCIES) + @echo Generating bindings for $*... + $(PERL) -I $(WebCoreScripts) -I $(UISCRIPTCONTEXT_DIR) -I $(DumpRenderTree)/Bindings $(WebCoreScripts)/generate-bindings.pl --defines "$(FEATURE_AND_PLATFORM_DEFINES)" --idlFileNamesList $(IDL_FILE_NAMES_LIST) --outputDir . --generator DumpRenderTree --idlAttributesFile $(IDL_ATTRIBUTES_FILE) $< +-# + ++# + + WEB_PREFERENCES_GENERATED_FILES = \ + TestOptionsGeneratedWebKitLegacyKeyMapping.cpp \ diff --git a/Tools/MiniBrowser/gtk/BrowserTab.c b/Tools/MiniBrowser/gtk/BrowserTab.c index 61616b96e2f4e21aa6d098445e0f1a933e512a9c..33732da18013679a869ff8eb2b44543413f7cf0f 100644 --- a/Tools/MiniBrowser/gtk/BrowserTab.c @@ -22299,7 +22259,7 @@ index 61616b96e2f4e21aa6d098445e0f1a933e512a9c..33732da18013679a869ff8eb2b445434 } diff --git a/Tools/MiniBrowser/gtk/BrowserWindow.c b/Tools/MiniBrowser/gtk/BrowserWindow.c -index 626ce2207e845c40439643d2494e9497b1222810..d741e74c463a59aed2d04acad8635edcfbea664e 100644 +index 58eb5ae39e2e042435c520553e7540388ba98dbc..61033c71963e61f8ccd7a22c9acf42451f9872ca 100644 --- a/Tools/MiniBrowser/gtk/BrowserWindow.c +++ b/Tools/MiniBrowser/gtk/BrowserWindow.c @@ -73,7 +73,7 @@ struct _BrowserWindowClass { @@ -22332,7 +22292,7 @@ index 626ce2207e845c40439643d2494e9497b1222810..d741e74c463a59aed2d04acad8635edc gtk_window_set_title(GTK_WINDOW(window), privateTitle ? privateTitle : title); g_free(privateTitle); } -@@ -508,8 +502,12 @@ static gboolean webViewDecidePolicy(WebKitWebView *webView, WebKitPolicyDecision +@@ -522,8 +516,12 @@ static gboolean webViewDecidePolicy(WebKitWebView *webView, WebKitPolicyDecision return FALSE; WebKitNavigationAction *navigationAction = webkit_navigation_policy_decision_get_navigation_action(WEBKIT_NAVIGATION_POLICY_DECISION(decision)); @@ -22347,7 +22307,7 @@ index 626ce2207e845c40439643d2494e9497b1222810..d741e74c463a59aed2d04acad8635edc return FALSE; /* Multiple tabs are not allowed in editor mode. */ -@@ -1491,6 +1489,12 @@ static gboolean browserWindowDeleteEvent(GtkWidget *widget, GdkEventAny* event) +@@ -1500,6 +1498,12 @@ static gboolean browserWindowDeleteEvent(GtkWidget *widget, GdkEventAny* event) } #endif @@ -22360,7 +22320,7 @@ index 626ce2207e845c40439643d2494e9497b1222810..d741e74c463a59aed2d04acad8635edc static void browser_window_class_init(BrowserWindowClass *klass) { GObjectClass *gobjectClass = G_OBJECT_CLASS(klass); -@@ -1504,6 +1508,14 @@ static void browser_window_class_init(BrowserWindowClass *klass) +@@ -1513,6 +1517,14 @@ static void browser_window_class_init(BrowserWindowClass *klass) GtkWidgetClass *widgetClass = GTK_WIDGET_CLASS(klass); widgetClass->delete_event = browserWindowDeleteEvent; #endif @@ -22376,7 +22336,7 @@ index 626ce2207e845c40439643d2494e9497b1222810..d741e74c463a59aed2d04acad8635edc /* Public API. */ diff --git a/Tools/MiniBrowser/gtk/BrowserWindow.h b/Tools/MiniBrowser/gtk/BrowserWindow.h -index c58ebc2beec7e722e8b65b3358b278400e9a1232..526ab90cce49d94f263ab48bbb87e99710f220c9 100644 +index 1fd07efb828b85b6d8def6c6cd92a0c11debfe1b..da9fac7975d477857ead2adb1d67108d51716d15 100644 --- a/Tools/MiniBrowser/gtk/BrowserWindow.h +++ b/Tools/MiniBrowser/gtk/BrowserWindow.h @@ -42,7 +42,7 @@ G_BEGIN_DECLS @@ -22389,7 +22349,7 @@ index c58ebc2beec7e722e8b65b3358b278400e9a1232..526ab90cce49d94f263ab48bbb87e997 typedef struct _BrowserWindow BrowserWindow; diff --git a/Tools/MiniBrowser/gtk/main.c b/Tools/MiniBrowser/gtk/main.c -index 8be643a541511ba58255eaea7fd27dee12fbaa87..fd823c12ec853a7ae70cc343195546b47f08e0b2 100644 +index 451e0333dd4e8f5d6313fa80c5027ef2661a61ac..7398af4772ed9a479b1632e38af2d4ee6c664754 100644 --- a/Tools/MiniBrowser/gtk/main.c +++ b/Tools/MiniBrowser/gtk/main.c @@ -75,7 +75,12 @@ static char* timeZone; @@ -22857,7 +22817,7 @@ index d9f1afca539b01fcd9e32e4a5182a9b8101e5a92..4c6022fcd7fde326000dade4788f8691 # WebInspectorUI must come after JavaScriptCore and WebCore but before WebKit and WebKit2 my $webKitIndex = first { $projects[$_] eq "Source/WebKitLegacy" } 0..$#projects; diff --git a/Tools/WebKitTestRunner/CMakeLists.txt b/Tools/WebKitTestRunner/CMakeLists.txt -index b8056910b6cde5610c3e8e4c2992723f8cf80cd0..44367cb186bff1fb85e76cf8f0530170c68fc1ed 100644 +index 9e53f459e444b9c10fc5248f0e8059df6c1e0041..c17c875a7dd3ca05c4489578ab32378bca45a7c9 100644 --- a/Tools/WebKitTestRunner/CMakeLists.txt +++ b/Tools/WebKitTestRunner/CMakeLists.txt @@ -95,6 +95,10 @@ set(TestRunnerInjectedBundle_PRIVATE_LIBRARIES @@ -22872,7 +22832,7 @@ index b8056910b6cde5610c3e8e4c2992723f8cf80cd0..44367cb186bff1fb85e76cf8f0530170 "${WebKitTestRunner_DIR}/InjectedBundle/Bindings/AccessibilityController.idl" "${WebKitTestRunner_DIR}/InjectedBundle/Bindings/AccessibilityTextMarker.idl" diff --git a/Tools/WebKitTestRunner/TestController.cpp b/Tools/WebKitTestRunner/TestController.cpp -index c59538891446072875ce019bbc95079227765a30..a943e94b2e8710aa8bab87170f14a0d58fe0bfc5 100644 +index 8b4371425fc78e7ddb34966186d5985575fa5cb7..2b0763a80853e93595bf813155a4aadece62c11e 100644 --- a/Tools/WebKitTestRunner/TestController.cpp +++ b/Tools/WebKitTestRunner/TestController.cpp @@ -962,6 +962,7 @@ void TestController::createWebViewWithOptions(const TestOptions& options) @@ -22884,10 +22844,10 @@ index c59538891446072875ce019bbc95079227765a30..a943e94b2e8710aa8bab87170f14a0d5 decidePolicyForMediaKeySystemPermissionRequest, nullptr, // requestWebAuthenticationNoGesture diff --git a/Tools/WebKitTestRunner/mac/EventSenderProxy.mm b/Tools/WebKitTestRunner/mac/EventSenderProxy.mm -index fd94bd470a356591a88212836717113864184127..2a04ca6026fd23222fdf00517e564576fb250f79 100644 +index 397cacf02ea61a9463050870216251f5a2ef7b06..6d1b612e25ecf4bf4a80878d265844f668aa2ca3 100644 --- a/Tools/WebKitTestRunner/mac/EventSenderProxy.mm +++ b/Tools/WebKitTestRunner/mac/EventSenderProxy.mm -@@ -906,4 +906,51 @@ void EventSenderProxy::scaleGestureEnd(double scale) +@@ -907,4 +907,51 @@ void EventSenderProxy::scaleGestureEnd(double scale) #endif // ENABLE(MAC_GESTURE_EVENTS) @@ -22940,7 +22900,7 @@ index fd94bd470a356591a88212836717113864184127..2a04ca6026fd23222fdf00517e564576 + } // namespace WTR diff --git a/Tools/glib/dependencies/apt b/Tools/glib/dependencies/apt -index 77b192427f149554081d94fa074cc19ee8768cc9..159ccf2836e56b512a221e7b9825f870995627f6 100644 +index ab8ce021ed0a2a07de9faaaa0766c39006e48887..3d8a0522dd6e4d76d673e26236dc459119b3517f 100644 --- a/Tools/glib/dependencies/apt +++ b/Tools/glib/dependencies/apt @@ -1,11 +1,11 @@ @@ -22957,7 +22917,15 @@ index 77b192427f149554081d94fa074cc19ee8768cc9..159ccf2836e56b512a221e7b9825f870 echo $2 fi } -@@ -69,10 +69,12 @@ PACKAGES=( +@@ -60,6 +60,7 @@ PACKAGES=( + libpng-dev + libseccomp-dev + $(aptIfExists libsoup-3.0-dev) ++ libnghttp2-dev + libsqlite3-dev + libsystemd-dev + libtasn1-6-dev +@@ -70,10 +71,12 @@ PACKAGES=( $(aptIfExists libwpe-1.0-dev) $(aptIfExists libwpebackend-fdo-1.0-dev) libxml2-utils @@ -22971,10 +22939,10 @@ index 77b192427f149554081d94fa074cc19ee8768cc9..159ccf2836e56b512a221e7b9825f870 # These are dependencies necessary for running tests. diff --git a/Tools/gtk/dependencies/apt b/Tools/gtk/dependencies/apt -index 6af7a5608ff76205702e659d1c2393897c56eaad..401436dddf714e2616b44f61ed1b333071459a79 100644 +index e86c94f00fa39b8186a20c6e696a3ad02f93bbba..f7c96a597faf538281aa637cf8b8e1379d72023f 100644 --- a/Tools/gtk/dependencies/apt +++ b/Tools/gtk/dependencies/apt -@@ -37,6 +37,9 @@ PACKAGES+=( +@@ -36,6 +36,9 @@ PACKAGES+=( nasm unifdef xfonts-utils @@ -22985,10 +22953,10 @@ index 6af7a5608ff76205702e659d1c2393897c56eaad..401436dddf714e2616b44f61ed1b3330 # These are dependencies necessary for running tests. cups-daemon diff --git a/Tools/jhbuild/jhbuild-minimal.modules b/Tools/jhbuild/jhbuild-minimal.modules -index 4e3ec374e700c3459bcec321f9711ae2b61a1412..f6eff2f5b667469d4abe574355360c5f8071b96c 100644 +index cf94e7a391a2af0bc6a2f59c5d6df0113b939d95..e762744314b5982c20e9626878d728d748b3cd2d 100644 --- a/Tools/jhbuild/jhbuild-minimal.modules +++ b/Tools/jhbuild/jhbuild-minimal.modules -@@ -26,7 +26,6 @@ +@@ -24,7 +24,6 @@ @@ -22996,7 +22964,7 @@ index 4e3ec374e700c3459bcec321f9711ae2b61a1412..f6eff2f5b667469d4abe574355360c5f -@@ -247,9 +246,9 @@ +@@ -241,9 +240,9 @@ -DJPEGXL_ENABLE_SKCMS=ON"> libjxl.pc diff --git a/WebKit.xcworkspace/xcshareddata/xcschemes/Everything up to WebKit + Tools.xcscheme b/WebKit.xcworkspace/xcshareddata/xcschemes/Everything up to WebKit + Tools.xcscheme -index 4e712c4128ced91a4f7b93c543e4b231ec23e348..853d3d2f6b311c0fe009bf3ef5ab4d1d3c1d331e 100644 +index ded307890926eaf0ca169aaef39ea08bd982a47a..2db0c0abdda702fdff9314ba341b63c5d09289bc 100644 --- a/WebKit.xcworkspace/xcshareddata/xcschemes/Everything up to WebKit + Tools.xcscheme +++ b/WebKit.xcworkspace/xcshareddata/xcschemes/Everything up to WebKit + Tools.xcscheme @@ -188,6 +188,20 @@ diff --git a/browser_patches/webkit/pw_run.sh b/browser_patches/webkit/pw_run.sh index 1d2d6e516b..904c702d70 100755 --- a/browser_patches/webkit/pw_run.sh +++ b/browser_patches/webkit/pw_run.sh @@ -1,11 +1,14 @@ #!/usr/bin/env bash +function getWebkitCheckoutPath() { + echo ${WK_CHECKOUT_PATH:-"$HOME/webkit"} +} + function runOSX() { # if script is run as-is - if [[ -f "${SCRIPT_PATH}/EXPECTED_BUILDS" && -n "$WK_CHECKOUT_PATH" && -d "$WK_CHECKOUT_PATH/WebKitBuild/Release/Playwright.app" ]]; then + WK_CHECKOUT_PATH=$(getWebkitCheckoutPath) + if [[ -f "${SCRIPT_PATH}/EXPECTED_BUILDS" && -d "$WK_CHECKOUT_PATH/WebKitBuild/Release/Playwright.app" ]]; then DYLIB_PATH="$WK_CHECKOUT_PATH/WebKitBuild/Release" - elif [[ -f "${SCRIPT_PATH}/EXPECTED_BUILDS" && -d "$HOME/webkit/WebKitBuild/Release/Playwright.app" ]]; then - DYLIB_PATH="$HOME/webkit/WebKitBuild/Release" elif [[ -d $SCRIPT_PATH/Playwright.app ]]; then DYLIB_PATH="$SCRIPT_PATH" elif [[ -d $SCRIPT_PATH/WebKitBuild/Release/Playwright.app ]]; then @@ -34,13 +37,14 @@ function runLinux() { # Setting extra environment variables like LD_LIBRARY_PATH or WEBKIT_INJECTED_BUNDLE_PATH # is only needed when calling MiniBrowser from the build folder. The MiniBrowser from # the zip bundle wrapper already sets itself the needed env variables. + WK_CHECKOUT_PATH=$(getWebkitCheckoutPath) if [[ -d $SCRIPT_PATH/$MINIBROWSER_FOLDER ]]; then MINIBROWSER="$SCRIPT_PATH/$MINIBROWSER_FOLDER/MiniBrowser" - elif [[ -d $HOME/webkit/$BUILD_FOLDER ]]; then - LD_PATH="$HOME/webkit/$BUILD_FOLDER/$DEPENDENCIES_FOLDER/Root/lib:$SCRIPT_PATH/checkout/$BUILD_FOLDER/Release/bin" - GIO_DIR="$HOME/webkit/$BUILD_FOLDER/$DEPENDENCIES_FOLDER/Root/lib/gio/modules" - BUNDLE_DIR="$HOME/webkit/$BUILD_FOLDER/Release/lib" - MINIBROWSER="$HOME/webkit/$BUILD_FOLDER/Release/bin/MiniBrowser" + elif [[ -d $WK_CHECKOUT_PATH/$BUILD_FOLDER ]]; then + LD_PATH="$WK_CHECKOUT_PATH/$BUILD_FOLDER/$DEPENDENCIES_FOLDER/Root/lib:$SCRIPT_PATH/checkout/$BUILD_FOLDER/Release/bin" + GIO_DIR="$WK_CHECKOUT_PATH/$BUILD_FOLDER/$DEPENDENCIES_FOLDER/Root/lib/gio/modules" + BUNDLE_DIR="$WK_CHECKOUT_PATH/$BUILD_FOLDER/Release/lib" + MINIBROWSER="$WK_CHECKOUT_PATH/$BUILD_FOLDER/Release/bin/MiniBrowser" elif [[ -f $SCRIPT_PATH/MiniBrowser ]]; then MINIBROWSER="$SCRIPT_PATH/MiniBrowser" elif [[ -d $SCRIPT_PATH/$BUILD_FOLDER ]]; then From 0a052cb4d6712584a6d01bf7439450f68b87ff63 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Tue, 14 Nov 2023 12:55:34 -0800 Subject: [PATCH 052/491] feat(recorder): assert visibility tool (#28142) --- .../src/server/injected/highlight.css | 14 +++-- .../src/server/injected/recorder.ts | 57 ++++++++++++++----- .../playwright-core/src/server/recorder.ts | 10 ++-- .../src/server/recorder/csharp.ts | 2 + .../src/server/recorder/java.ts | 2 + .../src/server/recorder/javascript.ts | 2 + .../src/server/recorder/python.ts | 2 + .../src/server/recorder/recorderActions.ts | 12 +++- packages/recorder/src/recorder.css | 4 ++ packages/recorder/src/recorder.tsx | 14 +++-- packages/recorder/src/recorderTypes.ts | 2 +- 11 files changed, 88 insertions(+), 33 deletions(-) diff --git a/packages/playwright-core/src/server/injected/highlight.css b/packages/playwright-core/src/server/injected/highlight.css index 7c7105c131..33ee63720f 100644 --- a/packages/playwright-core/src/server/injected/highlight.css +++ b/packages/playwright-core/src/server/injected/highlight.css @@ -170,10 +170,16 @@ x-pw-tool-item.pick-locator > x-div { mask-image: url("data:image/svg+xml;utf8,"); } -x-pw-tool-item.assert > x-div { - /* codicon: check-all */ - -webkit-mask-image: url("data:image/svg+xml;utf8,"); - mask-image: url("data:image/svg+xml;utf8,"); +x-pw-tool-item.text > x-div { + /* codicon: whole-word */ + -webkit-mask-image: url("data:image/svg+xml;utf8,"); + mask-image: url("data:image/svg+xml;utf8,"); +} + +x-pw-tool-item.visibility > x-div { + /* codicon: eye */ + -webkit-mask-image: url("data:image/svg+xml;utf8,"); + mask-image: url("data:image/svg+xml;utf8,"); } x-pw-tool-item.accept > x-div { diff --git a/packages/playwright-core/src/server/injected/recorder.ts b/packages/playwright-core/src/server/injected/recorder.ts index 03ac6d92b5..8785783308 100644 --- a/packages/playwright-core/src/server/injected/recorder.ts +++ b/packages/playwright-core/src/server/injected/recorder.ts @@ -83,7 +83,7 @@ class InspectTool implements RecorderTool { private _hoveredModel: HighlightModel | null = null; private _hoveredElement: HTMLElement | null = null; - constructor(private _recorder: Recorder) { + constructor(private _recorder: Recorder, private _assertVisibility: boolean) { } cursor() { @@ -97,7 +97,17 @@ class InspectTool implements RecorderTool { onClick(event: MouseEvent) { consumeEvent(event); - this._recorder.delegate.setSelector?.(this._hoveredModel ? this._hoveredModel.selector : ''); + if (this._assertVisibility) { + if (this._hoveredModel?.selector) { + this._recorder.delegate.recordAction?.({ + name: 'assertVisible', + selector: this._hoveredModel.selector, + signals: [], + }); + } + } else { + this._recorder.delegate.setSelector?.(this._hoveredModel ? this._hoveredModel.selector : ''); + } } onPointerDown(event: PointerEvent) { @@ -144,6 +154,8 @@ class InspectTool implements RecorderTool { onKeyDown(event: KeyboardEvent) { consumeEvent(event); + if (this._assertVisibility && event.key === 'Escape') + this._recorder.delegate.setMode?.('recording'); } onKeyUp(event: KeyboardEvent) { @@ -726,7 +738,8 @@ class Overlay { private _overlayElement: HTMLElement; private _recordToggle: HTMLElement; private _pickLocatorToggle: HTMLElement; - private _assertToggle: HTMLElement; + private _assertVisibilityToggle: HTMLElement; + private _assertTextToggle: HTMLElement; private _offsetX = 0; private _dragState: { offsetX: number, dragStart: { x: number, y: number } } | undefined; private _measure: { width: number, height: number } = { width: 0, height: 0 }; @@ -766,20 +779,31 @@ class Overlay { 'recording': 'recording-inspecting', 'recording-inspecting': 'recording', 'assertingText': 'recording-inspecting', + 'assertingVisibility': 'recording-inspecting', }; this._recorder.delegate.setMode?.(newMode[this._recorder.state.mode]); }); toolsListElement.appendChild(this._pickLocatorToggle); - this._assertToggle = this._recorder.injectedScript.document.createElement('x-pw-tool-item'); - this._assertToggle.title = 'Assert text and values'; - this._assertToggle.classList.add('assert'); - this._assertToggle.appendChild(this._recorder.injectedScript.document.createElement('x-div')); - this._assertToggle.addEventListener('click', () => { - if (!this._assertToggle.classList.contains('disabled')) + this._assertVisibilityToggle = this._recorder.injectedScript.document.createElement('x-pw-tool-item'); + this._assertVisibilityToggle.title = 'Assert visibility'; + this._assertVisibilityToggle.classList.add('visibility'); + this._assertVisibilityToggle.appendChild(this._recorder.injectedScript.document.createElement('x-div')); + this._assertVisibilityToggle.addEventListener('click', () => { + if (!this._assertVisibilityToggle.classList.contains('disabled')) + this._recorder.delegate.setMode?.(this._recorder.state.mode === 'assertingVisibility' ? 'recording' : 'assertingVisibility'); + }); + toolsListElement.appendChild(this._assertVisibilityToggle); + + this._assertTextToggle = this._recorder.injectedScript.document.createElement('x-pw-tool-item'); + this._assertTextToggle.title = 'Assert text and values'; + this._assertTextToggle.classList.add('text'); + this._assertTextToggle.appendChild(this._recorder.injectedScript.document.createElement('x-div')); + this._assertTextToggle.addEventListener('click', () => { + if (!this._assertTextToggle.classList.contains('disabled')) this._recorder.delegate.setMode?.(this._recorder.state.mode === 'assertingText' ? 'recording' : 'assertingText'); }); - toolsListElement.appendChild(this._assertToggle); + toolsListElement.appendChild(this._assertTextToggle); this._updateVisualPosition(); } @@ -794,10 +818,12 @@ class Overlay { } setUIState(state: UIState) { - this._recordToggle.classList.toggle('active', state.mode === 'recording' || state.mode === 'assertingText' || state.mode === 'recording-inspecting'); + this._recordToggle.classList.toggle('active', state.mode === 'recording' || state.mode === 'assertingText' || state.mode === 'assertingVisibility' || state.mode === 'recording-inspecting'); this._pickLocatorToggle.classList.toggle('active', state.mode === 'inspecting' || state.mode === 'recording-inspecting'); - this._assertToggle.classList.toggle('active', state.mode === 'assertingText'); - this._assertToggle.classList.toggle('disabled', state.mode === 'none' || state.mode === 'standby' || state.mode === 'inspecting'); + this._assertVisibilityToggle.classList.toggle('active', state.mode === 'assertingVisibility'); + this._assertVisibilityToggle.classList.toggle('disabled', state.mode === 'none' || state.mode === 'standby' || state.mode === 'inspecting'); + this._assertTextToggle.classList.toggle('active', state.mode === 'assertingText'); + this._assertTextToggle.classList.toggle('disabled', state.mode === 'none' || state.mode === 'standby' || state.mode === 'inspecting'); if (this._offsetX !== state.overlay.offsetX) { this._offsetX = state.overlay.offsetX; this._updateVisualPosition(); @@ -867,10 +893,11 @@ export class Recorder { this._tools = { 'none': new NoneTool(), 'standby': new NoneTool(), - 'inspecting': new InspectTool(this), + 'inspecting': new InspectTool(this, false), 'recording': new RecordActionTool(this), - 'recording-inspecting': new InspectTool(this), + 'recording-inspecting': new InspectTool(this, false), 'assertingText': new TextAssertionTool(this), + 'assertingVisibility': new InspectTool(this, true), }; this._currentTool = this._tools.none; if (injectedScript.window.top === injectedScript.window) { diff --git a/packages/playwright-core/src/server/recorder.ts b/packages/playwright-core/src/server/recorder.ts index b2b5e50014..4fc5a63cdf 100644 --- a/packages/playwright-core/src/server/recorder.ts +++ b/packages/playwright-core/src/server/recorder.ts @@ -246,8 +246,8 @@ export class Recorder implements InstrumentationListener { this._highlightedSelector = ''; this._mode = mode; this._recorderApp?.setMode(this._mode); - this._contextRecorder.setEnabled(this._mode === 'recording' || this._mode === 'assertingText'); - this._debugger.setMuted(this._mode === 'recording' || this._mode === 'assertingText'); + this._contextRecorder.setEnabled(this._mode === 'recording' || this._mode === 'assertingText' || this._mode === 'assertingVisibility'); + this._debugger.setMuted(this._mode === 'recording' || this._mode === 'assertingText' || this._mode === 'assertingVisibility'); if (this._mode !== 'none' && this._mode !== 'standby' && this._context.pages().length === 1) this._context.pages()[0].bringToFront().catch(() => {}); this._refreshOverlay(); @@ -281,7 +281,7 @@ export class Recorder implements InstrumentationListener { } async onBeforeCall(sdkObject: SdkObject, metadata: CallMetadata) { - if (this._omitCallTracking || this._mode === 'recording' || this._mode === 'assertingText') + if (this._omitCallTracking || this._mode === 'recording' || this._mode === 'assertingText' || this._mode === 'assertingVisibility') return; this._currentCallsMetadata.set(metadata, sdkObject); this._updateUserSources(); @@ -295,7 +295,7 @@ export class Recorder implements InstrumentationListener { } async onAfterCall(sdkObject: SdkObject, metadata: CallMetadata) { - if (this._omitCallTracking || this._mode === 'recording' || this._mode === 'assertingText') + if (this._omitCallTracking || this._mode === 'recording' || this._mode === 'assertingText' || this._mode === 'assertingVisibility') return; if (!metadata.error) this._currentCallsMetadata.delete(metadata); @@ -345,7 +345,7 @@ export class Recorder implements InstrumentationListener { } updateCallLog(metadatas: CallMetadata[]) { - if (this._mode === 'recording' || this._mode === 'assertingText') + if (this._mode === 'recording' || this._mode === 'assertingText' || this._mode === 'assertingVisibility') return; const logs: CallLog[] = []; for (const metadata of metadatas) { diff --git a/packages/playwright-core/src/server/recorder/csharp.ts b/packages/playwright-core/src/server/recorder/csharp.ts index 709b9df49d..edd288d6d8 100644 --- a/packages/playwright-core/src/server/recorder/csharp.ts +++ b/packages/playwright-core/src/server/recorder/csharp.ts @@ -158,6 +158,8 @@ export class CSharpLanguageGenerator implements LanguageGenerator { return `await Expect(${subject}.${this._asLocator(action.selector)}).${action.substring ? 'ToContainTextAsync' : 'ToHaveTextAsync'}(${quote(action.text)});`; case 'assertChecked': return `await Expect(${subject}.${this._asLocator(action.selector)})${action.checked ? '' : '.Not'}.ToBeCheckedAsync();`; + case 'assertVisible': + return `await Expect(${subject}.${this._asLocator(action.selector)}).ToBeVisibleAsync();`; case 'assertValue': { const assertion = action.value ? `ToHaveValueAsync(${quote(action.value)})` : `ToBeEmpty()`; return `await Expect(${subject}.${this._asLocator(action.selector)}).${assertion};`; diff --git a/packages/playwright-core/src/server/recorder/java.ts b/packages/playwright-core/src/server/recorder/java.ts index c15241a98e..742b4db93a 100644 --- a/packages/playwright-core/src/server/recorder/java.ts +++ b/packages/playwright-core/src/server/recorder/java.ts @@ -126,6 +126,8 @@ export class JavaLanguageGenerator implements LanguageGenerator { return `assertThat(${subject}.${this._asLocator(action.selector, inFrameLocator)}).${action.substring ? 'containsText' : 'hasText'}(${quote(action.text)});`; case 'assertChecked': return `assertThat(${subject}.${this._asLocator(action.selector, inFrameLocator)})${action.checked ? '' : '.not()'}.isChecked();`; + case 'assertVisible': + return `assertThat(${subject}.${this._asLocator(action.selector, inFrameLocator)}).isVisible();`; case 'assertValue': { const assertion = action.value ? `hasValue(${quote(action.value)})` : `isEmpty()`; return `assertThat(${subject}.${this._asLocator(action.selector, inFrameLocator)}).${assertion};`; diff --git a/packages/playwright-core/src/server/recorder/javascript.ts b/packages/playwright-core/src/server/recorder/javascript.ts index a68d3212fa..548e0f6071 100644 --- a/packages/playwright-core/src/server/recorder/javascript.ts +++ b/packages/playwright-core/src/server/recorder/javascript.ts @@ -129,6 +129,8 @@ export class JavaScriptLanguageGenerator implements LanguageGenerator { return `${this._isTest ? '' : '// '}await expect(${subject}.${this._asLocator(action.selector)}).${action.substring ? 'toContainText' : 'toHaveText'}(${quote(action.text)});`; case 'assertChecked': return `${this._isTest ? '' : '// '}await expect(${subject}.${this._asLocator(action.selector)})${action.checked ? '' : '.not'}.toBeChecked();`; + case 'assertVisible': + return `${this._isTest ? '' : '// '}await expect(${subject}.${this._asLocator(action.selector)}).toBeVisible();`; case 'assertValue': { const assertion = action.value ? `toHaveValue(${quote(action.value)})` : `toBeEmpty()`; return `${this._isTest ? '' : '// '}await expect(${subject}.${this._asLocator(action.selector)}).${assertion};`; diff --git a/packages/playwright-core/src/server/recorder/python.ts b/packages/playwright-core/src/server/recorder/python.ts index a0e60e32be..b00e02178c 100644 --- a/packages/playwright-core/src/server/recorder/python.ts +++ b/packages/playwright-core/src/server/recorder/python.ts @@ -138,6 +138,8 @@ export class PythonLanguageGenerator implements LanguageGenerator { return `expect(${subject}.${this._asLocator(action.selector)}).${action.substring ? 'to_contain_text' : 'to_have_text'}(${quote(action.text)})`; case 'assertChecked': return `expect(${subject}.${this._asLocator(action.selector)}).${action.checked ? 'to_be_checked()' : 'not_to_be_checked()'}`; + case 'assertVisible': + return `expect(${subject}.${this._asLocator(action.selector)}).to_be_visible()`; case 'assertValue': { const assertion = action.value ? `to_have_value(${quote(action.value)})` : `to_be_empty()`; return `expect(${subject}.${this._asLocator(action.selector)}).${assertion};`; diff --git a/packages/playwright-core/src/server/recorder/recorderActions.ts b/packages/playwright-core/src/server/recorder/recorderActions.ts index 3a4bbab325..3c9720cbc4 100644 --- a/packages/playwright-core/src/server/recorder/recorderActions.ts +++ b/packages/playwright-core/src/server/recorder/recorderActions.ts @@ -29,7 +29,8 @@ export type ActionName = 'setInputFiles' | 'assertText' | 'assertValue' | - 'assertChecked'; + 'assertChecked' | + 'assertVisible'; export type ActionBase = { name: ActionName, @@ -113,8 +114,13 @@ export type AssertCheckedAction = ActionBase & { checked: boolean, }; -export type Action = ClickAction | CheckAction | ClosesPageAction | OpenPageAction | UncheckAction | FillAction | NavigateAction | PressAction | SelectAction | SetInputFilesAction | AssertTextAction | AssertValueAction | AssertCheckedAction; -export type AssertAction = AssertCheckedAction | AssertValueAction | AssertTextAction; +export type AssertVisibleAction = ActionBase & { + name: 'assertVisible', + selector: string, +}; + +export type Action = ClickAction | CheckAction | ClosesPageAction | OpenPageAction | UncheckAction | FillAction | NavigateAction | PressAction | SelectAction | SetInputFilesAction | AssertTextAction | AssertValueAction | AssertCheckedAction | AssertVisibleAction; +export type AssertAction = AssertCheckedAction | AssertValueAction | AssertTextAction | AssertVisibleAction; // Signals. diff --git a/packages/recorder/src/recorder.css b/packages/recorder/src/recorder.css index 8219457295..fa7ce623db 100644 --- a/packages/recorder/src/recorder.css +++ b/packages/recorder/src/recorder.css @@ -29,6 +29,10 @@ } .recorder .codicon { + font-size: 16px; +} + +.recorder .codicon.circle-large-filled { font-size: 15px; } diff --git a/packages/recorder/src/recorder.tsx b/packages/recorder/src/recorder.tsx index a6d1e0571b..4f73faeff5 100644 --- a/packages/recorder/src/recorder.tsx +++ b/packages/recorder/src/recorder.tsx @@ -115,8 +115,8 @@ export const Recorder: React.FC = ({ return
- { - window.dispatch({ event: 'setMode', params: { mode: mode === 'none' || mode === 'standby' || mode === 'inspecting' ? 'recording' : 'none' } }); + { + window.dispatch({ event: 'setMode', params: { mode: mode === 'none' || mode === 'standby' || mode === 'inspecting' ? 'recording' : 'standby' } }); }}>Record { @@ -127,12 +127,16 @@ export const Recorder: React.FC = ({ 'recording': 'recording-inspecting', 'recording-inspecting': 'recording', 'assertingText': 'recording-inspecting', + 'assertingVisibility': 'recording-inspecting', }[mode]; window.dispatch({ event: 'setMode', params: { mode: newMode } }).catch(() => { }); - }}>Pick locator - { + }}> + { + window.dispatch({ event: 'setMode', params: { mode: mode === 'assertingVisibility' ? 'recording' : 'assertingVisibility' } }); + }}> + { window.dispatch({ event: 'setMode', params: { mode: mode === 'assertingText' ? 'recording' : 'assertingText' } }); - }}>Assert + }}> { copy(source.text); diff --git a/packages/recorder/src/recorderTypes.ts b/packages/recorder/src/recorderTypes.ts index 144ff5fdbc..e82e608554 100644 --- a/packages/recorder/src/recorderTypes.ts +++ b/packages/recorder/src/recorderTypes.ts @@ -18,7 +18,7 @@ import type { Language } from '../../playwright-core/src/utils/isomorphic/locato export type Point = { x: number, y: number }; -export type Mode = 'inspecting' | 'recording' | 'none' | 'assertingText' | 'recording-inspecting' | 'standby'; +export type Mode = 'inspecting' | 'recording' | 'none' | 'assertingText' | 'recording-inspecting' | 'standby' | 'assertingVisibility'; export type EventData = { event: 'clear' | 'resume' | 'step' | 'pause' | 'setMode' | 'selectorUpdated' | 'fileChanged'; From f76c261b166f742b016e89408c27d13a8e042d10 Mon Sep 17 00:00:00 2001 From: Hemang Rajyaguru <129292347+hemang-rajyaguru@users.noreply.github.com> Date: Tue, 14 Nov 2023 21:57:14 +0000 Subject: [PATCH 053/491] docs(ci.md): added google cloud CI docs for NodeJs using the playwright Docker Image (#27831) Fixes #27769 --- docs/src/ci.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/src/ci.md b/docs/src/ci.md index cfe23d70bf..280602cbe1 100644 --- a/docs/src/ci.md +++ b/docs/src/ci.md @@ -587,6 +587,19 @@ tests: - npm ci - npx playwright test --project=$PROJECT --shard=$SHARD ``` +### Google Cloud Build +* langs: js + +To run Playwright tests on Google Cloud Build, use our public Docker image ([see Dockerfile](./docker.md)). + +```yml +steps: +- name: mcr.microsoft.com/playwright:v%%VERSION%%-jammy + script: + ... + env: + - 'CI=true' +``` ## Caching browsers From 557f3afd74cf48a98ba01143f4516f9031626230 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Tue, 14 Nov 2023 15:17:42 -0800 Subject: [PATCH 054/491] feat(recorder): assert value as a separate tool (#28145) --- .../src/server/injected/highlight.css | 29 +++++++- .../src/server/injected/recorder.ts | 68 ++++++++++++++++--- .../playwright-core/src/server/recorder.ts | 10 +-- packages/recorder/src/recorder.tsx | 6 +- packages/recorder/src/recorderTypes.ts | 2 +- 5 files changed, 95 insertions(+), 20 deletions(-) diff --git a/packages/playwright-core/src/server/injected/highlight.css b/packages/playwright-core/src/server/injected/highlight.css index 33ee63720f..b0275a303c 100644 --- a/packages/playwright-core/src/server/injected/highlight.css +++ b/packages/playwright-core/src/server/injected/highlight.css @@ -55,6 +55,13 @@ x-pw-dialog-body { flex: auto; } +x-pw-dialog-body label { + margin: 5px 8px; + display: flex; + flex-direction: row; + align-items: center; +} + x-pw-highlight { position: absolute; top: 0; @@ -129,6 +136,14 @@ x-pw-tool-item:not(.disabled):hover { background-color: hsl(0, 0%, 86%); } +x-pw-tool-item.active { + background-color: rgba(138, 202, 228, 0.5); +} + +x-pw-tool-item.active:not(.disabled):hover { + background-color: #8acae4c4; +} + x-pw-tool-item > x-div { width: 100%; height: 100%; @@ -146,8 +161,12 @@ x-pw-tool-item.disabled > x-div { cursor: default; } -x-pw-tool-item.active > x-div { - background-color: #006ab1; +x-pw-tool-item.record.active { + background-color: transparent; +} + +x-pw-tool-item.record.active:hover { + background-color: hsl(0, 0%, 86%); } x-pw-tool-item.record.active > x-div { @@ -182,6 +201,12 @@ x-pw-tool-item.visibility > x-div { mask-image: url("data:image/svg+xml;utf8,"); } +x-pw-tool-item.value > x-div { + /* codicon: symbol-constant */ + -webkit-mask-image: url("data:image/svg+xml;utf8,"); + mask-image: url("data:image/svg+xml;utf8,"); +} + x-pw-tool-item.accept > x-div { -webkit-mask-image: url("data:image/svg+xml;utf8,"); mask-image: url("data:image/svg+xml;utf8,"); diff --git a/packages/playwright-core/src/server/injected/recorder.ts b/packages/playwright-core/src/server/injected/recorder.ts index 8785783308..6ecd28fe03 100644 --- a/packages/playwright-core/src/server/injected/recorder.ts +++ b/packages/playwright-core/src/server/injected/recorder.ts @@ -80,10 +80,14 @@ class NoneTool implements RecorderTool { } class InspectTool implements RecorderTool { + private _recorder: Recorder; private _hoveredModel: HighlightModel | null = null; private _hoveredElement: HTMLElement | null = null; + private _assertVisibility: boolean; - constructor(private _recorder: Recorder, private _assertVisibility: boolean) { + constructor(recorder: Recorder, assertVisibility: boolean) { + this._recorder = recorder; + this._assertVisibility = assertVisibility; } cursor() { @@ -104,6 +108,7 @@ class InspectTool implements RecorderTool { selector: this._hoveredModel.selector, signals: [], }); + this._recorder.delegate.setMode?.('recording'); } } else { this._recorder.delegate.setSelector?.(this._hoveredModel ? this._hoveredModel.selector : ''); @@ -138,7 +143,7 @@ class InspectTool implements RecorderTool { if (this._hoveredModel?.selector === model?.selector) return; this._hoveredModel = model; - this._recorder.updateHighlight(model, true); + this._recorder.updateHighlight(model, true, { color: this._assertVisibility ? '#8acae480' : undefined }); } onMouseLeave(event: MouseEvent) { @@ -170,13 +175,15 @@ class InspectTool implements RecorderTool { } class RecordActionTool implements RecorderTool { + private _recorder: Recorder; private _performingAction = false; private _hoveredModel: HighlightModel | null = null; private _hoveredElement: HTMLElement | null = null; private _activeModel: HighlightModel | null = null; private _expectProgrammaticKeyUp = false; - constructor(private _recorder: Recorder) { + constructor(recorder: Recorder) { + this._recorder = recorder; } cursor() { @@ -474,6 +481,7 @@ class RecordActionTool implements RecorderTool { } class TextAssertionTool implements RecorderTool { + private _recorder: Recorder; private _hoverHighlight: HighlightModel | null = null; private _action: actions.AssertAction | null = null; private _dialogElement: HTMLElement | null = null; @@ -481,8 +489,12 @@ class TextAssertionTool implements RecorderTool { private _cancelButton: HTMLElement; private _keyboardListener: ((event: KeyboardEvent) => void) | undefined; private _textCache = new Map(); + private _kind: 'text' | 'value'; + + constructor(recorder: Recorder, kind: 'text' | 'value') { + this._recorder = recorder; + this._kind = kind; - constructor(private _recorder: Recorder) { this._acceptButton = this._recorder.document.createElement('x-pw-tool-item'); this._acceptButton.title = 'Accept'; this._acceptButton.classList.add('accept'); @@ -523,7 +535,10 @@ class TextAssertionTool implements RecorderTool { const target = this._recorder.deepEventTarget(event); if (this._hoverHighlight?.elements[0] === target) return; - this._hoverHighlight = target.nodeName === 'INPUT' || target.nodeName === 'TEXTAREA' || elementText(new Map(), target).full ? { elements: [target], selector: '' } : null; + if (this._kind === 'text') + this._hoverHighlight = elementText(this._textCache, target).full ? { elements: [target], selector: '' } : null; + else + this._hoverHighlight = this._elementHasValue(target) ? generateSelector(this._recorder.injectedScript, target, { testIdAttributeName: this._recorder.state.testIdAttributeName }) : null; this._recorder.updateHighlight(this._hoverHighlight, true, { color: '#8acae480' }); } @@ -533,12 +548,22 @@ class TextAssertionTool implements RecorderTool { consumeEvent(event); } + onScroll(event: Event) { + this._recorder.updateHighlight(this._hoverHighlight, false, { color: '#8acae480' }); + } + + private _elementHasValue(element: Element) { + return element.nodeName === 'TEXTAREA' || element.nodeName === 'SELECT' || (element.nodeName === 'INPUT' && !['button', 'image', 'reset', 'submit'].includes((element as HTMLInputElement).type)); + } + private _generateAction(): actions.AssertAction | null { this._textCache.clear(); const target = this._hoverHighlight?.elements[0]; if (!target) return null; - if (target.nodeName === 'INPUT' || target.nodeName === 'TEXTAREA' || target.nodeName === 'SELECT') { + if (this._kind === 'value') { + if (!this._elementHasValue(target)) + return null; const { selector } = generateSelector(this._recorder.injectedScript, target, { testIdAttributeName: this._recorder.state.testIdAttributeName }); if (target.nodeName === 'INPUT' && ['checkbox', 'radio'].includes((target as HTMLInputElement).type.toLowerCase())) { return { @@ -553,7 +578,7 @@ class TextAssertionTool implements RecorderTool { name: 'assertValue', selector, signals: [], - value: (target as (HTMLInputElement | HTMLSelectElement)).value, + value: (target as (HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement)).value, }; } } else { @@ -637,7 +662,11 @@ class TextAssertionTool implements RecorderTool { cm.on('change', () => { if (this._action) { const selector = locatorOrSelectorAsSelector(this._recorder.state.language, cm.getValue(), this._recorder.state.testIdAttributeName); - const elements = this._recorder.injectedScript.querySelectorAll(parseSelector(selector), this._recorder.document); + let elements: Element[] = []; + try { + elements = this._recorder.injectedScript.querySelectorAll(parseSelector(selector), this._recorder.document); + } catch { + } cmElement.classList.toggle('does-not-match', !elements.length); this._hoverHighlight = elements.length ? { selector, @@ -735,16 +764,19 @@ class TextAssertionTool implements RecorderTool { } class Overlay { + private _recorder: Recorder; private _overlayElement: HTMLElement; private _recordToggle: HTMLElement; private _pickLocatorToggle: HTMLElement; private _assertVisibilityToggle: HTMLElement; private _assertTextToggle: HTMLElement; + private _assertValuesToggle: HTMLElement; private _offsetX = 0; private _dragState: { offsetX: number, dragStart: { x: number, y: number } } | undefined; private _measure: { width: number, height: number } = { width: 0, height: 0 }; - constructor(private _recorder: Recorder) { + constructor(recorder: Recorder) { + this._recorder = recorder; const document = this._recorder.injectedScript.document; this._overlayElement = document.createElement('x-pw-overlay'); @@ -780,6 +812,7 @@ class Overlay { 'recording-inspecting': 'recording', 'assertingText': 'recording-inspecting', 'assertingVisibility': 'recording-inspecting', + 'assertingValue': 'recording-inspecting', }; this._recorder.delegate.setMode?.(newMode[this._recorder.state.mode]); }); @@ -805,6 +838,16 @@ class Overlay { }); toolsListElement.appendChild(this._assertTextToggle); + this._assertValuesToggle = this._recorder.injectedScript.document.createElement('x-pw-tool-item'); + this._assertValuesToggle.title = 'Assert value'; + this._assertValuesToggle.classList.add('value'); + this._assertValuesToggle.appendChild(this._recorder.injectedScript.document.createElement('x-div')); + this._assertValuesToggle.addEventListener('click', () => { + if (!this._assertValuesToggle.classList.contains('disabled')) + this._recorder.delegate.setMode?.(this._recorder.state.mode === 'assertingValue' ? 'recording' : 'assertingValue'); + }); + toolsListElement.appendChild(this._assertValuesToggle); + this._updateVisualPosition(); } @@ -818,12 +861,14 @@ class Overlay { } setUIState(state: UIState) { - this._recordToggle.classList.toggle('active', state.mode === 'recording' || state.mode === 'assertingText' || state.mode === 'assertingVisibility' || state.mode === 'recording-inspecting'); + this._recordToggle.classList.toggle('active', state.mode === 'recording' || state.mode === 'assertingText' || state.mode === 'assertingVisibility' || state.mode === 'assertingValue' || state.mode === 'recording-inspecting'); this._pickLocatorToggle.classList.toggle('active', state.mode === 'inspecting' || state.mode === 'recording-inspecting'); this._assertVisibilityToggle.classList.toggle('active', state.mode === 'assertingVisibility'); this._assertVisibilityToggle.classList.toggle('disabled', state.mode === 'none' || state.mode === 'standby' || state.mode === 'inspecting'); this._assertTextToggle.classList.toggle('active', state.mode === 'assertingText'); this._assertTextToggle.classList.toggle('disabled', state.mode === 'none' || state.mode === 'standby' || state.mode === 'inspecting'); + this._assertValuesToggle.classList.toggle('active', state.mode === 'assertingValue'); + this._assertValuesToggle.classList.toggle('disabled', state.mode === 'none' || state.mode === 'standby' || state.mode === 'inspecting'); if (this._offsetX !== state.overlay.offsetX) { this._offsetX = state.overlay.offsetX; this._updateVisualPosition(); @@ -896,8 +941,9 @@ export class Recorder { 'inspecting': new InspectTool(this, false), 'recording': new RecordActionTool(this), 'recording-inspecting': new InspectTool(this, false), - 'assertingText': new TextAssertionTool(this), + 'assertingText': new TextAssertionTool(this, 'text'), 'assertingVisibility': new InspectTool(this, true), + 'assertingValue': new TextAssertionTool(this, 'value'), }; this._currentTool = this._tools.none; if (injectedScript.window.top === injectedScript.window) { diff --git a/packages/playwright-core/src/server/recorder.ts b/packages/playwright-core/src/server/recorder.ts index 4fc5a63cdf..bedcb7a691 100644 --- a/packages/playwright-core/src/server/recorder.ts +++ b/packages/playwright-core/src/server/recorder.ts @@ -246,8 +246,8 @@ export class Recorder implements InstrumentationListener { this._highlightedSelector = ''; this._mode = mode; this._recorderApp?.setMode(this._mode); - this._contextRecorder.setEnabled(this._mode === 'recording' || this._mode === 'assertingText' || this._mode === 'assertingVisibility'); - this._debugger.setMuted(this._mode === 'recording' || this._mode === 'assertingText' || this._mode === 'assertingVisibility'); + this._contextRecorder.setEnabled(this._mode === 'recording' || this._mode === 'assertingText' || this._mode === 'assertingVisibility' || this._mode === 'assertingValue'); + this._debugger.setMuted(this._mode === 'recording' || this._mode === 'assertingText' || this._mode === 'assertingVisibility' || this._mode === 'assertingValue'); if (this._mode !== 'none' && this._mode !== 'standby' && this._context.pages().length === 1) this._context.pages()[0].bringToFront().catch(() => {}); this._refreshOverlay(); @@ -281,7 +281,7 @@ export class Recorder implements InstrumentationListener { } async onBeforeCall(sdkObject: SdkObject, metadata: CallMetadata) { - if (this._omitCallTracking || this._mode === 'recording' || this._mode === 'assertingText' || this._mode === 'assertingVisibility') + if (this._omitCallTracking || this._mode === 'recording' || this._mode === 'assertingText' || this._mode === 'assertingVisibility' || this._mode === 'assertingValue') return; this._currentCallsMetadata.set(metadata, sdkObject); this._updateUserSources(); @@ -295,7 +295,7 @@ export class Recorder implements InstrumentationListener { } async onAfterCall(sdkObject: SdkObject, metadata: CallMetadata) { - if (this._omitCallTracking || this._mode === 'recording' || this._mode === 'assertingText' || this._mode === 'assertingVisibility') + if (this._omitCallTracking || this._mode === 'recording' || this._mode === 'assertingText' || this._mode === 'assertingVisibility' || this._mode === 'assertingValue') return; if (!metadata.error) this._currentCallsMetadata.delete(metadata); @@ -345,7 +345,7 @@ export class Recorder implements InstrumentationListener { } updateCallLog(metadatas: CallMetadata[]) { - if (this._mode === 'recording' || this._mode === 'assertingText' || this._mode === 'assertingVisibility') + if (this._mode === 'recording' || this._mode === 'assertingText' || this._mode === 'assertingVisibility' || this._mode === 'assertingValue') return; const logs: CallLog[] = []; for (const metadata of metadatas) { diff --git a/packages/recorder/src/recorder.tsx b/packages/recorder/src/recorder.tsx index 4f73faeff5..69f8983c3f 100644 --- a/packages/recorder/src/recorder.tsx +++ b/packages/recorder/src/recorder.tsx @@ -128,15 +128,19 @@ export const Recorder: React.FC = ({ 'recording-inspecting': 'recording', 'assertingText': 'recording-inspecting', 'assertingVisibility': 'recording-inspecting', + 'assertingValue': 'recording-inspecting', }[mode]; window.dispatch({ event: 'setMode', params: { mode: newMode } }).catch(() => { }); }}> { window.dispatch({ event: 'setMode', params: { mode: mode === 'assertingVisibility' ? 'recording' : 'assertingVisibility' } }); }}> - { + { window.dispatch({ event: 'setMode', params: { mode: mode === 'assertingText' ? 'recording' : 'assertingText' } }); }}> + { + window.dispatch({ event: 'setMode', params: { mode: mode === 'assertingValue' ? 'recording' : 'assertingValue' } }); + }}> { copy(source.text); diff --git a/packages/recorder/src/recorderTypes.ts b/packages/recorder/src/recorderTypes.ts index e82e608554..c2b51549c4 100644 --- a/packages/recorder/src/recorderTypes.ts +++ b/packages/recorder/src/recorderTypes.ts @@ -18,7 +18,7 @@ import type { Language } from '../../playwright-core/src/utils/isomorphic/locato export type Point = { x: number, y: number }; -export type Mode = 'inspecting' | 'recording' | 'none' | 'assertingText' | 'recording-inspecting' | 'standby' | 'assertingVisibility'; +export type Mode = 'inspecting' | 'recording' | 'none' | 'assertingText' | 'recording-inspecting' | 'standby' | 'assertingVisibility' | 'assertingValue'; export type EventData = { event: 'clear' | 'resume' | 'step' | 'pause' | 'setMode' | 'selectorUpdated' | 'fileChanged'; From b66839b039713382f5b877551f4c6deb35240534 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Wed, 15 Nov 2023 17:15:25 +0100 Subject: [PATCH 055/491] fix(exposeFunction): exposeFunction should not leak client side BindingCalls (#28163) This should already make it a bit better. There is more going on tho. https://github.com/microsoft/playwright/issues/28146 --- .../src/server/dispatchers/pageDispatcher.ts | 2 + tests/library/channels.spec.ts | 83 +++++++++++++++++++ 2 files changed, 85 insertions(+) diff --git a/packages/playwright-core/src/server/dispatchers/pageDispatcher.ts b/packages/playwright-core/src/server/dispatchers/pageDispatcher.ts index 60e53a7ea9..48f042f902 100644 --- a/packages/playwright-core/src/server/dispatchers/pageDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/pageDispatcher.ts @@ -364,9 +364,11 @@ export class BindingCallDispatcher extends Dispatcher<{ guid: string }, channels async resolve(params: channels.BindingCallResolveParams, metadata: CallMetadata): Promise { this._resolve!(parseArgument(params.result)); + this._dispose(); } async reject(params: channels.BindingCallRejectParams, metadata: CallMetadata): Promise { this._reject!(parseError(params.error)); + this._dispose(); } } diff --git a/tests/library/channels.spec.ts b/tests/library/channels.spec.ts index 0527ef6cf0..c46d463c5b 100644 --- a/tests/library/channels.spec.ts +++ b/tests/library/channels.spec.ts @@ -256,6 +256,89 @@ it('should work with the domain module', async ({ browserType, server, browserNa throw err; }); +it('exposeFunction should not leak', async ({ page, expectScopeState, server }) => { + await page.goto(server.EMPTY_PAGE); + let called = 0; + await page.exposeFunction('myFunction', () => ++called); + for (let i = 0; i < 10; ++i) + await page.evaluate(() => (window as any).myFunction({ foo: 'bar' })); + expect(called).toBe(10); + expectScopeState(page, { + '_guid': '', + 'objects': [ + { + '_guid': 'android', + 'objects': [], + }, + { + '_guid': 'browser-type', + 'objects': [], + }, + { + '_guid': 'browser-type', + 'objects': [], + }, + { + '_guid': 'browser-type', + 'objects': [ + { + '_guid': 'browser', + 'objects': [ + { + '_guid': 'browser-context', + 'objects': [ + { + '_guid': 'page', + 'objects': [ + { + '_guid': 'frame', + 'objects': [], + }, + { + '_guid': 'request', + 'objects': [ + { + '_guid': 'response', + 'objects': [], + }, + ], + }, + ], + }, + { + '_guid': 'request-context', + 'objects': [], + }, + { + '_guid': 'tracing', + 'objects': [], + }, + ], + }, + ], + }, + ], + }, + { + '_guid': 'electron', + 'objects': [], + }, + { + '_guid': 'localUtils', + 'objects': [], + }, + { + '_guid': 'Playwright', + 'objects': [], + }, + { + '_guid': 'selectors', + 'objects': [], + }, + ], + }); +}); + function compareObjects(a, b) { if (a._guid !== b._guid) return a._guid.localeCompare(b._guid); From bb241abaff5f510c3396e3200abaddf53d9f9c1f Mon Sep 17 00:00:00 2001 From: Playwright Service <89237858+playwrightmachine@users.noreply.github.com> Date: Wed, 15 Nov 2023 10:34:53 -0800 Subject: [PATCH 056/491] feat(webkit): roll to r1944 (#28148) Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Max Schmitt --- packages/playwright-core/browsers.json | 2 +- .../playwright-core/src/server/injected/domUtils.ts | 11 ++++++++--- .../src/server/injected/injectedScript.ts | 3 ++- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/packages/playwright-core/browsers.json b/packages/playwright-core/browsers.json index 26426381ed..ea205cd268 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -39,7 +39,7 @@ }, { "name": "webkit", - "revision": "1943", + "revision": "1944", "installByDefault": true, "revisionOverrides": { "mac10.14": "1446", diff --git a/packages/playwright-core/src/server/injected/domUtils.ts b/packages/playwright-core/src/server/injected/domUtils.ts index 1b4922c913..b20de88754 100644 --- a/packages/playwright-core/src/server/injected/domUtils.ts +++ b/packages/playwright-core/src/server/injected/domUtils.ts @@ -14,6 +14,11 @@ * limitations under the License. */ +let browserNameForWorkarounds = ''; +export function setBrowserName(name: string) { + browserNameForWorkarounds = name; +} + export function isInsideScope(scope: Node, element: Element | undefined): boolean { while (element) { if (scope.contains(element)) @@ -75,9 +80,9 @@ export function isElementStyleVisibilityVisible(element: Element, style?: CSSSty // Element.checkVisibility checks for content-visibility and also looks at // styles up the flat tree including user-agent ShadowRoots, such as the // details element for example. - // @ts-ignore Typescript doesn't know that checkVisibility exists yet. - if (Element.prototype.checkVisibility) { - // @ts-ignore Typescript doesn't know that checkVisibility exists yet. + // All the browser implement it, but WebKit has a bug which prevents us from using it: + // https://bugs.webkit.org/show_bug.cgi?id=264733 + if (browserNameForWorkarounds !== 'webkit') { if (!element.checkVisibility({ checkOpacity: false, checkVisibilityCSS: false })) return false; } else { diff --git a/packages/playwright-core/src/server/injected/injectedScript.ts b/packages/playwright-core/src/server/injected/injectedScript.ts index fe0affdab4..1c87f8bf42 100644 --- a/packages/playwright-core/src/server/injected/injectedScript.ts +++ b/packages/playwright-core/src/server/injected/injectedScript.ts @@ -24,7 +24,7 @@ import type { NestedSelectorBody, ParsedSelector, ParsedSelectorPart } from '../ import { visitAllSelectorParts, parseSelector, stringifySelector } from '../../utils/isomorphic/selectorParser'; import { type TextMatcher, elementMatchesText, elementText, type ElementText, getElementLabels } from './selectorUtils'; import { SelectorEvaluatorImpl, sortInDOMOrder } from './selectorEvaluator'; -import { enclosingShadowRootOrDocument, isElementVisible, parentElementOrShadowHost } from './domUtils'; +import { enclosingShadowRootOrDocument, isElementVisible, parentElementOrShadowHost, setBrowserName } from './domUtils'; import type { CSSComplexSelectorList } from '../../utils/isomorphic/cssParser'; import { generateSelector, type GenerateSelectorOptions } from './selectorGenerator'; import type * as channels from '@protocol/channels'; @@ -137,6 +137,7 @@ export class InjectedScript { this._stableRafCount = stableRafCount; this._browserName = browserName; + setBrowserName(browserName); this._setupGlobalListenersRemovalDetection(); this._setupHitTargetInterceptors(); From 84d1260d1a9f392834b7b814cc08b10e2f0fb4b8 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Wed, 15 Nov 2023 19:40:10 +0100 Subject: [PATCH 057/491] fix(recorder): inspect element when starting typing in locator editor (#28134) --- packages/recorder/src/recorder.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/recorder/src/recorder.tsx b/packages/recorder/src/recorder.tsx index 69f8983c3f..3b47c7d085 100644 --- a/packages/recorder/src/recorder.tsx +++ b/packages/recorder/src/recorder.tsx @@ -109,9 +109,11 @@ export const Recorder: React.FC = ({ }, [paused]); const onEditorChange = React.useCallback((selector: string) => { + if (mode === 'none') + window.dispatch({ event: 'setMode', params: { mode: 'standby' } }); setLocator(selector); window.dispatch({ event: 'selectorUpdated', params: { selector } }); - }, []); + }, [mode]); return
From 30aa8cd904bb3a239deba9b562a379f06607bde3 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Wed, 15 Nov 2023 19:51:30 +0100 Subject: [PATCH 058/491] docs(input): use web-first assertion for isChecked() (#28162) Fixes https://github.com/microsoft/playwright/issues/28161 --- docs/src/input.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/src/input.md b/docs/src/input.md index 1569dbd31d..f340e03f48 100644 --- a/docs/src/input.md +++ b/docs/src/input.md @@ -90,7 +90,7 @@ Using [`method: Locator.setChecked`] is the easiest way to check and uncheck a c await page.getByLabel('I agree to the terms above').check(); // Assert the checked state -expect(await page.getByLabel('Subscribe to newsletter').isChecked()).toBeTruthy(); +expect(page.getByLabel('Subscribe to newsletter')).toBeChecked(); // Select the radio button await page.getByLabel('XL').check(); @@ -101,7 +101,7 @@ await page.getByLabel('XL').check(); page.getByLabel("I agree to the terms above").check(); // Assert the checked state -assertTrue(page.getByLabel("Subscribe to newsletter").isChecked()); +assertTrue(page.getByLabel("Subscribe to newsletter")).isChecked(); // Select the radio button page.getByLabel("XL").check(); @@ -112,7 +112,7 @@ page.getByLabel("XL").check(); await page.get_by_label('I agree to the terms above').check() # Assert the checked state -assert await page.get_by_label('Subscribe to newsletter').is_checked() is True +await expect(page.get_by_label('Subscribe to newsletter')).to_be_checked() # Select the radio button await page.get_by_label('XL').check() @@ -123,7 +123,7 @@ await page.get_by_label('XL').check() page.get_by_label('I agree to the terms above').check() # Assert the checked state -assert page.get_by_label('Subscribe to newsletter').is_checked() is True +expect(page.get_by_label('Subscribe to newsletter')).to_be_checked() # Select the radio button page.get_by_label('XL').check() @@ -134,7 +134,7 @@ page.get_by_label('XL').check() await page.GetByLabel("I agree to the terms above").CheckAsync(); // Assert the checked state -Assert.True(await page.GetByLabel("Subscribe to newsletter").IsCheckedAsync()); +await Expect(page.GetByLabel("Subscribe to newsletter")).ToBeCheckedAsync(); // Select the radio button await page.GetByLabel("XL").CheckAsync(); From 611badcf74cfa1e798f9f168a9c03dd50127c6b1 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Wed, 15 Nov 2023 20:09:36 +0100 Subject: [PATCH 059/491] fix: setInputFiles test in driver mode (#28166) https://github.com/microsoft/playwright/pull/28156 --- .../src/server/dispatchers/browserContextDispatcher.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/playwright-core/src/server/dispatchers/browserContextDispatcher.ts b/packages/playwright-core/src/server/dispatchers/browserContextDispatcher.ts index 1805f0db64..e9505de2c0 100644 --- a/packages/playwright-core/src/server/dispatchers/browserContextDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/browserContextDispatcher.ts @@ -178,8 +178,6 @@ export class BrowserContextDispatcher extends Dispatcher { - if (!this._context._browser._isCollocatedWithServer) - throw new Error('Cannot create temp file: the browser is not co-located with the server'); const dir = this._context._browser.options.artifactsDir; const tmpDir = path.join(dir, 'upload-' + createGuid()); await fs.promises.mkdir(tmpDir); From 1c8ceb0a029533c7c05b20176aaffd41eb6f65bb Mon Sep 17 00:00:00 2001 From: Daniel <3473356+D4N14L@users.noreply.github.com> Date: Wed, 15 Nov 2023 12:25:03 -0800 Subject: [PATCH 060/491] fix(html-reporter): Include specified host and port in the logged instructions to launch the HTML report (#28144) Signed-off-by: Daniel <3473356+D4N14L@users.noreply.github.com> --- packages/playwright/src/reporters/html.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/playwright/src/reporters/html.ts b/packages/playwright/src/reporters/html.ts index ddac659269..418f74a9d2 100644 --- a/packages/playwright/src/reporters/html.ts +++ b/packages/playwright/src/reporters/html.ts @@ -134,10 +134,12 @@ class HtmlReporter extends EmptyReporter { } else if (!FullConfigInternal.from(this.config)?.cliListOnly) { const packageManagerCommand = getPackageManagerExecCommand(); const relativeReportPath = this._outputFolder === standaloneDefaultFolder() ? '' : ' ' + path.relative(process.cwd(), this._outputFolder); + const hostArg = this._options.host ? ` --host ${this._options.host}` : ''; + const portArg = this._options.port ? ` --port ${this._options.port}` : ''; console.log(''); console.log('To open last HTML report run:'); console.log(colors.cyan(` - ${packageManagerCommand} playwright show-report${relativeReportPath} + ${packageManagerCommand} playwright show-report${relativeReportPath}${hostArg}${portArg} `)); } } From 0867c3ce5b2f7563c99f279d433885d8ec8423d9 Mon Sep 17 00:00:00 2001 From: Playwright Service <89237858+playwrightmachine@users.noreply.github.com> Date: Wed, 15 Nov 2023 12:31:01 -0800 Subject: [PATCH 061/491] feat(chromium): roll to r1091 (#28171) Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> --- README.md | 4 +- packages/playwright-core/browsers.json | 8 +- .../src/server/deviceDescriptorsSource.json | 96 +++++++++---------- 3 files changed, 54 insertions(+), 54 deletions(-) diff --git a/README.md b/README.md index 6398a385cd..02fa52647a 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-120.0.6099.18-blue.svg?logo=google-chrome)](https://www.chromium.org/Home) [![Firefox version](https://img.shields.io/badge/firefox-119.0-blue.svg?logo=firefoxbrowser)](https://www.mozilla.org/en-US/firefox/new/) [![WebKit version](https://img.shields.io/badge/webkit-17.4-blue.svg?logo=safari)](https://webkit.org/) +[![npm version](https://img.shields.io/npm/v/playwright.svg)](https://www.npmjs.com/package/playwright) [![Chromium version](https://img.shields.io/badge/chromium-120.0.6099.28-blue.svg?logo=google-chrome)](https://www.chromium.org/Home) [![Firefox version](https://img.shields.io/badge/firefox-119.0-blue.svg?logo=firefoxbrowser)](https://www.mozilla.org/en-US/firefox/new/) [![WebKit version](https://img.shields.io/badge/webkit-17.4-blue.svg?logo=safari)](https://webkit.org/) ## [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 120.0.6099.18 | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| Chromium 120.0.6099.28 | :white_check_mark: | :white_check_mark: | :white_check_mark: | | WebKit 17.4 | :white_check_mark: | :white_check_mark: | :white_check_mark: | | Firefox 119.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 ea205cd268..ed555a981c 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -3,15 +3,15 @@ "browsers": [ { "name": "chromium", - "revision": "1090", + "revision": "1091", "installByDefault": true, - "browserVersion": "120.0.6099.18" + "browserVersion": "120.0.6099.28" }, { "name": "chromium-with-symbols", - "revision": "1090", + "revision": "1091", "installByDefault": false, - "browserVersion": "120.0.6099.18" + "browserVersion": "120.0.6099.28" }, { "name": "chromium-tip-of-tree", diff --git a/packages/playwright-core/src/server/deviceDescriptorsSource.json b/packages/playwright-core/src/server/deviceDescriptorsSource.json index 1a7a6c1fa1..d8294a421f 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/120.0.6099.18 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.28 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/120.0.6099.18 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.28 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/120.0.6099.18 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.28 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/120.0.6099.18 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.28 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/120.0.6099.18 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.28 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/120.0.6099.18 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.28 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/120.0.6099.18 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.28 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/120.0.6099.18 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.28 Safari/537.36", "viewport": { "width": 1138, "height": 712 @@ -978,7 +978,7 @@ "defaultBrowserType": "webkit" }, "LG Optimus L70": { - "userAgent": "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/120.0.6099.18 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/120.0.6099.28 Mobile Safari/537.36", "viewport": { "width": 384, "height": 640 @@ -989,7 +989,7 @@ "defaultBrowserType": "chromium" }, "LG Optimus L70 landscape": { - "userAgent": "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/120.0.6099.18 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/120.0.6099.28 Mobile Safari/537.36", "viewport": { "width": 640, "height": 384 @@ -1000,7 +1000,7 @@ "defaultBrowserType": "chromium" }, "Microsoft Lumia 550": { - "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 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/120.0.6099.28 Mobile Safari/537.36 Edge/14.14263", "viewport": { "width": 640, "height": 360 @@ -1011,7 +1011,7 @@ "defaultBrowserType": "chromium" }, "Microsoft Lumia 550 landscape": { - "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 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/120.0.6099.28 Mobile Safari/537.36 Edge/14.14263", "viewport": { "width": 360, "height": 640 @@ -1022,7 +1022,7 @@ "defaultBrowserType": "chromium" }, "Microsoft Lumia 950": { - "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 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/120.0.6099.28 Mobile Safari/537.36 Edge/14.14263", "viewport": { "width": 360, "height": 640 @@ -1033,7 +1033,7 @@ "defaultBrowserType": "chromium" }, "Microsoft Lumia 950 landscape": { - "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 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/120.0.6099.28 Mobile Safari/537.36 Edge/14.14263", "viewport": { "width": 640, "height": 360 @@ -1044,7 +1044,7 @@ "defaultBrowserType": "chromium" }, "Nexus 10": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.28 Safari/537.36", "viewport": { "width": 800, "height": 1280 @@ -1055,7 +1055,7 @@ "defaultBrowserType": "chromium" }, "Nexus 10 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.28 Safari/537.36", "viewport": { "width": 1280, "height": 800 @@ -1066,7 +1066,7 @@ "defaultBrowserType": "chromium" }, "Nexus 4": { - "userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.28 Mobile Safari/537.36", "viewport": { "width": 384, "height": 640 @@ -1077,7 +1077,7 @@ "defaultBrowserType": "chromium" }, "Nexus 4 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.28 Mobile Safari/537.36", "viewport": { "width": 640, "height": 384 @@ -1088,7 +1088,7 @@ "defaultBrowserType": "chromium" }, "Nexus 5": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.28 Mobile Safari/537.36", "viewport": { "width": 360, "height": 640 @@ -1099,7 +1099,7 @@ "defaultBrowserType": "chromium" }, "Nexus 5 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.28 Mobile Safari/537.36", "viewport": { "width": 640, "height": 360 @@ -1110,7 +1110,7 @@ "defaultBrowserType": "chromium" }, "Nexus 5X": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 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/120.0.6099.28 Mobile Safari/537.36", "viewport": { "width": 412, "height": 732 @@ -1121,7 +1121,7 @@ "defaultBrowserType": "chromium" }, "Nexus 5X landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 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/120.0.6099.28 Mobile Safari/537.36", "viewport": { "width": 732, "height": 412 @@ -1132,7 +1132,7 @@ "defaultBrowserType": "chromium" }, "Nexus 6": { - "userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.28 Mobile Safari/537.36", "viewport": { "width": 412, "height": 732 @@ -1143,7 +1143,7 @@ "defaultBrowserType": "chromium" }, "Nexus 6 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.28 Mobile Safari/537.36", "viewport": { "width": 732, "height": 412 @@ -1154,7 +1154,7 @@ "defaultBrowserType": "chromium" }, "Nexus 6P": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 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/120.0.6099.28 Mobile Safari/537.36", "viewport": { "width": 412, "height": 732 @@ -1165,7 +1165,7 @@ "defaultBrowserType": "chromium" }, "Nexus 6P landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 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/120.0.6099.28 Mobile Safari/537.36", "viewport": { "width": 732, "height": 412 @@ -1176,7 +1176,7 @@ "defaultBrowserType": "chromium" }, "Nexus 7": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.28 Safari/537.36", "viewport": { "width": 600, "height": 960 @@ -1187,7 +1187,7 @@ "defaultBrowserType": "chromium" }, "Nexus 7 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.28 Safari/537.36", "viewport": { "width": 960, "height": 600 @@ -1242,7 +1242,7 @@ "defaultBrowserType": "webkit" }, "Pixel 2": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 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/120.0.6099.28 Mobile Safari/537.36", "viewport": { "width": 411, "height": 731 @@ -1253,7 +1253,7 @@ "defaultBrowserType": "chromium" }, "Pixel 2 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 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/120.0.6099.28 Mobile Safari/537.36", "viewport": { "width": 731, "height": 411 @@ -1264,7 +1264,7 @@ "defaultBrowserType": "chromium" }, "Pixel 2 XL": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 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/120.0.6099.28 Mobile Safari/537.36", "viewport": { "width": 411, "height": 823 @@ -1275,7 +1275,7 @@ "defaultBrowserType": "chromium" }, "Pixel 2 XL landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 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/120.0.6099.28 Mobile Safari/537.36", "viewport": { "width": 823, "height": 411 @@ -1286,7 +1286,7 @@ "defaultBrowserType": "chromium" }, "Pixel 3": { - "userAgent": "Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 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/120.0.6099.28 Mobile Safari/537.36", "viewport": { "width": 393, "height": 786 @@ -1297,7 +1297,7 @@ "defaultBrowserType": "chromium" }, "Pixel 3 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 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/120.0.6099.28 Mobile Safari/537.36", "viewport": { "width": 786, "height": 393 @@ -1308,7 +1308,7 @@ "defaultBrowserType": "chromium" }, "Pixel 4": { - "userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.28 Mobile Safari/537.36", "viewport": { "width": 353, "height": 745 @@ -1319,7 +1319,7 @@ "defaultBrowserType": "chromium" }, "Pixel 4 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.28 Mobile Safari/537.36", "viewport": { "width": 745, "height": 353 @@ -1330,7 +1330,7 @@ "defaultBrowserType": "chromium" }, "Pixel 4a (5G)": { - "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.28 Mobile Safari/537.36", "screen": { "width": 412, "height": 892 @@ -1345,7 +1345,7 @@ "defaultBrowserType": "chromium" }, "Pixel 4a (5G) landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.28 Mobile Safari/537.36", "screen": { "height": 892, "width": 412 @@ -1360,7 +1360,7 @@ "defaultBrowserType": "chromium" }, "Pixel 5": { - "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.28 Mobile Safari/537.36", "screen": { "width": 393, "height": 851 @@ -1375,7 +1375,7 @@ "defaultBrowserType": "chromium" }, "Pixel 5 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.28 Mobile Safari/537.36", "screen": { "width": 851, "height": 393 @@ -1390,7 +1390,7 @@ "defaultBrowserType": "chromium" }, "Pixel 7": { - "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.28 Mobile Safari/537.36", "screen": { "width": 412, "height": 915 @@ -1405,7 +1405,7 @@ "defaultBrowserType": "chromium" }, "Pixel 7 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.28 Mobile Safari/537.36", "screen": { "width": 915, "height": 412 @@ -1420,7 +1420,7 @@ "defaultBrowserType": "chromium" }, "Moto G4": { - "userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.28 Mobile Safari/537.36", "viewport": { "width": 360, "height": 640 @@ -1431,7 +1431,7 @@ "defaultBrowserType": "chromium" }, "Moto G4 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.28 Mobile Safari/537.36", "viewport": { "width": 640, "height": 360 @@ -1442,7 +1442,7 @@ "defaultBrowserType": "chromium" }, "Desktop Chrome HiDPI": { - "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 Safari/537.36", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.28 Safari/537.36", "screen": { "width": 1792, "height": 1120 @@ -1457,7 +1457,7 @@ "defaultBrowserType": "chromium" }, "Desktop Edge HiDPI": { - "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 Safari/537.36 Edg/120.0.6099.18", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.28 Safari/537.36 Edg/120.0.6099.28", "screen": { "width": 1792, "height": 1120 @@ -1502,7 +1502,7 @@ "defaultBrowserType": "webkit" }, "Desktop Chrome": { - "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 Safari/537.36", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.28 Safari/537.36", "screen": { "width": 1920, "height": 1080 @@ -1517,7 +1517,7 @@ "defaultBrowserType": "chromium" }, "Desktop Edge": { - "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.18 Safari/537.36 Edg/120.0.6099.18", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.28 Safari/537.36 Edg/120.0.6099.28", "screen": { "width": 1920, "height": 1080 From 7ffcb42551d920a4733e37f1b16bb0441996bb92 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Wed, 15 Nov 2023 21:48:47 +0100 Subject: [PATCH 062/491] test: fix 'exposeFunction should not leak' in video mode (#28169) This is like how we do it with the other channel tests. In video mode we produce artifacts so we need to add them to our expectation. --- tests/library/channels.spec.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/library/channels.spec.ts b/tests/library/channels.spec.ts index c46d463c5b..dc3ec1b5f5 100644 --- a/tests/library/channels.spec.ts +++ b/tests/library/channels.spec.ts @@ -256,7 +256,7 @@ it('should work with the domain module', async ({ browserType, server, browserNa throw err; }); -it('exposeFunction should not leak', async ({ page, expectScopeState, server }) => { +it('exposeFunction should not leak', async ({ page, expectScopeState, server, video }) => { await page.goto(server.EMPTY_PAGE); let called = 0; await page.exposeFunction('myFunction', () => ++called); @@ -284,6 +284,7 @@ it('exposeFunction should not leak', async ({ page, expectScopeState, server }) { '_guid': 'browser', 'objects': [ + ...(video === 'on' ? [{ _guid: 'artifact', objects: [] }] : []), { '_guid': 'browser-context', 'objects': [ From 80bab8afae12603e9a8ed6a094cf7a7317e51a45 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Thu, 16 Nov 2023 00:10:13 +0100 Subject: [PATCH 063/491] fix(electron/android): re-add Element.prototype.checkVisibility check (#28173) Regressed in https://github.com/microsoft/playwright/pull/28148. --- packages/playwright-core/src/server/injected/domUtils.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/playwright-core/src/server/injected/domUtils.ts b/packages/playwright-core/src/server/injected/domUtils.ts index b20de88754..b88cb44c0a 100644 --- a/packages/playwright-core/src/server/injected/domUtils.ts +++ b/packages/playwright-core/src/server/injected/domUtils.ts @@ -82,7 +82,8 @@ export function isElementStyleVisibilityVisible(element: Element, style?: CSSSty // details element for example. // All the browser implement it, but WebKit has a bug which prevents us from using it: // https://bugs.webkit.org/show_bug.cgi?id=264733 - if (browserNameForWorkarounds !== 'webkit') { + // @ts-ignore + if (Element.prototype.checkVisibility && browserNameForWorkarounds !== 'webkit') { if (!element.checkVisibility({ checkOpacity: false, checkVisibilityCSS: false })) return false; } else { From 4575c9a182b72df4d6720690ebe5b4911d240a45 Mon Sep 17 00:00:00 2001 From: Siddharth Singha Roy Date: Thu, 16 Nov 2023 05:07:14 +0530 Subject: [PATCH 064/491] chore(logs): Add new log level to capture client-server message's metadata information (#28141) Goal - Capture minimal diagnostic information for each message being sent between the playwright client and server. --------- Co-authored-by: Siddharth Singha Roy --- .../playwright-core/src/common/debugLogger.ts | 1 + .../src/remote/playwrightConnection.ts | 18 +++++++++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/packages/playwright-core/src/common/debugLogger.ts b/packages/playwright-core/src/common/debugLogger.ts index 6948058685..bda373e0ef 100644 --- a/packages/playwright-core/src/common/debugLogger.ts +++ b/packages/playwright-core/src/common/debugLogger.ts @@ -28,6 +28,7 @@ const debugLoggerColorMap = { 'channel': 33, // blue 'server': 45, // cyan 'server:channel': 34, // green + 'server:metadata': 33, // blue }; export type LogName = keyof typeof debugLoggerColorMap; diff --git a/packages/playwright-core/src/remote/playwrightConnection.ts b/packages/playwright-core/src/remote/playwrightConnection.ts index 7da1eefee2..c6898f7148 100644 --- a/packages/playwright-core/src/remote/playwrightConnection.ts +++ b/packages/playwright-core/src/remote/playwrightConnection.ts @@ -76,15 +76,20 @@ export class PlaywrightConnection { const messageString = JSON.stringify(message); if (debugLogger.isEnabled('server:channel')) debugLogger.log('server:channel', `[${this._id}] ${monotonicTime() * 1000} SEND ► ${messageString}`); + if (debugLogger.isEnabled('server:metadata')) + this.logServerMetadata(message, messageString, 'SEND'); ws.send(messageString); } }; ws.on('message', async (message: string) => { await lock; const messageString = Buffer.from(message).toString(); + const jsonMessage = JSON.parse(messageString); if (debugLogger.isEnabled('server:channel')) debugLogger.log('server:channel', `[${this._id}] ${monotonicTime() * 1000} ◀ RECV ${messageString}`); - this._dispatcherConnection.dispatch(JSON.parse(messageString)); + if (debugLogger.isEnabled('server:metadata')) + this.logServerMetadata(jsonMessage, messageString, 'RECV'); + this._dispatcherConnection.dispatch(jsonMessage); }); ws.on('close', () => this._onDisconnect()); @@ -245,6 +250,17 @@ export class PlaywrightConnection { debugLogger.log('server', `[${this._id}] finished cleanup`); } + private logServerMetadata(message: object, messageString: string, direction: 'SEND' | 'RECV') { + const serverLogMetadata = { + wallTime: Date.now(), + id: (message as any).id, + guid: (message as any).guid, + method: (message as any).method, + payloadSizeInBytes: Buffer.byteLength(messageString, 'utf-8') + }; + debugLogger.log('server:metadata', (direction === 'SEND' ? 'SEND ► ' : '◀ RECV ') + JSON.stringify(serverLogMetadata)); + } + async close(reason?: { code: number, reason: string }) { if (this._disconnected) return; From 25b9c4eb4ae7aac078cecb731faaa32128c57c8a Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Wed, 15 Nov 2023 18:27:32 -0800 Subject: [PATCH 065/491] chore: do not lose error name for js errors (#28177) --- packages/playwright/src/util.ts | 4 ++-- tests/playwright-test/fixture-errors.spec.ts | 2 +- tests/playwright-test/list-files.spec.ts | 2 +- tests/playwright-test/playwright.trace.spec.ts | 2 +- tests/playwright-test/reporter.spec.ts | 8 ++++---- tests/playwright-test/runner.spec.ts | 2 +- tests/playwright-test/test-output-dir.spec.ts | 4 ++-- tests/playwright-test/ui-mode-test-source.spec.ts | 2 +- 8 files changed, 13 insertions(+), 13 deletions(-) diff --git a/packages/playwright/src/util.ts b/packages/playwright/src/util.ts index f4c2da22db..1e1acc62ba 100644 --- a/packages/playwright/src/util.ts +++ b/packages/playwright/src/util.ts @@ -32,11 +32,11 @@ const PLAYWRIGHT_CORE_PATH = path.dirname(require.resolve('playwright-core/packa export function filterStackTrace(e: Error): { message: string, stack: string } { if (process.env.PWDEBUGIMPL) - return { message: e.message, stack: e.stack || '' }; + return { message: e.name + ': ' + e.message, stack: e.stack || '' }; const stackLines = stringifyStackFrames(filteredStackTrace(e.stack?.split('\n') || [])); return { - message: e.message, + message: e.name + ': ' + e.message, stack: `${e.name}: ${e.message}\n${stackLines.join('\n')}` }; } diff --git a/tests/playwright-test/fixture-errors.spec.ts b/tests/playwright-test/fixture-errors.spec.ts index f776ae48c8..f1813ab26c 100644 --- a/tests/playwright-test/fixture-errors.spec.ts +++ b/tests/playwright-test/fixture-errors.spec.ts @@ -349,7 +349,7 @@ test('should throw when calling runTest twice', async ({ runInlineTest }) => { test('works', async ({foo}) => {}); `, }); - expect(result.results[0].error.message).toBe('Cannot provide fixture value for the second time'); + expect(result.results[0].error.message).toBe('Error: Cannot provide fixture value for the second time'); expect(result.exitCode).toBe(1); }); diff --git a/tests/playwright-test/list-files.spec.ts b/tests/playwright-test/list-files.spec.ts index 263d11a226..25058eabeb 100644 --- a/tests/playwright-test/list-files.spec.ts +++ b/tests/playwright-test/list-files.spec.ts @@ -94,7 +94,7 @@ test('should report error', async ({ runListFiles }) => { line: 3, column: 8, }, - message: 'Assignment to constant variable.', + message: 'TypeError: Assignment to constant variable.', stack: expect.stringContaining('TypeError: Assignment to constant variable.'), } }); diff --git a/tests/playwright-test/playwright.trace.spec.ts b/tests/playwright-test/playwright.trace.spec.ts index 5ac1c1fb31..ec75ae0ef6 100644 --- a/tests/playwright-test/playwright.trace.spec.ts +++ b/tests/playwright-test/playwright.trace.spec.ts @@ -668,7 +668,7 @@ test('should show non-expect error in trace', async ({ runInlineTest }, testInfo ' fixture: page', ' browserContext.newPage', 'expect.toBe', - 'undefinedVariable1 is not defined', + 'ReferenceError: undefinedVariable1 is not defined', 'After Hooks', ' fixture: page', ' fixture: context', diff --git a/tests/playwright-test/reporter.spec.ts b/tests/playwright-test/reporter.spec.ts index 08a200623b..d98b580c2e 100644 --- a/tests/playwright-test/reporter.spec.ts +++ b/tests/playwright-test/reporter.spec.ts @@ -454,7 +454,7 @@ for (const useIntermediateMergeReport of [false, true] as const) { `begin {\"title\":\"page.setContent\",\"category\":\"pw:api\"}`, `end {\"title\":\"page.setContent\",\"category\":\"pw:api\"}`, `begin {\"title\":\"page.click(input)\",\"category\":\"pw:api\"}`, - `end {\"title\":\"page.click(input)\",\"category\":\"pw:api\",\"error\":{\"message\":\"page.click: Timeout 1ms exceeded.\\nCall log:\\n \\u001b[2m- waiting for locator('input')\\u001b[22m\\n\",\"stack\":\"\",\"location\":\"\",\"snippet\":\"\"}}`, + `end {\"title\":\"page.click(input)\",\"category\":\"pw:api\",\"error\":{\"message\":\"Error: page.click: Timeout 1ms exceeded.\\nCall log:\\n \\u001b[2m- waiting for locator('input')\\u001b[22m\\n\",\"stack\":\"\",\"location\":\"\",\"snippet\":\"\"}}`, `begin {\"title\":\"After Hooks\",\"category\":\"hook\"}`, `begin {\"title\":\"fixture: page\",\"category\":\"fixture\"}`, `end {\"title\":\"fixture: page\",\"category\":\"fixture\"}`, @@ -567,7 +567,7 @@ for (const useIntermediateMergeReport of [false, true] as const) { }, { 'reporter': '' }); expect(result.exitCode).toBe(1); - expect(result.output).toContain(`%%got error: No tests found`); + expect(result.output).toContain(`%%got error: Error: No tests found`); }); test('should report require error to reporter', async ({ runInlineTest }) => { @@ -584,7 +584,7 @@ for (const useIntermediateMergeReport of [false, true] as const) { }, { 'reporter': '' }); expect(result.exitCode).toBe(1); - expect(result.output).toContain(`%%got error: Oh my!`); + expect(result.output).toContain(`%%got error: Error: Oh my!`); }); test('should report global setup error to reporter', async ({ runInlineTest }) => { @@ -608,7 +608,7 @@ for (const useIntermediateMergeReport of [false, true] as const) { }, { 'reporter': '' }); expect(result.exitCode).toBe(1); - expect(result.output).toContain(`%%got error: Oh my!`); + expect(result.output).toContain(`%%got error: Error: Oh my!`); }); test('should report correct tests/suites when using grep', async ({ runInlineTest }) => { diff --git a/tests/playwright-test/runner.spec.ts b/tests/playwright-test/runner.spec.ts index ceb03251c7..088a73ead1 100644 --- a/tests/playwright-test/runner.spec.ts +++ b/tests/playwright-test/runner.spec.ts @@ -236,7 +236,7 @@ test('should use the first occurring error when an unhandled exception was throw expect(result.exitCode).toBe(1); expect(result.passed).toBe(0); expect(result.failed).toBe(1); - expect(result.report.suites[0].specs[0].tests[0].results[0].error!.message).toBe('first error'); + expect(result.report.suites[0].specs[0].tests[0].results[0].error!.message).toBe('Error: first error'); }); test('worker interrupt should report errors', async ({ interactWithTestRunner }) => { diff --git a/tests/playwright-test/test-output-dir.spec.ts b/tests/playwright-test/test-output-dir.spec.ts index 07a5f5be15..6d74551303 100644 --- a/tests/playwright-test/test-output-dir.spec.ts +++ b/tests/playwright-test/test-output-dir.spec.ts @@ -51,12 +51,12 @@ test('should work and remove non-failures', async ({ runInlineTest }, testInfo) expect(result.results[0].status).toBe('failed'); expect(result.results[0].retry).toBe(0); // Should only fail the last retry check. - expect(result.results[0].error.message).toBe('Give me retries'); + expect(result.results[0].error.message).toBe('Error: Give me retries'); expect(result.results[1].status).toBe('failed'); expect(result.results[1].retry).toBe(1); // Should only fail the last retry check. - expect(result.results[1].error.message).toBe('Give me retries'); + expect(result.results[1].error.message).toBe('Error: Give me retries'); expect(result.results[2].status).toBe('passed'); expect(result.results[2].retry).toBe(2); diff --git a/tests/playwright-test/ui-mode-test-source.spec.ts b/tests/playwright-test/ui-mode-test-source.spec.ts index 8e7a35f5db..5d2973a8cf 100644 --- a/tests/playwright-test/ui-mode-test-source.spec.ts +++ b/tests/playwright-test/ui-mode-test-source.spec.ts @@ -97,7 +97,7 @@ test('should show top-level errors in file', async ({ runUITest }) => { page.locator('.CodeMirror-linewidget') ).toHaveText([ '            ', - 'Assignment to constant variable.' + 'TypeError: Assignment to constant variable.' ]); }); From aec4399d8f97e06470859e50e2acf81efe748d64 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Wed, 15 Nov 2023 18:38:43 -0800 Subject: [PATCH 066/491] docs: release notes for v1.40 (#28175) --- docs/src/release-notes-csharp.md | 44 +++++++++++++++++++++++++++++ docs/src/release-notes-java.md | 44 +++++++++++++++++++++++++++++ docs/src/release-notes-js.md | 48 ++++++++++++++++++++++++++++++++ docs/src/release-notes-python.md | 47 +++++++++++++++++++++++++++++++ 4 files changed, 183 insertions(+) diff --git a/docs/src/release-notes-csharp.md b/docs/src/release-notes-csharp.md index 1383b0a0b3..9209694046 100644 --- a/docs/src/release-notes-csharp.md +++ b/docs/src/release-notes-csharp.md @@ -4,6 +4,50 @@ title: "Release notes" toc_max_heading_level: 2 --- +## Version 1.40 + +### Test Generator Update + +![Playwright Test Generator](https://github.com/microsoft/playwright/assets/9881434/8c3d6fac-5381-4aaf-920f-6e22b964eec6) + +New tools to generate assertions: +- "Assert visibility" tool generates [`method: LocatorAssertions.toBeVisible`]. +- "Assert value" tool generates [`method: LocatorAssertions.toHaveValue`]. +- "Assert text" tool generates [`method: LocatorAssertions.toContainText`]. + +Here is an example of a generated test with assertions: + +```csharp +await Page.GotoAsync("https://playwright.dev/"); +await Page.GetByRole(AriaRole.Link, new() { Name = "Get started" }).ClickAsync(); +await Expect(Page.GetByLabel("Breadcrumbs").GetByRole(AriaRole.List)).ToContainTextAsync("Installation"); +await Expect(Page.GetByLabel("Search")).ToBeVisibleAsync(); +await Page.GetByLabel("Search").ClickAsync(); +await Page.GetByPlaceholder("Search docs").FillAsync("locator"); +await Expect(Page.GetByPlaceholder("Search docs")).ToHaveValueAsync("locator"); +``` + +### New APIs + +- Option [`option: reason`] in [`method: Page.close`], [`method: BrowserContext.close`] and [`method: Browser.close`]. Close reason is reported for all operations interrupted by the closure. +- Option [`option: firefoxUserPrefs`] in [`method: BrowserType.launchPersistentContext`]. + +### Other Changes + +- Methods [`method: Download.path`] and [`method: Download.createReadStream`] throw an error for failed and cancelled downloads. +- Playwright [docker image](./docker.md) now comes with Node.js v20. + +### Browser Versions + +* Chromium 120.0.6099.28 +* Mozilla Firefox 119.0 +* WebKit 17.4 + +This version was also tested against the following stable channels: + +* Google Chrome 119 +* Microsoft Edge 119 + ## Version 1.39 Evergreen browsers update. diff --git a/docs/src/release-notes-java.md b/docs/src/release-notes-java.md index a07ebf448b..041b390c09 100644 --- a/docs/src/release-notes-java.md +++ b/docs/src/release-notes-java.md @@ -4,6 +4,50 @@ title: "Release notes" toc_max_heading_level: 2 --- +## Version 1.40 + +### Test Generator Update + +![Playwright Test Generator](https://github.com/microsoft/playwright/assets/9881434/8c3d6fac-5381-4aaf-920f-6e22b964eec6) + +New tools to generate assertions: +- "Assert visibility" tool generates [`method: LocatorAssertions.toBeVisible`]. +- "Assert value" tool generates [`method: LocatorAssertions.toHaveValue`]. +- "Assert text" tool generates [`method: LocatorAssertions.toContainText`]. + +Here is an example of a generated test with assertions: + +```java +page.navigate("https://playwright.dev/"); +page.getByRole(AriaRole.LINK, new Page.GetByRoleOptions().setName("Get started")).click(); +assertThat(page.getByLabel("Breadcrumbs").getByRole(AriaRole.LIST)).containsText("Installation"); +assertThat(page.getByLabel("Search")).isVisible(); +page.getByLabel("Search").click(); +page.getByPlaceholder("Search docs").fill("locator"); +assertThat(page.getByPlaceholder("Search docs")).hasValue("locator"); +``` + +### New APIs + +- Option [`option: reason`] in [`method: Page.close`], [`method: BrowserContext.close`] and [`method: Browser.close`]. Close reason is reported for all operations interrupted by the closure. +- Option [`option: firefoxUserPrefs`] in [`method: BrowserType.launchPersistentContext`]. + +### Other Changes + +- Methods [`method: Download.path`] and [`method: Download.createReadStream`] throw an error for failed and cancelled downloads. +- Playwright [docker image](./docker.md) now comes with Node.js v20. + +### Browser Versions + +* Chromium 120.0.6099.28 +* Mozilla Firefox 119.0 +* WebKit 17.4 + +This version was also tested against the following stable channels: + +* Google Chrome 119 +* Microsoft Edge 119 + ## Version 1.39 Evergreen browsers update. diff --git a/docs/src/release-notes-js.md b/docs/src/release-notes-js.md index 164108ad1c..38a06d0149 100644 --- a/docs/src/release-notes-js.md +++ b/docs/src/release-notes-js.md @@ -6,6 +6,54 @@ toc_max_heading_level: 2 import LiteYouTube from '@site/src/components/LiteYouTube'; +## Version 1.40 + +### Test Generator Update + +![Playwright Test Generator](https://github.com/microsoft/playwright/assets/9881434/8c3d6fac-5381-4aaf-920f-6e22b964eec6) + +New tools to generate assertions: +- "Assert visibility" tool generates [`method: LocatorAssertions.toBeVisible`]. +- "Assert value" tool generates [`method: LocatorAssertions.toHaveValue`]. +- "Assert text" tool generates [`method: LocatorAssertions.toContainText`]. + +Here is an example of a generated test with assertions: + +```js +import { test, expect } from '@playwright/test'; + +test('test', async ({ page }) => { + await page.goto('https://playwright.dev/'); + await page.getByRole('link', { name: 'Get started' }).click(); + await expect(page.getByLabel('Breadcrumbs').getByRole('list')).toContainText('Installation'); + await expect(page.getByLabel('Search')).toBeVisible(); + await page.getByLabel('Search').click(); + await page.getByPlaceholder('Search docs').fill('locator'); + await expect(page.getByPlaceholder('Search docs')).toHaveValue('locator'); +}); +``` + +### New APIs + +- Option [`option: reason`] in [`method: Page.close`], [`method: BrowserContext.close`] and [`method: Browser.close`]. Close reason is reported for all operations interrupted by the closure. +- Option [`option: firefoxUserPrefs`] in [`method: BrowserType.launchPersistentContext`]. + +### Other Changes + +- Methods [`method: Download.path`] and [`method: Download.createReadStream`] throw an error for failed and cancelled downloads. +- Playwright [docker image](./docker.md) now comes with Node.js v20. + +### Browser Versions + +* Chromium 120.0.6099.28 +* Mozilla Firefox 119.0 +* WebKit 17.4 + +This version was also tested against the following stable channels: + +* Google Chrome 119 +* Microsoft Edge 119 + ## Version 1.39 None: + page.goto("https://playwright.dev/") + page.get_by_role("link", name="Get started").click() + expect(page.get_by_label("Breadcrumbs").get_by_role("list")).to_contain_text("Installation") + expect(page.get_by_label("Search")).to_be_visible() + page.get_by_label("Search").click() + page.get_by_placeholder("Search docs").fill("locator") + expect(page.get_by_placeholder("Search docs")).to_have_value("locator"); +``` + +### New APIs + +- Option [`option: reason`] in [`method: Page.close`], [`method: BrowserContext.close`] and [`method: Browser.close`]. Close reason is reported for all operations interrupted by the closure. +- Option [`option: firefoxUserPrefs`] in [`method: BrowserType.launchPersistentContext`]. + +### Other Changes + +- Method [`method: Download.path`] throws an error for failed and cancelled downloads. +- Playwright [docker image](./docker.md) now comes with Node.js v20. + +### Browser Versions + +* Chromium 120.0.6099.28 +* Mozilla Firefox 119.0 +* WebKit 17.4 + +This version was also tested against the following stable channels: + +* Google Chrome 119 +* Microsoft Edge 119 + ## Version 1.39 Evergreen browsers update. From 85438edb97772d501443eaf5deaeab9f064b2045 Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Wed, 15 Nov 2023 18:47:42 -0800 Subject: [PATCH 067/491] test: Intl.ListFormat is working in playwright all browsers (#28178) Fixes https://github.com/microsoft/playwright/issues/23978 --- tests/library/capabilities.spec.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/library/capabilities.spec.ts b/tests/library/capabilities.spec.ts index fa7d6a9ede..604ad479b1 100644 --- a/tests/library/capabilities.spec.ts +++ b/tests/library/capabilities.spec.ts @@ -283,3 +283,17 @@ it('should send no Content-Length header for GET requests with a Content-Type', ]); expect(request.headers['content-length']).toBe(undefined); }); + +it('Intl.ListFormat should work', async ({ page, server }) => { + it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/23978' }); + await page.goto(server.EMPTY_PAGE); + const formatted = await page.evaluate(() => { + const data = ['first', 'second', 'third']; + const listFormat = new Intl.ListFormat('en', { + type: 'disjunction', + style: 'short', + }); + return listFormat.format(data); + }); + expect(formatted).toBe('first, second, or third'); +}); From 8150b27413d1cd4606375eed626416edc0c45ef5 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Wed, 15 Nov 2023 20:05:36 -0800 Subject: [PATCH 068/491] chore: mark version 1.41.0-next (#28180) --- package-lock.json | 100 +++++++++--------- package.json | 2 +- .../playwright-browser-chromium/package.json | 4 +- .../playwright-browser-firefox/package.json | 4 +- .../playwright-browser-webkit/package.json | 4 +- packages/playwright-chromium/package.json | 4 +- packages/playwright-core/package.json | 2 +- packages/playwright-ct-core/package.json | 6 +- packages/playwright-ct-react/package.json | 4 +- packages/playwright-ct-react17/package.json | 4 +- packages/playwright-ct-solid/package.json | 4 +- packages/playwright-ct-svelte/package.json | 4 +- packages/playwright-ct-vue/package.json | 4 +- packages/playwright-ct-vue2/package.json | 4 +- packages/playwright-firefox/package.json | 4 +- packages/playwright-test/package.json | 4 +- packages/playwright-webkit/package.json | 4 +- packages/playwright/package.json | 4 +- 18 files changed, 83 insertions(+), 83 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0d7725e9e8..de3c6ed5de 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "playwright-internal", - "version": "1.40.0-next", + "version": "1.41.0-next", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "playwright-internal", - "version": "1.40.0-next", + "version": "1.41.0-next", "license": "Apache-2.0", "workspaces": [ "packages/*" @@ -6976,10 +6976,10 @@ } }, "packages/playwright": { - "version": "1.40.0-next", + "version": "1.41.0-next", "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.40.0-next" + "playwright-core": "1.41.0-next" }, "bin": { "playwright": "cli.js" @@ -6993,11 +6993,11 @@ }, "packages/playwright-browser-chromium": { "name": "@playwright/browser-chromium", - "version": "1.40.0-next", + "version": "1.41.0-next", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.40.0-next" + "playwright-core": "1.41.0-next" }, "engines": { "node": ">=16" @@ -7005,11 +7005,11 @@ }, "packages/playwright-browser-firefox": { "name": "@playwright/browser-firefox", - "version": "1.40.0-next", + "version": "1.41.0-next", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.40.0-next" + "playwright-core": "1.41.0-next" }, "engines": { "node": ">=16" @@ -7017,22 +7017,22 @@ }, "packages/playwright-browser-webkit": { "name": "@playwright/browser-webkit", - "version": "1.40.0-next", + "version": "1.41.0-next", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.40.0-next" + "playwright-core": "1.41.0-next" }, "engines": { "node": ">=16" } }, "packages/playwright-chromium": { - "version": "1.40.0-next", + "version": "1.41.0-next", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.40.0-next" + "playwright-core": "1.41.0-next" }, "bin": { "playwright": "cli.js" @@ -7042,7 +7042,7 @@ } }, "packages/playwright-core": { - "version": "1.40.0-next", + "version": "1.41.0-next", "license": "Apache-2.0", "bin": { "playwright-core": "cli.js" @@ -7053,11 +7053,11 @@ }, "packages/playwright-ct-core": { "name": "@playwright/experimental-ct-core", - "version": "1.40.0-next", + "version": "1.41.0-next", "license": "Apache-2.0", "dependencies": { - "playwright": "1.40.0-next", - "playwright-core": "1.40.0-next", + "playwright": "1.41.0-next", + "playwright-core": "1.41.0-next", "vite": "^4.4.10" }, "bin": { @@ -7069,10 +7069,10 @@ }, "packages/playwright-ct-react": { "name": "@playwright/experimental-ct-react", - "version": "1.40.0-next", + "version": "1.41.0-next", "license": "Apache-2.0", "dependencies": { - "@playwright/experimental-ct-core": "1.40.0-next", + "@playwright/experimental-ct-core": "1.41.0-next", "@vitejs/plugin-react": "^4.0.0" }, "bin": { @@ -7101,10 +7101,10 @@ }, "packages/playwright-ct-react17": { "name": "@playwright/experimental-ct-react17", - "version": "1.40.0-next", + "version": "1.41.0-next", "license": "Apache-2.0", "dependencies": { - "@playwright/experimental-ct-core": "1.40.0-next", + "@playwright/experimental-ct-core": "1.41.0-next", "@vitejs/plugin-react": "^4.0.0" }, "bin": { @@ -7133,10 +7133,10 @@ }, "packages/playwright-ct-solid": { "name": "@playwright/experimental-ct-solid", - "version": "1.40.0-next", + "version": "1.41.0-next", "license": "Apache-2.0", "dependencies": { - "@playwright/experimental-ct-core": "1.40.0-next", + "@playwright/experimental-ct-core": "1.41.0-next", "vite-plugin-solid": "^2.7.0" }, "bin": { @@ -7151,10 +7151,10 @@ }, "packages/playwright-ct-svelte": { "name": "@playwright/experimental-ct-svelte", - "version": "1.40.0-next", + "version": "1.41.0-next", "license": "Apache-2.0", "dependencies": { - "@playwright/experimental-ct-core": "1.40.0-next", + "@playwright/experimental-ct-core": "1.41.0-next", "@sveltejs/vite-plugin-svelte": "^2.1.1" }, "bin": { @@ -7169,10 +7169,10 @@ }, "packages/playwright-ct-vue": { "name": "@playwright/experimental-ct-vue", - "version": "1.40.0-next", + "version": "1.41.0-next", "license": "Apache-2.0", "dependencies": { - "@playwright/experimental-ct-core": "1.40.0-next", + "@playwright/experimental-ct-core": "1.41.0-next", "@vitejs/plugin-vue": "^4.2.1" }, "bin": { @@ -7220,10 +7220,10 @@ }, "packages/playwright-ct-vue2": { "name": "@playwright/experimental-ct-vue2", - "version": "1.40.0-next", + "version": "1.41.0-next", "license": "Apache-2.0", "dependencies": { - "@playwright/experimental-ct-core": "1.40.0-next", + "@playwright/experimental-ct-core": "1.41.0-next", "@vitejs/plugin-vue2": "^2.2.0" }, "bin": { @@ -7237,11 +7237,11 @@ } }, "packages/playwright-firefox": { - "version": "1.40.0-next", + "version": "1.41.0-next", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.40.0-next" + "playwright-core": "1.41.0-next" }, "bin": { "playwright": "cli.js" @@ -7275,10 +7275,10 @@ }, "packages/playwright-test": { "name": "@playwright/test", - "version": "1.40.0-next", + "version": "1.41.0-next", "license": "Apache-2.0", "dependencies": { - "playwright": "1.40.0-next" + "playwright": "1.41.0-next" }, "bin": { "playwright": "cli.js" @@ -7288,11 +7288,11 @@ } }, "packages/playwright-webkit": { - "version": "1.40.0-next", + "version": "1.41.0-next", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.40.0-next" + "playwright-core": "1.41.0-next" }, "bin": { "playwright": "cli.js" @@ -8032,33 +8032,33 @@ "@playwright/browser-chromium": { "version": "file:packages/playwright-browser-chromium", "requires": { - "playwright-core": "1.40.0-next" + "playwright-core": "1.41.0-next" } }, "@playwright/browser-firefox": { "version": "file:packages/playwright-browser-firefox", "requires": { - "playwright-core": "1.40.0-next" + "playwright-core": "1.41.0-next" } }, "@playwright/browser-webkit": { "version": "file:packages/playwright-browser-webkit", "requires": { - "playwright-core": "1.40.0-next" + "playwright-core": "1.41.0-next" } }, "@playwright/experimental-ct-core": { "version": "file:packages/playwright-ct-core", "requires": { - "playwright": "1.40.0-next", - "playwright-core": "1.40.0-next", + "playwright": "1.41.0-next", + "playwright-core": "1.41.0-next", "vite": "^4.4.10" } }, "@playwright/experimental-ct-react": { "version": "file:packages/playwright-ct-react", "requires": { - "@playwright/experimental-ct-core": "1.40.0-next", + "@playwright/experimental-ct-core": "1.41.0-next", "@vitejs/plugin-react": "^4.0.0" }, "dependencies": { @@ -8078,7 +8078,7 @@ "@playwright/experimental-ct-react17": { "version": "file:packages/playwright-ct-react17", "requires": { - "@playwright/experimental-ct-core": "1.40.0-next", + "@playwright/experimental-ct-core": "1.41.0-next", "@vitejs/plugin-react": "^4.0.0" }, "dependencies": { @@ -8098,7 +8098,7 @@ "@playwright/experimental-ct-solid": { "version": "file:packages/playwright-ct-solid", "requires": { - "@playwright/experimental-ct-core": "1.40.0-next", + "@playwright/experimental-ct-core": "1.41.0-next", "solid-js": "^1.7.0", "vite-plugin-solid": "^2.7.0" } @@ -8106,7 +8106,7 @@ "@playwright/experimental-ct-svelte": { "version": "file:packages/playwright-ct-svelte", "requires": { - "@playwright/experimental-ct-core": "1.40.0-next", + "@playwright/experimental-ct-core": "1.41.0-next", "@sveltejs/vite-plugin-svelte": "^2.1.1", "svelte": "^3.55.1" } @@ -8114,7 +8114,7 @@ "@playwright/experimental-ct-vue": { "version": "file:packages/playwright-ct-vue", "requires": { - "@playwright/experimental-ct-core": "1.40.0-next", + "@playwright/experimental-ct-core": "1.41.0-next", "@vitejs/plugin-vue": "^4.2.1" }, "dependencies": { @@ -8148,7 +8148,7 @@ "@playwright/experimental-ct-vue2": { "version": "file:packages/playwright-ct-vue2", "requires": { - "@playwright/experimental-ct-core": "1.40.0-next", + "@playwright/experimental-ct-core": "1.41.0-next", "@vitejs/plugin-vue2": "^2.2.0", "vue": "^2.7.14" } @@ -8156,7 +8156,7 @@ "@playwright/test": { "version": "file:packages/playwright-test", "requires": { - "playwright": "1.40.0-next" + "playwright": "1.41.0-next" } }, "@sindresorhus/is": { @@ -11000,13 +11000,13 @@ "version": "file:packages/playwright", "requires": { "fsevents": "2.3.2", - "playwright-core": "1.40.0-next" + "playwright-core": "1.41.0-next" } }, "playwright-chromium": { "version": "file:packages/playwright-chromium", "requires": { - "playwright-core": "1.40.0-next" + "playwright-core": "1.41.0-next" } }, "playwright-core": { @@ -11015,13 +11015,13 @@ "playwright-firefox": { "version": "file:packages/playwright-firefox", "requires": { - "playwright-core": "1.40.0-next" + "playwright-core": "1.41.0-next" } }, "playwright-webkit": { "version": "file:packages/playwright-webkit", "requires": { - "playwright-core": "1.40.0-next" + "playwright-core": "1.41.0-next" } }, "postcss": { diff --git a/package.json b/package.json index d489bedaaa..19fe0160f6 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "playwright-internal", "private": true, - "version": "1.40.0-next", + "version": "1.41.0-next", "description": "A high-level API to automate web browsers", "repository": { "type": "git", diff --git a/packages/playwright-browser-chromium/package.json b/packages/playwright-browser-chromium/package.json index 28213cd734..aad28dfb1e 100644 --- a/packages/playwright-browser-chromium/package.json +++ b/packages/playwright-browser-chromium/package.json @@ -1,6 +1,6 @@ { "name": "@playwright/browser-chromium", - "version": "1.40.0-next", + "version": "1.41.0-next", "description": "Playwright package that automatically installs Chromium", "repository": { "type": "git", @@ -27,6 +27,6 @@ "install": "node install.js" }, "dependencies": { - "playwright-core": "1.40.0-next" + "playwright-core": "1.41.0-next" } } diff --git a/packages/playwright-browser-firefox/package.json b/packages/playwright-browser-firefox/package.json index 5eae7b7e57..d5b71763d2 100644 --- a/packages/playwright-browser-firefox/package.json +++ b/packages/playwright-browser-firefox/package.json @@ -1,6 +1,6 @@ { "name": "@playwright/browser-firefox", - "version": "1.40.0-next", + "version": "1.41.0-next", "description": "Playwright package that automatically installs Firefox", "repository": { "type": "git", @@ -27,6 +27,6 @@ "install": "node install.js" }, "dependencies": { - "playwright-core": "1.40.0-next" + "playwright-core": "1.41.0-next" } } diff --git a/packages/playwright-browser-webkit/package.json b/packages/playwright-browser-webkit/package.json index f65b5cdb65..4e1834127f 100644 --- a/packages/playwright-browser-webkit/package.json +++ b/packages/playwright-browser-webkit/package.json @@ -1,6 +1,6 @@ { "name": "@playwright/browser-webkit", - "version": "1.40.0-next", + "version": "1.41.0-next", "description": "Playwright package that automatically installs WebKit", "repository": { "type": "git", @@ -27,6 +27,6 @@ "install": "node install.js" }, "dependencies": { - "playwright-core": "1.40.0-next" + "playwright-core": "1.41.0-next" } } diff --git a/packages/playwright-chromium/package.json b/packages/playwright-chromium/package.json index e775eed351..2e29a02b0f 100644 --- a/packages/playwright-chromium/package.json +++ b/packages/playwright-chromium/package.json @@ -1,6 +1,6 @@ { "name": "playwright-chromium", - "version": "1.40.0-next", + "version": "1.41.0-next", "description": "A high-level API to automate Chromium", "repository": { "type": "git", @@ -30,6 +30,6 @@ "install": "node install.js" }, "dependencies": { - "playwright-core": "1.40.0-next" + "playwright-core": "1.41.0-next" } } diff --git a/packages/playwright-core/package.json b/packages/playwright-core/package.json index b04249715e..94c00e4dd7 100644 --- a/packages/playwright-core/package.json +++ b/packages/playwright-core/package.json @@ -1,6 +1,6 @@ { "name": "playwright-core", - "version": "1.40.0-next", + "version": "1.41.0-next", "description": "A high-level API to automate web browsers", "repository": { "type": "git", diff --git a/packages/playwright-ct-core/package.json b/packages/playwright-ct-core/package.json index f3be8b02b2..47a9e18077 100644 --- a/packages/playwright-ct-core/package.json +++ b/packages/playwright-ct-core/package.json @@ -1,6 +1,6 @@ { "name": "@playwright/experimental-ct-core", - "version": "1.40.0-next", + "version": "1.41.0-next", "description": "Playwright Component Testing Helpers", "repository": { "type": "git", @@ -27,9 +27,9 @@ } }, "dependencies": { - "playwright-core": "1.40.0-next", + "playwright-core": "1.41.0-next", "vite": "^4.4.10", - "playwright": "1.40.0-next" + "playwright": "1.41.0-next" }, "bin": { "playwright": "cli.js" diff --git a/packages/playwright-ct-react/package.json b/packages/playwright-ct-react/package.json index fd4dead4bd..370a1c4e23 100644 --- a/packages/playwright-ct-react/package.json +++ b/packages/playwright-ct-react/package.json @@ -1,6 +1,6 @@ { "name": "@playwright/experimental-ct-react", - "version": "1.40.0-next", + "version": "1.41.0-next", "description": "Playwright Component Testing for React", "repository": { "type": "git", @@ -29,7 +29,7 @@ } }, "dependencies": { - "@playwright/experimental-ct-core": "1.40.0-next", + "@playwright/experimental-ct-core": "1.41.0-next", "@vitejs/plugin-react": "^4.0.0" }, "bin": { diff --git a/packages/playwright-ct-react17/package.json b/packages/playwright-ct-react17/package.json index d108e6b2d7..d7cfe3c0e2 100644 --- a/packages/playwright-ct-react17/package.json +++ b/packages/playwright-ct-react17/package.json @@ -1,6 +1,6 @@ { "name": "@playwright/experimental-ct-react17", - "version": "1.40.0-next", + "version": "1.41.0-next", "description": "Playwright Component Testing for React", "repository": { "type": "git", @@ -29,7 +29,7 @@ } }, "dependencies": { - "@playwright/experimental-ct-core": "1.40.0-next", + "@playwright/experimental-ct-core": "1.41.0-next", "@vitejs/plugin-react": "^4.0.0" }, "bin": { diff --git a/packages/playwright-ct-solid/package.json b/packages/playwright-ct-solid/package.json index a636ec90cf..c49efc13ca 100644 --- a/packages/playwright-ct-solid/package.json +++ b/packages/playwright-ct-solid/package.json @@ -1,6 +1,6 @@ { "name": "@playwright/experimental-ct-solid", - "version": "1.40.0-next", + "version": "1.41.0-next", "description": "Playwright Component Testing for Solid", "repository": { "type": "git", @@ -29,7 +29,7 @@ } }, "dependencies": { - "@playwright/experimental-ct-core": "1.40.0-next", + "@playwright/experimental-ct-core": "1.41.0-next", "vite-plugin-solid": "^2.7.0" }, "devDependencies": { diff --git a/packages/playwright-ct-svelte/package.json b/packages/playwright-ct-svelte/package.json index 0280c65ac3..366329cf1a 100644 --- a/packages/playwright-ct-svelte/package.json +++ b/packages/playwright-ct-svelte/package.json @@ -1,6 +1,6 @@ { "name": "@playwright/experimental-ct-svelte", - "version": "1.40.0-next", + "version": "1.41.0-next", "description": "Playwright Component Testing for Svelte", "repository": { "type": "git", @@ -29,7 +29,7 @@ } }, "dependencies": { - "@playwright/experimental-ct-core": "1.40.0-next", + "@playwright/experimental-ct-core": "1.41.0-next", "@sveltejs/vite-plugin-svelte": "^2.1.1" }, "devDependencies": { diff --git a/packages/playwright-ct-vue/package.json b/packages/playwright-ct-vue/package.json index a3be0a0c27..f63f5ed4f6 100644 --- a/packages/playwright-ct-vue/package.json +++ b/packages/playwright-ct-vue/package.json @@ -1,6 +1,6 @@ { "name": "@playwright/experimental-ct-vue", - "version": "1.40.0-next", + "version": "1.41.0-next", "description": "Playwright Component Testing for Vue", "repository": { "type": "git", @@ -29,7 +29,7 @@ } }, "dependencies": { - "@playwright/experimental-ct-core": "1.40.0-next", + "@playwright/experimental-ct-core": "1.41.0-next", "@vitejs/plugin-vue": "^4.2.1" }, "bin": { diff --git a/packages/playwright-ct-vue2/package.json b/packages/playwright-ct-vue2/package.json index f60d1130e7..7d72a2ad55 100644 --- a/packages/playwright-ct-vue2/package.json +++ b/packages/playwright-ct-vue2/package.json @@ -1,6 +1,6 @@ { "name": "@playwright/experimental-ct-vue2", - "version": "1.40.0-next", + "version": "1.41.0-next", "description": "Playwright Component Testing for Vue2", "repository": { "type": "git", @@ -29,7 +29,7 @@ } }, "dependencies": { - "@playwright/experimental-ct-core": "1.40.0-next", + "@playwright/experimental-ct-core": "1.41.0-next", "@vitejs/plugin-vue2": "^2.2.0" }, "devDependencies": { diff --git a/packages/playwright-firefox/package.json b/packages/playwright-firefox/package.json index 91014c3a94..e4fb0bc707 100644 --- a/packages/playwright-firefox/package.json +++ b/packages/playwright-firefox/package.json @@ -1,6 +1,6 @@ { "name": "playwright-firefox", - "version": "1.40.0-next", + "version": "1.41.0-next", "description": "A high-level API to automate Firefox", "repository": { "type": "git", @@ -30,6 +30,6 @@ "install": "node install.js" }, "dependencies": { - "playwright-core": "1.40.0-next" + "playwright-core": "1.41.0-next" } } diff --git a/packages/playwright-test/package.json b/packages/playwright-test/package.json index 6650333814..43da06534a 100644 --- a/packages/playwright-test/package.json +++ b/packages/playwright-test/package.json @@ -1,6 +1,6 @@ { "name": "@playwright/test", - "version": "1.40.0-next", + "version": "1.41.0-next", "description": "A high-level API to automate web browsers", "repository": { "type": "git", @@ -30,6 +30,6 @@ }, "scripts": {}, "dependencies": { - "playwright": "1.40.0-next" + "playwright": "1.41.0-next" } } diff --git a/packages/playwright-webkit/package.json b/packages/playwright-webkit/package.json index c734ffaff8..b02fff2e0c 100644 --- a/packages/playwright-webkit/package.json +++ b/packages/playwright-webkit/package.json @@ -1,6 +1,6 @@ { "name": "playwright-webkit", - "version": "1.40.0-next", + "version": "1.41.0-next", "description": "A high-level API to automate WebKit", "repository": { "type": "git", @@ -30,6 +30,6 @@ "install": "node install.js" }, "dependencies": { - "playwright-core": "1.40.0-next" + "playwright-core": "1.41.0-next" } } diff --git a/packages/playwright/package.json b/packages/playwright/package.json index 312715f20f..268413c1ce 100644 --- a/packages/playwright/package.json +++ b/packages/playwright/package.json @@ -1,6 +1,6 @@ { "name": "playwright", - "version": "1.40.0-next", + "version": "1.41.0-next", "description": "A high-level API to automate web browsers", "repository": { "type": "git", @@ -54,7 +54,7 @@ }, "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.40.0-next" + "playwright-core": "1.41.0-next" }, "optionalDependencies": { "fsevents": "2.3.2" From 3cc1dacd5cb529e7a9520b718241b7b842c59d75 Mon Sep 17 00:00:00 2001 From: Playwright Service <89237858+playwrightmachine@users.noreply.github.com> Date: Thu, 16 Nov 2023 04:48:03 -0800 Subject: [PATCH 069/491] feat(chromium-tip-of-tree): roll to r1169 (#28184) 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 ed555a981c..9ea1997a80 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -15,9 +15,9 @@ }, { "name": "chromium-tip-of-tree", - "revision": "1168", + "revision": "1169", "installByDefault": false, - "browserVersion": "121.0.6127.0" + "browserVersion": "121.0.6131.0" }, { "name": "firefox", From 19cfd0cc5e5e1e800bfbf8b5876ce08301b35c56 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Thu, 16 Nov 2023 10:14:56 -0800 Subject: [PATCH 070/491] chore: roll stable-test-runner to 1.40.0-beta-1700102862000 (#28192) --- .../stable-test-runner/package-lock.json | 46 +++++++++---------- .../stable-test-runner/package.json | 2 +- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/tests/playwright-test/stable-test-runner/package-lock.json b/tests/playwright-test/stable-test-runner/package-lock.json index d4816c0bc9..438b145385 100644 --- a/tests/playwright-test/stable-test-runner/package-lock.json +++ b/tests/playwright-test/stable-test-runner/package-lock.json @@ -5,15 +5,15 @@ "packages": { "": { "dependencies": { - "@playwright/test": "^1.39.0-beta-1697048429000" + "@playwright/test": "1.40.0-beta-1700102862000" } }, "node_modules/@playwright/test": { - "version": "1.39.0-beta-1697048429000", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.39.0-beta-1697048429000.tgz", - "integrity": "sha512-N02dfcxqPSF7xqAYnGjas21adW6FlBCcGRc1yK+I9ndziw3n69EmIvYaSdIcolkukg2EAYfQ1BPcYb1rYsYpTA==", + "version": "1.40.0-beta-1700102862000", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.40.0-beta-1700102862000.tgz", + "integrity": "sha512-LJbspMK/li0U05YQncC4xVRrQ+tzUhyQUMbXblD0eew28HUmEvPMONq9B8Z8j49wLYYnq7Xi/hwqwrzKM/towA==", "dependencies": { - "playwright": "1.39.0-beta-1697048429000" + "playwright": "1.40.0-beta-1700102862000" }, "bin": { "playwright": "cli.js" @@ -36,11 +36,11 @@ } }, "node_modules/playwright": { - "version": "1.39.0-beta-1697048429000", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.39.0-beta-1697048429000.tgz", - "integrity": "sha512-ZlhLytmrragEBMRTHERbFJJaBHct3EXL0BcPArSRp+GcQ3ynX14EVnqxSehWhYvqAliwmG3yVW1hpXkIBYF55w==", + "version": "1.40.0-beta-1700102862000", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.40.0-beta-1700102862000.tgz", + "integrity": "sha512-b39oLMi8q+TAALVBK6xo70xja6XILW8Zk19SXdDtypHuGi7za70LZ1hPzl1GJjwfKiPTzQXqg6m/ftfsdldYEg==", "dependencies": { - "playwright-core": "1.39.0-beta-1697048429000" + "playwright-core": "1.40.0-beta-1700102862000" }, "bin": { "playwright": "cli.js" @@ -53,9 +53,9 @@ } }, "node_modules/playwright-core": { - "version": "1.39.0-beta-1697048429000", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.39.0-beta-1697048429000.tgz", - "integrity": "sha512-0mPopcgbVq+bQkh4oOLyf29FhjvoG39O3q6neFgN1jPDSurCa5sqWUZtLmSgvSR4KXdXcr3BUNdOUKj8Rqgv0Q==", + "version": "1.40.0-beta-1700102862000", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.40.0-beta-1700102862000.tgz", + "integrity": "sha512-KB89ippUjlD+/xmpzl7fSpXQ5FqnSyRFE+Hc0PO4sqoM8LuPq+1FT8TDf2xmYtzMApllyECnwSmnmsCUkjH6aQ==", "bin": { "playwright-core": "cli.js" }, @@ -66,11 +66,11 @@ }, "dependencies": { "@playwright/test": { - "version": "1.39.0-beta-1697048429000", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.39.0-beta-1697048429000.tgz", - "integrity": "sha512-N02dfcxqPSF7xqAYnGjas21adW6FlBCcGRc1yK+I9ndziw3n69EmIvYaSdIcolkukg2EAYfQ1BPcYb1rYsYpTA==", + "version": "1.40.0-beta-1700102862000", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.40.0-beta-1700102862000.tgz", + "integrity": "sha512-LJbspMK/li0U05YQncC4xVRrQ+tzUhyQUMbXblD0eew28HUmEvPMONq9B8Z8j49wLYYnq7Xi/hwqwrzKM/towA==", "requires": { - "playwright": "1.39.0-beta-1697048429000" + "playwright": "1.40.0-beta-1700102862000" } }, "fsevents": { @@ -80,18 +80,18 @@ "optional": true }, "playwright": { - "version": "1.39.0-beta-1697048429000", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.39.0-beta-1697048429000.tgz", - "integrity": "sha512-ZlhLytmrragEBMRTHERbFJJaBHct3EXL0BcPArSRp+GcQ3ynX14EVnqxSehWhYvqAliwmG3yVW1hpXkIBYF55w==", + "version": "1.40.0-beta-1700102862000", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.40.0-beta-1700102862000.tgz", + "integrity": "sha512-b39oLMi8q+TAALVBK6xo70xja6XILW8Zk19SXdDtypHuGi7za70LZ1hPzl1GJjwfKiPTzQXqg6m/ftfsdldYEg==", "requires": { "fsevents": "2.3.2", - "playwright-core": "1.39.0-beta-1697048429000" + "playwright-core": "1.40.0-beta-1700102862000" } }, "playwright-core": { - "version": "1.39.0-beta-1697048429000", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.39.0-beta-1697048429000.tgz", - "integrity": "sha512-0mPopcgbVq+bQkh4oOLyf29FhjvoG39O3q6neFgN1jPDSurCa5sqWUZtLmSgvSR4KXdXcr3BUNdOUKj8Rqgv0Q==" + "version": "1.40.0-beta-1700102862000", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.40.0-beta-1700102862000.tgz", + "integrity": "sha512-KB89ippUjlD+/xmpzl7fSpXQ5FqnSyRFE+Hc0PO4sqoM8LuPq+1FT8TDf2xmYtzMApllyECnwSmnmsCUkjH6aQ==" } } } diff --git a/tests/playwright-test/stable-test-runner/package.json b/tests/playwright-test/stable-test-runner/package.json index 554283aba9..59c60fdeac 100644 --- a/tests/playwright-test/stable-test-runner/package.json +++ b/tests/playwright-test/stable-test-runner/package.json @@ -1,6 +1,6 @@ { "private": true, "dependencies": { - "@playwright/test": "1.39.0-beta-1697048429000" + "@playwright/test": "1.40.0-beta-1700102862000" } } From ee1e6cd72f867b34ac5878d16002cb76e1212593 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Thu, 16 Nov 2023 20:14:55 +0100 Subject: [PATCH 071/491] test: unskip service tests (#28170) --- tests/config/browserTest.ts | 2 +- tests/library/chromium/connect-over-cdp.spec.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/config/browserTest.ts b/tests/config/browserTest.ts index cab7e6f285..02555d42eb 100644 --- a/tests/config/browserTest.ts +++ b/tests/config/browserTest.ts @@ -54,7 +54,7 @@ const test = baseTest.extend }, { scope: 'worker' }], browserType: [async ({ playwright, browserName, mode }, run) => { - test.skip(mode.startsWith('service')); + test.skip(mode === 'service2'); await run(playwright[browserName]); }, { scope: 'worker' }], diff --git a/tests/library/chromium/connect-over-cdp.spec.ts b/tests/library/chromium/connect-over-cdp.spec.ts index a5d52c5e57..570460fe04 100644 --- a/tests/library/chromium/connect-over-cdp.spec.ts +++ b/tests/library/chromium/connect-over-cdp.spec.ts @@ -21,7 +21,7 @@ import fs from 'fs'; import { getUserAgent } from '../../../packages/playwright-core/lib/utils/userAgent'; import { suppressCertificateWarning } from '../../config/utils'; -test.skip(({ mode }) => mode.startsWith('service')); +test.skip(({ mode }) => mode === 'service2'); test('should connect to an existing cdp session', async ({ browserType, mode }, testInfo) => { const port = 9339 + testInfo.workerIndex; From 2bd7d67adc9fbbe27bb6934ca2dbf57e0a065245 Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Thu, 16 Nov 2023 11:37:57 -0800 Subject: [PATCH 072/491] chore: render testInfo errors in the Errors tab (#28179) Fixes https://github.com/microsoft/playwright/issues/28056 --- packages/playwright/src/worker/testTracing.ts | 13 +++- packages/playwright/src/worker/workerMain.ts | 17 ++---- packages/trace-viewer/src/entries.ts | 2 + packages/trace-viewer/src/traceModel.ts | 4 ++ packages/trace-viewer/src/ui/errorsTab.tsx | 61 ++++++++++++++----- packages/trace-viewer/src/ui/modelUtil.ts | 2 + packages/trace/src/trace.ts | 9 ++- tests/config/utils.ts | 3 +- .../playwright-test/playwright.trace.spec.ts | 4 +- tests/playwright-test/reporter-html.spec.ts | 2 +- 10 files changed, 83 insertions(+), 34 deletions(-) diff --git a/packages/playwright/src/worker/testTracing.ts b/packages/playwright/src/worker/testTracing.ts index 566f54e271..89a6e99d44 100644 --- a/packages/playwright/src/worker/testTracing.ts +++ b/packages/playwright/src/worker/testTracing.ts @@ -21,7 +21,8 @@ import fs from 'fs'; import path from 'path'; import { ManualPromise, calculateSha1, monotonicTime } from 'playwright-core/lib/utils'; import { yauzl, yazl } from 'playwright-core/lib/zipBundle'; -import type { TestInfo } from '../../types/test'; +import type { TestInfo, TestInfoError } from '../../types/test'; +import { filteredStackTrace } from '../util'; export type Attachment = TestInfo['attachments'][0]; @@ -101,6 +102,16 @@ export class TestTracing { }); } + appendForError(error: TestInfoError) { + const rawStack = error.stack?.split('\n') || []; + const stack = rawStack ? filteredStackTrace(rawStack) : []; + this._appendTraceEvent({ + type: 'error', + message: error.message || String(error.value), + stack, + }); + } + appendStdioToTrace(type: 'stdout' | 'stderr', chunk: string | Buffer) { this._appendTraceEvent({ type, diff --git a/packages/playwright/src/worker/workerMain.ts b/packages/playwright/src/worker/workerMain.ts index 864677ac5c..b3bb77a0bc 100644 --- a/packages/playwright/src/worker/workerMain.ts +++ b/packages/playwright/src/worker/workerMain.ts @@ -22,7 +22,7 @@ import { ConfigLoader } from '../common/configLoader'; import type { Suite, TestCase } from '../common/test'; import type { Annotation, FullConfigInternal, FullProjectInternal } from '../common/config'; import { FixtureRunner } from './fixtureRunner'; -import { ManualPromise, captureLibraryStackTrace, gracefullyCloseAll, removeFolders } from 'playwright-core/lib/utils'; +import { ManualPromise, gracefullyCloseAll, removeFolders } from 'playwright-core/lib/utils'; import { TestInfoImpl } from './testInfo'; import { TimeoutManager, type TimeSlot } from './timeoutManager'; import { ProcessRunner } from '../common/process'; @@ -372,7 +372,7 @@ export class WorkerMain extends ProcessRunner { return; } - const error = await testInfo._runAndFailOnError(async () => { + await testInfo._runAndFailOnError(async () => { // Now run the test itself. debugTest(`test function started`); const fn = test.fn; // Extract a variable to get a better stack trace ("myTest" vs "TestCase.myTest [as fn]"). @@ -380,17 +380,8 @@ export class WorkerMain extends ProcessRunner { debugTest(`test function finished`); }, 'allowSkips'); - // If there are no steps with errors in the test, but the test has an error - append artificial trace entry. - if (error && !testInfo._steps.some(s => !!s.error)) { - const frames = error.stack ? captureLibraryStackTrace(error.stack.split('\n')).frames : []; - const step = testInfo._addStep({ - wallTime: Date.now(), - title: error.message || 'error', - category: 'hook', - location: frames[0], - }); - step.complete({ error }); - } + for (const error of testInfo.errors) + testInfo._tracing.appendForError(error); }); if (didFailBeforeAllForSuite) { diff --git a/packages/trace-viewer/src/entries.ts b/packages/trace-viewer/src/entries.ts index 591fd0628a..1ea9717b53 100644 --- a/packages/trace-viewer/src/entries.ts +++ b/packages/trace-viewer/src/entries.ts @@ -36,6 +36,7 @@ export type ContextEntry = { actions: ActionEntry[]; events: (trace.EventTraceEvent | trace.ConsoleMessageTraceEvent)[]; stdio: trace.StdioTraceEvent[]; + errors: trace.ErrorTraceEvent[]; hasSource: boolean; }; @@ -68,6 +69,7 @@ export function createEmptyContext(): ContextEntry { resources: [], actions: [], events: [], + errors: [], stdio: [], hasSource: false }; diff --git a/packages/trace-viewer/src/traceModel.ts b/packages/trace-viewer/src/traceModel.ts index 0540be1c44..bf7a742cc1 100644 --- a/packages/trace-viewer/src/traceModel.ts +++ b/packages/trace-viewer/src/traceModel.ts @@ -223,6 +223,10 @@ export class TraceModel { contextEntry!.stdio.push(event); break; } + case 'error': { + contextEntry!.errors.push(event); + break; + } case 'console': { contextEntry!.events.push(event); break; diff --git a/packages/trace-viewer/src/ui/errorsTab.tsx b/packages/trace-viewer/src/ui/errorsTab.tsx index 380b02323a..eb1b529c26 100644 --- a/packages/trace-viewer/src/ui/errorsTab.tsx +++ b/packages/trace-viewer/src/ui/errorsTab.tsx @@ -20,20 +20,50 @@ import type * as modelUtil from './modelUtil'; import { PlaceholderPanel } from './placeholderPanel'; import { renderAction } from './actionList'; import type { Language } from '@isomorphic/locatorGenerators'; +import type { StackFrame } from '@protocol/channels'; + +type ErrorDescription = { + action?: modelUtil.ActionTraceEventInContext; + stack?: StackFrame[]; +}; type ErrorsTabModel = { - errors: Map; + errors: Map; }; +function errorsFromActions(model: modelUtil.MultiTraceModel): Map { + const errors = new Map(); + for (const action of model.actions || []) { + // Overwrite errors with the last one. + if (!action.error?.message || errors.has(action.error.message)) + continue; + errors.set(action.error.message, { + action, + stack: action.stack, + }); + } + return errors; +} + +function errorsFromTestRunner(model: modelUtil.MultiTraceModel): Map { + const actionErrors = errorsFromActions(model); + const errors = new Map(); + for (const error of model.errors || []) { + if (!error.message || errors.has(error.message)) + continue; + errors.set(error.message, actionErrors.get(error.message) || error); + } + return errors; +} + export function useErrorsTabModel(model: modelUtil.MultiTraceModel | undefined): ErrorsTabModel { return React.useMemo(() => { - const errors = new Map(); - for (const action of model?.actions || []) { - // Overwrite errors with the last one. - if (action.error?.message) - errors.set(action.error.message, action); - } - return { errors }; + if (!model) + return { errors: new Map() }; + // Feature detection: if there is test runner info, pick errors from the 'error' trace events. + // If there are no test errors, but there are action errors - render those instead. + const testHasErrors = !!model.errors.length; + return { errors: testHasErrors ? errorsFromTestRunner(model) : errorsFromActions(model) }; }, [model]); } @@ -46,13 +76,14 @@ export const ErrorsTab: React.FunctionComponent<{ return ; return
- {[...errorsModel.errors.entries()].map(([message, action]) => { + {[...errorsModel.errors.entries()].map(([message, error]) => { let location: string | undefined; let longLocation: string | undefined; - if (action.stack?.[0]) { - const file = action.stack[0].file.replace(/.*\/(.*)/, '$1'); - location = file + ':' + action.stack[0].line; - longLocation = action.stack[0].file + ':' + action.stack[0].line; + const stackFrame = error.stack?.[0]; + if (stackFrame) { + const file = stackFrame.file.replace(/.*\/(.*)/, '$1'); + location = file + ':' + stackFrame.line; + longLocation = stackFrame.file + ':' + stackFrame.line; } return
- {renderAction(action, { sdkLanguage })} + {error.action && renderAction(error.action, { sdkLanguage })} {location &&
- @ revealInSource(action)}>{location} + @ error.action && revealInSource(error.action)}>{location}
}
diff --git a/packages/trace-viewer/src/ui/modelUtil.ts b/packages/trace-viewer/src/ui/modelUtil.ts index f1379b8e46..f29b4f7616 100644 --- a/packages/trace-viewer/src/ui/modelUtil.ts +++ b/packages/trace-viewer/src/ui/modelUtil.ts @@ -61,6 +61,7 @@ export class MultiTraceModel { readonly actions: ActionTraceEventInContext[]; readonly events: (trace.EventTraceEvent | trace.ConsoleMessageTraceEvent)[]; readonly stdio: trace.StdioTraceEvent[]; + readonly errors: trace.ErrorTraceEvent[]; readonly hasSource: boolean; readonly sdkLanguage: Language | undefined; readonly testIdAttributeName: string | undefined; @@ -86,6 +87,7 @@ export class MultiTraceModel { this.actions = mergeActions(contexts); this.events = ([] as (trace.EventTraceEvent | trace.ConsoleMessageTraceEvent)[]).concat(...contexts.map(c => c.events)); this.stdio = ([] as trace.StdioTraceEvent[]).concat(...contexts.map(c => c.stdio)); + this.errors = ([] as trace.ErrorTraceEvent[]).concat(...contexts.map(c => c.errors)); this.hasSource = contexts.some(c => c.hasSource); this.resources = [...contexts.map(c => c.resources)].flat(); diff --git a/packages/trace/src/trace.ts b/packages/trace/src/trace.ts index 1155da43ec..651c41dc19 100644 --- a/packages/trace/src/trace.ts +++ b/packages/trace/src/trace.ts @@ -145,6 +145,12 @@ export type StdioTraceEvent = { base64?: string; }; +export type ErrorTraceEvent = { + type: 'error'; + message: string; + stack?: StackFrame[]; +}; + export type TraceEvent = ContextCreatedTraceEvent | ScreencastFrameTraceEvent | @@ -157,4 +163,5 @@ export type TraceEvent = ConsoleMessageTraceEvent | ResourceSnapshotTraceEvent | FrameSnapshotTraceEvent | - StdioTraceEvent; + StdioTraceEvent | + ErrorTraceEvent; diff --git a/tests/config/utils.ts b/tests/config/utils.ts index 53e177f955..88cff686b3 100644 --- a/tests/config/utils.ts +++ b/tests/config/utils.ts @@ -156,7 +156,7 @@ export async function parseTraceRaw(file: string): Promise<{ events: any[], reso }; } -export async function parseTrace(file: string): Promise<{ resources: Map, events: (EventTraceEvent | ConsoleMessageTraceEvent)[], actions: ActionTraceEvent[], apiNames: string[], traceModel: TraceModel, model: MultiTraceModel, actionTree: string[] }> { +export async function parseTrace(file: string): Promise<{ resources: Map, events: (EventTraceEvent | ConsoleMessageTraceEvent)[], actions: ActionTraceEvent[], apiNames: string[], traceModel: TraceModel, model: MultiTraceModel, actionTree: string[], errors: string[] }> { const backend = new TraceBackend(file); const traceModel = new TraceModel(); await traceModel.load(backend, () => {}); @@ -174,6 +174,7 @@ export async function parseTrace(file: string): Promise<{ resources: Map e.message), model, traceModel, actionTree, diff --git a/tests/playwright-test/playwright.trace.spec.ts b/tests/playwright-test/playwright.trace.spec.ts index ec75ae0ef6..c62e7c98ca 100644 --- a/tests/playwright-test/playwright.trace.spec.ts +++ b/tests/playwright-test/playwright.trace.spec.ts @@ -323,7 +323,6 @@ test('should not override trace file in afterAll', async ({ runInlineTest, serve ' fixture: page', ' browserContext.newPage', 'page.goto', - 'error', 'After Hooks', ' fixture: page', ' fixture: context', @@ -335,6 +334,7 @@ test('should not override trace file in afterAll', async ({ runInlineTest, serve ' fixture: request', ' apiRequestContext.dispose', ]); + expect(trace1.errors).toEqual([`'oh no!'`]); const error = await parseTrace(testInfo.outputPath('test-results', 'a-test-2', 'trace.zip')).catch(e => e); expect(error).toBeTruthy(); @@ -668,11 +668,11 @@ test('should show non-expect error in trace', async ({ runInlineTest }, testInfo ' fixture: page', ' browserContext.newPage', 'expect.toBe', - 'ReferenceError: undefinedVariable1 is not defined', 'After Hooks', ' fixture: page', ' fixture: context', ]); + expect(trace.errors).toEqual(['ReferenceError: undefinedVariable1 is not defined']); }); test('should not throw when attachment is missing', async ({ runInlineTest }, testInfo) => { diff --git a/tests/playwright-test/reporter-html.spec.ts b/tests/playwright-test/reporter-html.spec.ts index 8090972125..aedc1e6eb7 100644 --- a/tests/playwright-test/reporter-html.spec.ts +++ b/tests/playwright-test/reporter-html.spec.ts @@ -820,7 +820,7 @@ for (const useIntermediateMergeReport of [false, true] as const) { await showReport(); await page.locator('text=sample').first().click(); - await expect(page.locator('text=ouch')).toHaveCount(2); + await expect(page.locator('text=ouch')).toHaveCount(1); await page.locator('text=All').first().click(); await page.locator('text=sample').nth(1).click(); From ff706ec8bdd7f013e31ccde323b588aafb2b3869 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Thu, 16 Nov 2023 20:39:32 +0100 Subject: [PATCH 073/491] test: skip Intl.ListFormat test on ubuntu20.04 (#28185) It was failing across the Ubuntu 20.04 bots: ![image](https://github.com/microsoft/playwright/assets/17984549/3b80f04d-cae8-4288-8fd3-b94d9bf1ce03) This is most likely because on Ubuntu 20 libicu-dev has version 66.1. And according to https://wksearch.azurewebsites.net/#path=%2Fhome%2Fjoe%2Fwebkit%2FSource%2FJavaScriptCore%2Fruntime%2FIntlListFormat.h&line=32 the ListFormatter requires 67 which is the case in Ubuntu 22 (70). --- tests/library/capabilities.spec.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/library/capabilities.spec.ts b/tests/library/capabilities.spec.ts index 604ad479b1..58bb215a6a 100644 --- a/tests/library/capabilities.spec.ts +++ b/tests/library/capabilities.spec.ts @@ -17,6 +17,7 @@ import os from 'os'; import url from 'url'; import { contextTest as it, expect } from '../config/browserTest'; +import { hostPlatform } from '../../packages/playwright-core/src/utils/hostPlatform'; it('SharedArrayBuffer should work @smoke', async function({ contextFactory, httpsServer, browserName }) { it.fail(browserName === 'webkit', 'no shared array buffer on webkit'); @@ -284,8 +285,9 @@ it('should send no Content-Length header for GET requests with a Content-Type', expect(request.headers['content-length']).toBe(undefined); }); -it('Intl.ListFormat should work', async ({ page, server }) => { +it('Intl.ListFormat should work', async ({ page, server, browserName }) => { it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/23978' }); + it.skip(browserName === 'webkit' && hostPlatform.startsWith('ubuntu20.04'), 'libicu is too old and WebKit disables Intl.ListFormat by default then'); await page.goto(server.EMPTY_PAGE); const formatted = await page.evaluate(() => { const data = ['first', 'second', 'third']; From da6707f785bd03fbb12be1d7c3208588dc459f38 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Thu, 16 Nov 2023 11:44:10 -0800 Subject: [PATCH 074/491] fix(chromium): properly detect session closed errors for oopifs (#28197) Exposed by the flaky test `should not throw on exposeFunction when oopif detaches`. --- packages/playwright-core/src/server/chromium/crPage.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/playwright-core/src/server/chromium/crPage.ts b/packages/playwright-core/src/server/chromium/crPage.ts index d7a2a5dddf..2aa98d4f7f 100644 --- a/packages/playwright-core/src/server/chromium/crPage.ts +++ b/packages/playwright-core/src/server/chromium/crPage.ts @@ -46,6 +46,7 @@ import type { Protocol } from './protocol'; import { VideoRecorder } from './videoRecorder'; import { BrowserContext } from '../browserContext'; import { TargetClosedError } from '../errors'; +import { isSessionClosedError } from '../protocolError'; const UTILITY_WORLD_NAME = '__playwright_utility_world__'; @@ -132,7 +133,7 @@ export class CRPage implements PageDelegate { return cb(frameSession); return cb(frameSession).catch(e => { // Broadcasting a message to the closed iframe should be a noop. - if (e.message && e.message.includes('Target closed')) + if (isSessionClosedError(e)) return; throw e; }); From 61c089fcbd2cd58d97fcf90b71af481b420e944b Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Thu, 16 Nov 2023 13:19:36 -0800 Subject: [PATCH 075/491] feat(recorder): UX updates for assertion tools (#28198) - No locator editor. - No value editor for `toHaveValue`. - Visual feedback for `toBeVisible`/`toHaveValue`. - UI tweaks. --- docs/src/release-notes-csharp.md | 2 +- docs/src/release-notes-java.md | 2 +- docs/src/release-notes-js.md | 2 +- docs/src/release-notes-python.md | 2 +- .../src/server/injected/highlight.css | 22 +- .../src/server/injected/recorder.ts | 233 ++++++------------ 6 files changed, 103 insertions(+), 160 deletions(-) diff --git a/docs/src/release-notes-csharp.md b/docs/src/release-notes-csharp.md index 9209694046..a3b42b741a 100644 --- a/docs/src/release-notes-csharp.md +++ b/docs/src/release-notes-csharp.md @@ -8,7 +8,7 @@ toc_max_heading_level: 2 ### Test Generator Update -![Playwright Test Generator](https://github.com/microsoft/playwright/assets/9881434/8c3d6fac-5381-4aaf-920f-6e22b964eec6) +![Playwright Test Generator](https://github.com/microsoft/playwright/assets/9881434/e8d67e2e-f36d-4301-8631-023948d3e190) New tools to generate assertions: - "Assert visibility" tool generates [`method: LocatorAssertions.toBeVisible`]. diff --git a/docs/src/release-notes-java.md b/docs/src/release-notes-java.md index 041b390c09..5703cf9a26 100644 --- a/docs/src/release-notes-java.md +++ b/docs/src/release-notes-java.md @@ -8,7 +8,7 @@ toc_max_heading_level: 2 ### Test Generator Update -![Playwright Test Generator](https://github.com/microsoft/playwright/assets/9881434/8c3d6fac-5381-4aaf-920f-6e22b964eec6) +![Playwright Test Generator](https://github.com/microsoft/playwright/assets/9881434/e8d67e2e-f36d-4301-8631-023948d3e190) New tools to generate assertions: - "Assert visibility" tool generates [`method: LocatorAssertions.toBeVisible`]. diff --git a/docs/src/release-notes-js.md b/docs/src/release-notes-js.md index 38a06d0149..7a7ad52bb9 100644 --- a/docs/src/release-notes-js.md +++ b/docs/src/release-notes-js.md @@ -10,7 +10,7 @@ import LiteYouTube from '@site/src/components/LiteYouTube'; ### Test Generator Update -![Playwright Test Generator](https://github.com/microsoft/playwright/assets/9881434/8c3d6fac-5381-4aaf-920f-6e22b964eec6) +![Playwright Test Generator](https://github.com/microsoft/playwright/assets/9881434/e8d67e2e-f36d-4301-8631-023948d3e190) New tools to generate assertions: - "Assert visibility" tool generates [`method: LocatorAssertions.toBeVisible`]. diff --git a/docs/src/release-notes-python.md b/docs/src/release-notes-python.md index ada5a664bf..f3388431f3 100644 --- a/docs/src/release-notes-python.md +++ b/docs/src/release-notes-python.md @@ -8,7 +8,7 @@ toc_max_heading_level: 2 ### Test Generator Update -![Playwright Test Generator](https://github.com/microsoft/playwright/assets/9881434/8c3d6fac-5381-4aaf-920f-6e22b964eec6) +![Playwright Test Generator](https://github.com/microsoft/playwright/assets/9881434/e8d67e2e-f36d-4301-8631-023948d3e190) New tools to generate assertions: - "Assert visibility" tool generates [`method: LocatorAssertions.toBeVisible`]. diff --git a/packages/playwright-core/src/server/injected/highlight.css b/packages/playwright-core/src/server/injected/highlight.css index b0275a303c..e45e45b55e 100644 --- a/packages/playwright-core/src/server/injected/highlight.css +++ b/packages/playwright-core/src/server/injected/highlight.css @@ -44,9 +44,10 @@ x-pw-dialog { display: flex; flex-direction: column; position: absolute; - width: 500px; - height: 200px; + width: 400px; + height: 150px; z-index: 10; + font-size: 13px; } x-pw-dialog-body { @@ -217,6 +218,15 @@ x-pw-tool-item.cancel > x-div { mask-image: url("data:image/svg+xml;utf8,"); } +x-pw-tool-item.succeeded > x-div { + /* codicon: pass */ + -webkit-mask-image: url("data:image/svg+xml;utf8,") !important; + mask-image: url("data:image/svg+xml;utf8,") !important; + background-color: #388a34 !important; + -webkit-mask-size: 18px !important; + mask-size: 18px !important; +} + x-pw-overlay { position: absolute; top: 0; @@ -238,13 +248,15 @@ x-pw-overlay x-pw-tool-item { } textarea.text-editor { - font-family: system-ui, "Ubuntu", "Droid Sans", sans-serif; + font-family: system-ui,Ubuntu,Droid Sans,sans-serif; flex: auto; border: none; - margin: 6px; + margin: 6px 10px; color: #333; - outline: 1px solid transparent !important; + outline: 1px solid transparent!important; resize: none; + padding: 0; + font-size: 13px; } textarea.text-editor.does-not-match { diff --git a/packages/playwright-core/src/server/injected/recorder.ts b/packages/playwright-core/src/server/injected/recorder.ts index 6ecd28fe03..f88bddd528 100644 --- a/packages/playwright-core/src/server/injected/recorder.ts +++ b/packages/playwright-core/src/server/injected/recorder.ts @@ -24,28 +24,8 @@ import { isInsideScope } from './domUtils'; import { elementText } from './selectorUtils'; import type { ElementText } from './selectorUtils'; import { asLocator } from '../../utils/isomorphic/locatorGenerators'; -import type { Language } from '../../utils/isomorphic/locatorGenerators'; -import { locatorOrSelectorAsSelector } from '@isomorphic/locatorParser'; -import { parseSelector } from '@isomorphic/selectorParser'; import { normalizeWhiteSpace } from '@isomorphic/stringUtils'; -// @ts-ignore @no-check-deps -import CodeMirrorImpl from 'codemirror-shadow-1'; -import type CodeMirrorType from 'codemirror'; -// @no-check-deps -import codemirrorCSS from 'codemirror-shadow-1/lib/codemirror.css?inline'; -// @no-check-deps -import 'codemirror-shadow-1/mode/css/css'; -// @no-check-deps -import 'codemirror-shadow-1/mode/htmlmixed/htmlmixed'; -// @no-check-deps -import 'codemirror-shadow-1/mode/javascript/javascript'; -// @no-check-deps -import 'codemirror-shadow-1/mode/python/python'; -// @no-check-deps -import 'codemirror-shadow-1/mode/clike/clike'; -const CodeMirror = CodeMirrorImpl as typeof CodeMirrorType; - interface RecorderDelegate { performAction?(action: actions.Action): Promise; recordAction?(action: actions.Action): Promise; @@ -68,6 +48,7 @@ interface RecorderTool { onMouseDown?(event: MouseEvent): void; onMouseUp?(event: MouseEvent): void; onMouseMove?(event: MouseEvent): void; + onMouseEnter?(event: MouseEvent): void; onMouseLeave?(event: MouseEvent): void; onFocus?(event: Event): void; onScroll?(event: Event): void; @@ -109,6 +90,7 @@ class InspectTool implements RecorderTool { signals: [], }); this._recorder.delegate.setMode?.('recording'); + this._recorder.overlay?.flashToolSucceeded('assertingVisibility'); } } else { this._recorder.delegate.setSelector?.(this._hoveredModel ? this._hoveredModel.selector : ''); @@ -146,6 +128,10 @@ class InspectTool implements RecorderTool { this._recorder.updateHighlight(model, true, { color: this._assertVisibility ? '#8acae480' : undefined }); } + onMouseEnter(event: MouseEvent) { + consumeEvent(event); + } + onMouseLeave(event: MouseEvent) { consumeEvent(event); const window = this._recorder.injectedScript.window; @@ -518,14 +504,23 @@ class TextAssertionTool implements RecorderTool { } onClick(event: MouseEvent) { - if (!this._dialogElement) - this._showDialog(); consumeEvent(event); + if (this._kind === 'value') { + const action = this._generateAction(); + if (action) { + this._recorder.delegate.recordAction?.(action); + this._recorder.delegate.setMode?.('recording'); + this._recorder.overlay?.flashToolSucceeded('assertingValue'); + } + } else { + if (!this._dialogElement) + this._showDialog(); + } } onMouseDown(event: MouseEvent) { const target = this._recorder.deepEventTarget(event); - if (target.nodeName === 'SELECT') + if (this._elementHasValue(target)) event.preventDefault(); } @@ -618,7 +613,7 @@ class TextAssertionTool implements RecorderTool { if (!this._hoverHighlight?.elements[0]) return; this._action = this._generateAction(); - if (!this._action) + if (!this._action || this._action.name !== 'assertText') return; this._dialogElement = this._recorder.document.createElement('x-pw-dialog'); @@ -636,122 +631,41 @@ class TextAssertionTool implements RecorderTool { this._recorder.document.addEventListener('keydown', this._keyboardListener, true); const toolbarElement = this._recorder.document.createElement('x-pw-tools-list'); - toolbarElement.appendChild(this._createLabel(this._action)); + const labelElement = this._recorder.document.createElement('label'); + labelElement.textContent = 'Assert that element contains text'; + toolbarElement.appendChild(labelElement); toolbarElement.appendChild(this._recorder.document.createElement('x-spacer')); toolbarElement.appendChild(this._acceptButton); toolbarElement.appendChild(this._cancelButton); this._dialogElement.appendChild(toolbarElement); const bodyElement = this._recorder.document.createElement('x-pw-dialog-body'); - const cmStyle = this._recorder.document.createElement('style'); - const cmElement = this._recorder.document.createElement('x-locator-editor'); - cmStyle.textContent = codemirrorCSS; - bodyElement.appendChild(cmStyle); - bodyElement.appendChild(cmElement); - const cm = CodeMirror(cmElement, { - value: asLocator(this._recorder.state.language, this._action.selector), - mode: cmModeForLanguage(this._recorder.state.language), - readOnly: false, - lineNumbers: false, - lineWrapping: true, - }); - cm.on('keydown', (_, event) => { - if (event.key === 'Tab') - (event as any).codemirrorIgnore = true; - }); - cm.on('change', () => { - if (this._action) { - const selector = locatorOrSelectorAsSelector(this._recorder.state.language, cm.getValue(), this._recorder.state.testIdAttributeName); - let elements: Element[] = []; - try { - elements = this._recorder.injectedScript.querySelectorAll(parseSelector(selector), this._recorder.document); - } catch { - } - cmElement.classList.toggle('does-not-match', !elements.length); - this._hoverHighlight = elements.length ? { - selector, - elements, - } : null; - this._action.selector = selector; - this._recorder.updateHighlight(this._hoverHighlight, true); - } - }); - let elementToFocus: HTMLElement | null = null; const action = this._action; - if (action.name === 'assertText') { - const textElement = this._recorder.document.createElement('textarea'); - textElement.setAttribute('spellcheck', 'false'); - textElement.value = this._renderValue(this._action); - textElement.classList.add('text-editor'); + const textElement = this._recorder.document.createElement('textarea'); + textElement.setAttribute('spellcheck', 'false'); + textElement.value = this._renderValue(this._action); + textElement.classList.add('text-editor'); - const updateAndValidate = () => { - const newValue = normalizeWhiteSpace(textElement.value); - const target = this._hoverHighlight?.elements[0]; - if (!target) - return; - action.text = newValue; - const targetText = normalizeWhiteSpace(elementText(this._textCache, target).full); - const matches = action.substring ? newValue && targetText.includes(newValue) : targetText === newValue; - textElement.classList.toggle('does-not-match', !matches); - }; - textElement.addEventListener('input', updateAndValidate); - bodyElement.appendChild(textElement); - - // Add a toolbar substring checkbox. - const substringElement = this._recorder.document.createElement('label'); - substringElement.style.cursor = 'pointer'; - const checkboxElement = this._recorder.document.createElement('input'); - substringElement.appendChild(checkboxElement); - substringElement.appendChild(this._recorder.document.createTextNode('Substring')); - checkboxElement.type = 'checkbox'; - checkboxElement.style.cursor = 'pointer'; - checkboxElement.checked = action.substring; - checkboxElement.addEventListener('change', () => { - action.substring = checkboxElement.checked; - updateAndValidate(); - }); - toolbarElement.insertBefore(substringElement, this._acceptButton); - - elementToFocus = textElement; - } else if (action.name === 'assertValue') { - const textElement = this._recorder.document.createElement('textarea'); - textElement.setAttribute('spellcheck', 'false'); - textElement.value = this._renderValue(this._action); - textElement.classList.add('text-editor'); - - textElement.addEventListener('input', () => { - action.value = textElement.value; - }); - bodyElement.appendChild(textElement); - elementToFocus = textElement; - } else if (action.name === 'assertChecked') { - const labelElement = this._recorder.document.createElement('label'); - labelElement.textContent = 'Value:'; - const checkboxElement = this._recorder.document.createElement('input'); - labelElement.appendChild(checkboxElement); - checkboxElement.type = 'checkbox'; - checkboxElement.checked = action.checked; - checkboxElement.addEventListener('change', () => { - action.checked = checkboxElement.checked; - }); - bodyElement.appendChild(labelElement); - elementToFocus = labelElement; - } + const updateAndValidate = () => { + const newValue = normalizeWhiteSpace(textElement.value); + const target = this._hoverHighlight?.elements[0]; + if (!target) + return; + action.text = newValue; + const targetText = normalizeWhiteSpace(elementText(this._textCache, target).full); + const matches = newValue && targetText.includes(newValue); + textElement.classList.toggle('does-not-match', !matches); + }; + textElement.addEventListener('input', updateAndValidate); + bodyElement.appendChild(textElement); this._dialogElement.appendChild(bodyElement); this._recorder.highlight.appendChild(this._dialogElement); const position = this._recorder.highlight.tooltipPosition(this._recorder.highlight.firstBox()!, this._dialogElement); this._dialogElement.style.top = position.anchorTop + 'px'; this._dialogElement.style.left = position.anchorLeft + 'px'; - elementToFocus?.focus(); - cm.refresh(); - } - - private _createLabel(action: actions.AssertAction) { - const labelElement = this._recorder.document.createElement('label'); - labelElement.textContent = action.name === 'assertText' ? 'Assert text' : action.name === 'assertValue' ? 'Assert value' : 'Assert checked'; - return labelElement; + textElement.focus(); } private _closeDialog() { @@ -829,7 +743,7 @@ class Overlay { toolsListElement.appendChild(this._assertVisibilityToggle); this._assertTextToggle = this._recorder.injectedScript.document.createElement('x-pw-tool-item'); - this._assertTextToggle.title = 'Assert text and values'; + this._assertTextToggle.title = 'Assert text'; this._assertTextToggle.classList.add('text'); this._assertTextToggle.appendChild(this._recorder.injectedScript.document.createElement('x-div')); this._assertTextToggle.addEventListener('click', () => { @@ -853,7 +767,7 @@ class Overlay { install() { this._recorder.highlight.appendChild(this._overlayElement); - this._measure = this._overlayElement.getBoundingClientRect(); + this._updateVisualPosition(); } contains(element: Element) { @@ -874,13 +788,31 @@ class Overlay { this._updateVisualPosition(); } if (state.mode === 'none') - this._overlayElement.setAttribute('hidden', 'true'); + this._hideOverlay(); else - this._overlayElement.removeAttribute('hidden'); + this._showOverlay(); + } + + flashToolSucceeded(tool: 'assertingVisibility' | 'assertingValue') { + const element = tool === 'assertingVisibility' ? this._assertVisibilityToggle : this._assertValuesToggle; + element.classList.add('succeeded'); + setTimeout(() => element.classList.remove('succeeded'), 2000); + } + + private _hideOverlay() { + this._overlayElement.setAttribute('hidden', 'true'); + } + + private _showOverlay() { + if (!this._overlayElement.hasAttribute('hidden')) + return; + this._overlayElement.removeAttribute('hidden'); + this._updateVisualPosition(); } private _updateVisualPosition() { - this._overlayElement.style.left = (this._recorder.injectedScript.window.innerWidth / 2 + this._offsetX) + 'px'; + this._measure = this._overlayElement.getBoundingClientRect(); + this._overlayElement.style.left = ((this._recorder.injectedScript.window.innerWidth - this._measure.width) / 2 + this._offsetX) + 'px'; } onMouseMove(event: MouseEvent) { @@ -890,8 +822,8 @@ class Overlay { } if (this._dragState) { this._offsetX = this._dragState.offsetX + event.clientX - this._dragState.dragStart.x; - this._offsetX = Math.min(this._recorder.injectedScript.window.innerWidth / 2 - 10 - this._measure.width, this._offsetX); - this._offsetX = Math.max(10 - this._recorder.injectedScript.window.innerWidth / 2, this._offsetX); + const halfGapSize = (this._recorder.injectedScript.window.innerWidth - this._measure.width) / 2 - 10; + this._offsetX = Math.max(-halfGapSize, Math.min(halfGapSize, this._offsetX)); this._updateVisualPosition(); this._recorder.delegate.setOverlayState?.({ offsetX: this._offsetX }); consumeEvent(event); @@ -925,7 +857,7 @@ export class Recorder { private _tools: Record; private _actionSelectorModel: HighlightModel | null = null; readonly highlight: Highlight; - private _overlay: Overlay | undefined; + readonly overlay: Overlay | undefined; private _styleElement: HTMLStyleElement; state: UIState = { mode: 'none', testIdAttributeName: 'data-testid', language: 'javascript', overlay: { offsetX: 0 } }; readonly document: Document; @@ -947,8 +879,8 @@ export class Recorder { }; this._currentTool = this._tools.none; if (injectedScript.window.top === injectedScript.window) { - this._overlay = new Overlay(this); - this._overlay.setUIState(this.state); + this.overlay = new Overlay(this); + this.overlay.setUIState(this.state); } this._styleElement = this.document.createElement('style'); this._styleElement.textContent = ` @@ -976,11 +908,12 @@ export class Recorder { addEventListener(this.document, 'mouseup', event => this._onMouseUp(event as MouseEvent), true), addEventListener(this.document, 'mousemove', event => this._onMouseMove(event as MouseEvent), true), addEventListener(this.document, 'mouseleave', event => this._onMouseLeave(event as MouseEvent), true), + addEventListener(this.document, 'mouseenter', event => this._onMouseEnter(event as MouseEvent), true), addEventListener(this.document, 'focus', event => this._onFocus(event), true), addEventListener(this.document, 'scroll', event => this._onScroll(event), true), ]; this.highlight.install(); - this._overlay?.install(); + this.overlay?.install(); this.injectedScript.document.head.appendChild(this._styleElement); } @@ -1011,7 +944,7 @@ export class Recorder { this.state = state; this.highlight.setLanguage(state.language); this._switchCurrentTool(); - this._overlay?.setUIState(state); + this.overlay?.setUIState(state); // Race or scroll. if (this._actionSelectorModel?.selector && !this._actionSelectorModel?.elements.length) @@ -1030,7 +963,7 @@ export class Recorder { private _onClick(event: MouseEvent) { if (!event.isTrusted) return; - if (this._overlay?.onClick(event)) + if (this.overlay?.onClick(event)) return; if (this._ignoreOverlayEvent(event)) return; @@ -1072,7 +1005,7 @@ export class Recorder { private _onMouseUp(event: MouseEvent) { if (!event.isTrusted) return; - if (this._overlay?.onMouseUp(event)) + if (this.overlay?.onMouseUp(event)) return; if (this._ignoreOverlayEvent(event)) return; @@ -1082,13 +1015,21 @@ export class Recorder { private _onMouseMove(event: MouseEvent) { if (!event.isTrusted) return; - if (this._overlay?.onMouseMove(event)) + if (this.overlay?.onMouseMove(event)) return; if (this._ignoreOverlayEvent(event)) return; this._currentTool.onMouseMove?.(event); } + private _onMouseEnter(event: MouseEvent) { + if (!event.isTrusted) + return; + if (this._ignoreOverlayEvent(event)) + return; + this._currentTool.onMouseEnter?.(event); + } + private _onMouseLeave(event: MouseEvent) { if (!event.isTrusted) return; @@ -1149,7 +1090,7 @@ export class Recorder { deepEventTarget(event: Event): HTMLElement { for (const element of event.composedPath()) { - if (!this._overlay?.contains(element as Element)) + if (!this.overlay?.contains(element as Element)) return element as HTMLElement; } return event.composedPath()[0] as HTMLElement; @@ -1301,14 +1242,4 @@ export class PollingRecorder implements RecorderDelegate { } } -function cmModeForLanguage(language: Language): string { - if (language === 'python') - return 'python'; - if (language === 'java') - return 'text/x-java'; - if (language === 'csharp') - return 'text/x-csharp'; - return 'javascript'; -} - export default PollingRecorder; From 738155d85df63552c065dda3abc2ae5ba62a778a Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Thu, 16 Nov 2023 15:07:43 -0800 Subject: [PATCH 076/491] fix(dispatcher): only remove stale dispatcher after sending "create" (#28176) Otherwise, we might dispose objects referenced in the initializer of the new object being created, which triggers an exception on the client. --- .github/workflows/tests_stress.yml | 6 +++ .../src/server/dispatchers/dispatcher.ts | 7 +++- tests/stress/frames.spec.ts | 42 +++++++++++++++++++ 3 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 tests/stress/frames.spec.ts diff --git a/.github/workflows/tests_stress.yml b/.github/workflows/tests_stress.yml index 8cfecb8d7c..418c348c92 100644 --- a/.github/workflows/tests_stress.yml +++ b/.github/workflows/tests_stress.yml @@ -40,13 +40,19 @@ jobs: if: always() - run: npm run stest browsers -- --project=chromium if: always() + - run: npm run stest frames -- --project=chromium + if: always() - run: npm run stest contexts -- --project=webkit if: always() - run: npm run stest browsers -- --project=webkit if: always() + - run: npm run stest frames -- --project=webkit + if: always() - run: npm run stest contexts -- --project=firefox if: always() - run: npm run stest browsers -- --project=firefox if: always() + - run: npm run stest frames -- --project=firefox + if: always() - run: npm run stest heap -- --project=chromium if: always() diff --git a/packages/playwright-core/src/server/dispatchers/dispatcher.ts b/packages/playwright-core/src/server/dispatchers/dispatcher.ts index 31931d1d31..9bcca908ec 100644 --- a/packages/playwright-core/src/server/dispatchers/dispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/dispatcher.ts @@ -74,6 +74,7 @@ export class Dispatcher maxDispatchers) + } + + maybeDisposeStaleDispatchers(type: string) { + const list = this._dispatchersByType.get(type); + if (list && list.size > maxDispatchers) this._disposeStaleDispatchers(type, list); } diff --git a/tests/stress/frames.spec.ts b/tests/stress/frames.spec.ts new file mode 100644 index 0000000000..11005369c3 --- /dev/null +++ b/tests/stress/frames.spec.ts @@ -0,0 +1,42 @@ +/** + * Copyright (c) Microsoft Corporation. All rights reserved. + * + * 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 { contextTest as test } from '../config/browserTest'; + +test('cycle frames', async ({ page, server }) => { + const kFrameCount = 1200; + + await page.goto(server.EMPTY_PAGE); + let cb; + const promise = new Promise(f => cb = f); + let counter = 0; + page.on('frameattached', () => { + if (++counter === kFrameCount) + cb(); + }); + + page.evaluate(async ({ url, count }) => { + for (let i = 0; i < count; i++) { + const frame = document.createElement('iframe'); + frame.src = url; + document.body.appendChild(frame); + await new Promise(f => setTimeout(f, 10)); + frame.remove(); + } + }, { url: server.PREFIX + '/one-style.html', count: kFrameCount }).catch(() => {}); + await promise; + await new Promise(f => setTimeout(f, 500)); +}); From 5488c03d7f508737d65a4a932f06aec93969b6c4 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Thu, 16 Nov 2023 16:31:34 -0800 Subject: [PATCH 077/491] chore: make `asLocator()` always safe (#28207) --- packages/playwright-core/src/client/locator.ts | 2 +- .../src/utils/isomorphic/locatorGenerators.ts | 18 +++++++----------- .../src/utils/isomorphic/locatorParser.ts | 2 +- packages/playwright/src/index.ts | 2 +- packages/recorder/src/callLog.tsx | 5 ++--- packages/trace-viewer/src/ui/actionList.tsx | 2 +- packages/trace-viewer/src/ui/callTab.tsx | 2 +- packages/trace-viewer/src/ui/snapshotTab.tsx | 2 +- 8 files changed, 15 insertions(+), 20 deletions(-) diff --git a/packages/playwright-core/src/client/locator.ts b/packages/playwright-core/src/client/locator.ts index fb811ea861..d6b47037bb 100644 --- a/packages/playwright-core/src/client/locator.ts +++ b/packages/playwright-core/src/client/locator.ts @@ -355,7 +355,7 @@ export class Locator implements api.Locator { } toString() { - return asLocator('javascript', this._selector, undefined, true); + return asLocator('javascript', this._selector); } } diff --git a/packages/playwright-core/src/utils/isomorphic/locatorGenerators.ts b/packages/playwright-core/src/utils/isomorphic/locatorGenerators.ts index 3715537f1d..e51fa0b623 100644 --- a/packages/playwright-core/src/utils/isomorphic/locatorGenerators.ts +++ b/packages/playwright-core/src/utils/isomorphic/locatorGenerators.ts @@ -35,20 +35,16 @@ export interface LocatorFactory { chainLocators(locators: string[]): string; } -export function asLocator(lang: Language, selector: string, isFrameLocator: boolean = false, playSafe: boolean = false): string { - return asLocators(lang, selector, isFrameLocator, playSafe)[0]; +export function asLocator(lang: Language, selector: string, isFrameLocator: boolean = false): string { + return asLocators(lang, selector, isFrameLocator)[0]; } -export function asLocators(lang: Language, selector: string, isFrameLocator: boolean = false, playSafe: boolean = false, maxOutputSize = 20, preferredQuote?: Quote): string[] { - if (playSafe) { - try { - return innerAsLocators(new generators[lang](preferredQuote), parseSelector(selector), isFrameLocator, maxOutputSize); - } catch (e) { - // Tolerate invalid input. - return [selector]; - } - } else { +export function asLocators(lang: Language, selector: string, isFrameLocator: boolean = false, maxOutputSize = 20, preferredQuote?: Quote): string[] { + try { return innerAsLocators(new generators[lang](preferredQuote), parseSelector(selector), isFrameLocator, maxOutputSize); + } catch (e) { + // Tolerate invalid input. + return [selector]; } } diff --git a/packages/playwright-core/src/utils/isomorphic/locatorParser.ts b/packages/playwright-core/src/utils/isomorphic/locatorParser.ts index 3b291a4996..886574e580 100644 --- a/packages/playwright-core/src/utils/isomorphic/locatorParser.ts +++ b/packages/playwright-core/src/utils/isomorphic/locatorParser.ts @@ -219,7 +219,7 @@ export function locatorOrSelectorAsSelector(language: Language, locator: string, } try { const { selector, preferredQuote } = parseLocator(locator, testIdAttributeName); - const locators = asLocators(language, selector, undefined, undefined, undefined, preferredQuote); + const locators = asLocators(language, selector, undefined, undefined, preferredQuote); const digest = digestForComparison(locator); if (locators.some(candidate => digestForComparison(candidate) === digest)) return selector; diff --git a/packages/playwright/src/index.ts b/packages/playwright/src/index.ts index 20670b3462..1bc888ef45 100644 --- a/packages/playwright/src/index.ts +++ b/packages/playwright/src/index.ts @@ -729,7 +729,7 @@ function renderApiCall(apiName: string, params: any) { continue; let value; if (name === 'selector' && isString(params[name]) && params[name].startsWith('internal:')) { - const getter = asLocator('javascript', params[name], false, true); + const getter = asLocator('javascript', params[name]); apiName = apiName.replace(/^locator\./, 'locator.' + getter + '.'); apiName = apiName.replace(/^page\./, 'page.' + getter + '.'); apiName = apiName.replace(/^frame\./, 'frame.' + getter + '.'); diff --git a/packages/recorder/src/callLog.tsx b/packages/recorder/src/callLog.tsx index 022ffa75df..c47b1ac323 100644 --- a/packages/recorder/src/callLog.tsx +++ b/packages/recorder/src/callLog.tsx @@ -40,8 +40,7 @@ export const CallLogView: React.FC = ({ {log.map(callLog => { const expandOverride = expandOverrides.get(callLog.id); const isExpanded = typeof expandOverride === 'boolean' ? expandOverride : callLog.status !== 'done'; - const locator = callLog.params.selector ? asLocator(language, callLog.params.selector, false) : null; - const locatorCall = `page.${locator}`; + const locator = callLog.params.selector ? asLocator(language, callLog.params.selector) : null; let titlePrefix = callLog.title; let titleSuffix = ''; if (callLog.title.startsWith('expect.to') || callLog.title.startsWith('expect.not.to')) { @@ -63,7 +62,7 @@ export const CallLogView: React.FC = ({ }}> { titlePrefix } { callLog.params.url ? {callLog.params.url} : undefined } - { locator ? {locatorCall} : undefined } + { locator ? {`page.${locator}`} : undefined } { titleSuffix } { typeof callLog.duration === 'number' ? — {msToString(callLog.duration)} : undefined} diff --git a/packages/trace-viewer/src/ui/actionList.tsx b/packages/trace-viewer/src/ui/actionList.tsx index a0f239b712..886df28a4c 100644 --- a/packages/trace-viewer/src/ui/actionList.tsx +++ b/packages/trace-viewer/src/ui/actionList.tsx @@ -88,7 +88,7 @@ export const renderAction = ( }) => { const { sdkLanguage, revealConsole, isLive, showDuration, showBadges } = options; const { errors, warnings } = modelUtil.stats(action); - const locator = action.params.selector ? asLocator(sdkLanguage || 'javascript', action.params.selector, false /* isFrameLocator */, true /* playSafe */) : undefined; + const locator = action.params.selector ? asLocator(sdkLanguage || 'javascript', action.params.selector) : undefined; let time: string = ''; if (action.endTime) diff --git a/packages/trace-viewer/src/ui/callTab.tsx b/packages/trace-viewer/src/ui/callTab.tsx index bb4cd2a0b4..463ba5056c 100644 --- a/packages/trace-viewer/src/ui/callTab.tsx +++ b/packages/trace-viewer/src/ui/callTab.tsx @@ -86,7 +86,7 @@ function propertyToString(event: ActionTraceEvent, name: string, value: any, sdk if ((name === 'value' && isEval) || (name === 'received' && event.method === 'expect')) value = parseSerializedValue(value, new Array(10).fill({ handle: '' })); if (name === 'selector') - return { text: asLocator(sdkLanguage || 'javascript', event.params.selector, false /* isFrameLocator */, true /* playSafe */), type: 'locator', name: 'locator' }; + return { text: asLocator(sdkLanguage || 'javascript', event.params.selector), type: 'locator', name: 'locator' }; const type = typeof value; if (type !== 'object' || value === null) return { text: String(value), type, name }; diff --git a/packages/trace-viewer/src/ui/snapshotTab.tsx b/packages/trace-viewer/src/ui/snapshotTab.tsx index a292fd6785..9d0a03948b 100644 --- a/packages/trace-viewer/src/ui/snapshotTab.tsx +++ b/packages/trace-viewer/src/ui/snapshotTab.tsx @@ -245,7 +245,7 @@ export const InspectModeController: React.FunctionComponent<{ overlay: { offsetX: 0 }, }, { async setSelector(selector: string) { - setHighlightedLocator(asLocator(sdkLanguage, frameSelector + selector, false /* isFrameLocator */, true /* playSafe */)); + setHighlightedLocator(asLocator(sdkLanguage, frameSelector + selector)); }, highlightUpdated() { for (const r of recorders) { From 11bf96fe98decb99a6abd08578b4d27c06e49cd9 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Thu, 16 Nov 2023 16:31:42 -0800 Subject: [PATCH 078/491] test: unflake a few tests (#28205) --- tests/library/capabilities.spec.ts | 4 +--- tests/library/channels.spec.ts | 7 +++---- tests/stress/frames.spec.ts | 2 ++ 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/tests/library/capabilities.spec.ts b/tests/library/capabilities.spec.ts index 58bb215a6a..bc7415fa48 100644 --- a/tests/library/capabilities.spec.ts +++ b/tests/library/capabilities.spec.ts @@ -32,9 +32,7 @@ it('SharedArrayBuffer should work @smoke', async function({ contextFactory, http expect(await page.evaluate(() => typeof SharedArrayBuffer)).toBe('function'); }); -it('Web Assembly should work @smoke', async function({ page, server, browserName, platform }) { - it.fail(browserName === 'webkit' && platform === 'win32'); - +it('Web Assembly should work @smoke', async function({ page, server }) { await page.goto(server.PREFIX + '/wasm/table2.html'); expect(await page.evaluate('loadTable()')).toBe('42, 83'); }); diff --git a/tests/library/channels.spec.ts b/tests/library/channels.spec.ts index dc3ec1b5f5..44ad5e0943 100644 --- a/tests/library/channels.spec.ts +++ b/tests/library/channels.spec.ts @@ -35,6 +35,7 @@ const it = playwrightTest.extend<{}, { expectScopeState: (object: any, golden: a }); it.skip(({ mode }) => mode !== 'default'); +it.skip(({ video }) => video === 'on', 'Extra video artifacts in the objects list'); it('should scope context handles', async ({ browserType, server, expectScopeState }) => { const browser = await browserType.launch(); @@ -184,7 +185,7 @@ it('should scope browser handles', async ({ browserType, expectScopeState }) => expectScopeState(browserType, GOLDEN_PRECONDITION); }); -it('should not generate dispatchers for subresources w/o listeners', async ({ page, server, browserType, expectScopeState, video }) => { +it('should not generate dispatchers for subresources w/o listeners', async ({ page, server, browserType, expectScopeState }) => { server.setRedirect('/one-style.css', '/two-style.css'); server.setRedirect('/two-style.css', '/three-style.css'); server.setRedirect('/three-style.css', '/four-style.css'); @@ -201,7 +202,6 @@ it('should not generate dispatchers for subresources w/o listeners', async ({ pa { _guid: 'browser-type', objects: [ { _guid: 'browser', objects: [ - ...(video === 'on' ? [{ _guid: 'artifact', objects: [] }] : []), { _guid: 'browser-context', objects: [ { _guid: 'page', objects: [ @@ -256,7 +256,7 @@ it('should work with the domain module', async ({ browserType, server, browserNa throw err; }); -it('exposeFunction should not leak', async ({ page, expectScopeState, server, video }) => { +it('exposeFunction should not leak', async ({ page, expectScopeState, server }) => { await page.goto(server.EMPTY_PAGE); let called = 0; await page.exposeFunction('myFunction', () => ++called); @@ -284,7 +284,6 @@ it('exposeFunction should not leak', async ({ page, expectScopeState, server, vi { '_guid': 'browser', 'objects': [ - ...(video === 'on' ? [{ _guid: 'artifact', objects: [] }] : []), { '_guid': 'browser-context', 'objects': [ diff --git a/tests/stress/frames.spec.ts b/tests/stress/frames.spec.ts index 11005369c3..48489e7262 100644 --- a/tests/stress/frames.spec.ts +++ b/tests/stress/frames.spec.ts @@ -16,6 +16,8 @@ import { contextTest as test } from '../config/browserTest'; +test.slow(); + test('cycle frames', async ({ page, server }) => { const kFrameCount = 1200; From 62d4c3fe0261c7fad813746f93d617ef07b272b0 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Fri, 17 Nov 2023 13:36:50 -0800 Subject: [PATCH 079/491] fix(defineConfig): do not add an empty project list to project-less configs (#28224) Otherwise, merging two configs without `projects` property will create a config with an empty project list, which is semantically different and always leads to "No tests found". --- packages/playwright/src/common/configLoader.ts | 3 +++ tests/playwright-test/config.spec.ts | 3 +++ 2 files changed, 6 insertions(+) diff --git a/packages/playwright/src/common/configLoader.ts b/packages/playwright/src/common/configLoader.ts index 4d72ed73a0..9d648ed0ea 100644 --- a/packages/playwright/src/common/configLoader.ts +++ b/packages/playwright/src/common/configLoader.ts @@ -48,6 +48,9 @@ export const defineConfig = (...configs: any[]) => { ] }; + if (!result.projects && !config.projects) + continue; + const projectOverrides = new Map(); for (const project of config.projects || []) projectOverrides.set(project.name, project); diff --git a/tests/playwright-test/config.spec.ts b/tests/playwright-test/config.spec.ts index bddbde5f57..fb2f1888d2 100644 --- a/tests/playwright-test/config.spec.ts +++ b/tests/playwright-test/config.spec.ts @@ -546,6 +546,9 @@ test('should merge configs', async ({ runInlineTest }) => { command: 'echo 123', }] })); + + // Should not add an empty project list. + expect(defineConfig({}, {}).projects).toBeUndefined(); `, 'a.test.ts': ` import { test } from '@playwright/test'; From 34c8516d0974cd829d4e1d3295d6258c5c29b688 Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Fri, 17 Nov 2023 16:53:36 -0800 Subject: [PATCH 080/491] fix: correctly print number of interrupted tests in markdown (#28228) --- packages/playwright/src/reporters/markdown.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/playwright/src/reporters/markdown.ts b/packages/playwright/src/reporters/markdown.ts index 52f67a548e..a32d7bda42 100644 --- a/packages/playwright/src/reporters/markdown.ts +++ b/packages/playwright/src/reporters/markdown.ts @@ -57,7 +57,7 @@ class MarkdownReporter extends BaseReporter { } if (summary.interrupted.length) { lines.push(`
`); - lines.push(`${summary.flaky.length} interrupted`); + lines.push(`${summary.interrupted.length} interrupted`); this._printTestList(':warning:', summary.interrupted, lines, '
'); lines.push(`
`); lines.push(``); From 3f55587dd89a9e21bb3bdd4e24d33fffeb5563bc Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Fri, 17 Nov 2023 17:16:32 -0800 Subject: [PATCH 081/491] feat(vrt): bring back wait for font loading (#28226) --- .../src/server/screenshotter.ts | 7 +++++ tests/page/page-screenshot.spec.ts | 26 +++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/packages/playwright-core/src/server/screenshotter.ts b/packages/playwright-core/src/server/screenshotter.ts index 3fd4f9eb60..464d95bdc0 100644 --- a/packages/playwright-core/src/server/screenshotter.ts +++ b/packages/playwright-core/src/server/screenshotter.ts @@ -240,6 +240,13 @@ export class Screenshotter { await Promise.all(this._page.frames().map(async frame => { await frame.nonStallingEvaluateInExistingContext('(' + inPagePrepareForScreenshots.toString() + `)(${hideCaret}, ${disableAnimations})`, false, 'utility').catch(() => {}); })); + if (!process.env.PW_TEST_SCREENSHOT_NO_FONTS_READY) { + progress.log('waiting for fonts to load...'); + await Promise.all(this._page.frames().map(async frame => { + await frame.nonStallingEvaluateInExistingContext('document.fonts.ready', false, 'utility').catch(() => {}); + })); + progress.log('fonts loaded'); + } progress.cleanupWhenAborted(() => this._restorePageAfterScreenshot()); } diff --git a/tests/page/page-screenshot.spec.ts b/tests/page/page-screenshot.spec.ts index 1b4bb17169..ecb876cd15 100644 --- a/tests/page/page-screenshot.spec.ts +++ b/tests/page/page-screenshot.spec.ts @@ -824,6 +824,32 @@ it.describe('page screenshot animations', () => { 'onfinish', 'animationend' ]); }); + + it('should wait for fonts to load', async ({ page, server, isWindows, browserName, isLinux }) => { + it.fixme(isWindows, 'This requires a windows-specific test expectations. https://github.com/microsoft/playwright/issues/12707'); + await page.setViewportSize({ width: 500, height: 500 }); + const fontRequestPromise = new Promise(resolve => { + // Stall font loading. + server.setRoute('/webfont/iconfont.woff2', (request, response) => { + resolve({ request, response }); + }); + }); + await page.goto(server.PREFIX + '/webfont/webfont.html', { + waitUntil: 'domcontentloaded', // 'load' will not happen if webfont is pending + }); + + // Make sure screenshot times out while webfont is stalled. + const error = await page.screenshot({ timeout: 200, }).catch(e => e); + expect(error.message).toContain('waiting for fonts to load...'); + expect(error.message).toContain('Timeout 200ms exceeded'); + + const fontRequest = await fontRequestPromise; + server.serveFile(fontRequest.request, fontRequest.response); + const iconsScreenshot = await page.screenshot(); + expect(iconsScreenshot).toMatchSnapshot('screenshot-web-font.png', { + maxDiffPixels: 50, + }); + }); }); it('should throw if screenshot size is too large', async ({ page, browserName, isMac }) => { From 440f5e5d2be2e5b251dfed33eb4d65c5c01a97ba Mon Sep 17 00:00:00 2001 From: itchyny Date: Mon, 20 Nov 2023 18:58:10 +0900 Subject: [PATCH 082/491] fix: collect all errors in removeFolders (#28239) This PR fixes https://github.com/microsoft/playwright/pull/27790#pullrequestreview-1738958803. Previously this function returns only the first error when some of the promises fail. But the type annotation suggests that the original intention was to collect all the errors. This commit fixes the error values, and unexpected `TypeError: object is not iterable`. --- packages/playwright-core/src/utils/fileUtils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/playwright-core/src/utils/fileUtils.ts b/packages/playwright-core/src/utils/fileUtils.ts index 287899d509..751878a869 100644 --- a/packages/playwright-core/src/utils/fileUtils.ts +++ b/packages/playwright-core/src/utils/fileUtils.ts @@ -28,8 +28,8 @@ export async function mkdirIfNeeded(filePath: string) { export async function removeFolders(dirs: string[]): Promise { return await Promise.all(dirs.map((dir: string) => - fs.promises.rm(dir, { recursive: true, force: true, maxRetries: 10 }) - )).catch(e => e); + fs.promises.rm(dir, { recursive: true, force: true, maxRetries: 10 }).catch(e => e) + )); } export function canAccessFile(file: string) { From 5ef5e776f65b7d7abcec334333df84c722b2a6e9 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Mon, 20 Nov 2023 21:17:37 +0100 Subject: [PATCH 083/491] docs(browsers): add protocol to PLAYWRIGHT_FIREFOX_DOWNLOAD_HOST (#28246) --- docs/src/browsers.md | 64 ++++++++++++++++++++++---------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/docs/src/browsers.md b/docs/src/browsers.md index 12df8d88a9..011ce9001c 100644 --- a/docs/src/browsers.md +++ b/docs/src/browsers.md @@ -612,61 +612,61 @@ binaries. In this case, Playwright can be configured to download from a custom location using the `PLAYWRIGHT_DOWNLOAD_HOST` env variable. ```bash tab=bash-bash lang=js -PLAYWRIGHT_DOWNLOAD_HOST=192.0.2.1 npx playwright install +PLAYWRIGHT_DOWNLOAD_HOST=http://192.0.2.1 npx playwright install ``` ```batch tab=bash-batch lang=js -set PLAYWRIGHT_DOWNLOAD_HOST=192.0.2.1 +set PLAYWRIGHT_DOWNLOAD_HOST=http://192.0.2.1 npx playwright install ``` ```powershell tab=bash-powershell lang=js -$Env:PLAYWRIGHT_DOWNLOAD_HOST="192.0.2.1" +$Env:PLAYWRIGHT_DOWNLOAD_HOST="http://192.0.2.1" npx playwright install ``` ```bash tab=bash-bash lang=python pip install playwright -PLAYWRIGHT_DOWNLOAD_HOST=192.0.2.1 playwright install +PLAYWRIGHT_DOWNLOAD_HOST=http://192.0.2.1 playwright install ``` ```batch tab=bash-batch lang=python -set PLAYWRIGHT_DOWNLOAD_HOST=192.0.2.1 +set PLAYWRIGHT_DOWNLOAD_HOST=http://192.0.2.1 pip install playwright playwright install ``` ```powershell tab=bash-powershell lang=python -$Env:PLAYWRIGHT_DOWNLOAD_HOST="192.0.2.1" +$Env:PLAYWRIGHT_DOWNLOAD_HOST="http://192.0.2.1" pip install playwright playwright install ``` ```bash tab=bash-bash lang=java -PLAYWRIGHT_DOWNLOAD_HOST=192.0.2.1 mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="install" +PLAYWRIGHT_DOWNLOAD_HOST=http://192.0.2.1 mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="install" ``` ```batch tab=bash-batch lang=java -set PLAYWRIGHT_DOWNLOAD_HOST=192.0.2.1 +set PLAYWRIGHT_DOWNLOAD_HOST=http://192.0.2.1 mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="install" ``` ```powershell tab=bash-powershell lang=java -$Env:PLAYWRIGHT_DOWNLOAD_HOST="192.0.2.1" +$Env:PLAYWRIGHT_DOWNLOAD_HOST="http://192.0.2.1" mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="install" ``` ```bash tab=bash-bash lang=csharp -PLAYWRIGHT_DOWNLOAD_HOST=192.0.2.1 pwsh bin/Debug/netX/playwright.ps1 install +PLAYWRIGHT_DOWNLOAD_HOST=http://192.0.2.1 pwsh bin/Debug/netX/playwright.ps1 install ``` ```batch tab=bash-batch lang=csharp -set PLAYWRIGHT_DOWNLOAD_HOST=192.0.2.1 +set PLAYWRIGHT_DOWNLOAD_HOST=http://192.0.2.1 pwsh bin/Debug/netX/playwright.ps1 install ``` ```powershell tab=bash-powershell lang=csharp -$Env:PLAYWRIGHT_DOWNLOAD_HOST="192.0.2.1" +$Env:PLAYWRIGHT_DOWNLOAD_HOST="http://192.0.2.1" pwsh bin/Debug/netX/playwright.ps1 install ``` @@ -674,69 +674,69 @@ It is also possible to use a per-browser download hosts using `PLAYWRIGHT_CHROMI take precedence over `PLAYWRIGHT_DOWNLOAD_HOST`. ```bash tab=bash-bash lang=js -PLAYWRIGHT_FIREFOX_DOWNLOAD_HOST=203.0.113.3 PLAYWRIGHT_DOWNLOAD_HOST=192.0.2.1 npx playwright install +PLAYWRIGHT_FIREFOX_DOWNLOAD_HOST=http://203.0.113.3 PLAYWRIGHT_DOWNLOAD_HOST=http://192.0.2.1 npx playwright install ``` ```batch tab=bash-batch lang=js -set PLAYWRIGHT_FIREFOX_DOWNLOAD_HOST=203.0.113.3 -set PLAYWRIGHT_DOWNLOAD_HOST=192.0.2.1 +set PLAYWRIGHT_FIREFOX_DOWNLOAD_HOST=http://203.0.113.3 +set PLAYWRIGHT_DOWNLOAD_HOST=http://192.0.2.1 npx playwright install ``` ```powershell tab=bash-powershell lang=js -$Env:PLAYWRIGHT_FIREFOX_DOWNLOAD_HOST="203.0.113.3" -$Env:PLAYWRIGHT_DOWNLOAD_HOST="192.0.2.1" +$Env:PLAYWRIGHT_FIREFOX_DOWNLOAD_HOST="http://203.0.113.3" +$Env:PLAYWRIGHT_DOWNLOAD_HOST="http://192.0.2.1" npx playwright install ``` ```bash tab=bash-bash lang=python pip install playwright -PLAYWRIGHT_FIREFOX_DOWNLOAD_HOST=203.0.113.3 PLAYWRIGHT_DOWNLOAD_HOST=192.0.2.1 playwright install +PLAYWRIGHT_FIREFOX_DOWNLOAD_HOST=http://203.0.113.3 PLAYWRIGHT_DOWNLOAD_HOST=http://192.0.2.1 playwright install ``` ```batch tab=bash-batch lang=python -set PLAYWRIGHT_FIREFOX_DOWNLOAD_HOST=203.0.113.3 -set PLAYWRIGHT_DOWNLOAD_HOST=192.0.2.1 +set PLAYWRIGHT_FIREFOX_DOWNLOAD_HOST=http://203.0.113.3 +set PLAYWRIGHT_DOWNLOAD_HOST=http://192.0.2.1 pip install playwright playwright install ``` ```powershell tab=bash-powershell lang=python -$Env:PLAYWRIGHT_FIREFOX_DOWNLOAD_HOST="203.0.113.3" -$Env:PLAYWRIGHT_DOWNLOAD_HOST="192.0.2.1" +$Env:PLAYWRIGHT_FIREFOX_DOWNLOAD_HOST="http://203.0.113.3" +$Env:PLAYWRIGHT_DOWNLOAD_HOST="http://192.0.2.1" pip install playwright playwright install ``` ```bash tab=bash-bash lang=java -PLAYWRIGHT_FIREFOX_DOWNLOAD_HOST=203.0.113.3 PLAYWRIGHT_DOWNLOAD_HOST=192.0.2.1 mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="install" +PLAYWRIGHT_FIREFOX_DOWNLOAD_HOST=http://203.0.113.3 PLAYWRIGHT_DOWNLOAD_HOST=http://192.0.2.1 mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="install" ``` ```batch tab=bash-batch lang=java -set PLAYWRIGHT_FIREFOX_DOWNLOAD_HOST=203.0.113.3 -set PLAYWRIGHT_DOWNLOAD_HOST=192.0.2.1 +set PLAYWRIGHT_FIREFOX_DOWNLOAD_HOST=http://203.0.113.3 +set PLAYWRIGHT_DOWNLOAD_HOST=http://192.0.2.1 mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="install" ``` ```powershell tab=bash-powershell lang=java -$Env:PLAYWRIGHT_FIREFOX_DOWNLOAD_HOST="203.0.113.3" -$Env:PLAYWRIGHT_DOWNLOAD_HOST="192.0.2.1" +$Env:PLAYWRIGHT_FIREFOX_DOWNLOAD_HOST="http://203.0.113.3" +$Env:PLAYWRIGHT_DOWNLOAD_HOST="http://192.0.2.1" mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="install" ``` ```bash tab=bash-bash lang=csharp -PLAYWRIGHT_FIREFOX_DOWNLOAD_HOST=203.0.113.3 PLAYWRIGHT_DOWNLOAD_HOST=192.0.2.1 pwsh bin/Debug/netX/playwright.ps1 install +PLAYWRIGHT_FIREFOX_DOWNLOAD_HOST=http://203.0.113.3 PLAYWRIGHT_DOWNLOAD_HOST=http://192.0.2.1 pwsh bin/Debug/netX/playwright.ps1 install ``` ```batch tab=bash-batch lang=csharp -set PLAYWRIGHT_FIREFOX_DOWNLOAD_HOST=203.0.113.3 -set PLAYWRIGHT_DOWNLOAD_HOST=192.0.2.1 +set PLAYWRIGHT_FIREFOX_DOWNLOAD_HOST=http://203.0.113.3 +set PLAYWRIGHT_DOWNLOAD_HOST=http://192.0.2.1 pwsh bin/Debug/netX/playwright.ps1 install ``` ```powershell tab=bash-powershell lang=csharp -$Env:PLAYWRIGHT_DOWNLOAD_HOST="192.0.2.1" -$Env:PLAYWRIGHT_FIREFOX_DOWNLOAD_HOST="203.0.113.3" +$Env:PLAYWRIGHT_DOWNLOAD_HOST="http://192.0.2.1" +$Env:PLAYWRIGHT_FIREFOX_DOWNLOAD_HOST="http://203.0.113.3" pwsh bin/Debug/netX/playwright.ps1 install ``` ## Managing browser binaries From 8c880cad7625f07cd43211badb9f36a86da29871 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Tue, 21 Nov 2023 17:54:01 +0100 Subject: [PATCH 084/491] docs(release-notes): 1.40 nits (#28271) --- docs/src/release-notes-csharp.md | 2 +- docs/src/release-notes-java.md | 1 - docs/src/release-notes-python.md | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/src/release-notes-csharp.md b/docs/src/release-notes-csharp.md index a3b42b741a..620ee32bd9 100644 --- a/docs/src/release-notes-csharp.md +++ b/docs/src/release-notes-csharp.md @@ -35,7 +35,7 @@ await Expect(Page.GetByPlaceholder("Search docs")).ToHaveValueAsync("locator"); ### Other Changes - Methods [`method: Download.path`] and [`method: Download.createReadStream`] throw an error for failed and cancelled downloads. -- Playwright [docker image](./docker.md) now comes with Node.js v20. +- Playwright [docker image](./docker.md) now comes with .NET 8 (new LTS). ### Browser Versions diff --git a/docs/src/release-notes-java.md b/docs/src/release-notes-java.md index 5703cf9a26..2fac82f2a3 100644 --- a/docs/src/release-notes-java.md +++ b/docs/src/release-notes-java.md @@ -35,7 +35,6 @@ assertThat(page.getByPlaceholder("Search docs")).hasValue("locator"); ### Other Changes - Methods [`method: Download.path`] and [`method: Download.createReadStream`] throw an error for failed and cancelled downloads. -- Playwright [docker image](./docker.md) now comes with Node.js v20. ### Browser Versions diff --git a/docs/src/release-notes-python.md b/docs/src/release-notes-python.md index f3388431f3..f01882178c 100644 --- a/docs/src/release-notes-python.md +++ b/docs/src/release-notes-python.md @@ -38,7 +38,6 @@ def test_example(page: Page) -> None: ### Other Changes - Method [`method: Download.path`] throws an error for failed and cancelled downloads. -- Playwright [docker image](./docker.md) now comes with Node.js v20. ### Browser Versions From d7d1c80cf7b9f2120bc66839269f8438dcfbe1e5 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Tue, 21 Nov 2023 17:54:23 +0100 Subject: [PATCH 085/491] docs(python): add ignoreCase to NotToHaveAttribute (#28267) https://github.com/microsoft/playwright-python/issues/2078 --- docs/src/api/class-locatorassertions.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/src/api/class-locatorassertions.md b/docs/src/api/class-locatorassertions.md index 20e7c3f0fe..cf78c6ce95 100644 --- a/docs/src/api/class-locatorassertions.md +++ b/docs/src/api/class-locatorassertions.md @@ -244,6 +244,12 @@ Attribute name. Expected attribute value. +### option: LocatorAssertions.NotToHaveAttribute.ignoreCase +* since: v1.40 +- `ignoreCase` <[boolean]> + +Whether to perform case-insensitive match. [`option: ignoreCase`] option takes precedence over the corresponding regular expression flag if specified. + ### option: LocatorAssertions.NotToHaveAttribute.timeout = %%-csharp-java-python-assertions-timeout-%% * since: v1.18 From 7caa212a1f56c89282390263737650e4f20355bd Mon Sep 17 00:00:00 2001 From: Playwright Service <89237858+playwrightmachine@users.noreply.github.com> Date: Tue, 21 Nov 2023 12:47:26 -0800 Subject: [PATCH 086/491] feat(webkit): roll to r1947 (#28270) Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> --- packages/playwright-core/browsers.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/playwright-core/browsers.json b/packages/playwright-core/browsers.json index 9ea1997a80..5f0fb2ef58 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -39,7 +39,7 @@ }, { "name": "webkit", - "revision": "1944", + "revision": "1947", "installByDefault": true, "revisionOverrides": { "mac10.14": "1446", From 91c5bac52b2002509933b7df0a062dddf58ae5ef Mon Sep 17 00:00:00 2001 From: Playwright Service <89237858+playwrightmachine@users.noreply.github.com> Date: Wed, 22 Nov 2023 01:15:24 -0800 Subject: [PATCH 087/491] feat(chromium-tip-of-tree): roll to r1170 (#28277) 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 5f0fb2ef58..520e06e93c 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -15,9 +15,9 @@ }, { "name": "chromium-tip-of-tree", - "revision": "1169", + "revision": "1170", "installByDefault": false, - "browserVersion": "121.0.6131.0" + "browserVersion": "121.0.6139.0" }, { "name": "firefox", From e405c1deead316101a35318f8f6c89ea87d495d5 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Wed, 22 Nov 2023 19:40:23 +0100 Subject: [PATCH 088/491] docs(trace-viewer): fix
syntax (#28276) --- docs/src/trace-viewer-intro-csharp-java-python.md | 4 ++-- docs/src/trace-viewer.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/src/trace-viewer-intro-csharp-java-python.md b/docs/src/trace-viewer-intro-csharp-java-python.md index 84bbc8dd00..35d8335c7a 100644 --- a/docs/src/trace-viewer-intro-csharp-java-python.md +++ b/docs/src/trace-viewer-intro-csharp-java-python.md @@ -28,8 +28,8 @@ Options for tracing are: This will record the trace and place it into the file named `trace.zip` in your `test-results` directory. -
If you are not using Pytest, click here to learn how to record traces. - +
+If you are not using Pytest, click here to learn how to record traces. ```python async browser = await chromium.launch() diff --git a/docs/src/trace-viewer.md b/docs/src/trace-viewer.md index 5d60be491d..7ffe10f4a9 100644 --- a/docs/src/trace-viewer.md +++ b/docs/src/trace-viewer.md @@ -177,8 +177,8 @@ Options for tracing are: This will record the trace and place it into the file named `trace.zip` in your `test-results` directory. -
If you are not using Pytest, click here to learn how to record traces. - +
+If you are not using Pytest, click here to learn how to record traces. ```python async browser = await chromium.launch() From 2f1b0d6ff751dec78e6f94175457422200cafcf1 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Wed, 22 Nov 2023 20:26:21 +0100 Subject: [PATCH 089/491] fix(install): hang on server side connection close (#28278) --- .../server/registry/oopDownloadBrowserMain.ts | 7 +++++ tests/installation/playwright-cdn.spec.ts | 27 +++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/packages/playwright-core/src/server/registry/oopDownloadBrowserMain.ts b/packages/playwright-core/src/server/registry/oopDownloadBrowserMain.ts index a34a9b20c7..e205f2c4fc 100644 --- a/packages/playwright-core/src/server/registry/oopDownloadBrowserMain.ts +++ b/packages/playwright-core/src/server/registry/oopDownloadBrowserMain.ts @@ -85,6 +85,13 @@ function downloadFile(options: DownloadParams): Promise { file.on('error', error => promise.reject(error)); response.pipe(file); response.on('data', onData); + response.on('close', () => { + if (response.complete) + return; + file.close(); + log(`-- download failed, server closed connection`); + promise.reject(new Error(`Download failed: server closed connection. URL: ${options.url}`)); + }); }, (error: any) => promise.reject(error)); return promise; diff --git a/tests/installation/playwright-cdn.spec.ts b/tests/installation/playwright-cdn.spec.ts index fabc8012fb..3d202da81d 100644 --- a/tests/installation/playwright-cdn.spec.ts +++ b/tests/installation/playwright-cdn.spec.ts @@ -68,3 +68,30 @@ test(`playwright cdn should race with a timeout`, async ({ exec }) => { await new Promise(resolve => server.close(resolve)); } }); + +test(`npx playwright install should not hang when CDN closes the connection`, async ({ exec }) => { + let retryCount = 0; + const server = http.createServer((req, res) => { + ++retryCount; + res.writeHead(200, { + 'Content-Length': 100 * 1024 * 1024, + 'Content-Type': 'application/zip', + }); + res.end('a'); + }); + await new Promise(resolve => server.listen(0, resolve)); + try { + await exec('npm i playwright'); + const result = await exec('npx playwright install', { + env: { + PLAYWRIGHT_DOWNLOAD_HOST: `http://127.0.0.1:${(server.address() as AddressInfo).port}`, + DEBUG: 'pw:install', + }, + expectToExitWithError: true + }); + expect(retryCount).toBe(3); + expect([...result.matchAll(/Download failed: server closed connection/g)]).toHaveLength(3); + } finally { + await new Promise(resolve => server.close(resolve)); + } +}); From 3b5cfae455cfdd43961b16c7c214604ae3ea822b Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Wed, 22 Nov 2023 21:31:05 +0100 Subject: [PATCH 090/491] chore: update TypeScript to 5.3 (#28300) https://devblogs.microsoft.com/typescript/announcing-typescript-5-3/ --- package-lock.json | 222 +++++++++++++++++++++++----------------------- package.json | 6 +- 2 files changed, 114 insertions(+), 114 deletions(-) diff --git a/package-lock.json b/package-lock.json index de3c6ed5de..d81d5498e4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,8 +32,8 @@ "@types/resize-observer-browser": "^0.1.7", "@types/ws": "^8.5.3", "@types/xml2js": "^0.4.9", - "@typescript-eslint/eslint-plugin": "^6.6.0", - "@typescript-eslint/parser": "^6.6.0", + "@typescript-eslint/eslint-plugin": "^6.12.0", + "@typescript-eslint/parser": "^6.12.0", "@vitejs/plugin-basic-ssl": "^1.0.1", "@vitejs/plugin-react": "^3.1.0", "@zip.js/zip.js": "^2.7.29", @@ -61,7 +61,7 @@ "react-dom": "^18.1.0", "socksv5": "0.0.6", "ssim.js": "^3.5.0", - "typescript": "^5.2.2", + "typescript": "^5.3.2", "vite": "^4.3.9", "ws": "^8.5.0", "xml2js": "^0.5.0", @@ -1260,9 +1260,9 @@ } }, "node_modules/@types/json-schema": { - "version": "7.0.12", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", - "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true }, "node_modules/@types/node": { @@ -1305,9 +1305,9 @@ "license": "MIT" }, "node_modules/@types/semver": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.1.tgz", - "integrity": "sha512-cJRQXpObxfNKkFAZbJl2yjWtJCqELQIdShsogr1d2MilP8dKD9TE/nEKHkJgUNHdGKCQaf9HbIynuV2csLGVLg==", + "version": "7.5.6", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz", + "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==", "dev": true }, "node_modules/@types/tern": { @@ -1337,16 +1337,16 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.6.0.tgz", - "integrity": "sha512-CW9YDGTQnNYMIo5lMeuiIG08p4E0cXrXTbcZ2saT/ETE7dWUrNxlijsQeU04qAAKkILiLzdQz+cGFxCJjaZUmA==", + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.12.0.tgz", + "integrity": "sha512-XOpZ3IyJUIV1b15M7HVOpgQxPPF7lGXgsfcEIu3yDxFPaf/xZKt7s9QO/pbk7vpWQyVulpJbu4E5LwpZiQo4kA==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.6.0", - "@typescript-eslint/type-utils": "6.6.0", - "@typescript-eslint/utils": "6.6.0", - "@typescript-eslint/visitor-keys": "6.6.0", + "@typescript-eslint/scope-manager": "6.12.0", + "@typescript-eslint/type-utils": "6.12.0", + "@typescript-eslint/utils": "6.12.0", + "@typescript-eslint/visitor-keys": "6.12.0", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -1387,15 +1387,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.6.0.tgz", - "integrity": "sha512-setq5aJgUwtzGrhW177/i+DMLqBaJbdwGj2CPIVFFLE0NCliy5ujIdLHd2D1ysmlmsjdL2GWW+hR85neEfc12w==", + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.12.0.tgz", + "integrity": "sha512-s8/jNFPKPNRmXEnNXfuo1gemBdVmpQsK1pcu+QIvuNJuhFzGrpD7WjOcvDc/+uEdfzSYpNu7U/+MmbScjoQ6vg==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "6.6.0", - "@typescript-eslint/types": "6.6.0", - "@typescript-eslint/typescript-estree": "6.6.0", - "@typescript-eslint/visitor-keys": "6.6.0", + "@typescript-eslint/scope-manager": "6.12.0", + "@typescript-eslint/types": "6.12.0", + "@typescript-eslint/typescript-estree": "6.12.0", + "@typescript-eslint/visitor-keys": "6.12.0", "debug": "^4.3.4" }, "engines": { @@ -1415,13 +1415,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.6.0.tgz", - "integrity": "sha512-pT08u5W/GT4KjPUmEtc2kSYvrH8x89cVzkA0Sy2aaOUIw6YxOIjA8ilwLr/1fLjOedX1QAuBpG9XggWqIIfERw==", + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.12.0.tgz", + "integrity": "sha512-5gUvjg+XdSj8pcetdL9eXJzQNTl3RD7LgUiYTl8Aabdi8hFkaGSYnaS6BLc0BGNaDH+tVzVwmKtWvu0jLgWVbw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.6.0", - "@typescript-eslint/visitor-keys": "6.6.0" + "@typescript-eslint/types": "6.12.0", + "@typescript-eslint/visitor-keys": "6.12.0" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -1432,13 +1432,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.6.0.tgz", - "integrity": "sha512-8m16fwAcEnQc69IpeDyokNO+D5spo0w1jepWWY2Q6y5ZKNuj5EhVQXjtVAeDDqvW6Yg7dhclbsz6rTtOvcwpHg==", + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.12.0.tgz", + "integrity": "sha512-WWmRXxhm1X8Wlquj+MhsAG4dU/Blvf1xDgGaYCzfvStP2NwPQh6KBvCDbiOEvaE0filhranjIlK/2fSTVwtBng==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "6.6.0", - "@typescript-eslint/utils": "6.6.0", + "@typescript-eslint/typescript-estree": "6.12.0", + "@typescript-eslint/utils": "6.12.0", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, @@ -1459,9 +1459,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.6.0.tgz", - "integrity": "sha512-CB6QpJQ6BAHlJXdwUmiaXDBmTqIE2bzGTDLADgvqtHWuhfNP3rAOK7kAgRMAET5rDRr9Utt+qAzRBdu3AhR3sg==", + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.12.0.tgz", + "integrity": "sha512-MA16p/+WxM5JG/F3RTpRIcuOghWO30//VEOvzubM8zuOOBYXsP+IfjoCXXiIfy2Ta8FRh9+IO9QLlaFQUU+10Q==", "dev": true, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -1472,13 +1472,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.6.0.tgz", - "integrity": "sha512-hMcTQ6Al8MP2E6JKBAaSxSVw5bDhdmbCEhGW/V8QXkb9oNsFkA4SBuOMYVPxD3jbtQ4R/vSODBsr76R6fP3tbA==", + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.12.0.tgz", + "integrity": "sha512-vw9E2P9+3UUWzhgjyyVczLWxZ3GuQNT7QpnIY3o5OMeLO/c8oHljGc8ZpryBMIyympiAAaKgw9e5Hl9dCWFOYw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.6.0", - "@typescript-eslint/visitor-keys": "6.6.0", + "@typescript-eslint/types": "6.12.0", + "@typescript-eslint/visitor-keys": "6.12.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -1514,17 +1514,17 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.6.0.tgz", - "integrity": "sha512-mPHFoNa2bPIWWglWYdR0QfY9GN0CfvvXX1Sv6DlSTive3jlMTUy+an67//Gysc+0Me9pjitrq0LJp0nGtLgftw==", + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.12.0.tgz", + "integrity": "sha512-LywPm8h3tGEbgfyjYnu3dauZ0U7R60m+miXgKcZS8c7QALO9uWJdvNoP+duKTk2XMWc7/Q3d/QiCuLN9X6SWyQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.6.0", - "@typescript-eslint/types": "6.6.0", - "@typescript-eslint/typescript-estree": "6.6.0", + "@typescript-eslint/scope-manager": "6.12.0", + "@typescript-eslint/types": "6.12.0", + "@typescript-eslint/typescript-estree": "6.12.0", "semver": "^7.5.4" }, "engines": { @@ -1554,12 +1554,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.6.0.tgz", - "integrity": "sha512-L61uJT26cMOfFQ+lMZKoJNbAEckLe539VhTxiGHrWl5XSKQgA0RTBZJW2HFPy5T0ZvPVSD93QsrTKDkfNwJGyQ==", + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.12.0.tgz", + "integrity": "sha512-rg3BizTZHF1k3ipn8gfrzDXXSFKyOEB5zxYXInQ6z0hUvmQlhaZQzK+YmHmNViMA9HzW5Q9+bPPt90bU6GQwyw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.6.0", + "@typescript-eslint/types": "6.12.0", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -3604,9 +3604,9 @@ "dev": true }, "node_modules/fast-glob": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", - "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", "dev": true, "dependencies": { "@nodelib/fs.stat": "^2.0.2", @@ -6452,9 +6452,9 @@ "license": "MIT" }, "node_modules/typescript": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", - "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz", + "integrity": "sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -8253,9 +8253,9 @@ } }, "@types/json-schema": { - "version": "7.0.12", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", - "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true }, "@types/node": { @@ -8293,9 +8293,9 @@ "dev": true }, "@types/semver": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.1.tgz", - "integrity": "sha512-cJRQXpObxfNKkFAZbJl2yjWtJCqELQIdShsogr1d2MilP8dKD9TE/nEKHkJgUNHdGKCQaf9HbIynuV2csLGVLg==", + "version": "7.5.6", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz", + "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==", "dev": true }, "@types/tern": { @@ -8324,16 +8324,16 @@ } }, "@typescript-eslint/eslint-plugin": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.6.0.tgz", - "integrity": "sha512-CW9YDGTQnNYMIo5lMeuiIG08p4E0cXrXTbcZ2saT/ETE7dWUrNxlijsQeU04qAAKkILiLzdQz+cGFxCJjaZUmA==", + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.12.0.tgz", + "integrity": "sha512-XOpZ3IyJUIV1b15M7HVOpgQxPPF7lGXgsfcEIu3yDxFPaf/xZKt7s9QO/pbk7vpWQyVulpJbu4E5LwpZiQo4kA==", "dev": true, "requires": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.6.0", - "@typescript-eslint/type-utils": "6.6.0", - "@typescript-eslint/utils": "6.6.0", - "@typescript-eslint/visitor-keys": "6.6.0", + "@typescript-eslint/scope-manager": "6.12.0", + "@typescript-eslint/type-utils": "6.12.0", + "@typescript-eslint/utils": "6.12.0", + "@typescript-eslint/visitor-keys": "6.12.0", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -8354,54 +8354,54 @@ } }, "@typescript-eslint/parser": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.6.0.tgz", - "integrity": "sha512-setq5aJgUwtzGrhW177/i+DMLqBaJbdwGj2CPIVFFLE0NCliy5ujIdLHd2D1ysmlmsjdL2GWW+hR85neEfc12w==", + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.12.0.tgz", + "integrity": "sha512-s8/jNFPKPNRmXEnNXfuo1gemBdVmpQsK1pcu+QIvuNJuhFzGrpD7WjOcvDc/+uEdfzSYpNu7U/+MmbScjoQ6vg==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "6.6.0", - "@typescript-eslint/types": "6.6.0", - "@typescript-eslint/typescript-estree": "6.6.0", - "@typescript-eslint/visitor-keys": "6.6.0", + "@typescript-eslint/scope-manager": "6.12.0", + "@typescript-eslint/types": "6.12.0", + "@typescript-eslint/typescript-estree": "6.12.0", + "@typescript-eslint/visitor-keys": "6.12.0", "debug": "^4.3.4" } }, "@typescript-eslint/scope-manager": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.6.0.tgz", - "integrity": "sha512-pT08u5W/GT4KjPUmEtc2kSYvrH8x89cVzkA0Sy2aaOUIw6YxOIjA8ilwLr/1fLjOedX1QAuBpG9XggWqIIfERw==", + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.12.0.tgz", + "integrity": "sha512-5gUvjg+XdSj8pcetdL9eXJzQNTl3RD7LgUiYTl8Aabdi8hFkaGSYnaS6BLc0BGNaDH+tVzVwmKtWvu0jLgWVbw==", "dev": true, "requires": { - "@typescript-eslint/types": "6.6.0", - "@typescript-eslint/visitor-keys": "6.6.0" + "@typescript-eslint/types": "6.12.0", + "@typescript-eslint/visitor-keys": "6.12.0" } }, "@typescript-eslint/type-utils": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.6.0.tgz", - "integrity": "sha512-8m16fwAcEnQc69IpeDyokNO+D5spo0w1jepWWY2Q6y5ZKNuj5EhVQXjtVAeDDqvW6Yg7dhclbsz6rTtOvcwpHg==", + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.12.0.tgz", + "integrity": "sha512-WWmRXxhm1X8Wlquj+MhsAG4dU/Blvf1xDgGaYCzfvStP2NwPQh6KBvCDbiOEvaE0filhranjIlK/2fSTVwtBng==", "dev": true, "requires": { - "@typescript-eslint/typescript-estree": "6.6.0", - "@typescript-eslint/utils": "6.6.0", + "@typescript-eslint/typescript-estree": "6.12.0", + "@typescript-eslint/utils": "6.12.0", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" } }, "@typescript-eslint/types": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.6.0.tgz", - "integrity": "sha512-CB6QpJQ6BAHlJXdwUmiaXDBmTqIE2bzGTDLADgvqtHWuhfNP3rAOK7kAgRMAET5rDRr9Utt+qAzRBdu3AhR3sg==", + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.12.0.tgz", + "integrity": "sha512-MA16p/+WxM5JG/F3RTpRIcuOghWO30//VEOvzubM8zuOOBYXsP+IfjoCXXiIfy2Ta8FRh9+IO9QLlaFQUU+10Q==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.6.0.tgz", - "integrity": "sha512-hMcTQ6Al8MP2E6JKBAaSxSVw5bDhdmbCEhGW/V8QXkb9oNsFkA4SBuOMYVPxD3jbtQ4R/vSODBsr76R6fP3tbA==", + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.12.0.tgz", + "integrity": "sha512-vw9E2P9+3UUWzhgjyyVczLWxZ3GuQNT7QpnIY3o5OMeLO/c8oHljGc8ZpryBMIyympiAAaKgw9e5Hl9dCWFOYw==", "dev": true, "requires": { - "@typescript-eslint/types": "6.6.0", - "@typescript-eslint/visitor-keys": "6.6.0", + "@typescript-eslint/types": "6.12.0", + "@typescript-eslint/visitor-keys": "6.12.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -8421,17 +8421,17 @@ } }, "@typescript-eslint/utils": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.6.0.tgz", - "integrity": "sha512-mPHFoNa2bPIWWglWYdR0QfY9GN0CfvvXX1Sv6DlSTive3jlMTUy+an67//Gysc+0Me9pjitrq0LJp0nGtLgftw==", + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.12.0.tgz", + "integrity": "sha512-LywPm8h3tGEbgfyjYnu3dauZ0U7R60m+miXgKcZS8c7QALO9uWJdvNoP+duKTk2XMWc7/Q3d/QiCuLN9X6SWyQ==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.6.0", - "@typescript-eslint/types": "6.6.0", - "@typescript-eslint/typescript-estree": "6.6.0", + "@typescript-eslint/scope-manager": "6.12.0", + "@typescript-eslint/types": "6.12.0", + "@typescript-eslint/typescript-estree": "6.12.0", "semver": "^7.5.4" }, "dependencies": { @@ -8447,12 +8447,12 @@ } }, "@typescript-eslint/visitor-keys": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.6.0.tgz", - "integrity": "sha512-L61uJT26cMOfFQ+lMZKoJNbAEckLe539VhTxiGHrWl5XSKQgA0RTBZJW2HFPy5T0ZvPVSD93QsrTKDkfNwJGyQ==", + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.12.0.tgz", + "integrity": "sha512-rg3BizTZHF1k3ipn8gfrzDXXSFKyOEB5zxYXInQ6z0hUvmQlhaZQzK+YmHmNViMA9HzW5Q9+bPPt90bU6GQwyw==", "dev": true, "requires": { - "@typescript-eslint/types": "6.6.0", + "@typescript-eslint/types": "6.12.0", "eslint-visitor-keys": "^3.4.1" } }, @@ -9795,9 +9795,9 @@ "dev": true }, "fast-glob": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", - "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", "dev": true, "requires": { "@nodelib/fs.stat": "^2.0.2", @@ -11734,9 +11734,9 @@ "dev": true }, "typescript": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", - "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz", + "integrity": "sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==", "dev": true }, "unbox-primitive": { diff --git a/package.json b/package.json index 19fe0160f6..59f7c252c5 100644 --- a/package.json +++ b/package.json @@ -70,8 +70,8 @@ "@types/resize-observer-browser": "^0.1.7", "@types/ws": "^8.5.3", "@types/xml2js": "^0.4.9", - "@typescript-eslint/eslint-plugin": "^6.6.0", - "@typescript-eslint/parser": "^6.6.0", + "@typescript-eslint/eslint-plugin": "^6.12.0", + "@typescript-eslint/parser": "^6.12.0", "@vitejs/plugin-basic-ssl": "^1.0.1", "@vitejs/plugin-react": "^3.1.0", "@zip.js/zip.js": "^2.7.29", @@ -99,7 +99,7 @@ "react-dom": "^18.1.0", "socksv5": "0.0.6", "ssim.js": "^3.5.0", - "typescript": "^5.2.2", + "typescript": "^5.3.2", "vite": "^4.3.9", "ws": "^8.5.0", "xml2js": "^0.5.0", From 2b4d51b1e84494adb1d4c8e1df8c5a4589950b72 Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Wed, 22 Nov 2023 12:31:54 -0800 Subject: [PATCH 091/491] docs: grep for project dependencies (#28299) Fixes https://github.com/microsoft/playwright/issues/28296 --- docs/src/test-cli-js.md | 4 ++-- docs/src/test-projects-js.md | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/src/test-cli-js.md b/docs/src/test-cli-js.md index 99176626c6..e005c53714 100644 --- a/docs/src/test-cli-js.md +++ b/docs/src/test-cli-js.md @@ -84,8 +84,8 @@ Complete set of Playwright Test options is available in the [configuration file] | `-c ` or `--config `| Configuration file. If not passed, defaults to `playwright.config.ts` or `playwright.config.js` in the current directory. | | `-c ` or `--config `| Configuration file. If not passed, defaults to `playwright.config.ts` or `playwright.config.js` in the current directory. | | `--forbid-only` | Whether to disallow `test.only`. Useful on CI.| -| `-g ` or `--grep ` | Only run tests matching this regular expression. For example, this will run `'should add to cart'` when passed `-g "add to cart"`. The regular expression will be tested against the string that consists of the test file name, `test.describe` name (if any) and the test name divided by spaces, e.g. `my-test.spec.ts my-suite my-test`. | -| `--grep-invert ` | Only run tests **not** matching this regular expression. The opposite of `--grep`. | +| `-g ` or `--grep ` | Only run tests matching this regular expression. For example, this will run `'should add to cart'` when passed `-g "add to cart"`. The regular expression will be tested against the string that consists of the test file name, `test.describe` name (if any) and the test name divided by spaces, e.g. `my-test.spec.ts my-suite my-test`. The filter does not apply to the tests from dependcy projects, i.e. Playwright will still run all tests from [project dependencies](./test-projects.md#dependencies). | +| `--grep-invert ` | Only run tests **not** matching this regular expression. The opposite of `--grep`. The filter does not apply to the tests from dependcy projects, i.e. Playwright will still run all tests from [project dependencies](./test-projects.md#dependencies).| | `--global-timeout ` | Total timeout for the whole test run in milliseconds. By default, there is no global timeout. Learn more about [various timeouts](./test-timeouts.md).| | `--list` | list all the tests, but do not run them.| | `--max-failures ` or `-x`| Stop after the first `N` test failures. Passing `-x` stops after the first failure.| diff --git a/docs/src/test-projects-js.md b/docs/src/test-projects-js.md index 88b1ec0161..d401ae9c20 100644 --- a/docs/src/test-projects-js.md +++ b/docs/src/test-projects-js.md @@ -214,6 +214,9 @@ You can also teardown your setup by adding a [`property: TestProject.teardown`] global setup and teardown +### Test filtering + +If `--grep/--grep-invert` [option](./test-cli.md#reference) is used, test file name filter is specified in [command line](./test-cli.md) or [test.only()](./api/class-test.md#test-only) is used, the will only apply to the tests from the deepest projects in the project dependency chain. In other words, if a matching test belongs to a project that has project dependencies, Playwright will run all the tests from the project depdencies ignoring the filters. ## Custom project parameters From 958e45316e0df430967d8fbf02676b3c4241a54a Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Wed, 22 Nov 2023 22:39:59 +0100 Subject: [PATCH 092/491] test(itest): add CDN TCP connection hangs test (#28301) --- tests/installation/playwright-cdn.spec.ts | 33 +++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/tests/installation/playwright-cdn.spec.ts b/tests/installation/playwright-cdn.spec.ts index 3d202da81d..e1e438ecdc 100644 --- a/tests/installation/playwright-cdn.spec.ts +++ b/tests/installation/playwright-cdn.spec.ts @@ -15,6 +15,7 @@ */ import { test, expect } from './npmTest'; import http from 'http'; +import net from 'net'; import type { AddressInfo } from 'net'; const CDNS = [ @@ -95,3 +96,35 @@ test(`npx playwright install should not hang when CDN closes the connection`, as await new Promise(resolve => server.close(resolve)); } }); + +test(`npx playwright install should not hang when CDN TCP connection stalls`, async ({ exec }) => { + let retryCount = 0; + const socketsToDestroy = []; + const server = net.createServer(socket => { + socketsToDestroy.push(socket); + ++retryCount; + socket.write('HTTP/1.1 200 OK\r\n'); + socket.write('Content-Length: 100000000\r\n'); + socket.write('Content-Type: application/zip\r\n'); + socket.write('\r\n'); + socket.write('a'); + }); + await new Promise(resolve => server.listen(0, resolve)); + try { + await exec('npm i playwright'); + const result = await exec('npx playwright install', { + env: { + PLAYWRIGHT_DOWNLOAD_HOST: `http://127.0.0.1:${(server.address() as AddressInfo).port}`, + DEBUG: 'pw:install', + PLAYWRIGHT_DOWNLOAD_CONNECTION_TIMEOUT: '1000', + }, + expectToExitWithError: true + }); + expect(retryCount).toBe(3); + expect([...result.matchAll(/timed out after/g)]).toHaveLength(3); + } finally { + for (const socket of socketsToDestroy) + socket.destroy(); + await new Promise(resolve => server.close(resolve)); + } +}); From 1f953c214866cbf71d22501656f690dd4b83f7d9 Mon Sep 17 00:00:00 2001 From: Aislinn Hayes Date: Thu, 23 Nov 2023 10:06:06 +0000 Subject: [PATCH 093/491] docs: add a link to screenshots api in the docs (#28288) --- docs/src/screenshots.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/screenshots.md b/docs/src/screenshots.md index 8044e6aad9..28aca1200b 100644 --- a/docs/src/screenshots.md +++ b/docs/src/screenshots.md @@ -31,7 +31,7 @@ await Page.ScreenshotAsync(new() }); ``` -Screenshots API accepts many parameters for image format, clip area, quality, etc. Make sure to check them out. +[Screenshots API](./api/class-page#page-screenshot) accepts many parameters for image format, clip area, quality, etc. Make sure to check them out. From 24c52d5755751b2345632622a85068668588fc7e Mon Sep 17 00:00:00 2001 From: Playwright Service <89237858+playwrightmachine@users.noreply.github.com> Date: Thu, 23 Nov 2023 02:06:28 -0800 Subject: [PATCH 094/491] chore(driver): roll driver to recent Node.js LTS version (#28308) Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> --- utils/build/build-playwright-driver.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/build/build-playwright-driver.sh b/utils/build/build-playwright-driver.sh index a22a7e0e6a..48208ff2cf 100755 --- a/utils/build/build-playwright-driver.sh +++ b/utils/build/build-playwright-driver.sh @@ -4,7 +4,7 @@ set -x trap "cd $(pwd -P)" EXIT SCRIPT_PATH="$(cd "$(dirname "$0")" ; pwd -P)" -NODE_VERSION="20.9.0" # autogenerated via ./update-playwright-driver-version.mjs +NODE_VERSION="20.10.0" # autogenerated via ./update-playwright-driver-version.mjs cd "$(dirname "$0")" PACKAGE_VERSION=$(node -p "require('../../package.json').version") From 82f970e5c192425aead7cfabdebaf167d8a27734 Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Thu, 23 Nov 2023 02:09:38 -0800 Subject: [PATCH 095/491] chore: print more accurate MiB instead of Mb when downloading browsers (#28304) Fixes https://github.com/microsoft/playwright/issues/28283 --- packages/playwright-core/src/server/registry/browserFetcher.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/playwright-core/src/server/registry/browserFetcher.ts b/packages/playwright-core/src/server/registry/browserFetcher.ts index 9dfc6dc448..154edf1114 100644 --- a/packages/playwright-core/src/server/registry/browserFetcher.ts +++ b/packages/playwright-core/src/server/registry/browserFetcher.ts @@ -170,5 +170,5 @@ function getBasicDownloadProgress(): OnProgressCallback { function toMegabytes(bytes: number) { const mb = bytes / 1024 / 1024; - return `${Math.round(mb * 10) / 10} Mb`; + return `${Math.round(mb * 10) / 10} MiB`; } From 3d54d377ba600d0999845faafaf8dcd03cc21f33 Mon Sep 17 00:00:00 2001 From: Debbie O'Brien Date: Thu, 23 Nov 2023 13:05:03 +0100 Subject: [PATCH 096/491] docs: Update codegen documentation and screenshots (#28302) --- docs/src/codegen-intro.md | 24 ++++++++++----- docs/src/codegen.md | 44 +++++++++++++++++++-------- docs/src/getting-started-vscode-js.md | 13 +++++--- 3 files changed, 56 insertions(+), 25 deletions(-) diff --git a/docs/src/codegen-intro.md b/docs/src/codegen-intro.md index f469b8a93c..1fd05ff6b3 100644 --- a/docs/src/codegen-intro.md +++ b/docs/src/codegen-intro.md @@ -36,25 +36,33 @@ pwsh bin/Debug/netX/playwright.ps1 codegen demo.playwright.dev/todomvc Run `codegen` and perform actions in the browser. Playwright will generate the code for the user interactions. `Codegen` will look at the rendered page and figure out the recommended locator, prioritizing role, text and test id locators. If the generator identifies multiple elements matching the locator, it will improve the locator to make it resilient and uniquely identify the target element, therefore eliminating and reducing test(s) failing and flaking due to locators. +With the test generator you can record: +* Actions like click or fill by simply interacting with the page +* Assertions by clicking on one of the icons in the toolbar and then clicking on an element on the page to assert against. You can choose: + * `'assert visibility'` to assert that an element is visible + * `'assert text'` to assert that an element contains specific text + * `'assert value'` to assert that an element has a specific value + ###### * langs: js -![Recording a test](https://github.com/microsoft/playwright/assets/13063165/9effe72a-3bfd-42e1-87f3-2e6b0a2b71f9) +![Recording a test](https://github.com/microsoft/playwright/assets/13063165/34a79ea1-639e-4cb3-8115-bfdc78e3d34d) ###### * langs: java -![recording a test](https://github.com/microsoft/playwright/assets/13063165/26183fc4-a8a1-4d1c-9cdc-aca404a6eb9c) +![recording a test](https://github.com/microsoft/playwright/assets/13063165/ec9c4071-4af8-4ae7-8b36-aebcc29bdbbb) ###### * langs: python -![recording a test](https://github.com/microsoft/playwright/assets/13063165/57ed3f29-6436-4f2b-98ad-05de92d30075) +![recording a test](https://github.com/microsoft/playwright/assets/13063165/9751b609-6e4c-486b-a961-f86f177b1d58) ###### * langs: csharp -![recording a test](https://github.com/microsoft/playwright/assets/13063165/06bd474b-cdd1-4384-9de2-c745f296c78c) +![recording a test](https://github.com/microsoft/playwright/assets/13063165/53bdfb6f-d462-4ce0-ab95-0619faaebf1e) + ###### * langs: js, java, python, csharp @@ -77,22 +85,22 @@ You can generate [locators](/locators.md) with the test generator. ###### * langs: js -![picking a locator](https://github.com/microsoft/playwright/assets/13063165/4e46e1dd-dac2-4372-b643-00f896bb7e5f) +![picking a locator](https://github.com/microsoft/playwright/assets/13063165/2c8a12e2-4e98-4fdd-af92-1d73ae696d86) ###### * langs: java -![picking a locator](https://github.com/microsoft/playwright/assets/13063165/6200e6d1-e420-422c-9b62-831ec3fd43ea) +![picking a locator](https://github.com/microsoft/playwright/assets/13063165/733b48fd-5edf-4150-93f0-018adc52b6ff) ###### * langs: python -![picking a locator](https://github.com/microsoft/playwright/assets/13063165/49ad6214-dfec-4aae-b86c-0fdf05278293) +![picking a locator](https://github.com/microsoft/playwright/assets/13063165/95d11f48-96a4-46b9-9c2a-63c3aa4fdce7) ###### * langs: csharp -![picking a locator](https://github.com/microsoft/playwright/assets/13063165/d8d47fbc-38d6-4a6b-a9ab-4c40380f480b) +![picking a locator](https://github.com/microsoft/playwright/assets/13063165/1478f56f-422f-4276-9696-0674041f11dc) ### Emulation diff --git a/docs/src/codegen.md b/docs/src/codegen.md index 20fd37050f..55136e6ac8 100644 --- a/docs/src/codegen.md +++ b/docs/src/codegen.md @@ -27,17 +27,25 @@ To record a test click on the **Record new** button from the Testing sidebar. Th In the browser go to the URL you wish to test and start clicking around to record your user actions. -clicking delete button on todo app with locator highlighted +![generating user actions](https://github.com/microsoft/playwright/assets/13063165/1d4c8f37-8325-4816-a665-d0e95e63f509) -Playwright will record your actions and generate the test code directly in VS Code. Once you are done recording click the **cancel** button or close the browser window. You can then inspect your `test-1.spec.ts` file and see your generated test and then manually improve the test by adding [ assertions](test-assertions). +Playwright will record your actions and generate the test code directly in VS Code. You can also generate assertions by choosing one of the icons in the toolbar and then clicking on an element on the page to assert against. The following assertions can be generated: + * `'assert visibility'` to assert that an element is visible + * `'assert text'` to assert that an element contains specific text + * `'assert value'` to assert that an element has a specific value -vs code showing recorded actions of test +![generating assertions](https://github.com/microsoft/playwright/assets/13063165/d131eb35-b2ca-4bf4-a8ac-88b6e40dcf07) + +Once you are done recording click the **cancel** button or close the browser window. You can then inspect your `test-1.spec.ts` file and manually improve it if needed. + +![code from a generated test](https://github.com/microsoft/playwright/assets/13063165/2ba4c212-4713-460a-b054-6dc6b67a9a7c) ### Record at Cursor To record from a specific point in your test move your cursor to where you want to record more actions and then click the **Record at cursor** button from the Testing sidebar. If your browser window is not already open then first run the test with 'Show browser' checked and then click the **Record at cursor** button. -record at cursor in vs code + +![record at cursor in vs code](https://github.com/microsoft/playwright/assets/13063165/77948ab8-92a2-435f-9833-0944da5ae664) In the browser window start performing the actions you want to record. @@ -46,7 +54,7 @@ In the browser window start performing the actions you want to record. In the test file in VS Code you will see your new generated actions added to your test at the cursor position. -vs code showing test code generated +![code from a generated test](https://github.com/microsoft/playwright/assets/13063165/4f4bb34e-9cda-41fe-bf65-8d8016d84c7f) ### Generating locators @@ -85,25 +93,35 @@ pwsh bin/Debug/netX/playwright.ps1 codegen demo.playwright.dev/todomvc Run the `codegen` command and perform actions in the browser window. Playwright will generate the code for the user interactions which you can see in the Playwright Inspector window. Once you have finished recording your test stop the recording and press the **copy** button to copy your generated test into your editor. +With the test generator you can record: +* Actions like click or fill by simply interacting with the page +* Assertions by clicking on one of the icons in the toolbar and then clicking on an element on the page to assert against. You can choose: + * `'assert visibility'` to assert that an element is visible + * `'assert text'` to assert that an element contains specific text + * `'assert value'` to assert that an element has a specific value + ###### * langs: js -Recording a test +![Recording a test](https://github.com/microsoft/playwright/assets/13063165/34a79ea1-639e-4cb3-8115-bfdc78e3d34d) ###### * langs: java -Recording a test +![recording a test](https://github.com/microsoft/playwright/assets/13063165/ec9c4071-4af8-4ae7-8b36-aebcc29bdbbb) ###### * langs: python -Recording a test +![recording a test](https://github.com/microsoft/playwright/assets/13063165/9751b609-6e4c-486b-a961-f86f177b1d58) ###### * langs: csharp -Recording a test +![recording a test](https://github.com/microsoft/playwright/assets/13063165/53bdfb6f-d462-4ce0-ab95-0619faaebf1e) + +###### +* langs: js, java, python, csharp When you have finished interacting with the page, press the **record** button to stop the recording and use the **copy** button to copy the generated code to your editor. @@ -122,22 +140,22 @@ You can generate [locators](/locators.md) with the test generator. ###### * langs: js -Picking a locator +![picking a locator](https://github.com/microsoft/playwright/assets/13063165/2c8a12e2-4e98-4fdd-af92-1d73ae696d86) ###### * langs: java -Picking a locator +![picking a locator](https://github.com/microsoft/playwright/assets/13063165/733b48fd-5edf-4150-93f0-018adc52b6ff) ###### * langs: python -Picking a locator +![picking a locator](https://github.com/microsoft/playwright/assets/13063165/95d11f48-96a4-46b9-9c2a-63c3aa4fdce7) ###### * langs: csharp -Picking a locator +![picking a locator](https://github.com/microsoft/playwright/assets/13063165/1478f56f-422f-4276-9696-0674041f11dc) ## Emulation diff --git a/docs/src/getting-started-vscode-js.md b/docs/src/getting-started-vscode-js.md index 55d9075e91..af22b88337 100644 --- a/docs/src/getting-started-vscode-js.md +++ b/docs/src/getting-started-vscode-js.md @@ -145,23 +145,28 @@ CodeGen will auto generate your tests for you as you perform actions in the brow ### Record a New Test -To record a test click on the **Record new** button from the Testing sidebar. This will create a `test-1.spec.ts` file as well as open up a browser window. In the browser go to the URL you wish to test and start clicking around. Playwright will record your actions and generate a test for you. Once you are done recording click the **cancel** button or close the browser window. You can then inspect your `test-1.spec.ts` file and see your generated test. +To record a test click on the **Record new** button from the Testing sidebar. This will create a `test-1.spec.ts` file as well as open up a browser window. In the browser go to the URL you wish to test and start clicking around. Playwright will record your actions and generate the test code directly in VS Code. You can also generate assertions by choosing one of the icons in the toolbar and then clicking on an element on the page to assert against. The following assertions can be generated: + * `'assert visibility'` to assert that an element is visible + * `'assert text'` to assert that an element contains specific text + * `'assert value'` to assert that an element has a specific value + +Once you are done recording click the **cancel** button or close the browser window. You can then inspect your `test-1.spec.ts` file and see your generated test. -![record a new test](https://github.com/microsoft/playwright/assets/13063165/a81eb147-e479-4911-82b0-28fb47823c44) +![record a new test](https://github.com/microsoft/playwright/assets/13063165/0407f112-e1cd-41e7-a05d-ae64e24d27ed) ### Record at Cursor To record from a specific point in your test file click the **Record at cursor** button from the Testing sidebar. This generates actions into the existing test at the current cursor position. You can run the test, position the cursor at the end of the test and continue generating the test. -![record at cursor](https://github.com/microsoft/playwright/assets/13063165/a636d95f-6e72-4d02-9f9f-60e161089e99) +![record at cursor](https://github.com/microsoft/playwright/assets/13063165/96933ea1-4c84-453a-acd7-22b4d3bde185) ### Picking a Locator Pick a [locator](./locators.md) and copy it into your test file by clicking the **Pick locator** button form the testing sidebar. Then in the browser click the element you require and it will now show up in the **Pick locator** box in VS Code. Press 'enter' on your keyboard to copy the locator into the clipboard and then paste anywhere in your code. Or press 'escape' if you want to cancel. -![pick locators](https://github.com/microsoft/playwright/assets/13063165/dcb724a6-deb7-4993-b04a-3030cb76a22d) +![pick locators](https://github.com/microsoft/playwright/assets/13063165/9a1b2da9-9ac7-4def-a9e0-f94770364fc2) Playwright will look at your page and figure out the best locator, prioritizing [role, text and test id locators](./locators.md). If the generator finds multiple elements matching the locator, it will improve the locator to make it resilient and uniquely identify the target element, so you don't have to worry about failing tests due to locators. From ff2f93e36db0b7da4cbe111df070291d27c98258 Mon Sep 17 00:00:00 2001 From: Debbie O'Brien Date: Thu, 23 Nov 2023 14:01:05 +0100 Subject: [PATCH 097/491] docs: Add video for Playwright 1.40 (#28309) --- docs/src/release-notes-js.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/src/release-notes-js.md b/docs/src/release-notes-js.md index 7a7ad52bb9..4de5a423fd 100644 --- a/docs/src/release-notes-js.md +++ b/docs/src/release-notes-js.md @@ -8,6 +8,11 @@ import LiteYouTube from '@site/src/components/LiteYouTube'; ## Version 1.40 + + ### Test Generator Update ![Playwright Test Generator](https://github.com/microsoft/playwright/assets/9881434/e8d67e2e-f36d-4301-8631-023948d3e190) From 4faa0bcbb88f5e1b672b4a38b7d482478c147a2c Mon Sep 17 00:00:00 2001 From: Playwright Service <89237858+playwrightmachine@users.noreply.github.com> Date: Thu, 23 Nov 2023 07:04:04 -0800 Subject: [PATCH 098/491] feat(chromium-tip-of-tree): roll to r1171 (#28313) 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 520e06e93c..48cc918c5e 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -15,9 +15,9 @@ }, { "name": "chromium-tip-of-tree", - "revision": "1170", + "revision": "1171", "installByDefault": false, - "browserVersion": "121.0.6139.0" + "browserVersion": "121.0.6143.0" }, { "name": "firefox", From 7dd121c7840482ae79623633cb779981df3e14a0 Mon Sep 17 00:00:00 2001 From: Playwright Service <89237858+playwrightmachine@users.noreply.github.com> Date: Thu, 23 Nov 2023 07:04:35 -0800 Subject: [PATCH 099/491] feat(chromium): roll to r1092 (#28312) Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> --- README.md | 4 +- packages/playwright-core/browsers.json | 8 +- .../src/server/deviceDescriptorsSource.json | 96 +++++++++---------- 3 files changed, 54 insertions(+), 54 deletions(-) diff --git a/README.md b/README.md index 02fa52647a..7983c93952 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-120.0.6099.28-blue.svg?logo=google-chrome)](https://www.chromium.org/Home) [![Firefox version](https://img.shields.io/badge/firefox-119.0-blue.svg?logo=firefoxbrowser)](https://www.mozilla.org/en-US/firefox/new/) [![WebKit version](https://img.shields.io/badge/webkit-17.4-blue.svg?logo=safari)](https://webkit.org/) +[![npm version](https://img.shields.io/npm/v/playwright.svg)](https://www.npmjs.com/package/playwright) [![Chromium version](https://img.shields.io/badge/chromium-120.0.6099.35-blue.svg?logo=google-chrome)](https://www.chromium.org/Home) [![Firefox version](https://img.shields.io/badge/firefox-119.0-blue.svg?logo=firefoxbrowser)](https://www.mozilla.org/en-US/firefox/new/) [![WebKit version](https://img.shields.io/badge/webkit-17.4-blue.svg?logo=safari)](https://webkit.org/) ## [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 120.0.6099.28 | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| Chromium 120.0.6099.35 | :white_check_mark: | :white_check_mark: | :white_check_mark: | | WebKit 17.4 | :white_check_mark: | :white_check_mark: | :white_check_mark: | | Firefox 119.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 48cc918c5e..b3f883d929 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -3,15 +3,15 @@ "browsers": [ { "name": "chromium", - "revision": "1091", + "revision": "1092", "installByDefault": true, - "browserVersion": "120.0.6099.28" + "browserVersion": "120.0.6099.35" }, { "name": "chromium-with-symbols", - "revision": "1091", + "revision": "1092", "installByDefault": false, - "browserVersion": "120.0.6099.28" + "browserVersion": "120.0.6099.35" }, { "name": "chromium-tip-of-tree", diff --git a/packages/playwright-core/src/server/deviceDescriptorsSource.json b/packages/playwright-core/src/server/deviceDescriptorsSource.json index d8294a421f..cfadb9b4d0 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/120.0.6099.28 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.35 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/120.0.6099.28 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.35 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/120.0.6099.28 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.35 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/120.0.6099.28 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.35 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/120.0.6099.28 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.35 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/120.0.6099.28 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.35 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/120.0.6099.28 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.35 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/120.0.6099.28 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.35 Safari/537.36", "viewport": { "width": 1138, "height": 712 @@ -978,7 +978,7 @@ "defaultBrowserType": "webkit" }, "LG Optimus L70": { - "userAgent": "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/120.0.6099.28 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/120.0.6099.35 Mobile Safari/537.36", "viewport": { "width": 384, "height": 640 @@ -989,7 +989,7 @@ "defaultBrowserType": "chromium" }, "LG Optimus L70 landscape": { - "userAgent": "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/120.0.6099.28 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/120.0.6099.35 Mobile Safari/537.36", "viewport": { "width": 640, "height": 384 @@ -1000,7 +1000,7 @@ "defaultBrowserType": "chromium" }, "Microsoft Lumia 550": { - "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.28 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/120.0.6099.35 Mobile Safari/537.36 Edge/14.14263", "viewport": { "width": 640, "height": 360 @@ -1011,7 +1011,7 @@ "defaultBrowserType": "chromium" }, "Microsoft Lumia 550 landscape": { - "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.28 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/120.0.6099.35 Mobile Safari/537.36 Edge/14.14263", "viewport": { "width": 360, "height": 640 @@ -1022,7 +1022,7 @@ "defaultBrowserType": "chromium" }, "Microsoft Lumia 950": { - "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.28 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/120.0.6099.35 Mobile Safari/537.36 Edge/14.14263", "viewport": { "width": 360, "height": 640 @@ -1033,7 +1033,7 @@ "defaultBrowserType": "chromium" }, "Microsoft Lumia 950 landscape": { - "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.28 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/120.0.6099.35 Mobile Safari/537.36 Edge/14.14263", "viewport": { "width": 640, "height": 360 @@ -1044,7 +1044,7 @@ "defaultBrowserType": "chromium" }, "Nexus 10": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.28 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.35 Safari/537.36", "viewport": { "width": 800, "height": 1280 @@ -1055,7 +1055,7 @@ "defaultBrowserType": "chromium" }, "Nexus 10 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.28 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.35 Safari/537.36", "viewport": { "width": 1280, "height": 800 @@ -1066,7 +1066,7 @@ "defaultBrowserType": "chromium" }, "Nexus 4": { - "userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.28 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.35 Mobile Safari/537.36", "viewport": { "width": 384, "height": 640 @@ -1077,7 +1077,7 @@ "defaultBrowserType": "chromium" }, "Nexus 4 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.28 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.35 Mobile Safari/537.36", "viewport": { "width": 640, "height": 384 @@ -1088,7 +1088,7 @@ "defaultBrowserType": "chromium" }, "Nexus 5": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.28 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.35 Mobile Safari/537.36", "viewport": { "width": 360, "height": 640 @@ -1099,7 +1099,7 @@ "defaultBrowserType": "chromium" }, "Nexus 5 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.28 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.35 Mobile Safari/537.36", "viewport": { "width": 640, "height": 360 @@ -1110,7 +1110,7 @@ "defaultBrowserType": "chromium" }, "Nexus 5X": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.28 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/120.0.6099.35 Mobile Safari/537.36", "viewport": { "width": 412, "height": 732 @@ -1121,7 +1121,7 @@ "defaultBrowserType": "chromium" }, "Nexus 5X landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.28 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/120.0.6099.35 Mobile Safari/537.36", "viewport": { "width": 732, "height": 412 @@ -1132,7 +1132,7 @@ "defaultBrowserType": "chromium" }, "Nexus 6": { - "userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.28 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.35 Mobile Safari/537.36", "viewport": { "width": 412, "height": 732 @@ -1143,7 +1143,7 @@ "defaultBrowserType": "chromium" }, "Nexus 6 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.28 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.35 Mobile Safari/537.36", "viewport": { "width": 732, "height": 412 @@ -1154,7 +1154,7 @@ "defaultBrowserType": "chromium" }, "Nexus 6P": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.28 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/120.0.6099.35 Mobile Safari/537.36", "viewport": { "width": 412, "height": 732 @@ -1165,7 +1165,7 @@ "defaultBrowserType": "chromium" }, "Nexus 6P landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.28 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/120.0.6099.35 Mobile Safari/537.36", "viewport": { "width": 732, "height": 412 @@ -1176,7 +1176,7 @@ "defaultBrowserType": "chromium" }, "Nexus 7": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.28 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.35 Safari/537.36", "viewport": { "width": 600, "height": 960 @@ -1187,7 +1187,7 @@ "defaultBrowserType": "chromium" }, "Nexus 7 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.28 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.35 Safari/537.36", "viewport": { "width": 960, "height": 600 @@ -1242,7 +1242,7 @@ "defaultBrowserType": "webkit" }, "Pixel 2": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.28 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/120.0.6099.35 Mobile Safari/537.36", "viewport": { "width": 411, "height": 731 @@ -1253,7 +1253,7 @@ "defaultBrowserType": "chromium" }, "Pixel 2 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.28 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/120.0.6099.35 Mobile Safari/537.36", "viewport": { "width": 731, "height": 411 @@ -1264,7 +1264,7 @@ "defaultBrowserType": "chromium" }, "Pixel 2 XL": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.28 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/120.0.6099.35 Mobile Safari/537.36", "viewport": { "width": 411, "height": 823 @@ -1275,7 +1275,7 @@ "defaultBrowserType": "chromium" }, "Pixel 2 XL landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.28 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/120.0.6099.35 Mobile Safari/537.36", "viewport": { "width": 823, "height": 411 @@ -1286,7 +1286,7 @@ "defaultBrowserType": "chromium" }, "Pixel 3": { - "userAgent": "Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.28 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/120.0.6099.35 Mobile Safari/537.36", "viewport": { "width": 393, "height": 786 @@ -1297,7 +1297,7 @@ "defaultBrowserType": "chromium" }, "Pixel 3 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.28 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/120.0.6099.35 Mobile Safari/537.36", "viewport": { "width": 786, "height": 393 @@ -1308,7 +1308,7 @@ "defaultBrowserType": "chromium" }, "Pixel 4": { - "userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.28 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.35 Mobile Safari/537.36", "viewport": { "width": 353, "height": 745 @@ -1319,7 +1319,7 @@ "defaultBrowserType": "chromium" }, "Pixel 4 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.28 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.35 Mobile Safari/537.36", "viewport": { "width": 745, "height": 353 @@ -1330,7 +1330,7 @@ "defaultBrowserType": "chromium" }, "Pixel 4a (5G)": { - "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.28 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.35 Mobile Safari/537.36", "screen": { "width": 412, "height": 892 @@ -1345,7 +1345,7 @@ "defaultBrowserType": "chromium" }, "Pixel 4a (5G) landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.28 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.35 Mobile Safari/537.36", "screen": { "height": 892, "width": 412 @@ -1360,7 +1360,7 @@ "defaultBrowserType": "chromium" }, "Pixel 5": { - "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.28 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.35 Mobile Safari/537.36", "screen": { "width": 393, "height": 851 @@ -1375,7 +1375,7 @@ "defaultBrowserType": "chromium" }, "Pixel 5 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.28 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.35 Mobile Safari/537.36", "screen": { "width": 851, "height": 393 @@ -1390,7 +1390,7 @@ "defaultBrowserType": "chromium" }, "Pixel 7": { - "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.28 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.35 Mobile Safari/537.36", "screen": { "width": 412, "height": 915 @@ -1405,7 +1405,7 @@ "defaultBrowserType": "chromium" }, "Pixel 7 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.28 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.35 Mobile Safari/537.36", "screen": { "width": 915, "height": 412 @@ -1420,7 +1420,7 @@ "defaultBrowserType": "chromium" }, "Moto G4": { - "userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.28 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.35 Mobile Safari/537.36", "viewport": { "width": 360, "height": 640 @@ -1431,7 +1431,7 @@ "defaultBrowserType": "chromium" }, "Moto G4 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.28 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.35 Mobile Safari/537.36", "viewport": { "width": 640, "height": 360 @@ -1442,7 +1442,7 @@ "defaultBrowserType": "chromium" }, "Desktop Chrome HiDPI": { - "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.28 Safari/537.36", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.35 Safari/537.36", "screen": { "width": 1792, "height": 1120 @@ -1457,7 +1457,7 @@ "defaultBrowserType": "chromium" }, "Desktop Edge HiDPI": { - "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.28 Safari/537.36 Edg/120.0.6099.28", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.35 Safari/537.36 Edg/120.0.6099.35", "screen": { "width": 1792, "height": 1120 @@ -1502,7 +1502,7 @@ "defaultBrowserType": "webkit" }, "Desktop Chrome": { - "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.28 Safari/537.36", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.35 Safari/537.36", "screen": { "width": 1920, "height": 1080 @@ -1517,7 +1517,7 @@ "defaultBrowserType": "chromium" }, "Desktop Edge": { - "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.28 Safari/537.36 Edg/120.0.6099.28", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.35 Safari/537.36 Edg/120.0.6099.35", "screen": { "width": 1920, "height": 1080 From 54c9c7ff9cbc29a391b3de9e071cc01730d19eec Mon Sep 17 00:00:00 2001 From: Yair Cohen <56881475+yaircohendev@users.noreply.github.com> Date: Thu, 23 Nov 2023 19:00:02 +0200 Subject: [PATCH 100/491] docs(test-sharding) fix typo (#28215) --- docs/src/test-sharding-js.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/test-sharding-js.md b/docs/src/test-sharding-js.md index a18132304d..3522251593 100644 --- a/docs/src/test-sharding-js.md +++ b/docs/src/test-sharding-js.md @@ -49,7 +49,7 @@ This will produce a standard HTML report into `playwright-report` directory. GitHub Actions supports [sharding tests between multiple jobs](https://docs.github.com/en/actions/using-jobs/using-a-matrix-for-your-jobs) using the [`jobs..strategy.matrix`](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstrategymatrix) option. The `matrix` option will run a separate job for every possible combination of the provided options. -The following example shows you how to configure a job to run your tests on four machines in parallel and then merge the reports into a single report. Don't for get to add `reporter: process.env.CI ? 'blob' : 'html',` to your `playwright.config.ts` file as in the example above. +The following example shows you how to configure a job to run your tests on four machines in parallel and then merge the reports into a single report. Don't forget to add `reporter: process.env.CI ? 'blob' : 'html',` to your `playwright.config.ts` file as in the example above. 1. First we add a `matrix` option to our job configuration with the `shard` option containing the number of shards we want to create. `shard: [1/4, 2/4, 3/4, 4/4]` will create four shards, each with a different shard number. From 888f4965bfd87ef59d463286c88c4a658605d92e Mon Sep 17 00:00:00 2001 From: Playwright Service <89237858+playwrightmachine@users.noreply.github.com> Date: Fri, 24 Nov 2023 00:33:53 -0800 Subject: [PATCH 101/491] feat(webkit): roll to r1948 (#28323) Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> --- packages/playwright-core/browsers.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/playwright-core/browsers.json b/packages/playwright-core/browsers.json index b3f883d929..323a660a04 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -39,7 +39,7 @@ }, { "name": "webkit", - "revision": "1947", + "revision": "1948", "installByDefault": true, "revisionOverrides": { "mac10.14": "1446", From 854ae4eede25e5151708f2539fc5950622d3cfb2 Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Mon, 27 Nov 2023 11:38:52 -0800 Subject: [PATCH 102/491] docs: remove duplicate entry for --config (#28357) --- docs/src/test-cli-js.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/src/test-cli-js.md b/docs/src/test-cli-js.md index e005c53714..8c5666acaf 100644 --- a/docs/src/test-cli-js.md +++ b/docs/src/test-cli-js.md @@ -82,7 +82,6 @@ Complete set of Playwright Test options is available in the [configuration file] |`--browser`| Run test in a specific browser. Available options are `"chromium"`, `"firefox"`, `"webkit"` or `"all"` to run tests in all three browsers at the same time. | | `--debug`| Run tests with Playwright Inspector. Shortcut for `PWDEBUG=1` environment variable and `--timeout=0 --max-failures=1 --headed --workers=1` options.| | `-c ` or `--config `| Configuration file. If not passed, defaults to `playwright.config.ts` or `playwright.config.js` in the current directory. | -| `-c ` or `--config `| Configuration file. If not passed, defaults to `playwright.config.ts` or `playwright.config.js` in the current directory. | | `--forbid-only` | Whether to disallow `test.only`. Useful on CI.| | `-g ` or `--grep ` | Only run tests matching this regular expression. For example, this will run `'should add to cart'` when passed `-g "add to cart"`. The regular expression will be tested against the string that consists of the test file name, `test.describe` name (if any) and the test name divided by spaces, e.g. `my-test.spec.ts my-suite my-test`. The filter does not apply to the tests from dependcy projects, i.e. Playwright will still run all tests from [project dependencies](./test-projects.md#dependencies). | | `--grep-invert ` | Only run tests **not** matching this regular expression. The opposite of `--grep`. The filter does not apply to the tests from dependcy projects, i.e. Playwright will still run all tests from [project dependencies](./test-projects.md#dependencies).| From dc8ecc3ca404b211815c5541dfea6e59dbd19b9a Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Mon, 27 Nov 2023 12:43:56 -0800 Subject: [PATCH 103/491] fix(merge): normalize path separators when merging across platforms (#28227) --- packages/playwright/src/reporters/blob.ts | 2 + packages/playwright/src/reporters/merge.ts | 166 ++++++++++++---- tests/playwright-test/reporter-blob.spec.ts | 180 ++++++++++++++++-- .../playwright-test/reporter-markdown.spec.ts | 13 +- 4 files changed, 294 insertions(+), 67 deletions(-) diff --git a/packages/playwright/src/reporters/blob.ts b/packages/playwright/src/reporters/blob.ts index 057caab010..931345b01d 100644 --- a/packages/playwright/src/reporters/blob.ts +++ b/packages/playwright/src/reporters/blob.ts @@ -38,6 +38,7 @@ export type BlobReportMetadata = { userAgent: string; name?: string; shard?: { total: number, current: number }; + pathSeparator?: string; }; export class BlobReporter extends TeleReporterEmitter { @@ -59,6 +60,7 @@ export class BlobReporter extends TeleReporterEmitter { userAgent: getUserAgent(), name: process.env.PWTEST_BLOB_REPORT_NAME, shard: config.shard ?? undefined, + pathSeparator: path.sep, }; this._messages.push({ method: 'onBlobReportMetadata', diff --git a/packages/playwright/src/reporters/merge.ts b/packages/playwright/src/reporters/merge.ts index 8028d4d1e2..f2e4ec8eda 100644 --- a/packages/playwright/src/reporters/merge.ts +++ b/packages/playwright/src/reporters/merge.ts @@ -18,7 +18,7 @@ import fs from 'fs'; import path from 'path'; import type { ReporterDescription } from '../../types/test'; import type { FullConfigInternal } from '../common/config'; -import type { JsonConfig, JsonEvent, JsonFullResult, JsonProject, JsonSuite, JsonTestResultEnd } from '../isomorphic/teleReceiver'; +import type { JsonConfig, JsonEvent, JsonFullResult, JsonLocation, JsonProject, JsonSuite, JsonTestResultEnd, JsonTestStepStart } from '../isomorphic/teleReceiver'; import { TeleReporterReceiver } from '../isomorphic/teleReceiver'; import { JsonStringInternalizer, StringInternPool } from '../isomorphic/stringInternPool'; import { createReporters } from '../runner/reporters'; @@ -30,14 +30,13 @@ import { relativeFilePath } from '../util'; type StatusCallback = (message: string) => void; type ReportData = { - idsPatcher: IdsPatcher; + eventPatchers: JsonEventPatchers; reportFile: string; }; export async function createMergedReport(config: FullConfigInternal, dir: string, reporterDescriptions: ReporterDescription[], rootDirOverride: string | undefined) { const reporters = await createReporters(config, 'merge', reporterDescriptions); const multiplexer = new Multiplexer(reporters); - const receiver = new TeleReporterReceiver(path.sep, multiplexer, false, config.config); const stringPool = new StringInternPool(); let printStatus: StatusCallback = () => {}; @@ -50,6 +49,9 @@ export async function createMergedReport(config: FullConfigInternal, dir: string if (shardFiles.length === 0) throw new Error(`No report files found in ${dir}`); const eventData = await mergeEvents(dir, shardFiles, stringPool, printStatus, rootDirOverride); + // If expicit config is provided, use platform path separator, otherwise use the one from the report (if any). + const pathSep = rootDirOverride ? path.sep : (eventData.pathSeparatorFromMetadata ?? path.sep); + const receiver = new TeleReporterReceiver(pathSep, multiplexer, false, config.config); printStatus(`processing test events`); const dispatchEvents = async (events: JsonEvent[]) => { @@ -63,30 +65,17 @@ export async function createMergedReport(config: FullConfigInternal, dir: string }; await dispatchEvents(eventData.prologue); - for (const { reportFile, idsPatcher } of eventData.reports) { + for (const { reportFile, eventPatchers } of eventData.reports) { const reportJsonl = await fs.promises.readFile(reportFile); const events = parseTestEvents(reportJsonl); new JsonStringInternalizer(stringPool).traverse(events); - idsPatcher.patchEvents(events); - patchAttachmentPaths(events, dir); + eventPatchers.patchers.push(new AttachmentPathPatcher(dir)); + eventPatchers.patchEvents(events); await dispatchEvents(events); } await dispatchEvents(eventData.epilogue); } -function patchAttachmentPaths(events: JsonEvent[], resourceDir: string) { - for (const event of events) { - if (event.method !== 'onTestEnd') - continue; - for (const attachment of (event.params.result as JsonTestResultEnd).attachments) { - if (!attachment.path) - continue; - - attachment.path = path.join(resourceDir, attachment.path); - } - } -} - const commonEventNames = ['onBlobReportMetadata', 'onConfigure', 'onProject', 'onBegin', 'onEnd']; const commonEvents = new Set(commonEventNames); const commonEventRegex = new RegExp(`${commonEventNames.join('|')}`); @@ -186,8 +175,12 @@ async function mergeEvents(dir: string, shardReportFiles: string[], stringPool: salt = sha1 + '-' + i; saltSet.add(salt); - const idsPatcher = new IdsPatcher(stringPool, metadata.name, salt); - idsPatcher.patchEvents(parsedEvents); + const eventPatchers = new JsonEventPatchers(); + eventPatchers.patchers.push(new IdsPatcher(stringPool, metadata.name, salt)); + // Only patch path separators if we are merging reports with explicit config. + if (rootDirOverride) + eventPatchers.patchers.push(new PathSeparatorPatcher(metadata.pathSeparator)); + eventPatchers.patchEvents(parsedEvents); for (const event of parsedEvents) { if (event.method === 'onConfigure') @@ -200,7 +193,7 @@ async function mergeEvents(dir: string, shardReportFiles: string[], stringPool: // Save information about the reports to stream their test events later. reports.push({ - idsPatcher, + eventPatchers, reportFile: localPath, }); } @@ -215,7 +208,8 @@ async function mergeEvents(dir: string, shardReportFiles: string[], stringPool: epilogue: [ mergeEndEvents(endEvents), { method: 'onExit', params: undefined }, - ] + ], + pathSeparatorFromMetadata: blobs[0]?.metadata.pathSeparator, }; } @@ -338,26 +332,27 @@ class UniqueFileNameGenerator { } class IdsPatcher { - constructor(private _stringPool: StringInternPool, private _reportName: string | undefined, private _salt: string) { + constructor( + private _stringPool: StringInternPool, + private _reportName: string | undefined, + private _salt: string) { } - patchEvents(events: JsonEvent[]) { - for (const event of events) { - const { method, params } = event; - switch (method) { - case 'onProject': - this._onProject(params.project); - continue; - case 'onTestBegin': - case 'onStepBegin': - case 'onStepEnd': - case 'onStdIO': - params.testId = this._mapTestId(params.testId); - continue; - case 'onTestEnd': - params.test.testId = this._mapTestId(params.test.testId); - continue; - } + patchEvent(event: JsonEvent) { + const { method, params } = event; + switch (method) { + case 'onProject': + this._onProject(params.project); + return; + case 'onTestBegin': + case 'onStepBegin': + case 'onStepEnd': + case 'onStdIO': + params.testId = this._mapTestId(params.testId); + return; + case 'onTestEnd': + params.test.testId = this._mapTestId(params.test.testId); + return; } } @@ -377,3 +372,92 @@ class IdsPatcher { return this._stringPool.internString(testId + this._salt); } } + +class AttachmentPathPatcher { + constructor(private _resourceDir: string) { + } + + patchEvent(event: JsonEvent) { + if (event.method !== 'onTestEnd') + return; + for (const attachment of (event.params.result as JsonTestResultEnd).attachments) { + if (!attachment.path) + continue; + + attachment.path = path.join(this._resourceDir, attachment.path); + } + } +} + +class PathSeparatorPatcher { + private _from: string; + private _to: string; + constructor(from?: string) { + this._from = from ?? (path.sep === '/' ? '\\' : '/'); + this._to = path.sep; + } + + patchEvent(jsonEvent: JsonEvent) { + if (this._from === this._to) + return; + if (jsonEvent.method === 'onProject') { + this._updateProject(jsonEvent.params.project as JsonProject); + return; + } + if (jsonEvent.method === 'onTestEnd') { + const testResult = jsonEvent.params.result as JsonTestResultEnd; + testResult.errors.forEach(error => this._updateLocation(error.location)); + testResult.attachments.forEach(attachment => { + if (attachment.path) + attachment.path = this._updatePath(attachment.path); + }); + return; + } + if (jsonEvent.method === 'onStepBegin') { + const step = jsonEvent.params.step as JsonTestStepStart; + this._updateLocation(step.location); + return; + } + } + + private _updateProject(project: JsonProject) { + project.outputDir = this._updatePath(project.outputDir); + project.testDir = this._updatePath(project.testDir); + project.snapshotDir = this._updatePath(project.snapshotDir); + project.suites.forEach(suite => this._updateSuite(suite, true)); + } + + private _updateSuite(suite: JsonSuite, isFileSuite: boolean = false) { + this._updateLocation(suite.location); + if (isFileSuite) + suite.title = this._updatePath(suite.title); + for (const child of suite.suites) + this._updateSuite(child); + for (const test of suite.tests) + this._updateLocation(test.location); + } + + private _updateLocation(location?: JsonLocation) { + if (location) + location.file = this._updatePath(location.file); + } + + private _updatePath(text: string): string { + return text.split(this._from).join(this._to); + } +} + +interface JsonEventPatcher { + patchEvent(event: JsonEvent): void; +} + +class JsonEventPatchers { + readonly patchers: JsonEventPatcher[] = []; + + patchEvents(events: JsonEvent[]) { + for (const event of events) { + for (const patcher of this.patchers) + patcher.patchEvent(event); + } + } +} diff --git a/tests/playwright-test/reporter-blob.spec.ts b/tests/playwright-test/reporter-blob.spec.ts index 379306ef55..a2f1e04418 100644 --- a/tests/playwright-test/reporter-blob.spec.ts +++ b/tests/playwright-test/reporter-blob.spec.ts @@ -1259,15 +1259,36 @@ test('blob report should include version', async ({ runInlineTest }) => { const reportFiles = await fs.promises.readdir(reportDir); expect(reportFiles).toEqual(['report.zip']); - await extractZip(test.info().outputPath('blob-report', reportFiles[0]), { dir: test.info().outputPath('blob-report') }); - const reportFile = test.info().outputPath('blob-report', reportFiles[0].replace(/\.zip$/, '.jsonl')); - const data = await fs.promises.readFile(reportFile, 'utf8'); - const events = data.split('\n').filter(Boolean).map(line => JSON.parse(line)); + const events = await extractReport(test.info().outputPath('blob-report', 'report.zip'), test.info().outputPath('tmp')); const metadataEvent = events.find(e => e.method === 'onBlobReportMetadata'); expect(metadataEvent.params.version).toBe(1); expect(metadataEvent.params.userAgent).toBe(getUserAgent()); }); +async function extractReport(reportZipFile: string, unzippedReportDir: string): Promise { + await extractZip(reportZipFile, { dir: unzippedReportDir }); + const reportFile = path.join(unzippedReportDir, path.basename(reportZipFile).replace(/\.zip$/, '.jsonl')); + const data = await fs.promises.readFile(reportFile, 'utf8'); + const events = data.split('\n').filter(Boolean).map(line => JSON.parse(line)); + return events; +} + +async function zipReport(events: any[], zipFilePath: string) { + const lines = events.map(e => JSON.stringify(e) + '\n'); + const zipFile = new yazl.ZipFile(); + const zipFinishPromise = new Promise((resolve, reject) => { + (zipFile as any).on('error', error => reject(error)); + zipFile.outputStream.pipe(fs.createWriteStream(zipFilePath)).on('close', () => { + resolve(undefined); + }).on('error', error => reject(error)); + }); + const content = Readable.from(lines); + zipFile.addReadStream(content, 'report.jsonl'); + zipFile.end(); + await zipFinishPromise; +} + + test('merge-reports should throw if report version is from the future', async ({ runInlineTest, mergeReports }) => { const reportDir = test.info().outputPath('blob-report'); const files = { @@ -1293,28 +1314,15 @@ test('merge-reports should throw if report version is from the future', async ({ // Extract report and modify version. const reportZipFile = test.info().outputPath('blob-report', reportFiles[1]); - await extractZip(reportZipFile, { dir: test.info().outputPath('tmp') }); - const reportFile = test.info().outputPath('tmp', reportFiles[1].replace(/\.zip$/, '.jsonl')); - const data = await fs.promises.readFile(reportFile, 'utf8'); - const events = data.split('\n').filter(Boolean).map(line => JSON.parse(line)); + const events = await extractReport(reportZipFile, test.info().outputPath('tmp')); + const metadataEvent = events.find(e => e.method === 'onBlobReportMetadata'); expect(metadataEvent.params.version).toBeTruthy(); ++metadataEvent.params.version; - const modifiedLines = events.map(e => JSON.stringify(e) + '\n'); // Zip it back. await fs.promises.rm(reportZipFile, { force: true }); - const zipFile = new yazl.ZipFile(); - const zipFinishPromise = new Promise((resolve, reject) => { - (zipFile as any).on('error', error => reject(error)); - zipFile.outputStream.pipe(fs.createWriteStream(reportZipFile)).on('close', () => { - resolve(undefined); - }).on('error', error => reject(error)); - }); - const content = Readable.from(modifiedLines); - zipFile.addReadStream(content, path.basename(reportFile)); - zipFile.end(); - await zipFinishPromise; + await zipReport(events, reportZipFile); const { exitCode, output } = await mergeReports(reportDir, { 'PW_TEST_HTML_REPORT_OPEN': 'never' }, { additionalArgs: ['--reporter', 'html'] }); expect(exitCode).toBe(1); @@ -1512,3 +1520,135 @@ test('merge reports same rootDirs', async ({ runInlineTest, mergeReports }) => { expect(output).toContain(`test: ${test.info().outputPath('tests', 'dir1', 'a.test.js')}`); expect(output).toContain(`test: ${test.info().outputPath('tests', 'dir2', 'b.test.js')}`); }); + + +function patchPathSeparators(json: any) { + const from = (path.sep === '/') ? /\//g : /\\/g; + const to = (path.sep === '/') ? '\\' : '/'; + + function patchPathSeparatorsRecursive(obj: any) { + if (typeof obj !== 'object') + return; + for (const key in obj) { + if (/file|dir|path|^title$/i.test(key) && typeof obj[key] === 'string') + obj[key] = obj[key].replace(from, to); + patchPathSeparatorsRecursive(obj[key]); + } + } + patchPathSeparatorsRecursive(json); +} + +test('merge reports with different rootDirs and path separators', async ({ runInlineTest, mergeReports }) => { + test.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/27877' }); + const files1 = { + 'echo-reporter.js': ` + export default class EchoReporter { + onBegin(config, suite) { + console.log('rootDir:', config.rootDir); + } + onTestBegin(test) { + console.log('test:', test.location.file); + console.log('test title:', test.titlePath()[2]); + } + }; + `, + 'merge.config.ts': `module.exports = { + testDir: 'mergeRoot', + reporter: './echo-reporter.js' + };`, + 'dir1/playwright.config.ts': `module.exports = { + reporter: [['blob', { outputDir: 'blob-report' }]] + };`, + 'dir1/tests1/a.test.js': ` + import { test, expect } from '@playwright/test'; + test('math 1', async ({}) => { }); + `, + }; + await runInlineTest(files1, { workers: 1 }, undefined, { additionalArgs: ['--config', test.info().outputPath('dir1/playwright.config.ts')] }); + + const files2 = { + 'dir2/playwright.config.ts': `module.exports = { + reporter: [['blob', { outputDir: 'blob-report' }]] + };`, + 'dir2/tests2/b.test.js': ` + import { test, expect } from '@playwright/test'; + test('math 2', async ({}) => { }); + `, + }; + await runInlineTest(files2, { workers: 1 }, undefined, { additionalArgs: ['--config', test.info().outputPath('dir2/playwright.config.ts')] }); + + const allReportsDir = test.info().outputPath('all-blob-reports'); + await fs.promises.mkdir(allReportsDir, { recursive: true }); + + // Extract report and change path separators. + const reportZipFile = test.info().outputPath('dir1', 'blob-report', 'report.zip'); + const events = await extractReport(reportZipFile, test.info().outputPath('tmp')); + events.forEach(patchPathSeparators); + + // Zip it back. + const report1 = path.join(allReportsDir, 'report-1.zip'); + await zipReport(events, report1); + + // Copy second report as is. + await fs.promises.cp(test.info().outputPath('dir2', 'blob-report', 'report.zip'), path.join(allReportsDir, 'report-2.zip')); + + { + const { exitCode, output } = await mergeReports(allReportsDir, undefined, { additionalArgs: ['--config', 'merge.config.ts'] }); + expect(exitCode).toBe(0); + expect(output).toContain(`rootDir: ${test.info().outputPath('mergeRoot')}`); + expect(output).toContain(`test: ${test.info().outputPath('mergeRoot', 'tests1', 'a.test.js')}`); + expect(output).toContain(`test title: ${'tests1' + path.sep + 'a.test.js'}`); + expect(output).toContain(`test: ${test.info().outputPath('mergeRoot', 'tests2', 'b.test.js')}`); + expect(output).toContain(`test title: ${'tests2' + path.sep + 'b.test.js'}`); + } +}); + +test('merge reports without --config preserves path separators', async ({ runInlineTest, mergeReports }) => { + test.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/27877' }); + const files1 = { + 'echo-reporter.js': ` + export default class EchoReporter { + onBegin(config, suite) { + console.log('rootDir:', config.rootDir); + } + onTestBegin(test) { + console.log('test:', test.location.file); + console.log('test title:', test.titlePath()[2]); + } + }; + `, + 'dir1/playwright.config.ts': `module.exports = { + reporter: [['blob', { outputDir: 'blob-report' }]] + };`, + 'dir1/tests1/a.test.js': ` + import { test, expect } from '@playwright/test'; + test('math 1', async ({}) => { }); + `, + 'dir1/tests2/b.test.js': ` + import { test, expect } from '@playwright/test'; + test('math 2', async ({}) => { }); + `, + }; + await runInlineTest(files1, { workers: 1 }, undefined, { additionalArgs: ['--config', test.info().outputPath('dir1/playwright.config.ts')] }); + + const allReportsDir = test.info().outputPath('all-blob-reports'); + await fs.promises.mkdir(allReportsDir, { recursive: true }); + + // Extract report and change path separators. + const reportZipFile = test.info().outputPath('dir1', 'blob-report', 'report.zip'); + const events = await extractReport(reportZipFile, test.info().outputPath('tmp')); + events.forEach(patchPathSeparators); + + // Zip it back. + const report1 = path.join(allReportsDir, 'report-1.zip'); + await zipReport(events, report1); + + const { exitCode, output } = await mergeReports(allReportsDir, undefined, { additionalArgs: ['--reporter', './echo-reporter.js'] }); + expect(exitCode).toBe(0); + const otherSeparator = path.sep === '/' ? '\\' : '/'; + expect(output).toContain(`rootDir: ${test.info().outputPath('dir1').replaceAll(path.sep, otherSeparator)}`); + expect(output).toContain(`test: ${test.info().outputPath('dir1', 'tests1', 'a.test.js').replaceAll(path.sep, otherSeparator)}`); + expect(output).toContain(`test title: ${'tests1' + otherSeparator + 'a.test.js'}`); + expect(output).toContain(`test: ${test.info().outputPath('dir1', 'tests2', 'b.test.js').replaceAll(path.sep, otherSeparator)}`); + expect(output).toContain(`test title: ${'tests2' + otherSeparator + 'b.test.js'}`); +}); diff --git a/tests/playwright-test/reporter-markdown.spec.ts b/tests/playwright-test/reporter-markdown.spec.ts index 0241cb495c..d24f2561c1 100644 --- a/tests/playwright-test/reporter-markdown.spec.ts +++ b/tests/playwright-test/reporter-markdown.spec.ts @@ -14,7 +14,8 @@ * limitations under the License. */ -import * as fs from 'fs'; +import fs from 'fs'; +import path from 'path'; import { expect, test } from './playwright-test-fixtures'; test('simple report', async ({ runInlineTest }) => { @@ -25,7 +26,7 @@ test('simple report', async ({ runInlineTest }) => { reporter: 'markdown', }; `, - 'a.test.js': ` + 'dir1/a.test.js': ` import { test, expect } from '@playwright/test'; test('math 1', async ({}) => { expect(1 + 1).toBe(2); @@ -38,7 +39,7 @@ test('simple report', async ({ runInlineTest }) => { }); test.skip('skipped 1', async ({}) => {}); `, - 'b.test.js': ` + 'dir2/b.test.js': ` import { test, expect } from '@playwright/test'; test('math 2', async ({}) => { expect(1 + 1).toBe(2); @@ -63,13 +64,13 @@ test('simple report', async ({ runInlineTest }) => { expect(exitCode).toBe(1); const reportFile = await fs.promises.readFile(test.info().outputPath('report.md')); expect(reportFile.toString()).toContain(`**2 failed** -:x: a.test.js:6:11 › failing 1 -:x: b.test.js:6:11 › failing 2 +:x: dir1${path.sep}a.test.js:6:11 › failing 1 +:x: dir2${path.sep}b.test.js:6:11 › failing 2
2 flaky -:warning: a.test.js:9:11 › flaky 1
:warning: c.test.js:6:11 › flaky 2
+:warning: dir1${path.sep}a.test.js:9:11 › flaky 1
From 41728e709825f02557378d2e613b0cca478fdf4b Mon Sep 17 00:00:00 2001 From: Alexander Kachkaev Date: Mon, 27 Nov 2023 20:44:24 +0000 Subject: [PATCH 104/491] chore(install): Improve `ECONNRESET` handling in `downloadFile` (#28344) See https://github.com/microsoft/playwright/issues/28329#issuecomment-1826753106 for context --- .../src/server/registry/oopDownloadBrowserMain.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/playwright-core/src/server/registry/oopDownloadBrowserMain.ts b/packages/playwright-core/src/server/registry/oopDownloadBrowserMain.ts index e205f2c4fc..247581277e 100644 --- a/packages/playwright-core/src/server/registry/oopDownloadBrowserMain.ts +++ b/packages/playwright-core/src/server/registry/oopDownloadBrowserMain.ts @@ -85,12 +85,15 @@ function downloadFile(options: DownloadParams): Promise { file.on('error', error => promise.reject(error)); response.pipe(file); response.on('data', onData); - response.on('close', () => { - if (response.complete) - return; + response.on('error', (error: any) => { file.close(); - log(`-- download failed, server closed connection`); - promise.reject(new Error(`Download failed: server closed connection. URL: ${options.url}`)); + if (error?.code === 'ECONNRESET') { + log(`-- download failed, server closed connection`); + promise.reject(new Error(`Download failed: server closed connection. URL: ${options.url}`)); + } else { + log(`-- download failed, unexpected error`); + promise.reject(new Error(`Download failed: ${error?.message ?? error}. URL: ${options.url}`)); + } }); }, (error: any) => promise.reject(error)); return promise; From 96ce1a8f882de57ddc3935b4c239f4d0bc18a0b6 Mon Sep 17 00:00:00 2001 From: Playwright Service <89237858+playwrightmachine@users.noreply.github.com> Date: Mon, 27 Nov 2023 12:51:30 -0800 Subject: [PATCH 105/491] feat(webkit): roll to r1949 (#28358) Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> --- packages/playwright-core/browsers.json | 2 +- packages/playwright-core/src/server/webkit/protocol.d.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/playwright-core/browsers.json b/packages/playwright-core/browsers.json index 323a660a04..f1f2c137e3 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -39,7 +39,7 @@ }, { "name": "webkit", - "revision": "1948", + "revision": "1949", "installByDefault": true, "revisionOverrides": { "mac10.14": "1446", diff --git a/packages/playwright-core/src/server/webkit/protocol.d.ts b/packages/playwright-core/src/server/webkit/protocol.d.ts index 213e6da54c..87299324cb 100644 --- a/packages/playwright-core/src/server/webkit/protocol.d.ts +++ b/packages/playwright-core/src/server/webkit/protocol.d.ts @@ -536,7 +536,7 @@ export module Protocol { /** * Pseudo-style identifier (see enum PseudoId in RenderStyleConstants.h). */ - export type PseudoId = "first-line"|"first-letter"|"highlight"|"marker"|"before"|"after"|"selection"|"backdrop"|"scrollbar"|"scrollbar-thumb"|"scrollbar-button"|"scrollbar-track"|"scrollbar-track-piece"|"scrollbar-corner"|"resizer"; + export type PseudoId = "first-line"|"first-letter"|"highlight"|"marker"|"before"|"after"|"selection"|"backdrop"|"scrollbar"|"scrollbar-thumb"|"scrollbar-button"|"scrollbar-track"|"scrollbar-track-piece"|"scrollbar-corner"|"resizer"|"view-transition"|"view-transition-group"|"view-transition-image-pair"|"view-transition-old"|"view-transition-new"; /** * Pseudo-style identifier (see enum PseudoId in RenderStyleConstants.h). */ From f58c1f37eb36333ce2fa00d6beddc4a7909a1b39 Mon Sep 17 00:00:00 2001 From: Sander Date: Tue, 28 Nov 2023 00:53:50 +0100 Subject: [PATCH 106/491] fix(ct): empty slots or children (#28225) closes: https://github.com/microsoft/playwright/issues/28212 --- packages/playwright-ct-core/src/tsxTransform.ts | 9 ++++++--- packages/playwright-ct-core/types/component.d.ts | 2 +- packages/playwright-ct-react/registerSource.mjs | 4 ++-- packages/playwright-ct-react17/registerSource.mjs | 4 ++-- packages/playwright-ct-solid/registerSource.mjs | 4 ++-- packages/playwright-ct-vue/registerSource.mjs | 4 ++-- packages/playwright-ct-vue2/registerSource.mjs | 4 ++-- .../ct-react-vite/src/components/EmptyFragment.tsx | 3 ++- tests/components/ct-react-vite/tests/render.spec.tsx | 3 ++- .../ct-react17/src/components/EmptyFragment.tsx | 3 ++- tests/components/ct-react17/tests/render.spec.tsx | 3 ++- .../components/ct-solid/src/components/EmptyFragment.tsx | 3 ++- tests/components/ct-solid/tests/render.spec.tsx | 3 ++- .../ct-vue-cli/src/components/EmptyTemplate.vue | 8 +++++++- tests/components/ct-vue-cli/tests/render/render.spec.tsx | 3 ++- .../ct-vue-vite/src/components/EmptyTemplate.vue | 7 ++++++- .../components/ct-vue-vite/tests/render/render.spec.tsx | 3 ++- .../ct-vue2-cli/src/components/EmptyTemplate.vue | 8 +++++++- .../components/ct-vue2-cli/tests/render/render.spec.tsx | 3 ++- 19 files changed, 55 insertions(+), 26 deletions(-) diff --git a/packages/playwright-ct-core/src/tsxTransform.ts b/packages/playwright-ct-core/src/tsxTransform.ts index 1ad02c51a1..de15d3912a 100644 --- a/packages/playwright-ct-core/src/tsxTransform.ts +++ b/packages/playwright-ct-core/src/tsxTransform.ts @@ -121,12 +121,15 @@ export default declare((api: BabelAPI) => { children.push(t.spreadElement(child.expression)); } - path.replaceWith(t.objectExpression([ + const component = [ t.objectProperty(t.identifier('kind'), t.stringLiteral('jsx')), t.objectProperty(t.identifier('type'), t.stringLiteral(componentName)), t.objectProperty(t.identifier('props'), t.objectExpression(props)), - t.objectProperty(t.identifier('children'), t.arrayExpression(children)), - ])); + ]; + if (children.length) + component.push(t.objectProperty(t.identifier('children'), t.arrayExpression(children))); + + path.replaceWith(t.objectExpression(component)); } } }; diff --git a/packages/playwright-ct-core/types/component.d.ts b/packages/playwright-ct-core/types/component.d.ts index dc3ec2beaf..ddac25ab32 100644 --- a/packages/playwright-ct-core/types/component.d.ts +++ b/packages/playwright-ct-core/types/component.d.ts @@ -25,7 +25,7 @@ export type JsxComponent = { kind: 'jsx', type: string, props: Record, - children: JsxComponentChild[], + children?: JsxComponentChild[], }; export type MountOptions = { diff --git a/packages/playwright-ct-react/registerSource.mjs b/packages/playwright-ct-react/registerSource.mjs index 6231dae353..6ac6b8372d 100644 --- a/packages/playwright-ct-react/registerSource.mjs +++ b/packages/playwright-ct-react/registerSource.mjs @@ -71,7 +71,7 @@ async function __pwResolveComponent(component) { if (componentFactory) __pwRegistry.set(component.type, await componentFactory()); - if ('children' in component) + if (component.children?.length) await Promise.all(component.children.map(child => __pwResolveComponent(child))); } @@ -91,7 +91,7 @@ function __renderChild(child) { */ function __pwRender(component) { const componentFunc = __pwRegistry.get(component.type); - const children = component.children.map(child => __renderChild(child)).filter(child => { + const children = component.children?.map(child => __renderChild(child)).filter(child => { if (typeof child === 'string') return !!child.trim(); return true; diff --git a/packages/playwright-ct-react17/registerSource.mjs b/packages/playwright-ct-react17/registerSource.mjs index b50168ed0e..e6f89db935 100644 --- a/packages/playwright-ct-react17/registerSource.mjs +++ b/packages/playwright-ct-react17/registerSource.mjs @@ -70,7 +70,7 @@ async function __pwResolveComponent(component) { if (componentFactory) __pwRegistry.set(component.type, await componentFactory()); - if ('children' in component) + if (component.children?.length) await Promise.all(component.children.map(child => __pwResolveComponent(child))); } @@ -90,7 +90,7 @@ function __renderChild(child) { */ function __pwRender(component) { const componentFunc = __pwRegistry.get(component.type); - const children = component.children.map(child => __renderChild(child)).filter(child => { + const children = component.children?.map(child => __renderChild(child)).filter(child => { if (typeof child === 'string') return !!child.trim(); return true; diff --git a/packages/playwright-ct-solid/registerSource.mjs b/packages/playwright-ct-solid/registerSource.mjs index fed1b46540..1704a4ba9a 100644 --- a/packages/playwright-ct-solid/registerSource.mjs +++ b/packages/playwright-ct-solid/registerSource.mjs @@ -69,7 +69,7 @@ async function __pwResolveComponent(component) { if (componentFactory) __pwRegistry.set(component.type, await componentFactory()); - if ('children' in component) + if (component.children?.length) await Promise.all(component.children.map(child => __pwResolveComponent(child))); } @@ -89,7 +89,7 @@ function __pwCreateChild(child) { */ function __pwCreateComponent(component) { const componentFunc = __pwRegistry.get(component.type); - const children = component.children.map(child => __pwCreateChild(child)).filter(child => { + const children = component.children?.map(child => __pwCreateChild(child)).filter(child => { if (typeof child === 'string') return !!child.trim(); return true; diff --git a/packages/playwright-ct-vue/registerSource.mjs b/packages/playwright-ct-vue/registerSource.mjs index c78b3f6ac2..8ee030daf7 100644 --- a/packages/playwright-ct-vue/registerSource.mjs +++ b/packages/playwright-ct-vue/registerSource.mjs @@ -72,7 +72,7 @@ async function __pwResolveComponent(component) { if (componentFactory) __pwRegistry.set(component.type, await componentFactory()); - if ('children' in component) + if ('children' in component && component.children?.length) await Promise.all(component.children.map(child => __pwResolveComponent(child))); } @@ -156,7 +156,7 @@ function __pwCreateComponent(component) { if (typeof child !== 'string' && child.type === 'template' && child.kind === 'jsx') { const slotProperty = Object.keys(child.props).find(k => k.startsWith('v-slot:')); const slot = slotProperty ? slotProperty.substring('v-slot:'.length) : 'default'; - slots[slot] = child.children.map(__pwCreateChild); + slots[slot] = child.children?.map(__pwCreateChild); } else { children.push(__pwCreateChild(child)); } diff --git a/packages/playwright-ct-vue2/registerSource.mjs b/packages/playwright-ct-vue2/registerSource.mjs index e67a22dd2b..043c680229 100644 --- a/packages/playwright-ct-vue2/registerSource.mjs +++ b/packages/playwright-ct-vue2/registerSource.mjs @@ -71,7 +71,7 @@ async function __pwResolveComponent(component) { if (componentFactory) __pwRegistry.set(component.type, await componentFactory()); - if ('children' in component) + if ('children' in component && component.children?.length) await Promise.all(component.children.map(child => __pwResolveComponent(child))); } @@ -124,7 +124,7 @@ function __pwCreateComponent(component) { if (typeof child !== 'string' && child.type === 'template' && child.kind === 'jsx') { const slotProperty = Object.keys(child.props).find(k => k.startsWith('v-slot:')); const slot = slotProperty ? slotProperty.substring('v-slot:'.length) : 'default'; - nodeData.scopedSlots[slot] = () => child.children.map(c => __pwCreateChild(c)); + nodeData.scopedSlots[slot] = () => child.children?.map(c => __pwCreateChild(c)); } else { children.push(__pwCreateChild(child)); } diff --git a/tests/components/ct-react-vite/src/components/EmptyFragment.tsx b/tests/components/ct-react-vite/src/components/EmptyFragment.tsx index 64cb1177a3..b424841462 100644 --- a/tests/components/ct-react-vite/src/components/EmptyFragment.tsx +++ b/tests/components/ct-react-vite/src/components/EmptyFragment.tsx @@ -1,3 +1,4 @@ -export default function EmptyFragment() { +export default function EmptyFragment(props: unknown) { + Object.assign(window, { props }); return <>{[]}; } diff --git a/tests/components/ct-react-vite/tests/render.spec.tsx b/tests/components/ct-react-vite/tests/render.spec.tsx index f8ab02e819..107a18bf65 100644 --- a/tests/components/ct-react-vite/tests/render.spec.tsx +++ b/tests/components/ct-react-vite/tests/render.spec.tsx @@ -12,8 +12,9 @@ test('render attributes', async ({ mount }) => { await expect(component).toHaveClass('primary'); }); -test('get textContent of the empty fragment', async ({ mount }) => { +test('render an empty component', async ({ mount, page }) => { const component = await mount(); + expect(await page.evaluate(() => 'props' in window && window.props)).toEqual({}); expect(await component.allTextContents()).toEqual(['']); expect(await component.textContent()).toBe(''); await expect(component).toHaveText(''); diff --git a/tests/components/ct-react17/src/components/EmptyFragment.tsx b/tests/components/ct-react17/src/components/EmptyFragment.tsx index 64cb1177a3..b424841462 100644 --- a/tests/components/ct-react17/src/components/EmptyFragment.tsx +++ b/tests/components/ct-react17/src/components/EmptyFragment.tsx @@ -1,3 +1,4 @@ -export default function EmptyFragment() { +export default function EmptyFragment(props: unknown) { + Object.assign(window, { props }); return <>{[]}; } diff --git a/tests/components/ct-react17/tests/render.spec.tsx b/tests/components/ct-react17/tests/render.spec.tsx index 9795e753a9..ec9405c82f 100644 --- a/tests/components/ct-react17/tests/render.spec.tsx +++ b/tests/components/ct-react17/tests/render.spec.tsx @@ -20,8 +20,9 @@ test('render delayed data', async ({ mount }) => { await expect(component).toHaveText('complete'); }); -test('get textContent of the empty fragment', async ({ mount }) => { +test('render an empty component', async ({ mount, page }) => { const component = await mount(); + expect(await page.evaluate(() => 'props' in window && window.props)).toEqual({}); expect(await component.allTextContents()).toEqual(['']); expect(await component.textContent()).toBe(''); await expect(component).toHaveText(''); diff --git a/tests/components/ct-solid/src/components/EmptyFragment.tsx b/tests/components/ct-solid/src/components/EmptyFragment.tsx index 64cb1177a3..b424841462 100644 --- a/tests/components/ct-solid/src/components/EmptyFragment.tsx +++ b/tests/components/ct-solid/src/components/EmptyFragment.tsx @@ -1,3 +1,4 @@ -export default function EmptyFragment() { +export default function EmptyFragment(props: unknown) { + Object.assign(window, { props }); return <>{[]}; } diff --git a/tests/components/ct-solid/tests/render.spec.tsx b/tests/components/ct-solid/tests/render.spec.tsx index 70e5082bc2..199e5f4f03 100644 --- a/tests/components/ct-solid/tests/render.spec.tsx +++ b/tests/components/ct-solid/tests/render.spec.tsx @@ -12,8 +12,9 @@ test('render attributes', async ({ mount }) => { await expect(component).toHaveClass('primary'); }); -test('get textContent of the empty fragment', async ({ mount }) => { +test('render an empty component', async ({ mount, page }) => { const component = await mount(); + expect(await page.evaluate(() => 'props' in window && window.props)).toEqual({}); expect(await component.allTextContents()).toEqual(['']); expect(await component.textContent()).toBe(''); await expect(component).toHaveText(''); diff --git a/tests/components/ct-vue-cli/src/components/EmptyTemplate.vue b/tests/components/ct-vue-cli/src/components/EmptyTemplate.vue index ba5341392f..9b4c59695e 100644 --- a/tests/components/ct-vue-cli/src/components/EmptyTemplate.vue +++ b/tests/components/ct-vue-cli/src/components/EmptyTemplate.vue @@ -1,2 +1,8 @@ + \ No newline at end of file + + diff --git a/tests/components/ct-vue-cli/tests/render/render.spec.tsx b/tests/components/ct-vue-cli/tests/render/render.spec.tsx index 1fb6964fb3..c051422fdd 100644 --- a/tests/components/ct-vue-cli/tests/render/render.spec.tsx +++ b/tests/components/ct-vue-cli/tests/render/render.spec.tsx @@ -7,8 +7,9 @@ test('render props', async ({ mount }) => { await expect(component).toContainText('Submit'); }); -test('get textContent of the empty template', async ({ mount }) => { +test('render an empty component', async ({ page, mount }) => { const component = await mount(); + expect(await page.evaluate(() => 'slots' in window && window.slots)).toEqual({}); expect(await component.allTextContents()).toEqual(['']); expect(await component.textContent()).toBe(''); await expect(component).toHaveText(''); diff --git a/tests/components/ct-vue-vite/src/components/EmptyTemplate.vue b/tests/components/ct-vue-vite/src/components/EmptyTemplate.vue index ba5341392f..0c47e38098 100644 --- a/tests/components/ct-vue-vite/src/components/EmptyTemplate.vue +++ b/tests/components/ct-vue-vite/src/components/EmptyTemplate.vue @@ -1,2 +1,7 @@ + \ No newline at end of file + diff --git a/tests/components/ct-vue-vite/tests/render/render.spec.tsx b/tests/components/ct-vue-vite/tests/render/render.spec.tsx index 918f0e0113..e4cf96ec8f 100644 --- a/tests/components/ct-vue-vite/tests/render/render.spec.tsx +++ b/tests/components/ct-vue-vite/tests/render/render.spec.tsx @@ -12,8 +12,9 @@ test('render attributes', async ({ mount }) => { await expect(component).toHaveClass('primary'); }); -test('get textContent of the empty template', async ({ mount }) => { +test('render an empty component', async ({ page, mount }) => { const component = await mount(); + expect(await page.evaluate(() => 'slots' in window && window.slots)).toEqual({}); expect(await component.allTextContents()).toEqual(['']); expect(await component.textContent()).toBe(''); await expect(component).toHaveText(''); diff --git a/tests/components/ct-vue2-cli/src/components/EmptyTemplate.vue b/tests/components/ct-vue2-cli/src/components/EmptyTemplate.vue index ba5341392f..9b4c59695e 100644 --- a/tests/components/ct-vue2-cli/src/components/EmptyTemplate.vue +++ b/tests/components/ct-vue2-cli/src/components/EmptyTemplate.vue @@ -1,2 +1,8 @@ + \ No newline at end of file + + diff --git a/tests/components/ct-vue2-cli/tests/render/render.spec.tsx b/tests/components/ct-vue2-cli/tests/render/render.spec.tsx index 42307ff8f6..699269545d 100644 --- a/tests/components/ct-vue2-cli/tests/render/render.spec.tsx +++ b/tests/components/ct-vue2-cli/tests/render/render.spec.tsx @@ -12,8 +12,9 @@ test('render attributes', async ({ mount }) => { await expect(component).toHaveClass('primary'); }); -test('get textContent of the empty template', async ({ mount }) => { +test('render an empty component', async ({ page, mount }) => { const component = await mount(); + expect(await page.evaluate(() => 'slots' in window && window.slots)).toEqual({}); expect(await component.allTextContents()).toEqual(['']); expect(await component.textContent()).toBe(''); await expect(component).toHaveText(''); From f61e445f2b2a0abf8334c3b3c13ec5f986631cb0 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Mon, 27 Nov 2023 16:08:20 -0800 Subject: [PATCH 107/491] Revert "chore(test runner): remove fake skipped test results (#27762)" (#28360) This reverts commit 210168e36db026448f419a53e86865335f1332be. Fixes #28321. --- packages/playwright/src/common/test.ts | 15 ++-- .../playwright/src/isomorphic/teleReceiver.ts | 12 ++- packages/playwright/src/runner/dispatcher.ts | 76 ++++++++++--------- .../playwright/src/runner/failureTracker.ts | 2 +- tests/playwright-test/hooks.spec.ts | 8 +- tests/playwright-test/retry.spec.ts | 4 +- tests/playwright-test/runner.spec.ts | 4 +- tests/playwright-test/test-modifiers.spec.ts | 2 +- tests/playwright-test/test-serial.spec.ts | 60 +-------------- 9 files changed, 72 insertions(+), 111 deletions(-) diff --git a/packages/playwright/src/common/test.ts b/packages/playwright/src/common/test.ts index 030ee77f42..a1afee5b08 100644 --- a/packages/playwright/src/common/test.ts +++ b/packages/playwright/src/common/test.ts @@ -239,9 +239,6 @@ export class TestCase extends Base implements reporterTypes.TestCase { _poolDigest = ''; _workerHash = ''; _projectId = ''; - // This is different from |results.length| because sometimes we do not run the test, but consume - // an attempt, for example when skipping tests in a serial suite after a failure. - _runAttempts = 0; // Annotations known statically before running the test, e.g. `test.skip()` or `test.describe.skip()`. _staticAnnotations: Annotation[] = []; @@ -259,10 +256,16 @@ export class TestCase extends Base implements reporterTypes.TestCase { } outcome(): 'skipped' | 'expected' | 'unexpected' | 'flaky' { - const results = this.results.filter(result => result.status !== 'interrupted'); - if (results.every(result => result.status === 'skipped')) + // Ignore initial skips that may be a result of "skipped because previous test in serial mode failed". + const results = [...this.results]; + while (results[0]?.status === 'skipped' || results[0]?.status === 'interrupted') + results.shift(); + + // All runs were skipped. + if (!results.length) return 'skipped'; - const failures = results.filter(result => result.status !== this.expectedStatus); + + const failures = results.filter(result => result.status !== 'skipped' && result.status !== 'interrupted' && result.status !== this.expectedStatus); if (!failures.length) // all passed return 'expected'; if (failures.length === results.length) // all failed diff --git a/packages/playwright/src/isomorphic/teleReceiver.ts b/packages/playwright/src/isomorphic/teleReceiver.ts index 2850fcdc74..c5d82d810d 100644 --- a/packages/playwright/src/isomorphic/teleReceiver.ts +++ b/packages/playwright/src/isomorphic/teleReceiver.ts @@ -492,10 +492,16 @@ export class TeleTestCase implements reporterTypes.TestCase { } outcome(): 'skipped' | 'expected' | 'unexpected' | 'flaky' { - const results = this.results.filter(result => result.status !== 'interrupted'); - if (results.every(result => result.status === 'skipped')) + // Ignore initial skips that may be a result of "skipped because previous test in serial mode failed". + const results = [...this.results]; + while (results[0]?.status === 'skipped' || results[0]?.status === 'interrupted') + results.shift(); + + // All runs were skipped. + if (!results.length) return 'skipped'; - const failures = results.filter(result => result.status !== this.expectedStatus); + + const failures = results.filter(result => result.status !== 'skipped' && result.status !== 'interrupted' && result.status !== this.expectedStatus); if (!failures.length) // all passed return 'expected'; if (failures.length === results.length) // all failed diff --git a/packages/playwright/src/runner/dispatcher.ts b/packages/playwright/src/runner/dispatcher.ts index 2cf66f44cc..6fb315114e 100644 --- a/packages/playwright/src/runner/dispatcher.ts +++ b/packages/playwright/src/runner/dispatcher.ts @@ -290,7 +290,7 @@ class JobDispatcher { test.expectedStatus = params.expectedStatus; test.annotations = params.annotations; test.timeout = params.timeout; - const isFailure = result.status !== test.expectedStatus; + const isFailure = result.status !== 'skipped' && result.status !== test.expectedStatus; if (isFailure) this._failedTests.add(test); this._reportTestEnd(test, result); @@ -369,17 +369,22 @@ class JobDispatcher { } result.errors = [...errors]; result.error = result.errors[0]; - result.status = 'failed'; + result.status = errors.length ? 'failed' : 'skipped'; this._reportTestEnd(test, result); this._failedTests.add(test); } - private _handleFatalErrors(errors: TestError[]) { - const test = this._remainingByTestId.values().next().value as TestCase | undefined; - if (test) { - this._failTestWithErrors(test, errors); + private _massSkipTestsFromRemaining(testIds: Set, errors: TestError[]) { + for (const test of this._remainingByTestId.values()) { + if (!testIds.has(test.id)) + continue; + if (!this._failureTracker.hasReachedMaxFailures()) { + this._failTestWithErrors(test, errors); + errors = []; // Only report errors for the first test. + } this._remainingByTestId.delete(test.id); - } else if (errors.length) { + } + if (errors.length) { // We had fatal errors after all tests have passed - most likely in some teardown. // Let's just fail the test run. this._failureTracker.onWorkerError(); @@ -407,28 +412,23 @@ class JobDispatcher { } if (params.fatalErrors.length) { - // In case of fatal errors, report the first remaining test as failing with these errors, - // and "skip" all other tests to avoid running into the same issue over and over. - this._handleFatalErrors(params.fatalErrors); - this._remainingByTestId.clear(); + // In case of fatal errors, report first remaining test as failing with these errors, + // and all others as skipped. + this._massSkipTestsFromRemaining(new Set(this._remainingByTestId.keys()), params.fatalErrors); } - // Handle tests that should be skipped because of the setup failure. - for (const testId of params.skipTestsDueToSetupFailure) - this._remainingByTestId.delete(testId); + this._massSkipTestsFromRemaining(new Set(params.skipTestsDueToSetupFailure), []); if (params.unexpectedExitError) { - if (this._currentlyRunning) { - // When worker exits during a test, we blame the test itself. - this._failTestWithErrors(this._currentlyRunning.test, [params.unexpectedExitError]); - this._remainingByTestId.delete(this._currentlyRunning.test.id); - } else { - // The most common situation when worker exits while not running a test is: - // worker failed to require the test file (at the start) because of an exception in one of imports. - // In this case, "skip" all remaining tests, to avoid running into the same exception over and over. - this._handleFatalErrors([params.unexpectedExitError]); - this._remainingByTestId.clear(); - } + // When worker exits during a test, we blame the test itself. + // + // The most common situation when worker exits while not running a test is: + // worker failed to require the test file (at the start) because of an exception in one of imports. + // In this case, "skip" all remaining tests, to avoid running into the same exception over and over. + if (this._currentlyRunning) + this._massSkipTestsFromRemaining(new Set([this._currentlyRunning.test.id]), [params.unexpectedExitError]); + else + this._massSkipTestsFromRemaining(new Set(this._remainingByTestId.keys()), [params.unexpectedExitError]); } const retryCandidates = new Set(); @@ -446,22 +446,26 @@ class JobDispatcher { serialSuitesWithFailures.add(outermostSerialSuite); } + // If we have failed tests that belong to a serial suite, + // we should skip all future tests from the same serial suite. + const testsBelongingToSomeSerialSuiteWithFailures = [...this._remainingByTestId.values()].filter(test => { + let parent: Suite | undefined = test.parent; + while (parent && !serialSuitesWithFailures.has(parent)) + parent = parent.parent; + return !!parent; + }); + this._massSkipTestsFromRemaining(new Set(testsBelongingToSomeSerialSuiteWithFailures.map(test => test.id)), []); + for (const serialSuite of serialSuitesWithFailures) { - serialSuite.allTests().forEach(test => { - // Skip remaining tests from serial suites with failures. - this._remainingByTestId.delete(test.id); - // Schedule them for the retry all together. - retryCandidates.add(test); - }); + // Add all tests from failed serial suites for possible retry. + // These will only be retried together, because they have the same + // "retries" setting and the same number of previous runs. + serialSuite.allTests().forEach(test => retryCandidates.add(test)); } - for (const test of this._job.tests) { - if (!this._remainingByTestId.has(test.id)) - ++test._runAttempts; - } const remaining = [...this._remainingByTestId.values()]; for (const test of retryCandidates) { - if (test._runAttempts < test.retries + 1) + if (test.results.length < test.retries + 1) remaining.push(test); } diff --git a/packages/playwright/src/runner/failureTracker.ts b/packages/playwright/src/runner/failureTracker.ts index 2835403387..fc2202b3ea 100644 --- a/packages/playwright/src/runner/failureTracker.ts +++ b/packages/playwright/src/runner/failureTracker.ts @@ -31,7 +31,7 @@ export class FailureTracker { } onTestEnd(test: TestCase, result: TestResult) { - if (result.status !== test.expectedStatus) + if (result.status !== 'skipped' && result.status !== test.expectedStatus) ++this._failureCount; } diff --git a/tests/playwright-test/hooks.spec.ts b/tests/playwright-test/hooks.spec.ts index 59725ee0f5..dcc497f6dc 100644 --- a/tests/playwright-test/hooks.spec.ts +++ b/tests/playwright-test/hooks.spec.ts @@ -399,7 +399,7 @@ test('beforeAll failure should prevent the test, but not afterAll', async ({ run }); expect(result.exitCode).toBe(1); expect(result.failed).toBe(1); - expect(result.didNotRun).toBe(1); + expect(result.skipped).toBe(1); expect(result.outputLines).toEqual([ 'beforeAll', 'afterAll', @@ -499,7 +499,7 @@ test('beforeAll timeout should be reported and prevent more tests', async ({ run }, { timeout: 1000 }); expect(result.exitCode).toBe(1); expect(result.failed).toBe(1); - expect(result.didNotRun).toBe(1); + expect(result.skipped).toBe(1); expect(result.outputLines).toEqual([ 'beforeAll', 'afterAll', @@ -688,7 +688,7 @@ test('unhandled rejection during beforeAll should be reported and prevent more t }); expect(result.exitCode).toBe(1); expect(result.failed).toBe(1); - expect(result.didNotRun).toBe(1); + expect(result.skipped).toBe(1); expect(result.outputLines).toEqual([ 'beforeAll', 'afterAll', @@ -801,7 +801,7 @@ test('beforeAll failure should only prevent tests that are affected', async ({ r }); expect(result.exitCode).toBe(1); expect(result.failed).toBe(1); - expect(result.didNotRun).toBe(1); + expect(result.skipped).toBe(1); expect(result.passed).toBe(1); expect(result.outputLines).toEqual([ 'beforeAll', diff --git a/tests/playwright-test/retry.spec.ts b/tests/playwright-test/retry.spec.ts index 93f52010bd..d6cad8a080 100644 --- a/tests/playwright-test/retry.spec.ts +++ b/tests/playwright-test/retry.spec.ts @@ -216,8 +216,8 @@ test('should retry beforeAll failure', async ({ runInlineTest }) => { expect(result.exitCode).toBe(1); expect(result.passed).toBe(0); expect(result.failed).toBe(1); - expect(result.didNotRun).toBe(1); - expect(result.output.split('\n')[2]).toBe('××F'); + expect(result.skipped).toBe(1); + expect(result.output.split('\n')[2]).toBe('×°×°F°'); expect(result.output).toContain('BeforeAll is bugged!'); }); diff --git a/tests/playwright-test/runner.spec.ts b/tests/playwright-test/runner.spec.ts index 088a73ead1..51a8c9346e 100644 --- a/tests/playwright-test/runner.spec.ts +++ b/tests/playwright-test/runner.spec.ts @@ -111,7 +111,7 @@ test('should report subprocess creation error', async ({ runInlineTest }, testIn expect(result.exitCode).toBe(1); expect(result.passed).toBe(0); expect(result.failed).toBe(1); - expect(result.didNotRun).toBe(1); + expect(result.skipped).toBe(1); expect(result.output).toContain('Error: worker process exited unexpectedly (code=42, signal=null)'); }); @@ -634,9 +634,9 @@ test('should not hang on worker error in test file', async ({ runInlineTest }) = `, }, { 'timeout': 3000 }); expect(result.exitCode).toBe(1); - expect(result.results).toHaveLength(1); expect(result.results[0].status).toBe('failed'); expect(result.results[0].error.message).toContain('Error: worker process exited unexpectedly'); + expect(result.results[1].status).toBe('skipped'); }); test('fast double SIGINT should be ignored', async ({ interactWithTestRunner }) => { diff --git a/tests/playwright-test/test-modifiers.spec.ts b/tests/playwright-test/test-modifiers.spec.ts index ed3abc5e12..ce9a1aff9a 100644 --- a/tests/playwright-test/test-modifiers.spec.ts +++ b/tests/playwright-test/test-modifiers.spec.ts @@ -640,7 +640,7 @@ test('static modifiers should be added in serial mode', async ({ runInlineTest } }); expect(result.exitCode).toBe(1); expect(result.passed).toBe(0); - expect(result.didNotRun).toBe(3); + expect(result.skipped).toBe(3); expect(result.report.suites[0].specs[0].tests[0].annotations).toEqual([{ type: 'slow' }]); expect(result.report.suites[0].specs[1].tests[0].annotations).toEqual([{ type: 'fixme' }]); expect(result.report.suites[0].specs[2].tests[0].annotations).toEqual([{ type: 'skip' }]); diff --git a/tests/playwright-test/test-serial.spec.ts b/tests/playwright-test/test-serial.spec.ts index 5059b8108b..085d175097 100644 --- a/tests/playwright-test/test-serial.spec.ts +++ b/tests/playwright-test/test-serial.spec.ts @@ -47,7 +47,7 @@ test('test.describe.serial should work', async ({ runInlineTest }) => { expect(result.exitCode).toBe(1); expect(result.passed).toBe(2); expect(result.failed).toBe(1); - expect(result.didNotRun).toBe(2); + expect(result.skipped).toBe(2); expect(result.outputLines).toEqual([ 'test1', 'test2', @@ -87,7 +87,7 @@ test('test.describe.serial should work in describe', async ({ runInlineTest }) = expect(result.exitCode).toBe(1); expect(result.passed).toBe(2); expect(result.failed).toBe(1); - expect(result.didNotRun).toBe(2); + expect(result.skipped).toBe(2); expect(result.outputLines).toEqual([ 'test1', 'test2', @@ -128,7 +128,7 @@ test('test.describe.serial should work with retry', async ({ runInlineTest }) => expect(result.passed).toBe(2); expect(result.flaky).toBe(1); expect(result.failed).toBe(1); - expect(result.didNotRun).toBe(1); + expect(result.skipped).toBe(1); expect(result.outputLines).toEqual([ 'test1', 'test2', @@ -272,7 +272,7 @@ test('test.describe.serial should work with test.fail', async ({ runInlineTest } expect(result.exitCode).toBe(1); expect(result.passed).toBe(2); expect(result.failed).toBe(1); - expect(result.didNotRun).toBe(1); + expect(result.skipped).toBe(1); expect(result.outputLines).toEqual([ 'zero', 'one', @@ -394,55 +394,3 @@ test('test.describe.serial should work with fullyParallel', async ({ runInlineTe 'two', ]); }); - -test('serial fail + skip is failed', async ({ runInlineTest }) => { - const result = await runInlineTest({ - 'a.test.ts': ` - import { test, expect } from '@playwright/test'; - test.describe.configure({ mode: 'serial', retries: 1 }); - test.describe.serial('serial suite', () => { - test('one', async () => { - expect(test.info().retry).toBe(0); - }); - test('two', async () => { - expect(1).toBe(2); - }); - test('three', async () => { - }); - }); - `, - }, { workers: 1 }); - expect(result.exitCode).toBe(1); - expect(result.passed).toBe(0); - expect(result.skipped).toBe(0); - expect(result.flaky).toBe(1); - expect(result.failed).toBe(1); - expect(result.interrupted).toBe(0); - expect(result.didNotRun).toBe(1); -}); - -test('serial skip + fail is failed', async ({ runInlineTest }) => { - const result = await runInlineTest({ - 'a.test.ts': ` - import { test, expect } from '@playwright/test'; - test.describe.configure({ mode: 'serial', retries: 1 }); - test.describe.serial('serial suite', () => { - test('one', async () => { - expect(test.info().retry).toBe(1); - }); - test('two', async () => { - expect(1).toBe(2); - }); - test('three', async () => { - }); - }); - `, - }, { workers: 1 }); - expect(result.exitCode).toBe(1); - expect(result.passed).toBe(0); - expect(result.skipped).toBe(0); - expect(result.flaky).toBe(1); - expect(result.failed).toBe(1); - expect(result.interrupted).toBe(0); - expect(result.didNotRun).toBe(1); -}); From cea28b2df9192bd60a55fb6d5f4e553748f83758 Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Mon, 27 Nov 2023 16:37:30 -0800 Subject: [PATCH 108/491] docs: clarify beforeAll execution on exception (#28361) Reference https://github.com/microsoft/playwright/issues/28285 --- docs/src/test-api/class-test.md | 24 ++++++++++++++++++++++++ packages/playwright/types/test.d.ts | 24 ++++++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/docs/src/test-api/class-test.md b/docs/src/test-api/class-test.md index 6c3ac71d07..4fde7bb576 100644 --- a/docs/src/test-api/class-test.md +++ b/docs/src/test-api/class-test.md @@ -55,6 +55,8 @@ When called in the scope of a test file, runs after all tests in the file. When Note that worker process is restarted on test failures, and `afterAll` hook runs again in the new worker. Learn more about [workers and failures](../test-retries.md). +Playwright will continue running all applicable hooks even if some of them have failed. + **Usage** ```js @@ -76,6 +78,10 @@ Hook function that takes one or two arguments: an object with worker fixtures an Declares an `afterAll` hook with a title that is executed once per worker after all tests. +**Details** + +See [`method: Test.afterAll#1`]. + **Usage** ```js @@ -110,6 +116,8 @@ When called in the scope of a test file, runs after each test in the file. When You can access all the same [Fixtures] as the test function itself, and also the [TestInfo] object that gives a lot of useful information. For example, you can check whether the test succeeded or failed. +Playwright will continue running all applicable hooks even if some of them have failed. + **Usage** ```js title="example.spec.ts" @@ -139,6 +147,10 @@ Hook function that takes one or two arguments: an object with fixtures and optio Declares an `afterEach` hook with a title that is executed after each test. +**Details** + +See [`method: Test.afterEach#1`]. + **Usage** ```js title="example.spec.ts" @@ -181,6 +193,8 @@ When called in the scope of a test file, runs before all tests in the file. When Note that worker process is restarted on test failures, and `beforeAll` hook runs again in the new worker. Learn more about [workers and failures](../test-retries.md). +Playwright will continue running all applicable hooks even if some of them have failed. + You can use [`method: Test.afterAll#1`] to teardown any resources set up in `beforeAll`. **Usage** @@ -213,6 +227,10 @@ Hook function that takes one or two arguments: an object with worker fixtures an Declares a `beforeAll` hook with a title that is executed once per worker process before all tests. +**Details** + +See [`method: Test.beforeAll#1`]. + **Usage** ```js title="example.spec.ts" @@ -252,6 +270,8 @@ When called in the scope of a test file, runs before each test in the file. When You can access all the same [Fixtures] as the test function itself, and also the [TestInfo] object that gives a lot of useful information. For example, you can navigate the page before starting the test. +Playwright will continue running all applicable hooks even if some of them have failed. + You can use [`method: Test.afterEach#1`] to teardown any resources set up in `beforeEach`. **Usage** @@ -281,6 +301,10 @@ Hook function that takes one or two arguments: an object with fixtures and optio Declares a `beforeEach` hook with a title that is executed before each test. +**Details** + +See [`method: Test.beforeEach#1`]. + **Usage** ```js title="example.spec.ts" diff --git a/packages/playwright/types/test.d.ts b/packages/playwright/types/test.d.ts index bbf9da8314..d8a7d5b8bd 100644 --- a/packages/playwright/types/test.d.ts +++ b/packages/playwright/types/test.d.ts @@ -3072,6 +3072,8 @@ export interface TestType Date: Mon, 27 Nov 2023 16:43:47 -0800 Subject: [PATCH 109/491] chore: do not add to the internal action logs (#28365) Fixes https://github.com/microsoft/playwright/issues/28319 --- .../src/server/trace/recorder/tracing.ts | 2 + packages/trace-viewer/src/traceModel.ts | 5 ++- tests/playwright-test/ui-mode-trace.spec.ts | 39 ++++++++++++++++--- 3 files changed, 40 insertions(+), 6 deletions(-) diff --git a/packages/playwright-core/src/server/trace/recorder/tracing.ts b/packages/playwright-core/src/server/trace/recorder/tracing.ts index f5e84f442f..131f87ae2c 100644 --- a/packages/playwright-core/src/server/trace/recorder/tracing.ts +++ b/packages/playwright-core/src/server/trace/recorder/tracing.ts @@ -373,6 +373,8 @@ export class Tracing extends SdkObject implements InstrumentationListener, Snaps } onCallLog(sdkObject: SdkObject, metadata: CallMetadata, logName: string, message: string) { + if (metadata.isServerSide || metadata.internal) + return; if (logName !== 'api') return; const event = createActionLogTraceEvent(metadata, message); diff --git a/packages/trace-viewer/src/traceModel.ts b/packages/trace-viewer/src/traceModel.ts index bf7a742cc1..c80ab25474 100644 --- a/packages/trace-viewer/src/traceModel.ts +++ b/packages/trace-viewer/src/traceModel.ts @@ -190,7 +190,10 @@ export class TraceModel { } case 'log': { const existing = actionMap.get(event.callId); - existing!.log.push({ + // We have some corrupted traces out there, tolerate them. + if (!existing) + return; + existing.log.push({ time: event.time, message: event.message, }); diff --git a/tests/playwright-test/ui-mode-trace.spec.ts b/tests/playwright-test/ui-mode-trace.spec.ts index 68b1bd1d97..74b28455b8 100644 --- a/tests/playwright-test/ui-mode-trace.spec.ts +++ b/tests/playwright-test/ui-mode-trace.spec.ts @@ -19,7 +19,7 @@ import { test, expect, retries } from './ui-mode-fixtures'; test.describe.configure({ mode: 'parallel', retries }); -test('should merge trace events', async ({ runUITest, server }) => { +test('should merge trace events', async ({ runUITest }) => { const { page } = await runUITest({ 'a.test.ts': ` import { test, expect } from '@playwright/test'; @@ -99,7 +99,7 @@ test('should merge screenshot assertions', async ({ runUITest }, testInfo) => { ]); }); -test('should locate sync assertions in source', async ({ runUITest, server }) => { +test('should locate sync assertions in source', async ({ runUITest }) => { const { page } = await runUITest({ 'a.test.ts': ` import { test, expect } from '@playwright/test'; @@ -118,7 +118,7 @@ test('should locate sync assertions in source', async ({ runUITest, server }) => ).toHaveText('4 expect(1).toBe(1);'); }); -test('should show snapshots for sync assertions', async ({ runUITest, server }) => { +test('should show snapshots for sync assertions', async ({ runUITest }) => { const { page } = await runUITest({ 'a.test.ts': ` import { test, expect } from '@playwright/test'; @@ -150,7 +150,7 @@ test('should show snapshots for sync assertions', async ({ runUITest, server }) ).toHaveText('Submit'); }); -test('should show image diff', async ({ runUITest, server }) => { +test('should show image diff', async ({ runUITest }) => { const { page } = await runUITest({ 'playwright.config.js': ` module.exports = { @@ -175,7 +175,7 @@ test('should show image diff', async ({ runUITest, server }) => { await expect(page.locator('.image-diff-view .image-wrapper img')).toBeVisible(); }); -test('should show screenshot', async ({ runUITest, server }) => { +test('should show screenshot', async ({ runUITest }) => { const { page } = await runUITest({ 'playwright.config.js': ` module.exports = { @@ -197,3 +197,32 @@ test('should show screenshot', async ({ runUITest, server }) => { await expect(page.getByText('Screenshots', { exact: true })).toBeVisible(); await expect(page.locator('.attachment-item img')).toHaveCount(1); }); + +test('should not fail on internal page logs', async ({ runUITest, server }) => { + const { page } = await runUITest({ + 'a.test.ts': ` + import { test, expect } from '@playwright/test'; + test('pass', async ({ browser }, testInfo) => { + const context = await browser.newContext({ storageState: { cookies: [], origins: [] } }); + const page = await context.newPage(); + await page.goto("${server.EMPTY_PAGE}"); + await page.context().storageState({ path: testInfo.outputPath('storage.json') }); + }); + `, + }); + + await page.getByText('pass').dblclick(); + const listItem = page.getByTestId('actions-tree').getByRole('listitem'); + + await expect( + listItem, + 'action list' + ).toHaveText([ + /Before Hooks[\d.]+m?s/, + /browser.newContext[\d.]+m?s/, + /browserContext.newPage[\d.]+m?s/, + /page.goto/, + /browserContext.storageState[\d.]+m?s/, + /After Hooks/, + ]); +}); From 2e762fd3d20ee41ec0a5b4c338af6d4d241680d1 Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Tue, 28 Nov 2023 08:47:44 -0800 Subject: [PATCH 110/491] fix: parse report.jsonl without creating large string (#28366) Reference https://github.com/microsoft/playwright/issues/28362 --- packages/playwright/src/reporters/merge.ts | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/packages/playwright/src/reporters/merge.ts b/packages/playwright/src/reporters/merge.ts index f2e4ec8eda..442e55f26d 100644 --- a/packages/playwright/src/reporters/merge.ts +++ b/packages/playwright/src/reporters/merge.ts @@ -81,19 +81,37 @@ const commonEvents = new Set(commonEventNames); const commonEventRegex = new RegExp(`${commonEventNames.join('|')}`); function parseCommonEvents(reportJsonl: Buffer): JsonEvent[] { - return reportJsonl.toString().split('\n') + return splitBufferLines(reportJsonl) + .map(line => line.toString('utf8')) .filter(line => commonEventRegex.test(line)) // quick filter .map(line => JSON.parse(line) as JsonEvent) .filter(event => commonEvents.has(event.method)); } function parseTestEvents(reportJsonl: Buffer): JsonEvent[] { - return reportJsonl.toString().split('\n') + return splitBufferLines(reportJsonl) + .map(line => line.toString('utf8')) .filter(line => line.length) .map(line => JSON.parse(line) as JsonEvent) .filter(event => !commonEvents.has(event.method)); } +function splitBufferLines(buffer: Buffer) { + const lines = []; + let start = 0; + while (start < buffer.length) { + // 0x0A is the byte for '\n' + const end = buffer.indexOf(0x0A, start); + if (end === -1) { + lines.push(buffer.slice(start)); + break; + } + lines.push(buffer.slice(start, end)); + start = end + 1; + } + return lines; +} + async function extractAndParseReports(dir: string, shardFiles: string[], internalizer: JsonStringInternalizer, printStatus: StatusCallback) { const shardEvents: { file: string, localPath: string, metadata: BlobReportMetadata, parsedEvents: JsonEvent[] }[] = []; await fs.promises.mkdir(path.join(dir, 'resources'), { recursive: true }); From 15a8ba515826aab095fc48f4cba2a2bb0c2182fa Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Tue, 28 Nov 2023 17:52:16 -0800 Subject: [PATCH 111/491] fix(route): correctly remove expired handlers (#28385) * Check if handler is still in the route list before calling it * Check if the handler is still in the list before removing it after `times` expiration --- .../src/client/browserContext.ts | 5 ++++- packages/playwright-core/src/client/page.ts | 5 ++++- tests/library/browsercontext-route.spec.ts | 19 +++++++++++++++++++ tests/page/page-route.spec.ts | 19 +++++++++++++++++++ 4 files changed, 46 insertions(+), 2 deletions(-) diff --git a/packages/playwright-core/src/client/browserContext.ts b/packages/playwright-core/src/client/browserContext.ts index a74996bbe2..8c6023a703 100644 --- a/packages/playwright-core/src/client/browserContext.ts +++ b/packages/playwright-core/src/client/browserContext.ts @@ -197,8 +197,11 @@ export class BrowserContext extends ChannelOwner for (const routeHandler of routeHandlers) { if (!routeHandler.matches(route.request().url())) continue; + const index = this._routes.indexOf(routeHandler); + if (index === -1) + continue; if (routeHandler.willExpire()) - this._routes.splice(this._routes.indexOf(routeHandler), 1); + this._routes.splice(index, 1); const handled = await routeHandler.handle(route); if (!this._routes.length) this._wrapApiCall(() => this._updateInterceptionPatterns(), true).catch(() => {}); diff --git a/packages/playwright-core/src/client/page.ts b/packages/playwright-core/src/client/page.ts index 0430e3c0bc..ac15fb4ffe 100644 --- a/packages/playwright-core/src/client/page.ts +++ b/packages/playwright-core/src/client/page.ts @@ -177,8 +177,11 @@ export class Page extends ChannelOwner implements api.Page for (const routeHandler of routeHandlers) { if (!routeHandler.matches(route.request().url())) continue; + const index = this._routes.indexOf(routeHandler); + if (index === -1) + continue; if (routeHandler.willExpire()) - this._routes.splice(this._routes.indexOf(routeHandler), 1); + this._routes.splice(index, 1); const handled = await routeHandler.handle(route); if (!this._routes.length) this._wrapApiCall(() => this._updateInterceptionPatterns(), true).catch(() => {}); diff --git a/tests/library/browsercontext-route.spec.ts b/tests/library/browsercontext-route.spec.ts index e600948216..a597cf1157 100644 --- a/tests/library/browsercontext-route.spec.ts +++ b/tests/library/browsercontext-route.spec.ts @@ -208,6 +208,25 @@ it('should support the times parameter with route matching', async ({ context, p expect(intercepted).toHaveLength(1); }); +it('should work if handler with times parameter was removed from another handler', async ({ context, page, server }) => { + const intercepted = []; + const handler = async route => { + intercepted.push('first'); + void route.continue(); + }; + await context.route('**/*', handler, { times: 1 }); + await context.route('**/*', async route => { + intercepted.push('second'); + await context.unroute('**/*', handler); + await route.fallback(); + }); + await page.goto(server.EMPTY_PAGE); + expect(intercepted).toEqual(['second']); + intercepted.length = 0; + await page.goto(server.EMPTY_PAGE); + expect(intercepted).toEqual(['second']); +}); + it('should support async handler w/ times', async ({ context, page, server }) => { await context.route('**/empty.html', async route => { await new Promise(f => setTimeout(f, 100)); diff --git a/tests/page/page-route.spec.ts b/tests/page/page-route.spec.ts index 8c0a1bc80c..b33054b8bb 100644 --- a/tests/page/page-route.spec.ts +++ b/tests/page/page-route.spec.ts @@ -902,6 +902,25 @@ it('should support the times parameter with route matching', async ({ page, serv expect(intercepted).toHaveLength(1); }); +it('should work if handler with times parameter was removed from another handler', async ({ page, server }) => { + const intercepted = []; + const handler = async route => { + intercepted.push('first'); + void route.continue(); + }; + await page.route('**/*', handler, { times: 1 }); + await page.route('**/*', async route => { + intercepted.push('second'); + await page.unroute('**/*', handler); + await route.fallback(); + }); + await page.goto(server.EMPTY_PAGE); + expect(intercepted).toEqual(['second']); + intercepted.length = 0; + await page.goto(server.EMPTY_PAGE); + expect(intercepted).toEqual(['second']); +}); + it('should support async handler w/ times', async ({ page, server }) => { await page.route('**/empty.html', async route => { await new Promise(f => setTimeout(f, 100)); From 1901a1a1555536e26beb74c77695a866b501f27f Mon Sep 17 00:00:00 2001 From: Playwright Service <89237858+playwrightmachine@users.noreply.github.com> Date: Tue, 28 Nov 2023 19:45:12 -0800 Subject: [PATCH 112/491] feat(chromium-tip-of-tree): roll to r1172 (#28374) 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 f1f2c137e3..6670597039 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -15,9 +15,9 @@ }, { "name": "chromium-tip-of-tree", - "revision": "1171", + "revision": "1172", "installByDefault": false, - "browserVersion": "121.0.6143.0" + "browserVersion": "121.0.6153.0" }, { "name": "firefox", From c137b23a6c977a35384bb254cb7d2fe662d917f8 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Wed, 29 Nov 2023 11:27:19 -0800 Subject: [PATCH 113/491] fix(trace-viewer): scroll over multiple pages (#28316) Fixes https://github.com/microsoft/playwright/issues/28208 --- packages/trace-viewer/src/ui/timeline.css | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/trace-viewer/src/ui/timeline.css b/packages/trace-viewer/src/ui/timeline.css index 216ba94b63..dfc9e23682 100644 --- a/packages/trace-viewer/src/ui/timeline.css +++ b/packages/trace-viewer/src/ui/timeline.css @@ -68,6 +68,7 @@ bottom: 0; left: 0; right: 0; + pointer-events: none; } .timeline-bar { From 35c2633013a2b782bc2df35913999057106fa4d2 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Wed, 29 Nov 2023 12:16:10 -0800 Subject: [PATCH 114/491] docs: state that grid support is experimental (#28406) --- docs/src/selenium-grid.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/selenium-grid.md b/docs/src/selenium-grid.md index c7b4a1b4d3..636bfb6d43 100644 --- a/docs/src/selenium-grid.md +++ b/docs/src/selenium-grid.md @@ -1,11 +1,11 @@ --- id: selenium-grid -title: "Selenium Grid" +title: "Selenium Grid (experimental)" --- ## Introduction -Playwright can connect to [Selenium Grid Hub](https://www.selenium.dev/documentation/grid/) that runs Selenium 4 to launch **Google Chrome** or **Microsoft Edge** browser, instead of running browser on the local machine. +Playwright can connect to [Selenium Grid Hub](https://www.selenium.dev/documentation/grid/) that runs Selenium 4 to launch **Google Chrome** or **Microsoft Edge** browser, instead of running browser on the local machine. Note this feature is **experimental** and is prioritized accordingly. :::warning There is a risk of Playwright integration with Selenium Grid Hub breaking in the future. Make sure you weight risks against benefits before using it. From 92a0d24069c2f43ecec89ac9528ff47577273b9d Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Wed, 29 Nov 2023 17:28:17 -0800 Subject: [PATCH 115/491] fix: restore timeout error name (#28408) Fixes https://github.com/microsoft/playwright/issues/28404 --- packages/playwright-core/src/client/errors.ts | 8 +++++++- tests/page/expect-timeout.spec.ts | 5 +++++ tests/playwright-test/reporter.spec.ts | 2 +- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/packages/playwright-core/src/client/errors.ts b/packages/playwright-core/src/client/errors.ts index 06446ba3b6..4d8a08c4ac 100644 --- a/packages/playwright-core/src/client/errors.ts +++ b/packages/playwright-core/src/client/errors.ts @@ -18,11 +18,17 @@ import type { SerializedError } from '@protocol/channels'; import { isError } from '../utils'; import { parseSerializedValue, serializeValue } from '../protocol/serializers'; -export class TimeoutError extends Error {} +export class TimeoutError extends Error { + constructor(message: string) { + super(message); + this.name = 'TimeoutError'; + } +} export class TargetClosedError extends Error { constructor(cause?: string) { super(cause || 'Target page, context or browser has been closed'); + this.name = 'TargetClosedError'; } } diff --git a/tests/page/expect-timeout.spec.ts b/tests/page/expect-timeout.spec.ts index 1dd83daca4..16b3210caa 100644 --- a/tests/page/expect-timeout.spec.ts +++ b/tests/page/expect-timeout.spec.ts @@ -50,3 +50,8 @@ test('should not print timed out error message when page closes', async ({ page expect(stripAnsi(error.message)).toContain('expect.toHaveText with timeout 100000ms'); expect(stripAnsi(error.message)).not.toContain('Timed out'); }); + +test('should have timeout error name', async ({ page }) => { + const error = await page.waitForSelector('#not-found', { timeout: 1 }).catch(e => e); + expect(error.name).toBe('TimeoutError'); +}); diff --git a/tests/playwright-test/reporter.spec.ts b/tests/playwright-test/reporter.spec.ts index d98b580c2e..fa303bf7b8 100644 --- a/tests/playwright-test/reporter.spec.ts +++ b/tests/playwright-test/reporter.spec.ts @@ -454,7 +454,7 @@ for (const useIntermediateMergeReport of [false, true] as const) { `begin {\"title\":\"page.setContent\",\"category\":\"pw:api\"}`, `end {\"title\":\"page.setContent\",\"category\":\"pw:api\"}`, `begin {\"title\":\"page.click(input)\",\"category\":\"pw:api\"}`, - `end {\"title\":\"page.click(input)\",\"category\":\"pw:api\",\"error\":{\"message\":\"Error: page.click: Timeout 1ms exceeded.\\nCall log:\\n \\u001b[2m- waiting for locator('input')\\u001b[22m\\n\",\"stack\":\"\",\"location\":\"\",\"snippet\":\"\"}}`, + `end {\"title\":\"page.click(input)\",\"category\":\"pw:api\",\"error\":{\"message\":\"TimeoutError: page.click: Timeout 1ms exceeded.\\nCall log:\\n \\u001b[2m- waiting for locator('input')\\u001b[22m\\n\",\"stack\":\"\",\"location\":\"\",\"snippet\":\"\"}}`, `begin {\"title\":\"After Hooks\",\"category\":\"hook\"}`, `begin {\"title\":\"fixture: page\",\"category\":\"fixture\"}`, `end {\"title\":\"fixture: page\",\"category\":\"fixture\"}`, From b06f9ab055057db5b1f09c8cb9a8ce45d987f085 Mon Sep 17 00:00:00 2001 From: Playwright Service <89237858+playwrightmachine@users.noreply.github.com> Date: Thu, 30 Nov 2023 05:41:23 -0800 Subject: [PATCH 116/491] feat(chromium): roll to r1093 (#28421) --- README.md | 4 +- packages/playwright-core/browsers.json | 8 +- .../src/server/deviceDescriptorsSource.json | 96 +++++++++---------- 3 files changed, 54 insertions(+), 54 deletions(-) diff --git a/README.md b/README.md index 7983c93952..10a44f155c 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-120.0.6099.35-blue.svg?logo=google-chrome)](https://www.chromium.org/Home) [![Firefox version](https://img.shields.io/badge/firefox-119.0-blue.svg?logo=firefoxbrowser)](https://www.mozilla.org/en-US/firefox/new/) [![WebKit version](https://img.shields.io/badge/webkit-17.4-blue.svg?logo=safari)](https://webkit.org/) +[![npm version](https://img.shields.io/npm/v/playwright.svg)](https://www.npmjs.com/package/playwright) [![Chromium version](https://img.shields.io/badge/chromium-120.0.6099.56-blue.svg?logo=google-chrome)](https://www.chromium.org/Home) [![Firefox version](https://img.shields.io/badge/firefox-119.0-blue.svg?logo=firefoxbrowser)](https://www.mozilla.org/en-US/firefox/new/) [![WebKit version](https://img.shields.io/badge/webkit-17.4-blue.svg?logo=safari)](https://webkit.org/) ## [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 120.0.6099.35 | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| Chromium 120.0.6099.56 | :white_check_mark: | :white_check_mark: | :white_check_mark: | | WebKit 17.4 | :white_check_mark: | :white_check_mark: | :white_check_mark: | | Firefox 119.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 6670597039..70747f821e 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -3,15 +3,15 @@ "browsers": [ { "name": "chromium", - "revision": "1092", + "revision": "1093", "installByDefault": true, - "browserVersion": "120.0.6099.35" + "browserVersion": "120.0.6099.56" }, { "name": "chromium-with-symbols", - "revision": "1092", + "revision": "1093", "installByDefault": false, - "browserVersion": "120.0.6099.35" + "browserVersion": "120.0.6099.56" }, { "name": "chromium-tip-of-tree", diff --git a/packages/playwright-core/src/server/deviceDescriptorsSource.json b/packages/playwright-core/src/server/deviceDescriptorsSource.json index cfadb9b4d0..c39e027bf6 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/120.0.6099.35 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.56 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/120.0.6099.35 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.56 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/120.0.6099.35 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.56 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/120.0.6099.35 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.56 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/120.0.6099.35 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.56 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/120.0.6099.35 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.56 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/120.0.6099.35 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.56 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/120.0.6099.35 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.56 Safari/537.36", "viewport": { "width": 1138, "height": 712 @@ -978,7 +978,7 @@ "defaultBrowserType": "webkit" }, "LG Optimus L70": { - "userAgent": "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/120.0.6099.35 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/120.0.6099.56 Mobile Safari/537.36", "viewport": { "width": 384, "height": 640 @@ -989,7 +989,7 @@ "defaultBrowserType": "chromium" }, "LG Optimus L70 landscape": { - "userAgent": "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/120.0.6099.35 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/120.0.6099.56 Mobile Safari/537.36", "viewport": { "width": 640, "height": 384 @@ -1000,7 +1000,7 @@ "defaultBrowserType": "chromium" }, "Microsoft Lumia 550": { - "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.35 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/120.0.6099.56 Mobile Safari/537.36 Edge/14.14263", "viewport": { "width": 640, "height": 360 @@ -1011,7 +1011,7 @@ "defaultBrowserType": "chromium" }, "Microsoft Lumia 550 landscape": { - "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.35 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/120.0.6099.56 Mobile Safari/537.36 Edge/14.14263", "viewport": { "width": 360, "height": 640 @@ -1022,7 +1022,7 @@ "defaultBrowserType": "chromium" }, "Microsoft Lumia 950": { - "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.35 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/120.0.6099.56 Mobile Safari/537.36 Edge/14.14263", "viewport": { "width": 360, "height": 640 @@ -1033,7 +1033,7 @@ "defaultBrowserType": "chromium" }, "Microsoft Lumia 950 landscape": { - "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.35 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/120.0.6099.56 Mobile Safari/537.36 Edge/14.14263", "viewport": { "width": 640, "height": 360 @@ -1044,7 +1044,7 @@ "defaultBrowserType": "chromium" }, "Nexus 10": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.35 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.56 Safari/537.36", "viewport": { "width": 800, "height": 1280 @@ -1055,7 +1055,7 @@ "defaultBrowserType": "chromium" }, "Nexus 10 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.35 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.56 Safari/537.36", "viewport": { "width": 1280, "height": 800 @@ -1066,7 +1066,7 @@ "defaultBrowserType": "chromium" }, "Nexus 4": { - "userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.35 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.56 Mobile Safari/537.36", "viewport": { "width": 384, "height": 640 @@ -1077,7 +1077,7 @@ "defaultBrowserType": "chromium" }, "Nexus 4 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.35 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.56 Mobile Safari/537.36", "viewport": { "width": 640, "height": 384 @@ -1088,7 +1088,7 @@ "defaultBrowserType": "chromium" }, "Nexus 5": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.35 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.56 Mobile Safari/537.36", "viewport": { "width": 360, "height": 640 @@ -1099,7 +1099,7 @@ "defaultBrowserType": "chromium" }, "Nexus 5 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.35 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.56 Mobile Safari/537.36", "viewport": { "width": 640, "height": 360 @@ -1110,7 +1110,7 @@ "defaultBrowserType": "chromium" }, "Nexus 5X": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.35 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/120.0.6099.56 Mobile Safari/537.36", "viewport": { "width": 412, "height": 732 @@ -1121,7 +1121,7 @@ "defaultBrowserType": "chromium" }, "Nexus 5X landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.35 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/120.0.6099.56 Mobile Safari/537.36", "viewport": { "width": 732, "height": 412 @@ -1132,7 +1132,7 @@ "defaultBrowserType": "chromium" }, "Nexus 6": { - "userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.35 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.56 Mobile Safari/537.36", "viewport": { "width": 412, "height": 732 @@ -1143,7 +1143,7 @@ "defaultBrowserType": "chromium" }, "Nexus 6 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.35 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.56 Mobile Safari/537.36", "viewport": { "width": 732, "height": 412 @@ -1154,7 +1154,7 @@ "defaultBrowserType": "chromium" }, "Nexus 6P": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.35 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/120.0.6099.56 Mobile Safari/537.36", "viewport": { "width": 412, "height": 732 @@ -1165,7 +1165,7 @@ "defaultBrowserType": "chromium" }, "Nexus 6P landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.35 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/120.0.6099.56 Mobile Safari/537.36", "viewport": { "width": 732, "height": 412 @@ -1176,7 +1176,7 @@ "defaultBrowserType": "chromium" }, "Nexus 7": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.35 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.56 Safari/537.36", "viewport": { "width": 600, "height": 960 @@ -1187,7 +1187,7 @@ "defaultBrowserType": "chromium" }, "Nexus 7 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.35 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.56 Safari/537.36", "viewport": { "width": 960, "height": 600 @@ -1242,7 +1242,7 @@ "defaultBrowserType": "webkit" }, "Pixel 2": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.35 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/120.0.6099.56 Mobile Safari/537.36", "viewport": { "width": 411, "height": 731 @@ -1253,7 +1253,7 @@ "defaultBrowserType": "chromium" }, "Pixel 2 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.35 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/120.0.6099.56 Mobile Safari/537.36", "viewport": { "width": 731, "height": 411 @@ -1264,7 +1264,7 @@ "defaultBrowserType": "chromium" }, "Pixel 2 XL": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.35 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/120.0.6099.56 Mobile Safari/537.36", "viewport": { "width": 411, "height": 823 @@ -1275,7 +1275,7 @@ "defaultBrowserType": "chromium" }, "Pixel 2 XL landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.35 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/120.0.6099.56 Mobile Safari/537.36", "viewport": { "width": 823, "height": 411 @@ -1286,7 +1286,7 @@ "defaultBrowserType": "chromium" }, "Pixel 3": { - "userAgent": "Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.35 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/120.0.6099.56 Mobile Safari/537.36", "viewport": { "width": 393, "height": 786 @@ -1297,7 +1297,7 @@ "defaultBrowserType": "chromium" }, "Pixel 3 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.35 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/120.0.6099.56 Mobile Safari/537.36", "viewport": { "width": 786, "height": 393 @@ -1308,7 +1308,7 @@ "defaultBrowserType": "chromium" }, "Pixel 4": { - "userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.35 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.56 Mobile Safari/537.36", "viewport": { "width": 353, "height": 745 @@ -1319,7 +1319,7 @@ "defaultBrowserType": "chromium" }, "Pixel 4 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.35 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.56 Mobile Safari/537.36", "viewport": { "width": 745, "height": 353 @@ -1330,7 +1330,7 @@ "defaultBrowserType": "chromium" }, "Pixel 4a (5G)": { - "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.35 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.56 Mobile Safari/537.36", "screen": { "width": 412, "height": 892 @@ -1345,7 +1345,7 @@ "defaultBrowserType": "chromium" }, "Pixel 4a (5G) landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.35 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.56 Mobile Safari/537.36", "screen": { "height": 892, "width": 412 @@ -1360,7 +1360,7 @@ "defaultBrowserType": "chromium" }, "Pixel 5": { - "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.35 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.56 Mobile Safari/537.36", "screen": { "width": 393, "height": 851 @@ -1375,7 +1375,7 @@ "defaultBrowserType": "chromium" }, "Pixel 5 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.35 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.56 Mobile Safari/537.36", "screen": { "width": 851, "height": 393 @@ -1390,7 +1390,7 @@ "defaultBrowserType": "chromium" }, "Pixel 7": { - "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.35 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.56 Mobile Safari/537.36", "screen": { "width": 412, "height": 915 @@ -1405,7 +1405,7 @@ "defaultBrowserType": "chromium" }, "Pixel 7 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.35 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.56 Mobile Safari/537.36", "screen": { "width": 915, "height": 412 @@ -1420,7 +1420,7 @@ "defaultBrowserType": "chromium" }, "Moto G4": { - "userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.35 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.56 Mobile Safari/537.36", "viewport": { "width": 360, "height": 640 @@ -1431,7 +1431,7 @@ "defaultBrowserType": "chromium" }, "Moto G4 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.35 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.56 Mobile Safari/537.36", "viewport": { "width": 640, "height": 360 @@ -1442,7 +1442,7 @@ "defaultBrowserType": "chromium" }, "Desktop Chrome HiDPI": { - "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.35 Safari/537.36", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.56 Safari/537.36", "screen": { "width": 1792, "height": 1120 @@ -1457,7 +1457,7 @@ "defaultBrowserType": "chromium" }, "Desktop Edge HiDPI": { - "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.35 Safari/537.36 Edg/120.0.6099.35", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.56 Safari/537.36 Edg/120.0.6099.56", "screen": { "width": 1792, "height": 1120 @@ -1502,7 +1502,7 @@ "defaultBrowserType": "webkit" }, "Desktop Chrome": { - "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.35 Safari/537.36", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.56 Safari/537.36", "screen": { "width": 1920, "height": 1080 @@ -1517,7 +1517,7 @@ "defaultBrowserType": "chromium" }, "Desktop Edge": { - "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.35 Safari/537.36 Edg/120.0.6099.35", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.56 Safari/537.36 Edg/120.0.6099.56", "screen": { "width": 1920, "height": 1080 From f21455017eec051f7e6552e10d735d84e3ecbf6f Mon Sep 17 00:00:00 2001 From: Playwright Service <89237858+playwrightmachine@users.noreply.github.com> Date: Thu, 30 Nov 2023 05:41:58 -0800 Subject: [PATCH 117/491] feat(chromium-tip-of-tree): roll to r1173 (#28422) --- 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 70747f821e..3265246acd 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -15,9 +15,9 @@ }, { "name": "chromium-tip-of-tree", - "revision": "1172", + "revision": "1173", "installByDefault": false, - "browserVersion": "121.0.6153.0" + "browserVersion": "121.0.6157.0" }, { "name": "firefox", From 8efa8dbc1dc53a53b24bf6936120daf116ec9c45 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Thu, 30 Nov 2023 13:26:03 -0800 Subject: [PATCH 118/491] fix(ui-mode): make UI Mode projects scrollable (#28438) Fixes https://github.com/microsoft/playwright/issues/28393 https://github.com/microsoft/playwright/assets/17984549/5791e422-f4e6-4202-b66c-b77bbb476c04 --- packages/trace-viewer/src/ui/uiModeView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/trace-viewer/src/ui/uiModeView.tsx b/packages/trace-viewer/src/ui/uiModeView.tsx index ca5a911a2a..3f0f9f67c4 100644 --- a/packages/trace-viewer/src/ui/uiModeView.tsx +++ b/packages/trace-viewer/src/ui/uiModeView.tsx @@ -316,7 +316,7 @@ const FiltersView: React.FC<{ Status: {statusLine} Projects: {projectsLine}
- {expanded &&
+ {expanded &&
{[...statusFilters.entries()].map(([status, value]) => { return
From da6a36062ef836b1b8e6351f7d061b0c62b7c689 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Thu, 30 Nov 2023 14:04:42 -0800 Subject: [PATCH 119/491] docs(docker): add note on how to create your own (#28434) https://github.com/microsoft/playwright/issues/28383 --------- Signed-off-by: Max Schmitt Co-authored-by: Dmitry Gozman --- docs/src/docker.md | 18 +++++++++++++----- utils/doclint/cli.js | 1 + 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/docs/src/docker.md b/docs/src/docker.md index de3627f21b..b4af453699 100644 --- a/docs/src/docker.md +++ b/docs/src/docker.md @@ -108,12 +108,12 @@ See our [Continuous Integration guides](./ci.md) for sample configs. See [all available image tags]. Docker images are published automatically by GitHub Actions. We currently publish images with the -following tags (`v1.33.0` in this case is an example:): +following tags: - `:next` - tip-of-tree image version based on Ubuntu 22.04 LTS (Jammy Jellyfish). - `:next-jammy` - tip-of-tree image version based on Ubuntu 22.04 LTS (Jammy Jellyfish). -- `:v1.33.0` - Playwright v1.33.0 release docker image based on Ubuntu 22.04 LTS (Jammy Jellyfish). -- `:v1.33.0-jammy` - Playwright v1.33.0 release docker image based on Ubuntu 22.04 LTS (Jammy Jellyfish). -- `:v1.33.0-focal` - Playwright v1.33.0 release docker image based on Ubuntu 20.04 LTS (Focal Fossa). +- `:v%%VERSION%%` - Playwright v%%VERSION%% release docker image based on Ubuntu 22.04 LTS (Jammy Jellyfish). +- `:v%%VERSION%%-jammy` - Playwright v%%VERSION%% release docker image based on Ubuntu 22.04 LTS (Jammy Jellyfish). +- `:v%%VERSION%%-focal` - Playwright v%%VERSION%% release docker image based on Ubuntu 20.04 LTS (Focal Fossa). - `:sha-XXXXXXX` - docker image for every commit that changed docker files or browsers, marked with a [short sha](https://git-scm.com/book/en/v2/Git-Tools-Revision-Selection#Short-SHA-1) (first 7 digits of the SHA commit). @@ -136,7 +136,15 @@ Browser builds for Firefox and WebKit are built for the [glibc](https://en.wikip ### Build the image -Use [`//utils/docker/build.sh`](https://github.com/microsoft/playwright/blob/main/utils/docker/build.sh) to build the image. +To run Playwright inside Docker, you need to have Node.js, [Playwright browsers](./browsers.md#install-browsers) and [browser system dependencies](./browsers.md#install-system-dependencies) installed. See the following Dockerfile: + +```Dockerfile +FROM node:20-bookworm + +RUN npx -y playwright@%%VERSION%% install --with-deps +``` + +Note: official images published to [Microsoft Artifact Registry] are built using [`//utils/docker/build.sh`](https://github.com/microsoft/playwright/blob/main/utils/docker/build.sh) script. ```txt ./utils/docker/build.sh jammy playwright:localbuild-jammy diff --git a/utils/doclint/cli.js b/utils/doclint/cli.js index ada19fab09..5dccfa65f0 100755 --- a/utils/doclint/cli.js +++ b/utils/doclint/cli.js @@ -199,6 +199,7 @@ async function run() { 'html', 'bash', 'sh', + 'Dockerfile', ]); if (!allowedCodeLangs.has(node.codeLang.split(' ')[0])) throw new Error(`${path.relative(PROJECT_DIR, filePath)} contains code block with invalid code block language ${node.codeLang}`); From 0a7a10d0f69a84c91b41776bf9f9b1d3ccd4a8b9 Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Thu, 30 Nov 2023 17:42:45 -0800 Subject: [PATCH 120/491] feat(vrt): allow providing screenshot style (#28229) --- docs/src/api/class-elementhandle.md | 3 + docs/src/api/class-locator.md | 3 + docs/src/api/class-locatorassertions.md | 6 + docs/src/api/class-page.md | 3 + docs/src/api/class-pageassertions.md | 6 + docs/src/api/params.md | 7 ++ docs/src/test-api/class-testconfig.md | 1 + .../playwright-core/src/protocol/validator.ts | 3 + packages/playwright-core/src/server/frames.ts | 2 +- .../src/server/injected/highlight.ts | 4 +- .../src/server/injected/injectedScript.ts | 2 +- .../src/server/screenshotter.ts | 114 +++++++++--------- packages/playwright-core/types/types.d.ts | 21 ++++ .../src/matchers/toMatchSnapshot.ts | 1 + packages/playwright/types/test.d.ts | 33 +++++ packages/protocol/src/channels.ts | 6 + packages/protocol/src/protocol.yml | 1 + tests/page/page-screenshot.spec.ts | 30 +++++ .../hide-should-work-chromium.png | Bin 0 -> 35985 bytes .../hide-should-work-firefox.png | Bin 0 -> 45962 bytes .../hide-should-work-webkit.png | Bin 0 -> 39880 bytes .../remove-should-work-chromium.png | Bin 0 -> 36201 bytes .../remove-should-work-firefox.png | Bin 0 -> 46454 bytes .../remove-should-work-webkit.png | Bin 0 -> 40063 bytes .../to-have-screenshot.spec.ts | 41 +++++++ 25 files changed, 228 insertions(+), 59 deletions(-) create mode 100644 tests/page/page-screenshot.spec.ts-snapshots/hide-should-work-chromium.png create mode 100644 tests/page/page-screenshot.spec.ts-snapshots/hide-should-work-firefox.png create mode 100644 tests/page/page-screenshot.spec.ts-snapshots/hide-should-work-webkit.png create mode 100644 tests/page/page-screenshot.spec.ts-snapshots/remove-should-work-chromium.png create mode 100644 tests/page/page-screenshot.spec.ts-snapshots/remove-should-work-firefox.png create mode 100644 tests/page/page-screenshot.spec.ts-snapshots/remove-should-work-webkit.png diff --git a/docs/src/api/class-elementhandle.md b/docs/src/api/class-elementhandle.md index 1a7297be31..df73e558d0 100644 --- a/docs/src/api/class-elementhandle.md +++ b/docs/src/api/class-elementhandle.md @@ -749,6 +749,9 @@ Returns the buffer with the captured screenshot. ### option: ElementHandle.screenshot.maskColor = %%-screenshot-option-mask-color-%% * since: v1.34 +### option: ElementHandle.screenshot.style = %%-screenshot-option-style-%% +* since: v1.41 + ## async method: ElementHandle.scrollIntoViewIfNeeded * since: v1.8 diff --git a/docs/src/api/class-locator.md b/docs/src/api/class-locator.md index 5781878f6f..f59e0f09f3 100644 --- a/docs/src/api/class-locator.md +++ b/docs/src/api/class-locator.md @@ -1913,6 +1913,9 @@ Returns the buffer with the captured screenshot. ### option: Locator.screenshot.maskColor = %%-screenshot-option-mask-color-%% * since: v1.34 +### option: Locator.screenshot.style = %%-screenshot-option-style-%% +* since: v1.41 + ## async method: Locator.scrollIntoViewIfNeeded * since: v1.14 diff --git a/docs/src/api/class-locatorassertions.md b/docs/src/api/class-locatorassertions.md index cf78c6ce95..ccca23455d 100644 --- a/docs/src/api/class-locatorassertions.md +++ b/docs/src/api/class-locatorassertions.md @@ -1541,6 +1541,9 @@ Snapshot name. ### option: LocatorAssertions.toHaveScreenshot#1.maskColor = %%-screenshot-option-mask-color-%% * since: v1.35 +### option: LocatorAssertions.toHaveScreenshot#1.style = %%-screenshot-option-style-%% +* since: v1.41 + ### option: LocatorAssertions.toHaveScreenshot#1.omitBackground = %%-screenshot-option-omit-background-%% * since: v1.23 @@ -1587,6 +1590,9 @@ Note that screenshot assertions only work with Playwright test runner. ### option: LocatorAssertions.toHaveScreenshot#2.maskColor = %%-screenshot-option-mask-color-%% * since: v1.35 +### option: LocatorAssertions.toHaveScreenshot#2.style = %%-screenshot-option-style-%% +* since: v1.41 + ### option: LocatorAssertions.toHaveScreenshot#2.omitBackground = %%-screenshot-option-omit-background-%% * since: v1.23 diff --git a/docs/src/api/class-page.md b/docs/src/api/class-page.md index e875547905..807f749c95 100644 --- a/docs/src/api/class-page.md +++ b/docs/src/api/class-page.md @@ -3399,6 +3399,9 @@ Returns the buffer with the captured screenshot. ### option: Page.screenshot.maskColor = %%-screenshot-option-mask-color-%% * since: v1.34 +### option: Page.screenshot.style = %%-screenshot-option-style-%% +* since: v1.41 + ## async method: Page.selectOption * since: v1.8 * discouraged: Use locator-based [`method: Locator.selectOption`] instead. Read more about [locators](../locators.md). diff --git a/docs/src/api/class-pageassertions.md b/docs/src/api/class-pageassertions.md index fcb4f38652..c79ddb50eb 100644 --- a/docs/src/api/class-pageassertions.md +++ b/docs/src/api/class-pageassertions.md @@ -161,6 +161,9 @@ Snapshot name. ### option: PageAssertions.toHaveScreenshot#1.maskColor = %%-screenshot-option-mask-color-%% * since: v1.35 +### option: PageAssertions.toHaveScreenshot#1.style = %%-screenshot-option-style-%% +* since: v1.41 + ### option: PageAssertions.toHaveScreenshot#1.omitBackground = %%-screenshot-option-omit-background-%% * since: v1.23 @@ -212,6 +215,9 @@ Note that screenshot assertions only work with Playwright test runner. ### option: PageAssertions.toHaveScreenshot#2.maskColor = %%-screenshot-option-mask-color-%% * since: v1.35 +### option: PageAssertions.toHaveScreenshot#2.style = %%-screenshot-option-style-%% +* since: v1.41 + ### option: PageAssertions.toHaveScreenshot#2.omitBackground = %%-screenshot-option-omit-background-%% * since: v1.23 diff --git a/docs/src/api/params.md b/docs/src/api/params.md index 293b415c44..441ad4e544 100644 --- a/docs/src/api/params.md +++ b/docs/src/api/params.md @@ -1132,6 +1132,13 @@ Defaults to `"css"`. When set to `"hide"`, screenshot will hide text caret. When set to `"initial"`, text caret behavior will not be changed. Defaults to `"hide"`. +## screenshot-option-style +- `style` + +Stylesheet to apply while making the screenshot. This is where you can hide dynamic elements, make elements invisible +or change their properties to help you creating repeatable screenshots. This stylesheet pierces the Shadow DOM and applies +to the inner frames. + ## screenshot-options-common-list-v1.8 - %%-screenshot-option-animations-%% - %%-screenshot-option-omit-background-%% diff --git a/docs/src/test-api/class-testconfig.md b/docs/src/test-api/class-testconfig.md index 4ecc8e6206..5df9578846 100644 --- a/docs/src/test-api/class-testconfig.md +++ b/docs/src/test-api/class-testconfig.md @@ -47,6 +47,7 @@ export default defineConfig({ - `animations` ?<[ScreenshotAnimations]<"allow"|"disabled">> See [`option: animations`] in [`method: Page.screenshot`]. Defaults to `"disabled"`. - `caret` ?<[ScreenshotCaret]<"hide"|"initial">> See [`option: caret`] in [`method: Page.screenshot`]. Defaults to `"hide"`. - `scale` ?<[ScreenshotScale]<"css"|"device">> See [`option: scale`] in [`method: Page.screenshot`]. Defaults to `"css"`. + - `style` ?<[string]> See [`option: style`] in [`method: Page.screenshot`]. - `toMatchSnapshot` ?<[Object]> Configuration for the [`method: SnapshotAssertions.toMatchSnapshot#1`] method. - `threshold` ?<[float]> an acceptable perceived color difference between the same pixel in compared images, ranging from `0` (strict) and `1` (lax). `"pixelmatch"` comparator computes color difference in [YIQ color space](https://en.wikipedia.org/wiki/YIQ) and defaults `threshold` value to `0.2`. - `maxDiffPixels` ?<[int]> an acceptable amount of pixels that could be different, unset by default. diff --git a/packages/playwright-core/src/protocol/validator.ts b/packages/playwright-core/src/protocol/validator.ts index a9bb86a892..5a6e9363d8 100644 --- a/packages/playwright-core/src/protocol/validator.ts +++ b/packages/playwright-core/src/protocol/validator.ts @@ -1071,6 +1071,7 @@ scheme.PageExpectScreenshotParams = tObject({ selector: tString, }))), maskColor: tOptional(tString), + style: tOptional(tString), })), }); scheme.PageExpectScreenshotResult = tObject({ @@ -1095,6 +1096,7 @@ scheme.PageScreenshotParams = tObject({ selector: tString, }))), maskColor: tOptional(tString), + style: tOptional(tString), }); scheme.PageScreenshotResult = tObject({ binary: tBinary, @@ -1896,6 +1898,7 @@ scheme.ElementHandleScreenshotParams = tObject({ selector: tString, }))), maskColor: tOptional(tString), + style: tOptional(tString), }); scheme.ElementHandleScreenshotResult = tObject({ binary: tBinary, diff --git a/packages/playwright-core/src/server/frames.ts b/packages/playwright-core/src/server/frames.ts index f46484c579..68430aaeb3 100644 --- a/packages/playwright-core/src/server/frames.ts +++ b/packages/playwright-core/src/server/frames.ts @@ -847,7 +847,7 @@ export class Frame extends SdkObject { return result; } - async maskSelectors(selectors: ParsedSelector[], color?: string): Promise { + async maskSelectors(selectors: ParsedSelector[], color: string): Promise { const context = await this._utilityContext(); const injectedScript = await context.injectedScript(); await injectedScript.evaluate((injected, { parsed, color }) => { diff --git a/packages/playwright-core/src/server/injected/highlight.ts b/packages/playwright-core/src/server/injected/highlight.ts index 66e05491bc..3052846104 100644 --- a/packages/playwright-core/src/server/injected/highlight.ts +++ b/packages/playwright-core/src/server/injected/highlight.ts @@ -118,8 +118,8 @@ export class Highlight { this._innerUpdateHighlight(elements, options); } - maskElements(elements: Element[], color?: string) { - this._innerUpdateHighlight(elements, { color: color ? color : '#F0F' }); + maskElements(elements: Element[], color: string) { + this._innerUpdateHighlight(elements, { color: color }); } private _innerUpdateHighlight(elements: Element[], options: HighlightOptions) { diff --git a/packages/playwright-core/src/server/injected/injectedScript.ts b/packages/playwright-core/src/server/injected/injectedScript.ts index 1c87f8bf42..3af647e082 100644 --- a/packages/playwright-core/src/server/injected/injectedScript.ts +++ b/packages/playwright-core/src/server/injected/injectedScript.ts @@ -1125,7 +1125,7 @@ export class InjectedScript { return error; } - maskSelectors(selectors: ParsedSelector[], color?: string) { + maskSelectors(selectors: ParsedSelector[], color: string) { if (this._highlight) this.hideHighlight(); this._highlight = new Highlight(this); diff --git a/packages/playwright-core/src/server/screenshotter.ts b/packages/playwright-core/src/server/screenshotter.ts index 464d95bdc0..d6103786b6 100644 --- a/packages/playwright-core/src/server/screenshotter.ts +++ b/packages/playwright-core/src/server/screenshotter.ts @@ -28,24 +28,25 @@ import { MultiMap } from '../utils/multimap'; declare global { interface Window { - __cleanupScreenshot?: () => void; + __pwCleanupScreenshot?: () => void; } } export type ScreenshotOptions = { - type?: 'png' | 'jpeg', - quality?: number, - omitBackground?: boolean, - animations?: 'disabled' | 'allow', - mask?: { frame: Frame, selector: string}[], - maskColor?: string, - fullPage?: boolean, - clip?: Rect, - scale?: 'css' | 'device', - caret?: 'hide' | 'initial', + type?: 'png' | 'jpeg'; + quality?: number; + omitBackground?: boolean; + animations?: 'disabled' | 'allow'; + mask?: { frame: Frame, selector: string}[]; + maskColor?: string; + fullPage?: boolean; + clip?: Rect; + scale?: 'css' | 'device'; + caret?: 'hide' | 'initial'; + style?: string; }; -function inPagePrepareForScreenshots(hideCaret: boolean, disableAnimations: boolean) { +function inPagePrepareForScreenshots(screenshotStyle: string, disableAnimations: boolean) { const collectRoots = (root: Document | ShadowRoot, roots: (Document|ShadowRoot)[] = []): (Document|ShadowRoot)[] => { roots.push(root); const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT); @@ -58,29 +59,23 @@ function inPagePrepareForScreenshots(hideCaret: boolean, disableAnimations: bool return roots; }; - let documentRoots: (Document|ShadowRoot)[] | undefined; - const memoizedRoots = () => documentRoots ??= collectRoots(document); - - const styleTags: Element[] = []; - if (hideCaret) { - for (const root of memoizedRoots()) { - const styleTag = document.createElement('style'); - styleTag.textContent = ` - *:not(#playwright-aaaaaaaaaa.playwright-bbbbbbbbbbb.playwright-cccccccccc.playwright-dddddddddd.playwright-eeeeeeeee) { - caret-color: transparent !important; - } - `; - if (root === document) - document.documentElement.append(styleTag); - else - root.append(styleTag); - styleTags.push(styleTag); - } - } - const infiniteAnimationsToResume: Set = new Set(); + const roots = collectRoots(document); const cleanupCallbacks: (() => void)[] = []; + for (const root of roots) { + const styleTag = document.createElement('style'); + styleTag.textContent = screenshotStyle; + if (root === document) + document.documentElement.append(styleTag); + else + root.append(styleTag); + cleanupCallbacks.push(() => { + styleTag.remove(); + }); + + } if (disableAnimations) { + const infiniteAnimationsToResume: Set = new Set(); const handleAnimations = (root: Document|ShadowRoot): void => { for (const animation of root.getAnimations()) { if (!animation.effect || animation.playbackRate === 0 || infiniteAnimationsToResume.has(animation)) @@ -106,7 +101,7 @@ function inPagePrepareForScreenshots(hideCaret: boolean, disableAnimations: bool } } }; - for (const root of memoizedRoots()) { + for (const root of roots) { const handleRootAnimations: (() => void) = handleAnimations.bind(null, root); handleRootAnimations(); root.addEventListener('transitionrun', handleRootAnimations); @@ -116,23 +111,22 @@ function inPagePrepareForScreenshots(hideCaret: boolean, disableAnimations: bool root.removeEventListener('animationstart', handleRootAnimations); }); } + cleanupCallbacks.push(() => { + for (const animation of infiniteAnimationsToResume) { + try { + animation.play(); + } catch (e) { + // animation.play() should never throw, but + // we'd like to be on the safe side. + } + } + }); } - window.__cleanupScreenshot = () => { - for (const styleTag of styleTags) - styleTag.remove(); - - for (const animation of infiniteAnimationsToResume) { - try { - animation.play(); - } catch (e) { - // animation.play() should never throw, but - // we'd like to be on the safe side. - } - } + window.__pwCleanupScreenshot = () => { for (const cleanupCallback of cleanupCallbacks) cleanupCallback(); - delete window.__cleanupScreenshot; + delete window.__pwCleanupScreenshot; }; } @@ -178,7 +172,7 @@ export class Screenshotter { return this._queue.postTask(async () => { progress.log('taking page screenshot'); const { viewportSize } = await this._originalViewportSize(progress); - await this._preparePageForScreenshot(progress, options.caret !== 'initial', options.animations === 'disabled'); + await this._preparePageForScreenshot(progress, screenshotStyle(options), options.animations === 'disabled'); progress.throwIfAborted(); // Avoid restoring after failure - should be done by cleanup. if (options.fullPage) { @@ -207,7 +201,7 @@ export class Screenshotter { progress.log('taking element screenshot'); const { viewportSize } = await this._originalViewportSize(progress); - await this._preparePageForScreenshot(progress, options.caret !== 'initial', options.animations === 'disabled'); + await this._preparePageForScreenshot(progress, screenshotStyle(options), options.animations === 'disabled'); progress.throwIfAborted(); // Do not do extra work. await handle._waitAndScrollIntoViewIfNeeded(progress, true /* waitForVisible */); @@ -231,14 +225,11 @@ export class Screenshotter { }); } - async _preparePageForScreenshot(progress: Progress, hideCaret: boolean, disableAnimations: boolean) { - if (!hideCaret && !disableAnimations) - return; - + async _preparePageForScreenshot(progress: Progress, screenshotStyle: string, disableAnimations: boolean) { if (disableAnimations) progress.log(' disabled all CSS animations'); await Promise.all(this._page.frames().map(async frame => { - await frame.nonStallingEvaluateInExistingContext('(' + inPagePrepareForScreenshots.toString() + `)(${hideCaret}, ${disableAnimations})`, false, 'utility').catch(() => {}); + await frame.nonStallingEvaluateInExistingContext('(' + inPagePrepareForScreenshots.toString() + `)(${JSON.stringify(screenshotStyle)}, ${disableAnimations})`, false, 'utility').catch(() => {}); })); if (!process.env.PW_TEST_SCREENSHOT_NO_FONTS_READY) { progress.log('waiting for fonts to load...'); @@ -252,7 +243,7 @@ export class Screenshotter { async _restorePageAfterScreenshot() { await Promise.all(this._page.frames().map(async frame => { - frame.nonStallingEvaluateInExistingContext('window.__cleanupScreenshot && window.__cleanupScreenshot()', false, 'utility').catch(() => {}); + frame.nonStallingEvaluateInExistingContext('window.__pwCleanupScreenshot && window.__pwCleanupScreenshot()', false, 'utility').catch(() => {}); })); } @@ -276,7 +267,7 @@ export class Screenshotter { progress.throwIfAborted(); // Avoid extra work. await Promise.all([...framesToParsedSelectors.keys()].map(async frame => { - await frame.maskSelectors(framesToParsedSelectors.get(frame), options.maskColor); + await frame.maskSelectors(framesToParsedSelectors.get(frame), options.maskColor || '#F0F'); })); progress.cleanupWhenAborted(cleanup); return cleanup; @@ -368,3 +359,16 @@ export function validateScreenshotOptions(options: ScreenshotOptions): 'png' | ' } return format; } + +function screenshotStyle(options: ScreenshotOptions): string { + const parts: string[] = []; + if (options.caret !== 'initial') { + parts.push(` + *:not(#playwright-aaaaaaaaaa.playwright-bbbbbbbbbbb.playwright-cccccccccc.playwright-dddddddddd.playwright-eeeeeeeee) { + caret-color: transparent !important; + }`); + } + if (options.style) + parts.push(options.style); + return parts.join('\n'); +} diff --git a/packages/playwright-core/types/types.d.ts b/packages/playwright-core/types/types.d.ts index 51979c06cd..31dc4798ed 100644 --- a/packages/playwright-core/types/types.d.ts +++ b/packages/playwright-core/types/types.d.ts @@ -9967,6 +9967,13 @@ export interface ElementHandle extends JSHandle { */ scale?: "css"|"device"; + /** + * Stylesheet to apply while making the screenshot. This is where you can hide dynamic elements, make elements + * invisible or change their properties to help you creating repeatable screenshots. This stylesheet pierces the + * Shadow DOM and applies to the inner frames. + */ + style?: string; + /** * Maximum time in milliseconds. Defaults to `0` - no timeout. The default value can be changed via `actionTimeout` * option in the config, or by using the @@ -20037,6 +20044,13 @@ export interface LocatorScreenshotOptions { */ scale?: "css"|"device"; + /** + * Stylesheet to apply while making the screenshot. This is where you can hide dynamic elements, make elements + * invisible or change their properties to help you creating repeatable screenshots. This stylesheet pierces the + * Shadow DOM and applies to the inner frames. + */ + style?: string; + /** * Maximum time in milliseconds. Defaults to `0` - no timeout. The default value can be changed via `actionTimeout` * option in the config, or by using the @@ -20230,6 +20244,13 @@ export interface PageScreenshotOptions { */ scale?: "css"|"device"; + /** + * Stylesheet to apply while making the screenshot. This is where you can hide dynamic elements, make elements + * invisible or change their properties to help you creating repeatable screenshots. This stylesheet pierces the + * Shadow DOM and applies to the inner frames. + */ + style?: string; + /** * Maximum time in milliseconds. Defaults to `0` - no timeout. The default value can be changed via `actionTimeout` * option in the config, or by using the diff --git a/packages/playwright/src/matchers/toMatchSnapshot.ts b/packages/playwright/src/matchers/toMatchSnapshot.ts index bddc6d4178..0140abf899 100644 --- a/packages/playwright/src/matchers/toMatchSnapshot.ts +++ b/packages/playwright/src/matchers/toMatchSnapshot.ts @@ -356,6 +356,7 @@ export async function toHaveScreenshot( animations: config?.animations ?? 'disabled', scale: config?.scale ?? 'css', caret: config?.caret ?? 'hide', + style: config?.style ?? '', ...helper.allOptions, mask: (helper.allOptions.mask || []) as LocatorEx[], maskColor: helper.allOptions.maskColor, diff --git a/packages/playwright/types/test.d.ts b/packages/playwright/types/test.d.ts index d8a7d5b8bd..0c7f8d37a3 100644 --- a/packages/playwright/types/test.d.ts +++ b/packages/playwright/types/test.d.ts @@ -662,6 +662,11 @@ interface TestConfig { * to `"css"`. */ scale?: "css"|"device"; + + /** + * See `style` in [page.screenshot([options])](https://playwright.dev/docs/api/class-page#page-screenshot). + */ + style?: string; }; /** @@ -5951,6 +5956,13 @@ interface LocatorAssertions { */ scale?: "css"|"device"; + /** + * Stylesheet to apply while making the screenshot. This is where you can hide dynamic elements, make elements + * invisible or change their properties to help you creating repeatable screenshots. This stylesheet pierces the + * Shadow DOM and applies to the inner frames. + */ + style?: string; + /** * An acceptable perceived color difference in the [YIQ color space](https://en.wikipedia.org/wiki/YIQ) between the * same pixel in compared images, between zero (strict) and one (lax), default is configurable with @@ -6034,6 +6046,13 @@ interface LocatorAssertions { */ scale?: "css"|"device"; + /** + * Stylesheet to apply while making the screenshot. This is where you can hide dynamic elements, make elements + * invisible or change their properties to help you creating repeatable screenshots. This stylesheet pierces the + * Shadow DOM and applies to the inner frames. + */ + style?: string; + /** * An acceptable perceived color difference in the [YIQ color space](https://en.wikipedia.org/wiki/YIQ) between the * same pixel in compared images, between zero (strict) and one (lax), default is configurable with @@ -6298,6 +6317,13 @@ interface PageAssertions { */ scale?: "css"|"device"; + /** + * Stylesheet to apply while making the screenshot. This is where you can hide dynamic elements, make elements + * invisible or change their properties to help you creating repeatable screenshots. This stylesheet pierces the + * Shadow DOM and applies to the inner frames. + */ + style?: string; + /** * An acceptable perceived color difference in the [YIQ color space](https://en.wikipedia.org/wiki/YIQ) between the * same pixel in compared images, between zero (strict) and one (lax), default is configurable with @@ -6411,6 +6437,13 @@ interface PageAssertions { */ scale?: "css"|"device"; + /** + * Stylesheet to apply while making the screenshot. This is where you can hide dynamic elements, make elements + * invisible or change their properties to help you creating repeatable screenshots. This stylesheet pierces the + * Shadow DOM and applies to the inner frames. + */ + style?: string; + /** * An acceptable perceived color difference in the [YIQ color space](https://en.wikipedia.org/wiki/YIQ) between the * same pixel in compared images, between zero (strict) and one (lax), default is configurable with diff --git a/packages/protocol/src/channels.ts b/packages/protocol/src/channels.ts index 68d28335e1..cf2cd36501 100644 --- a/packages/protocol/src/channels.ts +++ b/packages/protocol/src/channels.ts @@ -1944,6 +1944,7 @@ export type PageExpectScreenshotParams = { selector: string, }[], maskColor?: string, + style?: string, }, }; export type PageExpectScreenshotOptions = { @@ -1971,6 +1972,7 @@ export type PageExpectScreenshotOptions = { selector: string, }[], maskColor?: string, + style?: string, }, }; export type PageExpectScreenshotResult = { @@ -1995,6 +1997,7 @@ export type PageScreenshotParams = { selector: string, }[], maskColor?: string, + style?: string, }; export type PageScreenshotOptions = { timeout?: number, @@ -2011,6 +2014,7 @@ export type PageScreenshotOptions = { selector: string, }[], maskColor?: string, + style?: string, }; export type PageScreenshotResult = { binary: Binary, @@ -3355,6 +3359,7 @@ export type ElementHandleScreenshotParams = { selector: string, }[], maskColor?: string, + style?: string, }; export type ElementHandleScreenshotOptions = { timeout?: number, @@ -3369,6 +3374,7 @@ export type ElementHandleScreenshotOptions = { selector: string, }[], maskColor?: string, + style?: string, }; export type ElementHandleScreenshotResult = { binary: Binary, diff --git a/packages/protocol/src/protocol.yml b/packages/protocol/src/protocol.yml index aac64bdc50..7eb7a96a9b 100644 --- a/packages/protocol/src/protocol.yml +++ b/packages/protocol/src/protocol.yml @@ -379,6 +379,7 @@ CommonScreenshotOptions: frame: Frame selector: string maskColor: string? + style: string? LaunchOptions: type: mixin diff --git a/tests/page/page-screenshot.spec.ts b/tests/page/page-screenshot.spec.ts index ecb876cd15..ef7bdd522c 100644 --- a/tests/page/page-screenshot.spec.ts +++ b/tests/page/page-screenshot.spec.ts @@ -543,6 +543,36 @@ it.describe('page screenshot', () => { maskColor: '#00FF00', })).toMatchSnapshot('mask-color-should-work.png'); }); + + it('should hide elements based on attr', async ({ page, server }) => { + await page.setViewportSize({ width: 500, height: 500 }); + await page.goto(server.PREFIX + '/grid.html'); + await page.locator('div').nth(5).evaluate(element => { + element.setAttribute('data-test-screenshot', 'hide'); + }); + expect(await page.screenshot({ + style: `[data-test-screenshot="hide"] { + visibility: hidden; + }` + })).toMatchSnapshot('hide-should-work.png'); + const visibility = await page.locator('div').nth(5).evaluate(element => element.style.visibility); + expect(visibility).toBe(''); + }); + + it('should remove elements based on attr', async ({ page, server }) => { + await page.setViewportSize({ width: 500, height: 500 }); + await page.goto(server.PREFIX + '/grid.html'); + await page.locator('div').nth(5).evaluate(element => { + element.setAttribute('data-test-screenshot', 'remove'); + }); + expect(await page.screenshot({ + style: `[data-test-screenshot="remove"] { + display: none; + }` + })).toMatchSnapshot('remove-should-work.png'); + const display = await page.locator('div').nth(5).evaluate(element => element.style.display); + expect(display).toBe(''); + }); }); }); diff --git a/tests/page/page-screenshot.spec.ts-snapshots/hide-should-work-chromium.png b/tests/page/page-screenshot.spec.ts-snapshots/hide-should-work-chromium.png new file mode 100644 index 0000000000000000000000000000000000000000..dd0549a4114a2f0b7c9957fcf72429d1f2f49f11 GIT binary patch literal 35985 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 K$Ukox@IL^(FIC+D literal 0 HcmV?d00001 diff --git a/tests/page/page-screenshot.spec.ts-snapshots/hide-should-work-firefox.png b/tests/page/page-screenshot.spec.ts-snapshots/hide-should-work-firefox.png new file mode 100644 index 0000000000000000000000000000000000000000..7af4f1af7002a3b0596c683b54a0ce3d678a36a4 GIT binary patch literal 45962 zcmeFZXIN8ByEaUfCP7p{fq;mJf;5p9S_AQUqNYb*h{F^E|fz1Kjo@$VCkj|l1(NUiTsYP*O%RCNqpOE%ipQio||z0 zqNO46^{_>OtF&(Jb~PH()t-|D>fq`gv_F`JRI`0D%aCf#Rt~y{1XsRn)99 z`%(JX`m2=}P$-VFqY;#pKG!|eKIKs;3Q5x;St)&Vq}8%sQz+UQ(s}Yx`nV{mWkgXZ z8f(!7Kq-Be%+*RiQ7Ha`pxG?~c|KUMI*Y&~iofxhfdP*+(|vpi9ueAf)$Cdz&s$Ah z`*ZM!4;?jT*8_Rtv|H^2;1TcIYAiGYdCHAmJ6(iFe8uCwabVGtMs+4kM1CjN>m0|h z=m$pnoJ_=|`|dg%tXR5-dhMJH#H2?^7mnj#DD=J>gR2R|QWv%e;&2GA#)Q zhNbhr)v&0YB$efu2EFFjsC~&LgEh!7%(bkcBCQQ9s$Vm83f^XEmx<_C*0I=7woq9d ztiY{UT3x$hNxV*_=x(3zlaeRfPvI2K&(C3UOya=rfs@^kR?NwGjEMZd{oo~}p8-7|6YhK|a{`l89enB+T;9eWEIrx~1!+Nhyx-3^03bim7Tulz-8Nz!h`9XVn zdc;#S`gF=Tb5Y5G{-a`>XCx$a75m&L8+H4;%e^JTq4Mm84uG0vw6$th_ z-<4t|(I%P(gXY&X$IFidKhiWEX^p6=_Z=zT8 z_jfl{yU@JDRXJx(ajCbPo+$cm&=XbbP0tCfmBen=Tq11V@%EPcidu^OEOX0~H|WI) z!P@h`uk!Od;8ilLM6k+*xnON;5A6Om6s-0v@SaS@G#7 zOa^#IoFrOT+W1tp&7=IH5yhxoY5lhoM{L~av(8G7^SpUQG25B-)G9)GVWxTSQBL$Xe93FDite<=Tl#GmfmdyKN!;VcCb!Bg~vMQHwr12PvRnA%x*pW zGTp3dO=1pJb5G|L?C$Djjn)rhie@Y)Eu!q5=!ul8ugBDu4>a}+PMQlx!U?zyDf>=1 zK7<+PySZ?AoM)Wl>bXD+eqI*Zk$mF>?aQ3X9mZe3ew|6Oc{b_5e+BJSkh_GxN$Skv zx^(XgjrnvN-*rz_d-jW1j=0O}Y_5=fBWsIi)KdqHPw7|;+k!vIpB4XgEQoKjRxjjm zMs1&O^A;Z!ibTYjC!#0I7du(giMP1%4B0YooJ)spuPATR>}<^2)4N{kA67WafUr?s z6+=;3e-Gb|rtlff)fHl~6P*g8Z=AShSwG-Yg@~7hLUx6aZ;U%B%%2ddDQFZ>B}@2Zn=-0duyatYQ}eJmN$hM z;o5$#Cr!&dn6tSDpmBLtUqeq4^MqZ^!ja0Ico+?@y61kv@gIDie!8aOtrcga^3clz z_H>Hqgy$6d1F$`4y8|n?_V)F6&iBd5=L#CCOD;eZGbP}J<{eHqPpXO(<`Q}5;ecR9MQH&8 z10G?c>}RdoMGPymf@_aTbSX+_X&+t-`Fu@iL*4TA4I1`uBRalg-(OmR1+=u@Wr5#W zpS5auCt^3D1Zgcu-+W1A&KW%lTHSpnljy35FP%Y|}x?w2m;ntZ%u((WQ0Yn+|P6?9YRNnhsyTOfvMc%i?9x(K8GsKEY8VC6csb+=~I zZa|CTVuWv72g*QI2vKHqYa=Rce{Fkvn>dxH(QPFP&*}a3*62&S-GkXzyPcQE{4-vt zE)*MR&hL?Gt5*3r;Vk;Ni~&8ffwI)k%}yO6XAN*;g$3Yup~fB`X&V3BS6cIBqnX0- zpdp#(?LwOmKYbFns-;<1rJ$yRJS%aZqFP`xRDLjex1#}7|55f3(>!D|H(h7Fx3$bT z*PH}?HJsZZI2leU)3AI&wQm6%9!j>Gu-jJF$!oXuoye zeGqwiW>@GAoG|Rnt#tH_pti>VwQQ>7&*fR=*Rmfe*9HPE`~NIVaWvstjpdd~oSfCk z@UkZ=A=;01bye)JjZ{8vJAeK0fzCOnj5Lah+}iHJvv3yC;MWu|^X;WXr}W@kTYX2p zhE-CiGp1;}(vO*_x#`Bam-X&zZ%^4K(IU#VHcTe$bmLZaw!I`(wx18b@{La9GO=H- zUKGBhR4U*nRwsYHc#@UY$Hi>;F&i^>b?u7FTiZ;xh)~Q0FiVmXZ1qX6=c*(N8*v)= ziQyd98(6v_CZC4uO}I8+cH?K)nM=@}Og{FMumROhdMbpW{0}fQA29V$U;C_LvuGO_ z2!iTTv-JWRl7a8Z4)!}ZjgAE&G4^!1H6gLaet2NB!v$4YO4!pExA~G)Y%WJ$4A&>! zR(A`2%^2TsBSDr5!NV(;L{F3(Nd9&l+qO5RWD?jse?cOFBHs4Q>NFMNk?QGM2IA@4 z4;cC2zuhU~e_YQNr$UGddXLf*z5T|+&%&n=Z`f}IYOO}>I#9xjlIpyu5FdjYQ<;eG znIF;%!fnJ~UwlUPO96i8`Acf)i6wF4FZkh8%daIvDdO8SjW1Ecnv@svX-WN<<6{iO z7e_89io$KA-}1###MfL)IY#z-5B%PTk23;4w@5(@MLa>H`2rO}OXhJN4e82ZaSJ=v zUH)dcAY72M-mjD*zDFm-g$i-x#CsL8-vi(WIv^ZQ@!ziFV^lCb1qOb|bLtN;5ITQ; z+@dnePGtshg>x^>!+&R&VR9h5JOeDie>(Y49k@11x==l%UQr*nlh|7lGJD}N5|K3m zgT%l}J6xcUREUZdK6sZ9FBKx|7bW?M{Lk^T@UBbbBJu|zC@A?gl1BeYrAtWQ zw;gc%+b!TP%6oY{+u$Y*$@mjB4asLhHKc$2Ct9CWI-wevZJKWDAjBQgd20aMOHX*l7h@aeJat+hq(fNM#M5$ZPLEHYnH$K;h^e|xSxXIJ zEUNsWj2_Krr$>(X^hoFtqz<=F!7hRHt{c0f=+rNJ_h7c>9J803Q|0T3%5;V83u2N8 z)10-=<&|86V<%2f7ua>IkTy9i{3!Q#)}Ou|WI<=4xkv;}s1b@(t;Hi;LH+*ydzUqm zZkk>yft(FbNwZfjR}fe{yXhO zKVaQKJgac3E4IXHK_csQlApxfF^*1=*&o7j85sCJTKYzT{@nHh{m|65_|bR z2`c$1_Jcx;6o@iBU}^Wq%=^3LUJ}DhKmMICjyWdBxlmUahU4%YMBWIQ1QrVo4-e^Z zo;GO*k$(7LN;)W4mUU%0e&R$_vz+_n1t*8W^3&fjP^Mc(M;T106Y81Q4hiieOr$pi z=VxrfhQ+eaH&6PraBy&_me~x5Rgci$r{3u;>UZWo2;@05g|%m!asAwURJOy}cb;u@ zbQF6Ay*1gakIQK)nqHz_v+N80a8eYTGho$bMeS4LYlJh(G0%0M((=OToiLJNUCGPK z)5#5vVL1E+nZ!ndNKe-h7tU+P-LftAg7rnjj}b7+Q{$l2%c{;!A0doGly3Qe>awo0Ok9wb&r z4i`~Hn|So^1avEd^puT2i?E+~xxGY-c&6xM;jpaKy8H{dyWuEn@tVsa8dYdb4KtiX z?X2lKYtu7Ws4jFr8Yo4IXLgNcH#b@s(cFp_U6;Scn+eNigbF*cK>Y)G9Lg+r7ac&o znT?8w?m+XkvMM~l8?e7Nwi`|uaT%1JhZBDGtD+-5`}UOHsrTw}?`Cc>Df191?>~H@ zqj!uIYyE8=2d0aAVSJDkKBQy^YZ8o!DnWzELh!IlP8D`c76f4xxegXALHRWVWarN^ zIk>Xn{gY?5BDb!Yj0vzt%yTu@w5RK#Dk+{to|DG% zDy++$+yAO`@~s(d9Da>%Z8u=uFCPiP_bIceJ@bLb2(3LOKw-zBut6E*7Nt+_yh^9; zgJz`1r=~3EB~OyqFC1R?MTXzjd)1=N#hORtgqO=P&_A~wmsFvuk(bW@+-gmM)VHrc_bp$j5?Z)ngA$-l*pq;!4^qTjD^=29MVW`{0v(GSLSdGeH`EU1 ziJ-sp(U+q^Vq05Jp0_!W)1=M_-{!H9J-_mYO1xsn6C=)|G0hODb1Yc;P;{Rw?DLH` z=NUOIQ0r{TpFUmvF1r&a!a*F8+mb`v_{`$VbQEepi3+e1K+JYBzq@pg>V*09vCsSF z^Cf}ibHwF1RSg_ijM!yo+0W12N%yLJSO*oCj7}+d<$e(Li;(?QZGfwqBDS{T#<1yl z$lCq306*Qs8&7sW9Sc%IkaX6|O_W)rt~$Uy&1DZOv@IKBB+TJr+x7d8er@P(#xfKx z_7&wUe_Ox4J3_nf{UjntPwC-6Qfvpj(}z?C7&t!L<|NH5~m(-nqIoAVjImT)h~ zt?linU?|{=y*4mz1uvZ&&g7)~Vgo~`N7a0B;KENr-|x-L`3=N#aWFW{rm!ZvR=Xp% z(uu9~7J)|(Of2VaJ3qORkDovSQriS_=78nmL$-N=F#Gy}Pu9`tcsgQUs3(tV$k8Bv z4NRJpvZBvg;FE#6ix(%=T;*4p!I)KOj$SDCnCINv{^_)mA4CbBJJ;x>hpq~hS8Bey zX|0&4x-=3xN>#eLZ8`S%xs4$D~t^)P;D7jQ|Ef& zMzcYeauct3$NH!rThK1bfL3DcD0W_q9qK1Jw+Fizwzksj*z0+usl6y}v;47AlWRRo zE9LlH?$^V5#Z%a-xA!w&*GQe;Qjq2NHc}#Wgw%a6PPK?73nan~ugYed6zhNPTHsV7 zP-j6RFMAH=qZ6B3m7FPP$U%%p*(Scl3B3-@|EhMrl?nzaka6liRp>OpXc4e4m1$AM zQ|Qq1(Qskx*yGqO=T|F{4dp)-j}iBgS3Wtj_m_D_d_{`%!U^j~XFJ1JUOkJFS8;7j zTQWks1N1`Mx52*#3v$ z{qSq~Dg>?Ogm)~fQ!PgOn%de%-<%E)_D8ZG)>z2i|0q4aKvQaLFi=zR*#|l3T0qw_ zRJm@c_TyEVbLvXQDL2A5cdxnL0wy12jst)F-Hs%NwQb?hGw!A~5uy)Z8=v@3rkiwq zWIzOL`h>BI3@7QuK0%%ID-1hNWQiWUgP#*yb5Wk^PXBOZt@}~BxMej(j+(0!8!19A zB*T@iA%UtF5wv~;Si6pJR}MGF^)-y~5L%XL?ax{5ntcs&3b7m!+yy)PMmnWl(k=RESwK zj?9S7;ubTJz3jPhc7BujqcKjb&zSMV`P(`y>(xQj9V(~&mO3`x2|n&KNmgOWQc0}( z+1>Vnu(dqK)*Y__!m5$=MkSom37+|+d$=)nDbv7Ucj8M}NW}(HJ;Et~b%x&6`q)um3cqPH+TL?$M=d0PMdssXve7Cn8$K$M?ZWBRo$-pP3#*R&*egnX~wky zkRQh~*i0{(2yFv-; zxH`B>ODgw|Od=b70Y>?~Hz4QXf+qEx0CKPsl#8K=ci?lTpoHZlw%bx6K0hs=Vj_C) zC##>p>Osd9EdrZo1?4`FjqHI@M*O%X72->9CM69i(V|1T8f?}ZZ9{o9;JoVqc{V4;t0_Os$d^X$fx?yLIE_pZwm+q~C^JjlOjc9-& zK=laj07J-2PqJq2T24qGn+Am_J;EDMSdz}47cwjBG_&=Xxr>~mc>xn&G=$OzS^1(n zTLb_Lr!m>|ulI=mrwctE|r>PNC2Q$^_9o)JYCgzzm_HAJQ;VlR z8tC3!8U8WxNa^^C?X8zA(1>2d17;Ii2?+_E3`3C!|A6Ih9K;+8Wv`E$c&phFsvToq znEx<8Z`aL5_@hVXxfOj*&a|gf-@A8@sF;N6-uF!K`q@YrtMeP*A};5$Kn3iz@y}WL z+b85b7mk86-jE$_RBYx`T2j{7ui?GzRq=;EPug~7p1z%^WJXzr#Zn~9wh}vTJd>-< zLC$uH`>feUg*QZtsouMHx=wjpB=QslTf~e_N>W1?BpnK#r{uh1i)ADB(Gr)xs%hlT z*WNfvN0g@_F27YHG2%hQ5WTQrri<&b#f#%MuC5YS{5$ul zxG`rg6Nf~K87q9&pTx_1s&sLDDBcNt74hk@qQ2xx&KoHYTH>pG@RddHqxI=klp0#ah>EcM7uohjWQ|Hh9=6~F7vT-D9P zT>n8nxVEKTJHZas233#iqAOMwX1qGzL`Dur1&h7jD@wdgyFG2B)~g6 zwea*_{-dklc>9Xou4YHn-X{&q(YhB)Hly_nY$O{InDL+HtO%*A_z!zoIdo-19WI)w z|72k!@!mt!nfz|-oiI%KwwkzO@0H>Q%LmaI4nl3M$_4()a=jq`nvY`=VUTAEGhX_J@g4uyNAI9nh_DOY(nlpQPKR+L(Y?IY&BDLLX2Z~gb@KS z|NAq!)KGM9SLQuxz-oE6Fi>VMHNJ4GVJrnAOjMRctLG_JE{%pMYUO8{mK^6S+K3}$ z%_x02j0e?r;c>{l$}dzOcBnFP=x5k31YKLo{c3fFhAH7appt6}d z_L$s_=-Y?OYWs(G%^6uq;6ToxrEGmw=fp`?tmkd+Pp}YoD#s?dpcC(8-n;{UwR_rF z=b_5aD5hEy=W~GvacUa&%I;ir(0yLXN8z3D#rv(TLe<=wAnq>tah7szN(45FBAx0+ z{)QBgIS(7&xsYyaiJLKZpFa*?bZT9WQPZ%|Q1b%E`Cda3$|j|=dQw!$w}KZMlFiwn z%7r9ZTmOGT3R_Ie0>Z%8y0x|O{m)cG(PqMius z!;W`zUK7|*hWCZb@Y5dpfrCRIlU`_|-75!rS!a*=6@LVcyvh#kEOF&_BXE4PV!C5C zfGq5Qkj#2*(%7DF9g+RDa^&vDf@Z;ooaAJh<1jyhz%?VRvUBUQTcFInfuO;GkJh#n zPyh~Z1Ob>^EDZCN0;c4V`NXW!Pi}l-qNee^q}5(rLXAg{jl2`K%Cc30_|dv!?l%{} z1=1uxHHb8sSh$j&qo$#3qNKH01|eXePg~#NW*RgWRdzS#J3h|{^jR>=3--eo!DBya z(osP*PG#a(s}jCyqKf$XdA&P5??J+6=erLQ1$*HT@GFhT6~qahjM@j(ZAVHiqE1WljlTW2vXte`QXTwhHo z4pqlNk$DYsg5$jl)&7kz42L7kC9T4B?0YV9)^KZQHpdF2ow*Mi*bCfv(bofrlU%s& z#g2E@QDu_0zG0ZbtCQ3%(21?HWuA*Xk7qdriSovXwIv*N5N7uDqb#n85kY^9&fJZ= z&}N3UsY|d(>T&9#VsP#e`VjCA=mm*ocM<2di9rDb+T5H9x!$OVn}k9u)BYmVu7~YF z&b{sydYj;bay1P>Sg!$ zN>2Z=nK3(-1eC5>+WQesfKxbWLF1ovC#DM%tL$5sZ~vYEQoN>c!Z*ieKg3d!b(Xc& z#8P83k69gRnqF6PQTF-A(T!3jJ4H zN0vmS8lB9TbGV4{`01Dk8fDnAtv{rN)DuM4f>KFdgLy#~4_dzqGMoVCFCf`Y3-Wd! zv2dYlnV)D?+vB##Ej@0YTMSq})6IT$v^6ZL$FcymgQ^&WE!FEnhMGHlKSLVUTUdoJ zLa<|0vhA)?&pHxeR8Wp;97fp)#}BrBh2iExZZHX4(#GCUfKx)eQ?WT7mV@zaTdw?t zQ~7=44*h1wI{^VZWyJ&8hBg!rgCOnPAD8=Vu=C|#kGV8pE1;D}hZ9D+=zT-vgb=24 zAuy%SDg;@=bE<6hLP!a(#G(u|q~Dd2dxqYl09MQE>5n+Ou$Mb{_s_%er9J~)%)DtT zgaJLM2mn7&ma=5Vg%07z=SokgcZ`fPPx%%Z1Szgy)|lKl9xxPW3YZd-{anw<7)R8Z zDcvf6)K*K^Zt=mPF=68)!-?~0XrJNgAUoVqrL7HKEQ8ePZtx2iWk8B~)M_=K=mbC1 zWP_!|kONpP(IsvnV%`h{VWIhDoI1nnKhTkr%V=Mks>hQ=pLBWiRG~5C;c8XF`1`}zYqk}##$OsXO3~|;Vu6yXtCpJ_6 z<3|v{oYVNWz;Q#ZZynV$*!V%85F&iZ)q;=ciYZjGTFz5c*2l_kzS|umh=*aCFv#;; zPcIkWy~58=EoxSp9Fl>IQ5`Z+Qwq6@2>1CXZW10W_JZYj=R?4M#cF%ZG1qyYwxG*C zQm~*(kj@yC{Y|rwBDm8<+{~Pj%U)U3$J!X;@Rg2DJ-GX;sSe1VLg@-dXtSezO<~3_ zFJy=Hs`zxSaq)J> z_!#RrpFK@P7qK?f{S^9kP-j2A^7g!>vg+k^)8@T5jT$upX68ivM7B zcvAyL#Xmt3DFBkpoLStaLYOQ6XeCoW?g68F^XKSJVBd&5U_Jx4DR{$u`+pw&AE?y- z{sV3b7u1NP_#5LQ3sN`Noj2xRumKkp{lmf_lQAC9og@ZCa^8aI$5S>>7^XE8r2!6q zEX@Bd9%*_7sbCELyNs+$kMzVZg>1AX6bO z0zN|gml0CfUnmFwGl~FGf+X`AQXR+uNeJLw{H7R*)^o_-B@6$tRje3}BV zD9BugcYtqUc$yV!9|Zv=(|{Ec&2rkU>!JW)a_C6|&wK}9@5BH+bHV{#;$LitJ2>C~ zSQ2D{MlYFv5r5|_Td7tLnH=FjHd7%JIsgUmFSdm~nfn1)7-SBG2bsQ+`WBe!Nt3A{ zTx7FjWX=VkRFwXU&B0b`NTyokkU1J=WD3Sb0bur)nN05Z(=|}rW$@5{u|3_#kCL@Nad=w%om@W? zrygH<%VPpi>qA@5O1(R2AErt0QijEGlo)IXtSt<1g$gs>=t3qm8sMCZrcaG&pbO$9 zd0E8A$3DJ~(0H+3UogED%u5bJrEZuW`q`OJLL#QD$3N&&maX(8D68JQ$rx820BaZB zb+CB7tFi9wFcWcodF66Z4+4-e7u^Qc>RVdSrO4UR1HcVL#uzN!`n6sXm=ImklfsX8 zuKI$mq$IQQ19)YexR07I?3Rg~v|=maeZOsc`sZVF$JNU+-H6+3cKLSQXaAz;@6G>< zqF;lr2;lbMEe6$Xr)Ux$B%(_7j2ZsP+_<_MKAyX~sE~JdlUkCOA^EQNdwU1iWz={1 zio#Q5%Kky;zbN~DyXlLsvtJ74>y!3J+eQElK-~KK(KMqRp4%U;FaQXJVzK88`oCiH z`)KR9xVsg^|aMZFYw{-Y+#owey1wG05^ z@GP{xddmXAvaBarS80+l2G%;twY4oirNoHZpVa*u|#$V|_$T&m# zL>JDI;g{)UvF%pf(N-B7c?)QBb8|?cbpn$K6CJT_%wjDmsXfVJQ)^G=-1+mL7yJG} z;!rgVXEAw7sR9F*zPqiu3#~;j#{!uD0%7)eL`6kIN?Vjv+OK93o!|C{02xv(kXhi6 z#&I*@e;E94vRR5@&xdo&%B08N+LS5H^29Q_y1M3KE!l6-vylSwNwyDXT>L{|oF)iM zjfSoSj|wslb67}vE`yX+R)r49ApMM8*Z|Fi7KEG6x&gdXu2E0;{#}J#0{rYB0{B8g z54|ec-df$x>QLt5EGk5Fj_!9;ucK?aV1Fv%@I|V#t0mU^>Z^3P+dAl2zZ*s zC0N20_%K>HYIvHd~in03oD4Ysp^@qQ-P9_@MdA9a5y#a~B>F?xThrDZFfx~EsEO`&*U1-3!9r~2}RBvf%)uJsuJ3yp~_OouV2Q=0sWvJh;l)trE|}% z@8&)wOyp4!c4uBhGpET$NXqg4mNgt&aA3J$5P5`xbsMYmj5u~w;rRX0j_cZZo`vXd zE&%ybr7gvcnV~USg$GKHfWZ?w!xb`<qwsDwQ z+EXf&_E~?g>oU`RQPyu(_6BuAqb@F5RCtY3TKoB9a18K1BSOyFKTBu@U!+x{By7I* zj#Dw+3Cp$JJxgrGzl*r5N64RX7uZpgi1$a- z)YM!K4sJv!^Nr6Z%ArT*6VXCoIc?Z$T|vg>Pgi>wvG_w?M$X_LYJ3W_KZ2ktH6uI# z*b<7ATQt97c^Q1i>P*151=*pxjKRv7~_Uu zeP>Tu&$t+k&d3)HTT78CBVq?a&;uVV)e#n+%oL{RsBFQ+#P<&ucFT$aKlx$pMMQSX9#8l7t$z{LsxSruwwmADL-_2}m|Ba%h>1^$G z`d3CrH5T%XIf- zYy7hqg6UzvKM)_xH&hj4!7R_bT#!=5EKMv1>c1;3B*C0*R(7N;hZy1!&oNB1vz9&P z09&a*B!Byqw*6?<3i^aa=3*Q?aS-`clVDP~D`Z%4Aw|?hESo#3TU&9~gNXhNa7C}> zs;I%BiH2xZb90`?IJpx$P1ET+i1mS3J1L7BHy9L%Bl6k)GxUhL{(wHjniR5|x$`us zyDOj@Q4Tiym<=Ehxlmc6yc;^T(rObP1cs!E6-==s<72D zAXEuc($M{i(zTYcqIMD&K#Qcr1;Mh7l7=owosv(TAsS=a!V5-q-Jv#PYS9bnxX7^B*QX5IcZ8Ufcmx! zCgjFl#N9Wk@_?tJW@)YDu}xflVr_NS5U^N!oe;4)Rk41BQ%6cy&yHzAz5Q2M?aDsa zxls>zSA=hj=_|PjhYQi5d%0wsj)H{|oKtnc*_mU(Vp8a!E3e`5e1Xfc*5Wi~Sha8lB??%!&8dO~HWoR0AL>wOMk#9VAfAil z1Qm;gZFrk;{ToC&9Y-!z)r2m@pGnU%>o}X=>Rej%1a)u2sG!;bAacaa#3`S*I+6H$ zIFp>%*1=P(d14+7iG}QBEa^i^YHG&)qJYB#42Rd|lHY}W -yKcUnkwT0&I;}x?b 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 literal 0 HcmV?d00001 diff --git a/tests/page/page-screenshot.spec.ts-snapshots/hide-should-work-webkit.png b/tests/page/page-screenshot.spec.ts-snapshots/hide-should-work-webkit.png new file mode 100644 index 0000000000000000000000000000000000000000..b3d74594d4cc8b7e46c724b531e1270cb02fdacc GIT binary patch literal 39880 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 z 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

QULH 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 literal 0 HcmV?d00001 diff --git a/tests/page/page-screenshot.spec.ts-snapshots/remove-should-work-chromium.png b/tests/page/page-screenshot.spec.ts-snapshots/remove-should-work-chromium.png new file mode 100644 index 0000000000000000000000000000000000000000..d35ddd42899d3509d49d323d789f5a7992051617 GIT binary patch literal 36201 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 z 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�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℘ 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 OK0aQ4p4UD0Wc>lR$F(Z} literal 0 HcmV?d00001 diff --git a/tests/page/page-screenshot.spec.ts-snapshots/remove-should-work-webkit.png b/tests/page/page-screenshot.spec.ts-snapshots/remove-should-work-webkit.png new file mode 100644 index 0000000000000000000000000000000000000000..88699136c19a7f71e8f836e563d844868a0766cd GIT binary patch literal 40063 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

8Z|&UeW+Z9x>|8yG%lH`} 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 { expect(result.exitCode).toBe(0); }); +test('should support style option', async ({ runInlineTest }) => { + const result = await runInlineTest({ + ...playwrightConfig({ + snapshotPathTemplate: '__screenshots__/{testFilePath}/{arg}{ext}', + }), + '__screenshots__/a.spec.js/snapshot.png': createImage(IMG_WIDTH, IMG_HEIGHT, 0, 255, 0), + 'a.spec.js': ` + const { test, expect } = require('@playwright/test'); + test('png', async ({ page }) => { + await page.setContent(''); + await expect(page).toHaveScreenshot('snapshot.png', { + style: 'body { background: #00FF00; }', + }); + }); + `, + }); + expect(result.exitCode).toBe(0); +}); + +test('should support style option in config', async ({ runInlineTest }) => { + const result = await runInlineTest({ + ...playwrightConfig({ + snapshotPathTemplate: '__screenshots__/{testFilePath}/{arg}{ext}', + expect: { + toHaveScreenshot: { + style: 'body { background: #00FF00; }', + }, + }, + }), + '__screenshots__/a.spec.js/snapshot.png': createImage(IMG_WIDTH, IMG_HEIGHT, 0, 255, 0), + 'a.spec.js': ` + const { test, expect } = require('@playwright/test'); + test('png', async ({ page }) => { + await page.setContent(''); + await expect(page).toHaveScreenshot('snapshot.png'); + }); + `, + }); + expect(result.exitCode).toBe(0); +}); + function playwrightConfig(obj: any) { return { 'playwright.config.js': ` From d296d057d3b6515010478c16cb771860685720aa Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Fri, 1 Dec 2023 09:33:51 -0800 Subject: [PATCH 121/491] Update bug.md Signed-off-by: Pavel Feldman --- .github/ISSUE_TEMPLATE/bug.md | 45 +++-------------------------------- 1 file changed, 3 insertions(+), 42 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug.md b/.github/ISSUE_TEMPLATE/bug.md index 52eb7d6a3e..662784b912 100644 --- a/.github/ISSUE_TEMPLATE/bug.md +++ b/.github/ISSUE_TEMPLATE/bug.md @@ -7,12 +7,6 @@ assignees: '' --- - - - - - - ### System info - Playwright Version: [v1.XX] - Operating System: [All, Windows 11, Ubuntu 20, macOS 13.2, etc.] @@ -21,44 +15,11 @@ assignees: '' ### Source code -- [ ] I provided exact source code that allows reproducing the issue locally. +[We will only be able to work on the issues that we can reproduce.] - - - - +[Please provide a self-contained example in a form of a snippet, archive or a repository.] -**Link to the GitHub repository with the repro** - -[https://github.com/your_profile/playwright_issue_title] - -or - -**Config file** - -```js -// playwright.config.ts -import { defineConfig, devices } from '@playwright/test'; - -export default defineConfig({ - projects: [ - { - name: 'chromium', - use: { ...devices['Desktop Chrome'], }, - }, - ] -}); -``` - -**Test file (self-contained)** - -```js -it('should check the box using setChecked', async ({ page }) => { - await page.setContent(``); - await page.getByRole('checkbox').check(); - await expect(page.getByRole('checkbox')).toBeChecked(); -}); -``` +[Repro scenario can't be large or have external dependencies.] **Steps** - [Run the test] From f44ef81af78ec79757262ff150e4fd43d6d3cf9a Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Fri, 1 Dec 2023 09:38:50 -0800 Subject: [PATCH 122/491] fix(snapshot): broken snapshot after use of setInputFiles (#28444) --- packages/trace-viewer/src/snapshotRenderer.ts | 4 +++- tests/library/trace-viewer.spec.ts | 6 +++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/trace-viewer/src/snapshotRenderer.ts b/packages/trace-viewer/src/snapshotRenderer.ts index f07035c041..625f011138 100644 --- a/packages/trace-viewer/src/snapshotRenderer.ts +++ b/packages/trace-viewer/src/snapshotRenderer.ts @@ -223,7 +223,9 @@ function snapshotScript(...targetIds: (string | undefined)[]) { scrollLefts.push(e); for (const element of root.querySelectorAll(`[__playwright_value_]`)) { - (element as HTMLInputElement | HTMLTextAreaElement).value = element.getAttribute('__playwright_value_')!; + const inputElement = element as HTMLInputElement | HTMLTextAreaElement; + if (inputElement.type !== 'file') + inputElement.value = inputElement.getAttribute('__playwright_value_')!; element.removeAttribute('__playwright_value_'); } for (const element of root.querySelectorAll(`[__playwright_checked_]`)) { diff --git a/tests/library/trace-viewer.spec.ts b/tests/library/trace-viewer.spec.ts index 426ebbf81b..b9e49da647 100644 --- a/tests/library/trace-viewer.spec.ts +++ b/tests/library/trace-viewer.spec.ts @@ -438,12 +438,13 @@ test('should restore scroll positions', async ({ page, runAndTrace, browserName expect(await frame.locator('div').evaluate(div => div.scrollTop)).toBe(136); }); -test('should restore control values', async ({ page, runAndTrace }) => { +test('should restore control values', async ({ page, runAndTrace, asset }) => { const traceViewer = await runAndTrace(async () => { await page.setContent(` + '); + const input = page.locator('input'); + await expect(input).toHaveValue('hello world'); + await input.focus(); + await page.keyboard.press('Meta'); + await expect(input).toHaveValue('hello world'); +}); + it('should be able to prevent selectAll', async ({ page, server, isMac }) => { await page.goto(server.PREFIX + '/input/textarea.html'); const textarea = await page.$('textarea'); @@ -358,25 +368,13 @@ it('should support MacOS shortcuts', async ({ page, server, platform, browserNam expect(await page.$eval('textarea', textarea => textarea.value)).toBe('some '); }); -it('should press the meta key', async ({ page, browserName, isMac, browserMajorVersion }) => { +it('should press the meta key', async ({ page }) => { const lastEvent = await captureLastKeydown(page); await page.keyboard.press('Meta'); const { key, code, metaKey } = await lastEvent.jsonValue(); - if (browserName === 'firefox' && !isMac) - expect(key).toBe('OS'); - else - expect(key).toBe('Meta'); - - if (browserName === 'firefox' && browserMajorVersion <= 117) - expect(code).toBe('OSLeft'); - else - expect(code).toBe('MetaLeft'); - - if (browserName === 'firefox' && !isMac) - expect(metaKey).toBe(false); - else - expect(metaKey).toBe(true); - + expect(key).toBe('Meta'); + expect(code).toBe('MetaLeft'); + expect(metaKey).toBe(true); }); it('should work with keyboard events with empty.html', async ({ page, server }) => { From 532e4c58021c5b6636c761c9ccb910e3bfd0a74c Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Fri, 8 Dec 2023 10:55:26 -0800 Subject: [PATCH 151/491] test: add a test for issue 28520 (#28552) References #28520. --- tests/page/page-evaluate.spec.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/page/page-evaluate.spec.ts b/tests/page/page-evaluate.spec.ts index 37aa0a6c8f..5e101f1b7a 100644 --- a/tests/page/page-evaluate.spec.ts +++ b/tests/page/page-evaluate.spec.ts @@ -769,3 +769,11 @@ it('should expose utilityScript', async ({ page }) => { utils: true, }); }); + +it('should work with Array.from/map', async ({ page }) => { + it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/28520' }); + expect(await page.evaluate(() => { + const r = (str, amount) => Array.from(Array(amount)).map(() => str).join(''); + return r('([a-f0-9]{2})', 3); + })).toBe('([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})'); +}); From a01ef0e9cec85a18f8c002a73a38e4a952522304 Mon Sep 17 00:00:00 2001 From: Playwright Service <89237858+playwrightmachine@users.noreply.github.com> Date: Fri, 8 Dec 2023 14:58:03 -0800 Subject: [PATCH 152/491] feat(chromium-tip-of-tree): roll to r1174 (#28559) 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 2f81bfc3b1..bbd3879960 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -15,9 +15,9 @@ }, { "name": "chromium-tip-of-tree", - "revision": "1173", + "revision": "1174", "installByDefault": false, - "browserVersion": "121.0.6157.0" + "browserVersion": "122.0.6172.0" }, { "name": "firefox", From 7827838d2209e433e70773b257a07d1a41d54a72 Mon Sep 17 00:00:00 2001 From: Playwright Service <89237858+playwrightmachine@users.noreply.github.com> Date: Fri, 8 Dec 2023 14:59:38 -0800 Subject: [PATCH 153/491] feat(chromium): roll to r1094 (#28560) Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> --- README.md | 4 +- packages/playwright-core/browsers.json | 8 +- .../src/server/chromium/protocol.d.ts | 138 ++++++++++++++---- .../src/server/deviceDescriptorsSource.json | 96 ++++++------ packages/playwright-core/types/protocol.d.ts | 138 ++++++++++++++---- 5 files changed, 268 insertions(+), 116 deletions(-) diff --git a/README.md b/README.md index 172e520c6b..75800292bb 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-120.0.6099.56-blue.svg?logo=google-chrome)](https://www.chromium.org/Home) [![Firefox version](https://img.shields.io/badge/firefox-120.0.1-blue.svg?logo=firefoxbrowser)](https://www.mozilla.org/en-US/firefox/new/) [![WebKit version](https://img.shields.io/badge/webkit-17.4-blue.svg?logo=safari)](https://webkit.org/) +[![npm version](https://img.shields.io/npm/v/playwright.svg)](https://www.npmjs.com/package/playwright) [![Chromium version](https://img.shields.io/badge/chromium-121.0.6167.8-blue.svg?logo=google-chrome)](https://www.chromium.org/Home) [![Firefox version](https://img.shields.io/badge/firefox-120.0.1-blue.svg?logo=firefoxbrowser)](https://www.mozilla.org/en-US/firefox/new/) [![WebKit version](https://img.shields.io/badge/webkit-17.4-blue.svg?logo=safari)](https://webkit.org/) ## [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 120.0.6099.56 | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| Chromium 121.0.6167.8 | :white_check_mark: | :white_check_mark: | :white_check_mark: | | WebKit 17.4 | :white_check_mark: | :white_check_mark: | :white_check_mark: | | Firefox 120.0.1 | :white_check_mark: | :white_check_mark: | :white_check_mark: | diff --git a/packages/playwright-core/browsers.json b/packages/playwright-core/browsers.json index bbd3879960..87be163608 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -3,15 +3,15 @@ "browsers": [ { "name": "chromium", - "revision": "1093", + "revision": "1094", "installByDefault": true, - "browserVersion": "120.0.6099.56" + "browserVersion": "121.0.6167.8" }, { "name": "chromium-with-symbols", - "revision": "1093", + "revision": "1094", "installByDefault": false, - "browserVersion": "120.0.6099.56" + "browserVersion": "121.0.6167.8" }, { "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 e6115a8e7f..49adee0f8b 100644 --- a/packages/playwright-core/src/server/chromium/protocol.d.ts +++ b/packages/playwright-core/src/server/chromium/protocol.d.ts @@ -675,7 +675,7 @@ may be used by the front-end as additional context. request?: AffectedRequest; } export type MixedContentResolutionStatus = "MixedContentBlocked"|"MixedContentAutomaticallyUpgraded"|"MixedContentWarning"; - export type MixedContentResourceType = "AttributionSrc"|"Audio"|"Beacon"|"CSPReport"|"Download"|"EventSource"|"Favicon"|"Font"|"Form"|"Frame"|"Image"|"Import"|"Manifest"|"Ping"|"PluginData"|"PluginResource"|"Prefetch"|"Resource"|"Script"|"ServiceWorker"|"SharedWorker"|"Stylesheet"|"Track"|"Video"|"Worker"|"XMLHttpRequest"|"XSLT"; + export type MixedContentResourceType = "AttributionSrc"|"Audio"|"Beacon"|"CSPReport"|"Download"|"EventSource"|"Favicon"|"Font"|"Form"|"Frame"|"Image"|"Import"|"Manifest"|"Ping"|"PluginData"|"PluginResource"|"Prefetch"|"Resource"|"Script"|"ServiceWorker"|"SharedWorker"|"SpeculationRules"|"Stylesheet"|"Track"|"Video"|"Worker"|"XMLHttpRequest"|"XSLT"; export interface MixedContentIssueDetails { /** * The type of resource causing the mixed content issue (css, js, iframe, @@ -1367,7 +1367,7 @@ events afterwards if enabled and recording. export type PermissionSetting = "granted"|"denied"|"prompt"; /** * Definition of PermissionDescriptor defined in the Permissions API: -https://w3c.github.io/permissions/#dictdef-permissiondescriptor. +https://w3c.github.io/permissions/#dom-permissiondescriptor. */ export interface PermissionDescriptor { /** @@ -2334,6 +2334,10 @@ A higher number has higher priority in the cascade order. * Font's family name reported by platform. */ familyName: string; + /** + * Font's PostScript name reported by platform. + */ + postScriptName: string; /** * Indicates if the font was downloaded or resolved locally. */ @@ -2464,6 +2468,28 @@ stylesheet rules) this rule came from. inherits: boolean; syntax: string; } + /** + * CSS font-palette-values rule representation. + */ + export interface CSSFontPaletteValuesRule { + /** + * The css style sheet identifier (absent for user agent stylesheet and user-specified +stylesheet rules) this rule came from. + */ + styleSheetId?: StyleSheetId; + /** + * Parent stylesheet's origin. + */ + origin: StyleSheetOrigin; + /** + * Associated font palette name. + */ + fontPaletteName: Value; + /** + * Associated style declaration. + */ + style: CSSStyle; + } /** * CSS property at-rule representation. */ @@ -2749,6 +2775,10 @@ attributes) for a DOM node identified by `nodeId`. * A list of CSS property registrations matching this node. */ cssPropertyRegistrations?: CSSPropertyRegistration[]; + /** + * A font-palette-values rule matching this node. + */ + cssFontPaletteValuesRule?: CSSFontPaletteValuesRule; /** * Id of the first parent element that does not have display: contents. */ @@ -5615,6 +5645,12 @@ A display feature that only splits content will have a 0 mask_length. */ maskLength: number; } + export interface DevicePosture { + /** + * Current posture of the device + */ + type: "continuous"|"folded"; + } export interface MediaFeature { name: string; value: string; @@ -5834,6 +5870,11 @@ change is not observed by the page, e.g. viewport-relative elements do not chang is turned-off. */ displayFeature?: DisplayFeature; + /** + * If set, the posture of a foldable device. If not set the posture is set +to continuous. + */ + devicePosture?: DevicePosture; } export type setDeviceMetricsOverrideReturnValue = { } @@ -6105,7 +6146,7 @@ on Android. */ userAgent: string; /** - * Browser langugage to emulate. + * Browser language to emulate. */ acceptLanguage?: string; /** @@ -8131,6 +8172,9 @@ records. * The reason why Chrome uses a specific transport protocol for HTTP semantics. */ export type AlternateProtocolUsage = "alternativeJobWonWithoutRace"|"alternativeJobWonRace"|"mainJobWonRace"|"mappingMissing"|"broken"|"dnsAlpnH3JobWonWithoutRace"|"dnsAlpnH3JobWonRace"|"unspecifiedReason"; + export interface ServiceWorkerRouterInfo { + ruleIdMatched: number; + } /** * HTTP response data. */ @@ -8195,6 +8239,10 @@ records. * Specifies that the request was served from the prefetch cache. */ fromPrefetchCache?: boolean; + /** + * Infomation about how Service Worker Static Router was used. + */ + serviceWorkerRouterInfo?: ServiceWorkerRouterInfo; /** * Total number of bytes received for this request so far. */ @@ -9957,7 +10005,7 @@ continueInterceptedRequest call. */ userAgent: string; /** - * Browser langugage to emulate. + * Browser language to emulate. */ acceptLanguage?: string; /** @@ -10889,7 +10937,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"|"ambient-light-sensor"|"attribution-reporting"|"autoplay"|"bluetooth"|"browsing-topics"|"camera"|"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-factor"|"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"|"cross-origin-isolated"|"direct-sockets"|"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"|"microphone"|"midi"|"otp-credentials"|"payment"|"picture-in-picture"|"private-aggregation"|"private-state-token-issuance"|"private-state-token-redemption"|"publickey-credentials-get"|"run-ad-auction"|"screen-wake-lock"|"serial"|"shared-autofill"|"shared-storage"|"shared-storage-select-url"|"smart-card"|"storage-access"|"sync-xhr"|"unload"|"usb"|"vertical-scroll"|"web-share"|"window-management"|"window-placement"|"xr-spatial-tracking"; + export type PermissionsPolicyFeature = "accelerometer"|"ambient-light-sensor"|"attribution-reporting"|"autoplay"|"bluetooth"|"browsing-topics"|"camera"|"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-factor"|"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"|"cross-origin-isolated"|"direct-sockets"|"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"|"microphone"|"midi"|"otp-credentials"|"payment"|"picture-in-picture"|"private-aggregation"|"private-state-token-issuance"|"private-state-token-redemption"|"publickey-credentials-get"|"run-ad-auction"|"screen-wake-lock"|"serial"|"shared-autofill"|"shared-storage"|"shared-storage-select-url"|"smart-card"|"storage-access"|"sync-xhr"|"unload"|"usb"|"usb-unrestricted"|"vertical-scroll"|"web-printing"|"web-share"|"window-management"|"window-placement"|"xr-spatial-tracking"; /** * Reason for a permissions policy feature to be disabled. */ @@ -11988,19 +12036,6 @@ as an ad. Only sent if frame is labelled as an ad and id is available. */ adScriptId?: AdScriptId; } - /** - * Returns all browser cookies for the page and all of its subframes. Depending -on the backend support, will return detailed cookie information in the -`cookies` field. - */ - export type getCookiesParameters = { - } - export type getCookiesReturnValue = { - /** - * Array of cookie objects. - */ - cookies: Network.Cookie[]; - } /** * Returns present frame tree structure. */ @@ -12250,6 +12285,10 @@ in which case the content will be scaled to fit the paper size. * Whether or not to generate tagged (accessible) PDF. Defaults to embedder choice. */ generateTaggedPDF?: boolean; + /** + * Whether or not to embed the document outline into the PDF. + */ + generateDocumentOutline?: boolean; } export type printToPDFReturnValue = { /** @@ -13228,6 +13267,7 @@ For cached script it is the last time the cache entry was validated. scriptResponseTime?: number; controlledClients?: Target.TargetID[]; targetId?: Target.TargetID; + routerRules?: string; } /** * ServiceWorker error message. @@ -13510,6 +13550,14 @@ SharedStorageAccessType.workletSet. */ ends: number[]; } + export interface AttributionReportingTriggerSpec { + /** + * number instead of integer because not all uint32 can be represented by +int + */ + triggerData: number[]; + eventReportWindows: AttributionReportingEventReportWindows; + } export type AttributionReportingTriggerDataMatching = "exact"|"modulus"; export interface AttributionReportingSourceRegistration { time: Network.TimeSinceEpoch; @@ -13517,7 +13565,7 @@ SharedStorageAccessType.workletSet. * duration in seconds */ expiry: number; - eventReportWindows: AttributionReportingEventReportWindows; + triggerSpecs: AttributionReportingTriggerSpec[]; /** * duration in seconds */ @@ -15623,6 +15671,18 @@ Otherwise, they will not be resolved. Defaults to true. Defaults to false. */ isUserVerified?: boolean; + /** + * Credentials created by this authenticator will have the backup +eligibility (BE) flag set to this value. Defaults to false. +https://w3c.github.io/webauthn/#sctn-credential-backup + */ + defaultBackupEligibility?: boolean; + /** + * Credentials created by this authenticator will have the backup state +(BS) flag set to this value. Defaults to false. +https://w3c.github.io/webauthn/#sctn-credential-backup + */ + defaultBackupState?: boolean; } export interface Credential { credentialId: binary; @@ -16093,6 +16153,14 @@ status is shared by prefetchStatusUpdated and prerenderStatusUpdated. filter out the ones that aren't necessary to the developers. */ export type PrefetchStatus = "PrefetchAllowed"|"PrefetchFailedIneligibleRedirect"|"PrefetchFailedInvalidRedirect"|"PrefetchFailedMIMENotSupported"|"PrefetchFailedNetError"|"PrefetchFailedNon2XX"|"PrefetchFailedPerPageLimitExceeded"|"PrefetchEvicted"|"PrefetchHeldback"|"PrefetchIneligibleRetryAfter"|"PrefetchIsPrivacyDecoy"|"PrefetchIsStale"|"PrefetchNotEligibleBrowserContextOffTheRecord"|"PrefetchNotEligibleDataSaverEnabled"|"PrefetchNotEligibleExistingProxy"|"PrefetchNotEligibleHostIsNonUnique"|"PrefetchNotEligibleNonDefaultStoragePartition"|"PrefetchNotEligibleSameSiteCrossOriginPrefetchRequiredProxy"|"PrefetchNotEligibleSchemeIsNotHttps"|"PrefetchNotEligibleUserHasCookies"|"PrefetchNotEligibleUserHasServiceWorker"|"PrefetchNotEligibleBatterySaverEnabled"|"PrefetchNotEligiblePreloadingDisabled"|"PrefetchNotFinishedInTime"|"PrefetchNotStarted"|"PrefetchNotUsedCookiesChanged"|"PrefetchProxyNotAvailable"|"PrefetchResponseUsed"|"PrefetchSuccessfulButNotUsed"|"PrefetchNotUsedProbeFailed"; + /** + * Information of headers to be displayed when the header mismatch occurred. + */ + export interface PrerenderMismatchedHeaders { + headerName: string; + initialValue?: string; + activationValue?: string; + } /** * Upsert. Currently, it is only emitted when a rule set added. @@ -16139,6 +16207,7 @@ filter out the ones that aren't necessary to the developers. that is incompatible with prerender and has caused the cancellation of the attempt. */ disallowedMojoInterface?: string; + mismatchedHeaders?: PrerenderMismatchedHeaders[]; } /** * Send a list of sources for all preloading attempts in a document. @@ -16168,9 +16237,13 @@ whether this account has ever been used to sign in to this RP before. */ export type LoginState = "SignIn"|"SignUp"; /** - * Whether the dialog shown is an account chooser or an auto re-authentication dialog. + * The types of FedCM dialogs. */ - export type DialogType = "AccountChooser"|"AutoReauthn"|"ConfirmIdpLogin"; + export type DialogType = "AccountChooser"|"AutoReauthn"|"ConfirmIdpLogin"|"Error"; + /** + * The buttons on the FedCM dialog. + */ + export type DialogButton = "ConfirmIdpLoginContinue"|"ErrorGotIt"|"ErrorMoreDetails"; /** * Corresponds to IdentityRequestAccount */ @@ -16201,6 +16274,13 @@ RP context was used appropriately. title: string; subtitle?: string; } + /** + * Triggered when a dialog is closed, either by user action, JS abort, +or a command below. + */ + export type dialogClosedPayload = { + dialogId: string; + } export type enableParameters = { /** @@ -16222,14 +16302,11 @@ normally happen, if this is unimportant to what's being tested. } export type selectAccountReturnValue = { } - /** - * Only valid if the dialog type is ConfirmIdpLogin. Acts as if the user had -clicked the continue button. - */ - export type confirmIdpLoginParameters = { + export type clickDialogButtonParameters = { dialogId: string; + dialogButton: DialogButton; } - export type confirmIdpLoginReturnValue = { + export type clickDialogButtonReturnValue = { } export type dismissDialogParameters = { dialogId: string; @@ -19044,6 +19121,7 @@ Error was thrown. "Preload.prerenderStatusUpdated": Preload.prerenderStatusUpdatedPayload; "Preload.preloadingAttemptSourcesUpdated": Preload.preloadingAttemptSourcesUpdatedPayload; "FedCm.dialogShown": FedCm.dialogShownPayload; + "FedCm.dialogClosed": FedCm.dialogClosedPayload; "Console.messageAdded": Console.messageAddedPayload; "Debugger.breakpointResolved": Debugger.breakpointResolvedPayload; "Debugger.paused": Debugger.pausedPayload; @@ -19401,7 +19479,6 @@ Error was thrown. "Page.getManifestIcons": Page.getManifestIconsParameters; "Page.getAppId": Page.getAppIdParameters; "Page.getAdScriptId": Page.getAdScriptIdParameters; - "Page.getCookies": Page.getCookiesParameters; "Page.getFrameTree": Page.getFrameTreeParameters; "Page.getLayoutMetrics": Page.getLayoutMetricsParameters; "Page.getNavigationHistory": Page.getNavigationHistoryParameters; @@ -19562,7 +19639,7 @@ Error was thrown. "FedCm.enable": FedCm.enableParameters; "FedCm.disable": FedCm.disableParameters; "FedCm.selectAccount": FedCm.selectAccountParameters; - "FedCm.confirmIdpLogin": FedCm.confirmIdpLoginParameters; + "FedCm.clickDialogButton": FedCm.clickDialogButtonParameters; "FedCm.dismissDialog": FedCm.dismissDialogParameters; "FedCm.resetCooldown": FedCm.resetCooldownParameters; "Console.clearMessages": Console.clearMessagesParameters; @@ -19980,7 +20057,6 @@ Error was thrown. "Page.getManifestIcons": Page.getManifestIconsReturnValue; "Page.getAppId": Page.getAppIdReturnValue; "Page.getAdScriptId": Page.getAdScriptIdReturnValue; - "Page.getCookies": Page.getCookiesReturnValue; "Page.getFrameTree": Page.getFrameTreeReturnValue; "Page.getLayoutMetrics": Page.getLayoutMetricsReturnValue; "Page.getNavigationHistory": Page.getNavigationHistoryReturnValue; @@ -20141,7 +20217,7 @@ Error was thrown. "FedCm.enable": FedCm.enableReturnValue; "FedCm.disable": FedCm.disableReturnValue; "FedCm.selectAccount": FedCm.selectAccountReturnValue; - "FedCm.confirmIdpLogin": FedCm.confirmIdpLoginReturnValue; + "FedCm.clickDialogButton": FedCm.clickDialogButtonReturnValue; "FedCm.dismissDialog": FedCm.dismissDialogReturnValue; "FedCm.resetCooldown": FedCm.resetCooldownReturnValue; "Console.clearMessages": Console.clearMessagesReturnValue; diff --git a/packages/playwright-core/src/server/deviceDescriptorsSource.json b/packages/playwright-core/src/server/deviceDescriptorsSource.json index 0170b59cad..d00a176958 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/120.0.6099.56 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.6167.8 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/120.0.6099.56 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.6167.8 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/120.0.6099.56 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.6167.8 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/120.0.6099.56 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.6167.8 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/120.0.6099.56 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.6167.8 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/120.0.6099.56 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.6167.8 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/120.0.6099.56 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.6167.8 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/120.0.6099.56 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.6167.8 Safari/537.36", "viewport": { "width": 1138, "height": 712 @@ -978,7 +978,7 @@ "defaultBrowserType": "webkit" }, "LG Optimus L70": { - "userAgent": "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/120.0.6099.56 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/121.0.6167.8 Mobile Safari/537.36", "viewport": { "width": 384, "height": 640 @@ -989,7 +989,7 @@ "defaultBrowserType": "chromium" }, "LG Optimus L70 landscape": { - "userAgent": "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/120.0.6099.56 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/121.0.6167.8 Mobile Safari/537.36", "viewport": { "width": 640, "height": 384 @@ -1000,7 +1000,7 @@ "defaultBrowserType": "chromium" }, "Microsoft Lumia 550": { - "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.56 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/121.0.6167.8 Mobile Safari/537.36 Edge/14.14263", "viewport": { "width": 640, "height": 360 @@ -1011,7 +1011,7 @@ "defaultBrowserType": "chromium" }, "Microsoft Lumia 550 landscape": { - "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.56 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/121.0.6167.8 Mobile Safari/537.36 Edge/14.14263", "viewport": { "width": 360, "height": 640 @@ -1022,7 +1022,7 @@ "defaultBrowserType": "chromium" }, "Microsoft Lumia 950": { - "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.56 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/121.0.6167.8 Mobile Safari/537.36 Edge/14.14263", "viewport": { "width": 360, "height": 640 @@ -1033,7 +1033,7 @@ "defaultBrowserType": "chromium" }, "Microsoft Lumia 950 landscape": { - "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.56 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/121.0.6167.8 Mobile Safari/537.36 Edge/14.14263", "viewport": { "width": 640, "height": 360 @@ -1044,7 +1044,7 @@ "defaultBrowserType": "chromium" }, "Nexus 10": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.56 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.6167.8 Safari/537.36", "viewport": { "width": 800, "height": 1280 @@ -1055,7 +1055,7 @@ "defaultBrowserType": "chromium" }, "Nexus 10 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.56 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.6167.8 Safari/537.36", "viewport": { "width": 1280, "height": 800 @@ -1066,7 +1066,7 @@ "defaultBrowserType": "chromium" }, "Nexus 4": { - "userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.56 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.6167.8 Mobile Safari/537.36", "viewport": { "width": 384, "height": 640 @@ -1077,7 +1077,7 @@ "defaultBrowserType": "chromium" }, "Nexus 4 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.56 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.6167.8 Mobile Safari/537.36", "viewport": { "width": 640, "height": 384 @@ -1088,7 +1088,7 @@ "defaultBrowserType": "chromium" }, "Nexus 5": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.56 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.6167.8 Mobile Safari/537.36", "viewport": { "width": 360, "height": 640 @@ -1099,7 +1099,7 @@ "defaultBrowserType": "chromium" }, "Nexus 5 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.56 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.6167.8 Mobile Safari/537.36", "viewport": { "width": 640, "height": 360 @@ -1110,7 +1110,7 @@ "defaultBrowserType": "chromium" }, "Nexus 5X": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.56 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/121.0.6167.8 Mobile Safari/537.36", "viewport": { "width": 412, "height": 732 @@ -1121,7 +1121,7 @@ "defaultBrowserType": "chromium" }, "Nexus 5X landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.56 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/121.0.6167.8 Mobile Safari/537.36", "viewport": { "width": 732, "height": 412 @@ -1132,7 +1132,7 @@ "defaultBrowserType": "chromium" }, "Nexus 6": { - "userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.56 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.6167.8 Mobile Safari/537.36", "viewport": { "width": 412, "height": 732 @@ -1143,7 +1143,7 @@ "defaultBrowserType": "chromium" }, "Nexus 6 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.56 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.6167.8 Mobile Safari/537.36", "viewport": { "width": 732, "height": 412 @@ -1154,7 +1154,7 @@ "defaultBrowserType": "chromium" }, "Nexus 6P": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.56 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/121.0.6167.8 Mobile Safari/537.36", "viewport": { "width": 412, "height": 732 @@ -1165,7 +1165,7 @@ "defaultBrowserType": "chromium" }, "Nexus 6P landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.56 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/121.0.6167.8 Mobile Safari/537.36", "viewport": { "width": 732, "height": 412 @@ -1176,7 +1176,7 @@ "defaultBrowserType": "chromium" }, "Nexus 7": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.56 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.6167.8 Safari/537.36", "viewport": { "width": 600, "height": 960 @@ -1187,7 +1187,7 @@ "defaultBrowserType": "chromium" }, "Nexus 7 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.56 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.6167.8 Safari/537.36", "viewport": { "width": 960, "height": 600 @@ -1242,7 +1242,7 @@ "defaultBrowserType": "webkit" }, "Pixel 2": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.56 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/121.0.6167.8 Mobile Safari/537.36", "viewport": { "width": 411, "height": 731 @@ -1253,7 +1253,7 @@ "defaultBrowserType": "chromium" }, "Pixel 2 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.56 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/121.0.6167.8 Mobile Safari/537.36", "viewport": { "width": 731, "height": 411 @@ -1264,7 +1264,7 @@ "defaultBrowserType": "chromium" }, "Pixel 2 XL": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.56 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/121.0.6167.8 Mobile Safari/537.36", "viewport": { "width": 411, "height": 823 @@ -1275,7 +1275,7 @@ "defaultBrowserType": "chromium" }, "Pixel 2 XL landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.56 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/121.0.6167.8 Mobile Safari/537.36", "viewport": { "width": 823, "height": 411 @@ -1286,7 +1286,7 @@ "defaultBrowserType": "chromium" }, "Pixel 3": { - "userAgent": "Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.56 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/121.0.6167.8 Mobile Safari/537.36", "viewport": { "width": 393, "height": 786 @@ -1297,7 +1297,7 @@ "defaultBrowserType": "chromium" }, "Pixel 3 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.56 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/121.0.6167.8 Mobile Safari/537.36", "viewport": { "width": 786, "height": 393 @@ -1308,7 +1308,7 @@ "defaultBrowserType": "chromium" }, "Pixel 4": { - "userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.56 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.6167.8 Mobile Safari/537.36", "viewport": { "width": 353, "height": 745 @@ -1319,7 +1319,7 @@ "defaultBrowserType": "chromium" }, "Pixel 4 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.56 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.6167.8 Mobile Safari/537.36", "viewport": { "width": 745, "height": 353 @@ -1330,7 +1330,7 @@ "defaultBrowserType": "chromium" }, "Pixel 4a (5G)": { - "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.56 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.6167.8 Mobile Safari/537.36", "screen": { "width": 412, "height": 892 @@ -1345,7 +1345,7 @@ "defaultBrowserType": "chromium" }, "Pixel 4a (5G) landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.56 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.6167.8 Mobile Safari/537.36", "screen": { "height": 892, "width": 412 @@ -1360,7 +1360,7 @@ "defaultBrowserType": "chromium" }, "Pixel 5": { - "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.56 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.6167.8 Mobile Safari/537.36", "screen": { "width": 393, "height": 851 @@ -1375,7 +1375,7 @@ "defaultBrowserType": "chromium" }, "Pixel 5 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.56 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.6167.8 Mobile Safari/537.36", "screen": { "width": 851, "height": 393 @@ -1390,7 +1390,7 @@ "defaultBrowserType": "chromium" }, "Pixel 7": { - "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.56 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.6167.8 Mobile Safari/537.36", "screen": { "width": 412, "height": 915 @@ -1405,7 +1405,7 @@ "defaultBrowserType": "chromium" }, "Pixel 7 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.56 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.6167.8 Mobile Safari/537.36", "screen": { "width": 915, "height": 412 @@ -1420,7 +1420,7 @@ "defaultBrowserType": "chromium" }, "Moto G4": { - "userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.56 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.6167.8 Mobile Safari/537.36", "viewport": { "width": 360, "height": 640 @@ -1431,7 +1431,7 @@ "defaultBrowserType": "chromium" }, "Moto G4 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.56 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.6167.8 Mobile Safari/537.36", "viewport": { "width": 640, "height": 360 @@ -1442,7 +1442,7 @@ "defaultBrowserType": "chromium" }, "Desktop Chrome HiDPI": { - "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.56 Safari/537.36", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.6167.8 Safari/537.36", "screen": { "width": 1792, "height": 1120 @@ -1457,7 +1457,7 @@ "defaultBrowserType": "chromium" }, "Desktop Edge HiDPI": { - "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.56 Safari/537.36 Edg/120.0.6099.56", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.6167.8 Safari/537.36 Edg/121.0.6167.8", "screen": { "width": 1792, "height": 1120 @@ -1502,7 +1502,7 @@ "defaultBrowserType": "webkit" }, "Desktop Chrome": { - "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.56 Safari/537.36", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.6167.8 Safari/537.36", "screen": { "width": 1920, "height": 1080 @@ -1517,7 +1517,7 @@ "defaultBrowserType": "chromium" }, "Desktop Edge": { - "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.56 Safari/537.36 Edg/120.0.6099.56", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.6167.8 Safari/537.36 Edg/121.0.6167.8", "screen": { "width": 1920, "height": 1080 diff --git a/packages/playwright-core/types/protocol.d.ts b/packages/playwright-core/types/protocol.d.ts index e6115a8e7f..49adee0f8b 100644 --- a/packages/playwright-core/types/protocol.d.ts +++ b/packages/playwright-core/types/protocol.d.ts @@ -675,7 +675,7 @@ may be used by the front-end as additional context. request?: AffectedRequest; } export type MixedContentResolutionStatus = "MixedContentBlocked"|"MixedContentAutomaticallyUpgraded"|"MixedContentWarning"; - export type MixedContentResourceType = "AttributionSrc"|"Audio"|"Beacon"|"CSPReport"|"Download"|"EventSource"|"Favicon"|"Font"|"Form"|"Frame"|"Image"|"Import"|"Manifest"|"Ping"|"PluginData"|"PluginResource"|"Prefetch"|"Resource"|"Script"|"ServiceWorker"|"SharedWorker"|"Stylesheet"|"Track"|"Video"|"Worker"|"XMLHttpRequest"|"XSLT"; + export type MixedContentResourceType = "AttributionSrc"|"Audio"|"Beacon"|"CSPReport"|"Download"|"EventSource"|"Favicon"|"Font"|"Form"|"Frame"|"Image"|"Import"|"Manifest"|"Ping"|"PluginData"|"PluginResource"|"Prefetch"|"Resource"|"Script"|"ServiceWorker"|"SharedWorker"|"SpeculationRules"|"Stylesheet"|"Track"|"Video"|"Worker"|"XMLHttpRequest"|"XSLT"; export interface MixedContentIssueDetails { /** * The type of resource causing the mixed content issue (css, js, iframe, @@ -1367,7 +1367,7 @@ events afterwards if enabled and recording. export type PermissionSetting = "granted"|"denied"|"prompt"; /** * Definition of PermissionDescriptor defined in the Permissions API: -https://w3c.github.io/permissions/#dictdef-permissiondescriptor. +https://w3c.github.io/permissions/#dom-permissiondescriptor. */ export interface PermissionDescriptor { /** @@ -2334,6 +2334,10 @@ A higher number has higher priority in the cascade order. * Font's family name reported by platform. */ familyName: string; + /** + * Font's PostScript name reported by platform. + */ + postScriptName: string; /** * Indicates if the font was downloaded or resolved locally. */ @@ -2464,6 +2468,28 @@ stylesheet rules) this rule came from. inherits: boolean; syntax: string; } + /** + * CSS font-palette-values rule representation. + */ + export interface CSSFontPaletteValuesRule { + /** + * The css style sheet identifier (absent for user agent stylesheet and user-specified +stylesheet rules) this rule came from. + */ + styleSheetId?: StyleSheetId; + /** + * Parent stylesheet's origin. + */ + origin: StyleSheetOrigin; + /** + * Associated font palette name. + */ + fontPaletteName: Value; + /** + * Associated style declaration. + */ + style: CSSStyle; + } /** * CSS property at-rule representation. */ @@ -2749,6 +2775,10 @@ attributes) for a DOM node identified by `nodeId`. * A list of CSS property registrations matching this node. */ cssPropertyRegistrations?: CSSPropertyRegistration[]; + /** + * A font-palette-values rule matching this node. + */ + cssFontPaletteValuesRule?: CSSFontPaletteValuesRule; /** * Id of the first parent element that does not have display: contents. */ @@ -5615,6 +5645,12 @@ A display feature that only splits content will have a 0 mask_length. */ maskLength: number; } + export interface DevicePosture { + /** + * Current posture of the device + */ + type: "continuous"|"folded"; + } export interface MediaFeature { name: string; value: string; @@ -5834,6 +5870,11 @@ change is not observed by the page, e.g. viewport-relative elements do not chang is turned-off. */ displayFeature?: DisplayFeature; + /** + * If set, the posture of a foldable device. If not set the posture is set +to continuous. + */ + devicePosture?: DevicePosture; } export type setDeviceMetricsOverrideReturnValue = { } @@ -6105,7 +6146,7 @@ on Android. */ userAgent: string; /** - * Browser langugage to emulate. + * Browser language to emulate. */ acceptLanguage?: string; /** @@ -8131,6 +8172,9 @@ records. * The reason why Chrome uses a specific transport protocol for HTTP semantics. */ export type AlternateProtocolUsage = "alternativeJobWonWithoutRace"|"alternativeJobWonRace"|"mainJobWonRace"|"mappingMissing"|"broken"|"dnsAlpnH3JobWonWithoutRace"|"dnsAlpnH3JobWonRace"|"unspecifiedReason"; + export interface ServiceWorkerRouterInfo { + ruleIdMatched: number; + } /** * HTTP response data. */ @@ -8195,6 +8239,10 @@ records. * Specifies that the request was served from the prefetch cache. */ fromPrefetchCache?: boolean; + /** + * Infomation about how Service Worker Static Router was used. + */ + serviceWorkerRouterInfo?: ServiceWorkerRouterInfo; /** * Total number of bytes received for this request so far. */ @@ -9957,7 +10005,7 @@ continueInterceptedRequest call. */ userAgent: string; /** - * Browser langugage to emulate. + * Browser language to emulate. */ acceptLanguage?: string; /** @@ -10889,7 +10937,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"|"ambient-light-sensor"|"attribution-reporting"|"autoplay"|"bluetooth"|"browsing-topics"|"camera"|"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-factor"|"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"|"cross-origin-isolated"|"direct-sockets"|"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"|"microphone"|"midi"|"otp-credentials"|"payment"|"picture-in-picture"|"private-aggregation"|"private-state-token-issuance"|"private-state-token-redemption"|"publickey-credentials-get"|"run-ad-auction"|"screen-wake-lock"|"serial"|"shared-autofill"|"shared-storage"|"shared-storage-select-url"|"smart-card"|"storage-access"|"sync-xhr"|"unload"|"usb"|"vertical-scroll"|"web-share"|"window-management"|"window-placement"|"xr-spatial-tracking"; + export type PermissionsPolicyFeature = "accelerometer"|"ambient-light-sensor"|"attribution-reporting"|"autoplay"|"bluetooth"|"browsing-topics"|"camera"|"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-factor"|"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"|"cross-origin-isolated"|"direct-sockets"|"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"|"microphone"|"midi"|"otp-credentials"|"payment"|"picture-in-picture"|"private-aggregation"|"private-state-token-issuance"|"private-state-token-redemption"|"publickey-credentials-get"|"run-ad-auction"|"screen-wake-lock"|"serial"|"shared-autofill"|"shared-storage"|"shared-storage-select-url"|"smart-card"|"storage-access"|"sync-xhr"|"unload"|"usb"|"usb-unrestricted"|"vertical-scroll"|"web-printing"|"web-share"|"window-management"|"window-placement"|"xr-spatial-tracking"; /** * Reason for a permissions policy feature to be disabled. */ @@ -11988,19 +12036,6 @@ as an ad. Only sent if frame is labelled as an ad and id is available. */ adScriptId?: AdScriptId; } - /** - * Returns all browser cookies for the page and all of its subframes. Depending -on the backend support, will return detailed cookie information in the -`cookies` field. - */ - export type getCookiesParameters = { - } - export type getCookiesReturnValue = { - /** - * Array of cookie objects. - */ - cookies: Network.Cookie[]; - } /** * Returns present frame tree structure. */ @@ -12250,6 +12285,10 @@ in which case the content will be scaled to fit the paper size. * Whether or not to generate tagged (accessible) PDF. Defaults to embedder choice. */ generateTaggedPDF?: boolean; + /** + * Whether or not to embed the document outline into the PDF. + */ + generateDocumentOutline?: boolean; } export type printToPDFReturnValue = { /** @@ -13228,6 +13267,7 @@ For cached script it is the last time the cache entry was validated. scriptResponseTime?: number; controlledClients?: Target.TargetID[]; targetId?: Target.TargetID; + routerRules?: string; } /** * ServiceWorker error message. @@ -13510,6 +13550,14 @@ SharedStorageAccessType.workletSet. */ ends: number[]; } + export interface AttributionReportingTriggerSpec { + /** + * number instead of integer because not all uint32 can be represented by +int + */ + triggerData: number[]; + eventReportWindows: AttributionReportingEventReportWindows; + } export type AttributionReportingTriggerDataMatching = "exact"|"modulus"; export interface AttributionReportingSourceRegistration { time: Network.TimeSinceEpoch; @@ -13517,7 +13565,7 @@ SharedStorageAccessType.workletSet. * duration in seconds */ expiry: number; - eventReportWindows: AttributionReportingEventReportWindows; + triggerSpecs: AttributionReportingTriggerSpec[]; /** * duration in seconds */ @@ -15623,6 +15671,18 @@ Otherwise, they will not be resolved. Defaults to true. Defaults to false. */ isUserVerified?: boolean; + /** + * Credentials created by this authenticator will have the backup +eligibility (BE) flag set to this value. Defaults to false. +https://w3c.github.io/webauthn/#sctn-credential-backup + */ + defaultBackupEligibility?: boolean; + /** + * Credentials created by this authenticator will have the backup state +(BS) flag set to this value. Defaults to false. +https://w3c.github.io/webauthn/#sctn-credential-backup + */ + defaultBackupState?: boolean; } export interface Credential { credentialId: binary; @@ -16093,6 +16153,14 @@ status is shared by prefetchStatusUpdated and prerenderStatusUpdated. filter out the ones that aren't necessary to the developers. */ export type PrefetchStatus = "PrefetchAllowed"|"PrefetchFailedIneligibleRedirect"|"PrefetchFailedInvalidRedirect"|"PrefetchFailedMIMENotSupported"|"PrefetchFailedNetError"|"PrefetchFailedNon2XX"|"PrefetchFailedPerPageLimitExceeded"|"PrefetchEvicted"|"PrefetchHeldback"|"PrefetchIneligibleRetryAfter"|"PrefetchIsPrivacyDecoy"|"PrefetchIsStale"|"PrefetchNotEligibleBrowserContextOffTheRecord"|"PrefetchNotEligibleDataSaverEnabled"|"PrefetchNotEligibleExistingProxy"|"PrefetchNotEligibleHostIsNonUnique"|"PrefetchNotEligibleNonDefaultStoragePartition"|"PrefetchNotEligibleSameSiteCrossOriginPrefetchRequiredProxy"|"PrefetchNotEligibleSchemeIsNotHttps"|"PrefetchNotEligibleUserHasCookies"|"PrefetchNotEligibleUserHasServiceWorker"|"PrefetchNotEligibleBatterySaverEnabled"|"PrefetchNotEligiblePreloadingDisabled"|"PrefetchNotFinishedInTime"|"PrefetchNotStarted"|"PrefetchNotUsedCookiesChanged"|"PrefetchProxyNotAvailable"|"PrefetchResponseUsed"|"PrefetchSuccessfulButNotUsed"|"PrefetchNotUsedProbeFailed"; + /** + * Information of headers to be displayed when the header mismatch occurred. + */ + export interface PrerenderMismatchedHeaders { + headerName: string; + initialValue?: string; + activationValue?: string; + } /** * Upsert. Currently, it is only emitted when a rule set added. @@ -16139,6 +16207,7 @@ filter out the ones that aren't necessary to the developers. that is incompatible with prerender and has caused the cancellation of the attempt. */ disallowedMojoInterface?: string; + mismatchedHeaders?: PrerenderMismatchedHeaders[]; } /** * Send a list of sources for all preloading attempts in a document. @@ -16168,9 +16237,13 @@ whether this account has ever been used to sign in to this RP before. */ export type LoginState = "SignIn"|"SignUp"; /** - * Whether the dialog shown is an account chooser or an auto re-authentication dialog. + * The types of FedCM dialogs. */ - export type DialogType = "AccountChooser"|"AutoReauthn"|"ConfirmIdpLogin"; + export type DialogType = "AccountChooser"|"AutoReauthn"|"ConfirmIdpLogin"|"Error"; + /** + * The buttons on the FedCM dialog. + */ + export type DialogButton = "ConfirmIdpLoginContinue"|"ErrorGotIt"|"ErrorMoreDetails"; /** * Corresponds to IdentityRequestAccount */ @@ -16201,6 +16274,13 @@ RP context was used appropriately. title: string; subtitle?: string; } + /** + * Triggered when a dialog is closed, either by user action, JS abort, +or a command below. + */ + export type dialogClosedPayload = { + dialogId: string; + } export type enableParameters = { /** @@ -16222,14 +16302,11 @@ normally happen, if this is unimportant to what's being tested. } export type selectAccountReturnValue = { } - /** - * Only valid if the dialog type is ConfirmIdpLogin. Acts as if the user had -clicked the continue button. - */ - export type confirmIdpLoginParameters = { + export type clickDialogButtonParameters = { dialogId: string; + dialogButton: DialogButton; } - export type confirmIdpLoginReturnValue = { + export type clickDialogButtonReturnValue = { } export type dismissDialogParameters = { dialogId: string; @@ -19044,6 +19121,7 @@ Error was thrown. "Preload.prerenderStatusUpdated": Preload.prerenderStatusUpdatedPayload; "Preload.preloadingAttemptSourcesUpdated": Preload.preloadingAttemptSourcesUpdatedPayload; "FedCm.dialogShown": FedCm.dialogShownPayload; + "FedCm.dialogClosed": FedCm.dialogClosedPayload; "Console.messageAdded": Console.messageAddedPayload; "Debugger.breakpointResolved": Debugger.breakpointResolvedPayload; "Debugger.paused": Debugger.pausedPayload; @@ -19401,7 +19479,6 @@ Error was thrown. "Page.getManifestIcons": Page.getManifestIconsParameters; "Page.getAppId": Page.getAppIdParameters; "Page.getAdScriptId": Page.getAdScriptIdParameters; - "Page.getCookies": Page.getCookiesParameters; "Page.getFrameTree": Page.getFrameTreeParameters; "Page.getLayoutMetrics": Page.getLayoutMetricsParameters; "Page.getNavigationHistory": Page.getNavigationHistoryParameters; @@ -19562,7 +19639,7 @@ Error was thrown. "FedCm.enable": FedCm.enableParameters; "FedCm.disable": FedCm.disableParameters; "FedCm.selectAccount": FedCm.selectAccountParameters; - "FedCm.confirmIdpLogin": FedCm.confirmIdpLoginParameters; + "FedCm.clickDialogButton": FedCm.clickDialogButtonParameters; "FedCm.dismissDialog": FedCm.dismissDialogParameters; "FedCm.resetCooldown": FedCm.resetCooldownParameters; "Console.clearMessages": Console.clearMessagesParameters; @@ -19980,7 +20057,6 @@ Error was thrown. "Page.getManifestIcons": Page.getManifestIconsReturnValue; "Page.getAppId": Page.getAppIdReturnValue; "Page.getAdScriptId": Page.getAdScriptIdReturnValue; - "Page.getCookies": Page.getCookiesReturnValue; "Page.getFrameTree": Page.getFrameTreeReturnValue; "Page.getLayoutMetrics": Page.getLayoutMetricsReturnValue; "Page.getNavigationHistory": Page.getNavigationHistoryReturnValue; @@ -20141,7 +20217,7 @@ Error was thrown. "FedCm.enable": FedCm.enableReturnValue; "FedCm.disable": FedCm.disableReturnValue; "FedCm.selectAccount": FedCm.selectAccountReturnValue; - "FedCm.confirmIdpLogin": FedCm.confirmIdpLoginReturnValue; + "FedCm.clickDialogButton": FedCm.clickDialogButtonReturnValue; "FedCm.dismissDialog": FedCm.dismissDialogReturnValue; "FedCm.resetCooldown": FedCm.resetCooldownReturnValue; "Console.clearMessages": Console.clearMessagesReturnValue; From dd9028cfe24a45de47ab1dfd5aa212537c2629e7 Mon Sep 17 00:00:00 2001 From: Andrey Lushnikov Date: Fri, 8 Dec 2023 15:26:24 -0800 Subject: [PATCH 154/491] test: fix browsercontext-basic offline test (#28558) Since the last firefox roll, firefox now does internal redirect to the error page when it's been forced into offline mode. --- tests/library/browsercontext-basic.spec.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/tests/library/browsercontext-basic.spec.ts b/tests/library/browsercontext-basic.spec.ts index 21ea63f860..0fd1f98390 100644 --- a/tests/library/browsercontext-basic.spec.ts +++ b/tests/library/browsercontext-basic.spec.ts @@ -233,11 +233,21 @@ it('setContent should work after disabling javascript', async ({ contextFactory await expect(page.locator('h1')).toHaveText('Hello'); }); -it('should work with offline option', async ({ browser, server }) => { +it('should work with offline option', async ({ browser, server, browserName }) => { const context = await browser.newContext({ offline: true }); const page = await context.newPage(); let error = null; - await page.goto(server.EMPTY_PAGE).catch(e => error = e); + if (browserName === 'firefox') { + // Firefox navigates to an error page, and this navigation might conflict with the + // next navigation we do in test. + // So we need to wait for the navigation explicitly. + await Promise.all([ + page.goto(server.EMPTY_PAGE).catch(e => error = e), + page.waitForEvent('framenavigated'), + ]); + } else { + await page.goto(server.EMPTY_PAGE).catch(e => error = e); + } expect(error).toBeTruthy(); await context.setOffline(false); const response = await page.goto(server.EMPTY_PAGE); From 23415da3db5b037da22e370c338d7f8587fe20b1 Mon Sep 17 00:00:00 2001 From: Playwright Service <89237858+playwrightmachine@users.noreply.github.com> Date: Mon, 11 Dec 2023 09:30:11 -0800 Subject: [PATCH 155/491] feat(webkit): roll to r1953 (#28574) Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> --- packages/playwright-core/browsers.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/playwright-core/browsers.json b/packages/playwright-core/browsers.json index 87be163608..ff779fc6e3 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -39,7 +39,7 @@ }, { "name": "webkit", - "revision": "1951", + "revision": "1953", "installByDefault": true, "revisionOverrides": { "mac10.14": "1446", From ee9a7dad12b02cfddd48b0a0cdd6c09cb9f5fd88 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Mon, 11 Dec 2023 17:35:29 -0800 Subject: [PATCH 156/491] docs: emphasize that `has` is a relative locator (#28588) References #28556. --- docs/src/api/params.md | 4 +- docs/src/locators.md | 57 +++++++++++++++++++++-- packages/playwright-core/types/types.d.ts | 45 ++++++++++++++---- 3 files changed, 92 insertions(+), 14 deletions(-) diff --git a/docs/src/api/params.md b/docs/src/api/params.md index 441ad4e544..c2c9e33c0d 100644 --- a/docs/src/api/params.md +++ b/docs/src/api/params.md @@ -1023,9 +1023,11 @@ For example, `"Playwright"` matches `
Playwright
`. ## locator-option-has - `has` <[Locator]> -Matches elements containing an element that matches an inner locator. Inner locator is queried against the outer one. +Narrows down the results of the method to those which contain elements matching this relative locator. For example, `article` that has `text=Playwright` matches `
Playwright
`. +Inner locator **must be relative** to the outer locator and is queried starting with the outer locator match, not the document root. For example, you can find `content` that has `div` in `
Playwright
`. However, looking for `content` that has `article div` will fail, because the inner locator must be relative and should not use any elements outside the `content`. + Note that outer and inner locators must belong to the same frame. Inner locator must not contain [FrameLocator]s. ## locator-option-has-not diff --git a/docs/src/locators.md b/docs/src/locators.md index ee036dbf79..720e5618ca 100644 --- a/docs/src/locators.md +++ b/docs/src/locators.md @@ -973,19 +973,21 @@ await page .ClickAsync(); ``` -We can also assert the product card to make sure there is only one +We can also assert the product card to make sure there is only one: ```js await expect(page .getByRole('listitem') - .filter({ has: page.getByText('Product 2') })) + .filter({ has: page.getByRole('heading', { name: 'Product 2' }) })) .toHaveCount(1); ``` ```java assertThat(page .getByRole(AriaRole.LISTITEM) - .filter(new Locator.FilterOptions().setHas(page.getByText("Product 2"))) + .filter(new Locator.FilterOptions() + .setHas(page.GetByRole(AriaRole.HEADING, + new Page.GetByRoleOptions().setName("Product 2")))) .hasCount(1); ``` @@ -1014,6 +1016,55 @@ await Expect(Page .ToHaveCountAsync(1); ``` +The filtering locator **must be relative** to the original locator and is queried starting with the original locator match, not the document root. Therefore, the following will not work, because the filtering locator starts matching from the `
    ` list element that is outside of the `
  • ` list item matched by the original locator: + +```js +// ✖ WRONG +await expect(page + .getByRole('listitem') + .filter({ has: page.getByRole('list').getByText('Product 2') })) + .toHaveCount(1); +``` + +```java +// ✖ WRONG +assertThat(page + .getByRole(AriaRole.LISTITEM) + .filter(new Locator.FilterOptions() + .setHas(page.GetByRole(AriaRole.LIST) + .GetByRole(AriaRole.HEADING, + new Page.GetByRoleOptions().setName("Product 2")))) + .hasCount(1); +``` + +```python async +# ✖ WRONG +await expect( + page.get_by_role("listitem").filter( + has=page.get_by_role("list").get_by_role("heading", name="Product 2") + ) +).to_have_count(1) +``` + +```python sync +# ✖ WRONG +expect( + page.get_by_role("listitem").filter( + has=page.get_by_role("list").get_by_role("heading", name="Product 2") + ) +).to_have_count(1) +``` + +```csharp +// ✖ WRONG +await Expect(Page + .GetByRole(AriaRole.Listitem) + .Filter(new() { + Has = page.GetByRole(AriaRole.List).GetByRole(AriaRole.Heading, new() { Name = "Product 2" }) + })) + .ToHaveCountAsync(1); +``` + ### Filter by not having child/descendant We can also filter by **not having** a matching element inside. diff --git a/packages/playwright-core/types/types.d.ts b/packages/playwright-core/types/types.d.ts index ffb24f26aa..0a6fea4859 100644 --- a/packages/playwright-core/types/types.d.ts +++ b/packages/playwright-core/types/types.d.ts @@ -3243,8 +3243,13 @@ export interface Page { */ locator(selector: string, options?: { /** - * Matches elements containing an element that matches an inner locator. Inner locator is queried against the outer - * one. For example, `article` that has `text=Playwright` matches `
    Playwright
    `. + * Narrows down the results of the method to those which contain elements matching this relative locator. For example, + * `article` that has `text=Playwright` matches `
    Playwright
    `. + * + * Inner locator **must be relative** to the outer locator and is queried starting with the outer locator match, not + * the document root. For example, you can find `content` that has `div` in + * `
    Playwright
    `. However, looking for `content` that has `article + * div` will fail, because the inner locator must be relative and should not use any elements outside the `content`. * * Note that outer and inner locators must belong to the same frame. Inner locator must not contain {@link * FrameLocator}s. @@ -6676,8 +6681,13 @@ export interface Frame { */ locator(selector: string, options?: { /** - * Matches elements containing an element that matches an inner locator. Inner locator is queried against the outer - * one. For example, `article` that has `text=Playwright` matches `
    Playwright
    `. + * Narrows down the results of the method to those which contain elements matching this relative locator. For example, + * `article` that has `text=Playwright` matches `
    Playwright
    `. + * + * Inner locator **must be relative** to the outer locator and is queried starting with the outer locator match, not + * the document root. For example, you can find `content` that has `div` in + * `
    Playwright
    `. However, looking for `content` that has `article + * div` will fail, because the inner locator must be relative and should not use any elements outside the `content`. * * Note that outer and inner locators must belong to the same frame. Inner locator must not contain {@link * FrameLocator}s. @@ -11252,8 +11262,13 @@ export interface Locator { */ filter(options?: { /** - * Matches elements containing an element that matches an inner locator. Inner locator is queried against the outer - * one. For example, `article` that has `text=Playwright` matches `
    Playwright
    `. + * Narrows down the results of the method to those which contain elements matching this relative locator. For example, + * `article` that has `text=Playwright` matches `
    Playwright
    `. + * + * Inner locator **must be relative** to the outer locator and is queried starting with the outer locator match, not + * the document root. For example, you can find `content` that has `div` in + * `
    Playwright
    `. However, looking for `content` that has `article + * div` will fail, because the inner locator must be relative and should not use any elements outside the `content`. * * Note that outer and inner locators must belong to the same frame. Inner locator must not contain {@link * FrameLocator}s. @@ -11956,8 +11971,13 @@ export interface Locator { */ locator(selectorOrLocator: string|Locator, options?: { /** - * Matches elements containing an element that matches an inner locator. Inner locator is queried against the outer - * one. For example, `article` that has `text=Playwright` matches `
    Playwright
    `. + * Narrows down the results of the method to those which contain elements matching this relative locator. For example, + * `article` that has `text=Playwright` matches `
    Playwright
    `. + * + * Inner locator **must be relative** to the outer locator and is queried starting with the outer locator match, not + * the document root. For example, you can find `content` that has `div` in + * `
    Playwright
    `. However, looking for `content` that has `article + * div` will fail, because the inner locator must be relative and should not use any elements outside the `content`. * * Note that outer and inner locators must belong to the same frame. Inner locator must not contain {@link * FrameLocator}s. @@ -17735,8 +17755,13 @@ export interface FrameLocator { */ locator(selectorOrLocator: string|Locator, options?: { /** - * Matches elements containing an element that matches an inner locator. Inner locator is queried against the outer - * one. For example, `article` that has `text=Playwright` matches `
    Playwright
    `. + * Narrows down the results of the method to those which contain elements matching this relative locator. For example, + * `article` that has `text=Playwright` matches `
    Playwright
    `. + * + * Inner locator **must be relative** to the outer locator and is queried starting with the outer locator match, not + * the document root. For example, you can find `content` that has `div` in + * `
    Playwright
    `. However, looking for `content` that has `article + * div` will fail, because the inner locator must be relative and should not use any elements outside the `content`. * * Note that outer and inner locators must belong to the same frame. Inner locator must not contain {@link * FrameLocator}s. From ac3600ec968a5f75b32e9ff2075e08b01b0a4f37 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Mon, 11 Dec 2023 17:35:39 -0800 Subject: [PATCH 157/491] feat: explain that argument is a regex (#28590) - in docs; - in the error message. Terminal output: ``` $ npx playwright test foobar Error: No tests found. Make sure that arguments are regular expressions matching test files. You may need to escape symbols like "$" or "*" and quote the arguments. ``` References #28551. --- docs/src/test-cli-js.md | 1 + packages/playwright/src/runner/tasks.ts | 10 +++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/docs/src/test-cli-js.md b/docs/src/test-cli-js.md index 8c5666acaf..29ff2e6b9f 100644 --- a/docs/src/test-cli-js.md +++ b/docs/src/test-cli-js.md @@ -78,6 +78,7 @@ Complete set of Playwright Test options is available in the [configuration file] | Option | Description | | :- | :- | +| Non-option arguments | Each argument is treated as a regular expression matched against the full test file path. Only tests from the files matching the pattern will be executed. Special symbols like `$` or `*` should be escaped with `\`. In many shells/terminals you may need to quote the arguments. | | `--headed` | Run tests in headed browsers. Useful for debugging. | |`--browser`| Run test in a specific browser. Available options are `"chromium"`, `"firefox"`, `"webkit"` or `"all"` to run tests in all three browsers at the same time. | | `--debug`| Run tests with Playwright Inspector. Shortcut for `PWDEBUG=1` environment variable and `--timeout=0 --max-failures=1 --headed --workers=1` options.| diff --git a/packages/playwright/src/runner/tasks.ts b/packages/playwright/src/runner/tasks.ts index 94095eaf61..7f874aef4e 100644 --- a/packages/playwright/src/runner/tasks.ts +++ b/packages/playwright/src/runner/tasks.ts @@ -198,8 +198,16 @@ function createLoadTask(mode: 'out-of-process' | 'in-process', options: { filter testRun.rootSuite = await createRootSuite(testRun, options.failOnLoadErrors ? errors : softErrors, !!options.filterOnly); testRun.failureTracker.onRootSuite(testRun.rootSuite); // Fail when no tests. - if (options.failOnLoadErrors && !testRun.rootSuite.allTests().length && !testRun.config.cliPassWithNoTests && !testRun.config.config.shard) + if (options.failOnLoadErrors && !testRun.rootSuite.allTests().length && !testRun.config.cliPassWithNoTests && !testRun.config.config.shard) { + if (testRun.config.cliArgs.length) { + throw new Error([ + `No tests found.`, + `Make sure that arguments are regular expressions matching test files.`, + `You may need to escape symbols like "$" or "*" and quote the arguments.`, + ].join('\n')); + } throw new Error(`No tests found`); + } }, }; } From 76ace0fc09d92fedf2137cebd9a8a1427daa9911 Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Mon, 11 Dec 2023 18:20:24 -0800 Subject: [PATCH 158/491] chore: workaround webkit screenshot animation issue (#28582) --- .../playwright-core/src/server/chromium/crPage.ts | 4 ++++ .../playwright-core/src/server/firefox/ffPage.ts | 4 ++++ packages/playwright-core/src/server/page.ts | 2 ++ .../playwright-core/src/server/screenshotter.ts | 14 ++++++++++++-- .../playwright-core/src/server/webkit/wkPage.ts | 4 ++++ tests/page/page-screenshot.spec.ts | 2 -- 6 files changed, 26 insertions(+), 4 deletions(-) diff --git a/packages/playwright-core/src/server/chromium/crPage.ts b/packages/playwright-core/src/server/chromium/crPage.ts index 2aa98d4f7f..1dd5237368 100644 --- a/packages/playwright-core/src/server/chromium/crPage.ts +++ b/packages/playwright-core/src/server/chromium/crPage.ts @@ -382,6 +382,10 @@ export class CRPage implements PageDelegate { throw new Error('Frame has been detached.'); return parentSession._adoptBackendNodeId(backendNodeId, await parent._mainContext()); } + + shouldToggleStyleSheetToSyncAnimations(): boolean { + return false; + } } class FrameSession { diff --git a/packages/playwright-core/src/server/firefox/ffPage.ts b/packages/playwright-core/src/server/firefox/ffPage.ts index 389c24ccfd..8b3876036c 100644 --- a/packages/playwright-core/src/server/firefox/ffPage.ts +++ b/packages/playwright-core/src/server/firefox/ffPage.ts @@ -594,6 +594,10 @@ export class FFPage implements PageDelegate { throw new Error('Frame has been detached.'); return context.createHandle(result.remoteObject) as dom.ElementHandle; } + + shouldToggleStyleSheetToSyncAnimations(): boolean { + return false; + } } function webSocketId(frameId: string, wsid: string): string { diff --git a/packages/playwright-core/src/server/page.ts b/packages/playwright-core/src/server/page.ts index da84d32385..a00d09cfe3 100644 --- a/packages/playwright-core/src/server/page.ts +++ b/packages/playwright-core/src/server/page.ts @@ -97,6 +97,8 @@ export interface PageDelegate { readonly cspErrorsAsynchronousForInlineScipts?: boolean; // Work around for mouse position in Firefox. resetForReuse(): Promise; + // WebKit hack. + shouldToggleStyleSheetToSyncAnimations(): boolean; } type EmulatedSize = { screen: types.Size, viewport: types.Size }; diff --git a/packages/playwright-core/src/server/screenshotter.ts b/packages/playwright-core/src/server/screenshotter.ts index 1ca0542198..696d929692 100644 --- a/packages/playwright-core/src/server/screenshotter.ts +++ b/packages/playwright-core/src/server/screenshotter.ts @@ -46,7 +46,16 @@ export type ScreenshotOptions = { style?: string; }; -function inPagePrepareForScreenshots(screenshotStyle: string, hideCaret: boolean, disableAnimations: boolean) { +function inPagePrepareForScreenshots(screenshotStyle: string, hideCaret: boolean, disableAnimations: boolean, syncAnimations: boolean) { + // In WebKit, sync the animations. + if (syncAnimations) { + const style = document.createElement('style'); + style.textContent = 'body {}'; + document.head.appendChild(style); + document.documentElement.getBoundingClientRect(); + style.remove(); + } + if (!screenshotStyle && !hideCaret && !disableAnimations) return; @@ -251,8 +260,9 @@ export class Screenshotter { async _preparePageForScreenshot(progress: Progress, screenshotStyle: string | undefined, hideCaret: boolean, disableAnimations: boolean) { if (disableAnimations) progress.log(' disabled all CSS animations'); + const syncAnimations = this._page._delegate.shouldToggleStyleSheetToSyncAnimations(); await Promise.all(this._page.frames().map(async frame => { - await frame.nonStallingEvaluateInExistingContext('(' + inPagePrepareForScreenshots.toString() + `)(${JSON.stringify(screenshotStyle)}, ${hideCaret}, ${disableAnimations})`, false, 'utility').catch(() => {}); + await frame.nonStallingEvaluateInExistingContext('(' + inPagePrepareForScreenshots.toString() + `)(${JSON.stringify(screenshotStyle)}, ${hideCaret}, ${disableAnimations}, ${syncAnimations})`, false, 'utility').catch(() => {}); })); if (!process.env.PW_TEST_SCREENSHOT_NO_FONTS_READY) { progress.log('waiting for fonts to load...'); diff --git a/packages/playwright-core/src/server/webkit/wkPage.ts b/packages/playwright-core/src/server/webkit/wkPage.ts index 6c219bf437..24d6ef63f6 100644 --- a/packages/playwright-core/src/server/webkit/wkPage.ts +++ b/packages/playwright-core/src/server/webkit/wkPage.ts @@ -1184,6 +1184,10 @@ export class WKPage implements PageDelegate { async _clearPermissions() { await this._pageProxySession.send('Emulation.resetPermissions', {}); } + + shouldToggleStyleSheetToSyncAnimations(): boolean { + return true; + } } /** diff --git a/tests/page/page-screenshot.spec.ts b/tests/page/page-screenshot.spec.ts index aaf051332e..fd7b8e91a4 100644 --- a/tests/page/page-screenshot.spec.ts +++ b/tests/page/page-screenshot.spec.ts @@ -691,7 +691,6 @@ it.describe('page screenshot animations', () => { }); it('should fire transitionend for finite transitions', async ({ page, server, browserName, platform }) => { - it.fixme(browserName === 'webkit' && platform === 'linux'); await page.goto(server.PREFIX + '/css-transition.html'); const div = page.locator('div'); await div.evaluate(el => { @@ -718,7 +717,6 @@ it.describe('page screenshot animations', () => { }); it('should capture screenshots after layoutchanges in transitionend event', async ({ page, server, browserName, platform }) => { - it.fixme(browserName === 'webkit' && platform === 'linux'); await page.goto(server.PREFIX + '/css-transition.html'); const div = page.locator('div'); await div.evaluate(el => { From d20a20b9b6f45103879c42351b9d5dabca76d049 Mon Sep 17 00:00:00 2001 From: Playwright Service <89237858+playwrightmachine@users.noreply.github.com> Date: Mon, 11 Dec 2023 18:42:34 -0800 Subject: [PATCH 159/491] feat(webkit): roll to r1954 (#28592) Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> --- packages/playwright-core/browsers.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/playwright-core/browsers.json b/packages/playwright-core/browsers.json index ff779fc6e3..41f562244f 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -39,7 +39,7 @@ }, { "name": "webkit", - "revision": "1953", + "revision": "1954", "installByDefault": true, "revisionOverrides": { "mac10.14": "1446", From 10dda30c7f417a92f782e21368fbb211a679838a Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Mon, 11 Dec 2023 21:18:48 -0800 Subject: [PATCH 160/491] fix(reporters): carefully handle empty lines (#28591) In some circumstances, like "No tests found" error, reporters produce a lot of unnecessary empty lines: ``` $ npx playwright test Error: No tests found $ ``` Also, `line` reporter removes the `npx playwright test` command entirely when `onError` happens before `onBegin`. --- packages/playwright/src/reporters/base.ts | 2 +- packages/playwright/src/reporters/line.ts | 15 ++++++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/packages/playwright/src/reporters/base.ts b/packages/playwright/src/reporters/base.ts index 7e94cb2f6a..de5a26640f 100644 --- a/packages/playwright/src/reporters/base.ts +++ b/packages/playwright/src/reporters/base.ts @@ -470,7 +470,7 @@ export function formatError(error: TestError, highlightCode: boolean): ErrorDeta tokens.push(snippet); } - if (parsedStack) { + if (parsedStack && parsedStack.stackLines.length) { tokens.push(''); tokens.push(colors.dim(parsedStack.stackLines.join('\n'))); } diff --git a/packages/playwright/src/reporters/line.ts b/packages/playwright/src/reporters/line.ts index a44f29d02e..7d875d0f31 100644 --- a/packages/playwright/src/reporters/line.ts +++ b/packages/playwright/src/reporters/line.ts @@ -21,6 +21,7 @@ class LineReporter extends BaseReporter { private _current = 0; private _failures = 0; private _lastTest: TestCase | undefined; + private _didBegin = false; override printsToStdio() { return true; @@ -28,8 +29,12 @@ class LineReporter extends BaseReporter { override onBegin(suite: Suite) { super.onBegin(suite); - console.log(this.generateStartingMessage()); - console.log(); + const startingMessage = this.generateStartingMessage(); + if (startingMessage) { + console.log(startingMessage); + console.log(); + } + this._didBegin = true; } override onStdOut(chunk: string | Buffer, test?: TestCase, result?: TestResult) { @@ -105,15 +110,15 @@ class LineReporter extends BaseReporter { override onError(error: TestError): void { super.onError(error); - const message = formatError(error, colors.enabled).message + '\n\n'; - if (!process.env.PW_TEST_DEBUG_REPORTERS) + const message = formatError(error, colors.enabled).message + '\n'; + if (!process.env.PW_TEST_DEBUG_REPORTERS && this._didBegin) process.stdout.write(`\u001B[1A\u001B[2K`); process.stdout.write(message); console.log(); } override async onEnd(result: FullResult) { - if (!process.env.PW_TEST_DEBUG_REPORTERS) + if (!process.env.PW_TEST_DEBUG_REPORTERS && this._didBegin) process.stdout.write(`\u001B[1A\u001B[2K`); await super.onEnd(result); this.epilogue(false); From af5a23e55ffcf3385ba1410980ad19d4795c89bd Mon Sep 17 00:00:00 2001 From: Playwright Service <89237858+playwrightmachine@users.noreply.github.com> Date: Tue, 12 Dec 2023 11:27:51 -0800 Subject: [PATCH 161/491] feat(chromium-tip-of-tree): roll to r1175 (#28605) 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 41f562244f..844dabd97c 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -15,9 +15,9 @@ }, { "name": "chromium-tip-of-tree", - "revision": "1174", + "revision": "1175", "installByDefault": false, - "browserVersion": "122.0.6172.0" + "browserVersion": "122.0.6179.2" }, { "name": "firefox", From 66e056c306bc2aca7fba22a1db15a5fe56c06003 Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Tue, 12 Dec 2023 12:20:44 -0800 Subject: [PATCH 162/491] fix: disable PaintHolding to be able to click in oopifs (#28604) Fixes https://github.com/microsoft/playwright/issues/28023 --- .../src/server/chromium/chromiumSwitches.ts | 3 ++- tests/library/chromium/oopif.spec.ts | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/packages/playwright-core/src/server/chromium/chromiumSwitches.ts b/packages/playwright-core/src/server/chromium/chromiumSwitches.ts index 65e458e81f..c3ceaeaaec 100644 --- a/packages/playwright-core/src/server/chromium/chromiumSwitches.ts +++ b/packages/playwright-core/src/server/chromium/chromiumSwitches.ts @@ -35,7 +35,8 @@ export const chromiumSwitches = [ // AvoidUnnecessaryBeforeUnloadCheckSync - https://github.com/microsoft/playwright/issues/14047 // Translate - https://github.com/microsoft/playwright/issues/16126 // HttpsUpgrades - https://github.com/microsoft/playwright/pull/27605 - '--disable-features=ImprovedCookieControls,LazyFrameLoading,GlobalMediaControls,DestroyProfileOnBrowserClose,MediaRouter,DialMediaRouteProvider,AcceptCHFrame,AutoExpandDetailsElement,CertificateTransparencyComponentUpdater,AvoidUnnecessaryBeforeUnloadCheckSync,Translate,HttpsUpgrades', + // PaintHolding - https://github.com/microsoft/playwright/issues/28023 + '--disable-features=ImprovedCookieControls,LazyFrameLoading,GlobalMediaControls,DestroyProfileOnBrowserClose,MediaRouter,DialMediaRouteProvider,AcceptCHFrame,AutoExpandDetailsElement,CertificateTransparencyComponentUpdater,AvoidUnnecessaryBeforeUnloadCheckSync,Translate,HttpsUpgrades,PaintHolding', '--allow-pre-commit-input', '--disable-hang-monitor', '--disable-ipc-flooding-protection', diff --git a/tests/library/chromium/oopif.spec.ts b/tests/library/chromium/oopif.spec.ts index 8a8db75745..5324e6d155 100644 --- a/tests/library/chromium/oopif.spec.ts +++ b/tests/library/chromium/oopif.spec.ts @@ -327,6 +327,20 @@ it('should emit filechooser event for iframe', async ({ page, server, browser }) expect(chooser).toBeTruthy(); }); +it('should be able to click in iframe', async ({ page, server, browser }) => { + it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/28023' }); + await page.goto(server.PREFIX + '/dynamic-oopif.html'); + expect(await countOOPIFs(browser)).toBe(1); + expect(page.frames().length).toBe(2); + const frame = page.frames()[1]; + await frame.setContent(``); + const [message] = await Promise.all([ + page.waitForEvent('console'), + frame.click('button'), + ]); + expect(message.text()).toBe('clicked'); +}); + it('should not throw on exposeFunction when oopif detaches', async ({ page, browser, server }) => { await page.goto(server.PREFIX + '/dynamic-oopif.html'); expect(await countOOPIFs(browser)).toBe(1); From d2dc8eb1e3e5342550dcc34d50500f8ceb75cd78 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Tue, 12 Dec 2023 14:01:01 -0800 Subject: [PATCH 163/491] fix(components): make sure defineConfig(c1, c2) works (#28608) --- packages/playwright-ct-core/index.d.ts | 3 ++ packages/playwright-ct-core/index.js | 4 +-- packages/playwright-ct-react/index.d.ts | 3 ++ packages/playwright-ct-react/index.js | 2 +- packages/playwright-ct-react17/index.d.ts | 3 ++ packages/playwright-ct-react17/index.js | 2 +- packages/playwright-ct-solid/index.d.ts | 3 ++ packages/playwright-ct-solid/index.js | 2 +- packages/playwright-ct-svelte/index.d.ts | 3 ++ packages/playwright-ct-svelte/index.js | 2 +- packages/playwright-ct-vue/index.d.ts | 3 ++ packages/playwright-ct-vue/index.js | 2 +- packages/playwright-ct-vue2/index.d.ts | 3 ++ packages/playwright-ct-vue2/index.js | 2 +- .../playwright/src/common/configLoader.ts | 4 +++ tests/playwright-test/config.spec.ts | 34 +++++++++++++++++++ tests/playwright-test/types.spec.ts | 20 +++++++++++ 17 files changed, 87 insertions(+), 8 deletions(-) diff --git a/packages/playwright-ct-core/index.d.ts b/packages/playwright-ct-core/index.d.ts index 397a4cdb7f..cd052db756 100644 --- a/packages/playwright-ct-core/index.d.ts +++ b/packages/playwright-ct-core/index.d.ts @@ -62,5 +62,8 @@ export const test: TestType< export function defineConfig(config: PlaywrightTestConfig): PlaywrightTestConfig; export function defineConfig(config: PlaywrightTestConfig): PlaywrightTestConfig; export function defineConfig(config: PlaywrightTestConfig): PlaywrightTestConfig; +export function defineConfig(config: PlaywrightTestConfig, ...configs: PlaywrightTestConfig[]): PlaywrightTestConfig; +export function defineConfig(config: PlaywrightTestConfig, ...configs: PlaywrightTestConfig[]): PlaywrightTestConfig; +export function defineConfig(config: PlaywrightTestConfig, ...configs: PlaywrightTestConfig[]): PlaywrightTestConfig; export { expect, devices } from 'playwright/test'; diff --git a/packages/playwright-ct-core/index.js b/packages/playwright-ct-core/index.js index c323f1c6ec..1b8fd60a2d 100644 --- a/packages/playwright-ct-core/index.js +++ b/packages/playwright-ct-core/index.js @@ -17,7 +17,7 @@ const { test: baseTest, expect, devices, defineConfig: originalDefineConfig } = require('playwright/test'); const { fixtures } = require('./lib/mount'); -const defineConfig = config => originalDefineConfig({ +const defineConfig = (config, ...configs) => originalDefineConfig({ ...config, build: { ...config.build, @@ -25,7 +25,7 @@ const defineConfig = config => originalDefineConfig({ [require.resolve('./lib/tsxTransform')] ], } -}); +}, ...configs); const test = baseTest.extend(fixtures); diff --git a/packages/playwright-ct-react/index.d.ts b/packages/playwright-ct-react/index.d.ts index 9c8d0a914d..3e994cf8fa 100644 --- a/packages/playwright-ct-react/index.d.ts +++ b/packages/playwright-ct-react/index.d.ts @@ -62,5 +62,8 @@ export const test: TestType< export function defineConfig(config: PlaywrightTestConfig): PlaywrightTestConfig; export function defineConfig(config: PlaywrightTestConfig): PlaywrightTestConfig; export function defineConfig(config: PlaywrightTestConfig): PlaywrightTestConfig; +export function defineConfig(config: PlaywrightTestConfig, ...configs: PlaywrightTestConfig[]): PlaywrightTestConfig; +export function defineConfig(config: PlaywrightTestConfig, ...configs: PlaywrightTestConfig[]): PlaywrightTestConfig; +export function defineConfig(config: PlaywrightTestConfig, ...configs: PlaywrightTestConfig[]): PlaywrightTestConfig; export { expect, devices } from 'playwright/test'; diff --git a/packages/playwright-ct-react/index.js b/packages/playwright-ct-react/index.js index 04d69e08f5..cc7b4fcb03 100644 --- a/packages/playwright-ct-react/index.js +++ b/packages/playwright-ct-react/index.js @@ -24,6 +24,6 @@ const plugin = () => { path.join(__dirname, 'registerSource.mjs'), () => import('@vitejs/plugin-react').then(plugin => plugin.default())); }; -const defineConfig = config => originalDefineConfig({ ...config, _plugins: [plugin] }); +const defineConfig = (config, ...configs) => originalDefineConfig({ ...config, _plugins: [plugin] }, ...configs); module.exports = { test, expect, devices, defineConfig }; diff --git a/packages/playwright-ct-react17/index.d.ts b/packages/playwright-ct-react17/index.d.ts index 9c8d0a914d..3e994cf8fa 100644 --- a/packages/playwright-ct-react17/index.d.ts +++ b/packages/playwright-ct-react17/index.d.ts @@ -62,5 +62,8 @@ export const test: TestType< export function defineConfig(config: PlaywrightTestConfig): PlaywrightTestConfig; export function defineConfig(config: PlaywrightTestConfig): PlaywrightTestConfig; export function defineConfig(config: PlaywrightTestConfig): PlaywrightTestConfig; +export function defineConfig(config: PlaywrightTestConfig, ...configs: PlaywrightTestConfig[]): PlaywrightTestConfig; +export function defineConfig(config: PlaywrightTestConfig, ...configs: PlaywrightTestConfig[]): PlaywrightTestConfig; +export function defineConfig(config: PlaywrightTestConfig, ...configs: PlaywrightTestConfig[]): PlaywrightTestConfig; export { expect, devices } from 'playwright/test'; diff --git a/packages/playwright-ct-react17/index.js b/packages/playwright-ct-react17/index.js index 04d69e08f5..cc7b4fcb03 100644 --- a/packages/playwright-ct-react17/index.js +++ b/packages/playwright-ct-react17/index.js @@ -24,6 +24,6 @@ const plugin = () => { path.join(__dirname, 'registerSource.mjs'), () => import('@vitejs/plugin-react').then(plugin => plugin.default())); }; -const defineConfig = config => originalDefineConfig({ ...config, _plugins: [plugin] }); +const defineConfig = (config, ...configs) => originalDefineConfig({ ...config, _plugins: [plugin] }, ...configs); module.exports = { test, expect, devices, defineConfig }; diff --git a/packages/playwright-ct-solid/index.d.ts b/packages/playwright-ct-solid/index.d.ts index 9c8d0a914d..3e994cf8fa 100644 --- a/packages/playwright-ct-solid/index.d.ts +++ b/packages/playwright-ct-solid/index.d.ts @@ -62,5 +62,8 @@ export const test: TestType< export function defineConfig(config: PlaywrightTestConfig): PlaywrightTestConfig; export function defineConfig(config: PlaywrightTestConfig): PlaywrightTestConfig; export function defineConfig(config: PlaywrightTestConfig): PlaywrightTestConfig; +export function defineConfig(config: PlaywrightTestConfig, ...configs: PlaywrightTestConfig[]): PlaywrightTestConfig; +export function defineConfig(config: PlaywrightTestConfig, ...configs: PlaywrightTestConfig[]): PlaywrightTestConfig; +export function defineConfig(config: PlaywrightTestConfig, ...configs: PlaywrightTestConfig[]): PlaywrightTestConfig; export { expect, devices } from 'playwright/test'; diff --git a/packages/playwright-ct-solid/index.js b/packages/playwright-ct-solid/index.js index 2176a7a95e..110c394255 100644 --- a/packages/playwright-ct-solid/index.js +++ b/packages/playwright-ct-solid/index.js @@ -24,6 +24,6 @@ const plugin = () => { path.join(__dirname, 'registerSource.mjs'), () => import('vite-plugin-solid').then(plugin => plugin.default())); }; -const defineConfig = config => originalDefineConfig({ ...config, _plugins: [plugin] }); +const defineConfig = (config, ...configs) => originalDefineConfig({ ...config, _plugins: [plugin] }, ...configs); module.exports = { test, expect, devices, defineConfig }; diff --git a/packages/playwright-ct-svelte/index.d.ts b/packages/playwright-ct-svelte/index.d.ts index 0fd476f7ed..d1f7a5a07b 100644 --- a/packages/playwright-ct-svelte/index.d.ts +++ b/packages/playwright-ct-svelte/index.d.ts @@ -70,5 +70,8 @@ export const test: TestType< export function defineConfig(config: PlaywrightTestConfig): PlaywrightTestConfig; export function defineConfig(config: PlaywrightTestConfig): PlaywrightTestConfig; export function defineConfig(config: PlaywrightTestConfig): PlaywrightTestConfig; +export function defineConfig(config: PlaywrightTestConfig, ...configs: PlaywrightTestConfig[]): PlaywrightTestConfig; +export function defineConfig(config: PlaywrightTestConfig, ...configs: PlaywrightTestConfig[]): PlaywrightTestConfig; +export function defineConfig(config: PlaywrightTestConfig, ...configs: PlaywrightTestConfig[]): PlaywrightTestConfig; export { expect, devices } from 'playwright/test'; diff --git a/packages/playwright-ct-svelte/index.js b/packages/playwright-ct-svelte/index.js index 53b5b14a17..313af1b634 100644 --- a/packages/playwright-ct-svelte/index.js +++ b/packages/playwright-ct-svelte/index.js @@ -24,6 +24,6 @@ const plugin = () => { path.join(__dirname, 'registerSource.mjs'), () => import('@sveltejs/vite-plugin-svelte').then(plugin => plugin.svelte())); }; -const defineConfig = config => originalDefineConfig({ ...config, _plugins: [plugin] }); +const defineConfig = (config, ...configs) => originalDefineConfig({ ...config, _plugins: [plugin] }, ...configs); module.exports = { test, expect, devices, defineConfig }; diff --git a/packages/playwright-ct-vue/index.d.ts b/packages/playwright-ct-vue/index.d.ts index 249fe643d7..6cb5c751b3 100644 --- a/packages/playwright-ct-vue/index.d.ts +++ b/packages/playwright-ct-vue/index.d.ts @@ -90,5 +90,8 @@ export const test: TestType< export function defineConfig(config: PlaywrightTestConfig): PlaywrightTestConfig; export function defineConfig(config: PlaywrightTestConfig): PlaywrightTestConfig; export function defineConfig(config: PlaywrightTestConfig): PlaywrightTestConfig; +export function defineConfig(config: PlaywrightTestConfig, ...configs: PlaywrightTestConfig[]): PlaywrightTestConfig; +export function defineConfig(config: PlaywrightTestConfig, ...configs: PlaywrightTestConfig[]): PlaywrightTestConfig; +export function defineConfig(config: PlaywrightTestConfig, ...configs: PlaywrightTestConfig[]): PlaywrightTestConfig; export { expect, devices } from 'playwright/test'; diff --git a/packages/playwright-ct-vue/index.js b/packages/playwright-ct-vue/index.js index cb9c629a1f..ce321f8415 100644 --- a/packages/playwright-ct-vue/index.js +++ b/packages/playwright-ct-vue/index.js @@ -24,6 +24,6 @@ const plugin = () => { path.join(__dirname, 'registerSource.mjs'), () => import('@vitejs/plugin-vue').then(plugin => plugin.default())); } -const defineConfig = config => originalDefineConfig({ ...config, _plugins: [plugin] }); +const defineConfig = (config, ...configs) => originalDefineConfig({ ...config, _plugins: [plugin] }, ...configs); module.exports = { test, expect, devices, defineConfig }; diff --git a/packages/playwright-ct-vue2/index.d.ts b/packages/playwright-ct-vue2/index.d.ts index 9e0a08a02c..b3a22bed84 100644 --- a/packages/playwright-ct-vue2/index.d.ts +++ b/packages/playwright-ct-vue2/index.d.ts @@ -90,5 +90,8 @@ export const test: TestType< export function defineConfig(config: PlaywrightTestConfig): PlaywrightTestConfig; export function defineConfig(config: PlaywrightTestConfig): PlaywrightTestConfig; export function defineConfig(config: PlaywrightTestConfig): PlaywrightTestConfig; +export function defineConfig(config: PlaywrightTestConfig, ...configs: PlaywrightTestConfig[]): PlaywrightTestConfig; +export function defineConfig(config: PlaywrightTestConfig, ...configs: PlaywrightTestConfig[]): PlaywrightTestConfig; +export function defineConfig(config: PlaywrightTestConfig, ...configs: PlaywrightTestConfig[]): PlaywrightTestConfig; export { expect, devices } from 'playwright/test'; diff --git a/packages/playwright-ct-vue2/index.js b/packages/playwright-ct-vue2/index.js index 9c8c71cb8e..8930a8a4f1 100644 --- a/packages/playwright-ct-vue2/index.js +++ b/packages/playwright-ct-vue2/index.js @@ -24,6 +24,6 @@ const plugin = () => { path.join(__dirname, 'registerSource.mjs'), () => import('@vitejs/plugin-vue2').then(plugin => plugin.default())); }; -const defineConfig = config => originalDefineConfig({ ...config, _plugins: [plugin] }); +const defineConfig = (config, ...configs) => originalDefineConfig({ ...config, _plugins: [plugin] }, ...configs); module.exports = { test, expect, devices, defineConfig }; diff --git a/packages/playwright/src/common/configLoader.ts b/packages/playwright/src/common/configLoader.ts index 9d648ed0ea..7fbbed257a 100644 --- a/packages/playwright/src/common/configLoader.ts +++ b/packages/playwright/src/common/configLoader.ts @@ -42,6 +42,10 @@ export const defineConfig = (...configs: any[]) => { ...result.use, ...config.use, }, + build: { + ...result.build, + ...config.build, + }, webServer: [ ...(Array.isArray(result.webServer) ? result.webServer : (result.webServer ? [result.webServer] : [])), ...(Array.isArray(config.webServer) ? config.webServer : (config.webServer ? [config.webServer] : [])), diff --git a/tests/playwright-test/config.spec.ts b/tests/playwright-test/config.spec.ts index fb2f1888d2..0d786de0ce 100644 --- a/tests/playwright-test/config.spec.ts +++ b/tests/playwright-test/config.spec.ts @@ -557,3 +557,37 @@ test('should merge configs', async ({ runInlineTest }) => { }); expect(result.exitCode).toBe(0); }); + +test('should merge ct configs', async ({ runInlineTest }) => { + const result = await runInlineTest({ + 'playwright.config.ts': ` + import { defineConfig, expect } from '@playwright/experimental-ct-react'; + const baseConfig = defineConfig({ + timeout: 10, + use: { + foo: 1, + }, + }); + const derivedConfig = defineConfig(baseConfig, { + grep: 'hi', + use: { + bar: 2, + }, + }); + + // Make sure ct-specific properties are preserved + // and config properties are merged. + expect(derivedConfig).toEqual(expect.objectContaining({ + use: { foo: 1, bar: 2 }, + grep: 'hi', + build: { babelPlugins: [expect.anything()] }, + _plugins: [expect.anything()], + })); + `, + 'a.test.ts': ` + import { test } from '@playwright/experimental-ct-react'; + test('pass', async ({}) => {}); + ` + }); + expect(result.exitCode).toBe(0); +}); diff --git a/tests/playwright-test/types.spec.ts b/tests/playwright-test/types.spec.ts index ffec4f889a..4e316cbeb8 100644 --- a/tests/playwright-test/types.spec.ts +++ b/tests/playwright-test/types.spec.ts @@ -268,6 +268,26 @@ test('should check types of fixtures', async ({ runTSC }) => { }, }); `, + 'playwright-define-merge.config.ts': ` + import { defineConfig } from '@playwright/test'; + const config0 = defineConfig({ + timeout: 1, + // @ts-expect-error + grep: 23, + }, { + timeout: 2, + }); + `, + 'playwright-define-merge-ct.config.ts': ` + import { defineConfig } from '@playwright/experimental-ct-vue'; + const config0 = defineConfig({ + timeout: 1, + // @ts-expect-error + grep: 23, + }, { + timeout: 2, + }); + `, }); expect(result.exitCode).toBe(0); }); From 297cfdfc5fbf7c83f75ea44dd605ad2229d6f0b8 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Tue, 12 Dec 2023 16:22:48 -0800 Subject: [PATCH 164/491] chore: use ReadonlyArray for input parameters (#28564) --- packages/playwright-core/types/types.d.ts | 46 +++++++++++------------ packages/playwright/types/test.d.ts | 18 ++++----- tests/playwright-test/expect.spec.ts | 14 +++++++ utils/generate_types/index.js | 34 +++++++++-------- 4 files changed, 64 insertions(+), 48 deletions(-) diff --git a/packages/playwright-core/types/types.d.ts b/packages/playwright-core/types/types.d.ts index 0a6fea4859..24a6e70088 100644 --- a/packages/playwright-core/types/types.d.ts +++ b/packages/playwright-core/types/types.d.ts @@ -3716,7 +3716,7 @@ export interface Page { * labels. Option is considered matching if all specified properties match. * @param options */ - selectOption(selector: string, values: null|string|ElementHandle|Array|{ + selectOption(selector: string, values: null|string|ElementHandle|ReadonlyArray|{ /** * Matches by `option.value`. Optional. */ @@ -3731,7 +3731,7 @@ export interface Page { * Matches by the index. Optional. */ index?: number; - }|Array|Array<{ + }|ReadonlyArray|ReadonlyArray<{ /** * Matches by `option.value`. Optional. */ @@ -3930,7 +3930,7 @@ export interface Page { * @param files * @param options */ - setInputFiles(selector: string, files: string|Array|{ + setInputFiles(selector: string, files: string|ReadonlyArray|{ /** * File name */ @@ -3945,7 +3945,7 @@ export interface Page { * File content */ buffer: Buffer; - }|Array<{ + }|ReadonlyArray<{ /** * File name */ @@ -6828,7 +6828,7 @@ export interface Frame { * labels. Option is considered matching if all specified properties match. * @param options */ - selectOption(selector: string, values: null|string|ElementHandle|Array|{ + selectOption(selector: string, values: null|string|ElementHandle|ReadonlyArray|{ /** * Matches by `option.value`. Optional. */ @@ -6843,7 +6843,7 @@ export interface Frame { * Matches by the index. Optional. */ index?: number; - }|Array|Array<{ + }|ReadonlyArray|ReadonlyArray<{ /** * Matches by `option.value`. Optional. */ @@ -7000,7 +7000,7 @@ export interface Frame { * @param files * @param options */ - setInputFiles(selector: string, files: string|Array|{ + setInputFiles(selector: string, files: string|ReadonlyArray|{ /** * File name */ @@ -7015,7 +7015,7 @@ export interface Frame { * File content */ buffer: Buffer; - }|Array<{ + }|ReadonlyArray<{ /** * File name */ @@ -8171,7 +8171,7 @@ export interface BrowserContext { * * For the cookie to apply to all subdomains as well, prefix domain with a dot, like this: ".example.com". */ - addCookies(cookies: Array<{ + addCookies(cookies: ReadonlyArray<{ name: string; value: string; @@ -8262,7 +8262,7 @@ export interface BrowserContext { * URLs are returned. * @param urls Optional list of URLs. */ - cookies(urls?: string|Array): Promise>; + cookies(urls?: string|ReadonlyArray): Promise>; /** * The method adds a function called `name` on the `window` object of every frame in every page in the context. When @@ -8327,7 +8327,7 @@ export interface BrowserContext { * - `'payment-handler'` * @param options */ - grantPermissions(permissions: Array, options?: { + grantPermissions(permissions: ReadonlyArray, options?: { /** * The [origin] to grant permissions to, e.g. "https://example.com". */ @@ -10082,7 +10082,7 @@ export interface ElementHandle extends JSHandle { * labels. Option is considered matching if all specified properties match. * @param options */ - selectOption(values: null|string|ElementHandle|Array|{ + selectOption(values: null|string|ElementHandle|ReadonlyArray|{ /** * Matches by `option.value`. Optional. */ @@ -10097,7 +10097,7 @@ export interface ElementHandle extends JSHandle { * Matches by the index. Optional. */ index?: number; - }|Array|Array<{ + }|ReadonlyArray|ReadonlyArray<{ /** * Matches by `option.value`. Optional. */ @@ -10224,7 +10224,7 @@ export interface ElementHandle extends JSHandle { * @param files * @param options */ - setInputFiles(files: string|Array|{ + setInputFiles(files: string|ReadonlyArray|{ /** * File name */ @@ -10239,7 +10239,7 @@ export interface ElementHandle extends JSHandle { * File content */ buffer: Buffer; - }|Array<{ + }|ReadonlyArray<{ /** * File name */ @@ -12243,7 +12243,7 @@ export interface Locator { * labels. Option is considered matching if all specified properties match. * @param options */ - selectOption(values: null|string|ElementHandle|Array|{ + selectOption(values: null|string|ElementHandle|ReadonlyArray|{ /** * Matches by `option.value`. Optional. */ @@ -12258,7 +12258,7 @@ export interface Locator { * Matches by the index. Optional. */ index?: number; - }|Array|Array<{ + }|ReadonlyArray|ReadonlyArray<{ /** * Matches by `option.value`. Optional. */ @@ -12422,7 +12422,7 @@ export interface Locator { * @param files * @param options */ - setInputFiles(files: string|Array|{ + setInputFiles(files: string|ReadonlyArray|{ /** * File name */ @@ -12437,7 +12437,7 @@ export interface Locator { * File content */ buffer: Buffer; - }|Array<{ + }|ReadonlyArray<{ /** * File name */ @@ -14931,7 +14931,7 @@ export interface AndroidInput { x: number; y: number; - }, segments: Array<{ + }, segments: ReadonlyArray<{ x: number; y: number; @@ -17332,7 +17332,7 @@ export interface FileChooser { * @param files * @param options */ - setFiles(files: string|Array|{ + setFiles(files: string|ReadonlyArray|{ /** * File name */ @@ -17347,7 +17347,7 @@ export interface FileChooser { * File content */ buffer: Buffer; - }|Array<{ + }|ReadonlyArray<{ /** * File name */ @@ -18010,7 +18010,7 @@ export interface Logger { * @param args message arguments * @param hints optional formatting hints */ - log(name: string, severity: "verbose"|"info"|"warning"|"error", message: string|Error, args: Array, hints: { + log(name: string, severity: "verbose"|"info"|"warning"|"error", message: string|Error, args: ReadonlyArray, hints: { /** * Optional preferred logger color. */ diff --git a/packages/playwright/types/test.d.ts b/packages/playwright/types/test.d.ts index 72d542f2b2..e0713271cc 100644 --- a/packages/playwright/types/test.d.ts +++ b/packages/playwright/types/test.d.ts @@ -2072,7 +2072,7 @@ export interface TestInfo { * (i.e. `test-results/a-test-title`), otherwise it will throw. * @param pathSegments Path segments to append at the end of the resulting path. */ - outputPath(...pathSegments: Array): string; + outputPath(...pathSegments: ReadonlyArray): string; /** * Changes the timeout for the currently running test. Zero means no timeout. Learn more about @@ -2134,7 +2134,7 @@ export interface TestInfo { * @param pathSegments The name of the snapshot or the path segments to define the snapshot file path. Snapshots with the same name in the * same test file are expected to be the same. */ - snapshotPath(...pathSegments: Array): string; + snapshotPath(...pathSegments: ReadonlyArray): string; /** * The list of annotations applicable to the current test. Includes annotations from the test, annotations from all @@ -5740,7 +5740,7 @@ interface LocatorAssertions { * @param expected Expected substring or RegExp or a list of those. * @param options */ - toContainText(expected: string|RegExp|Array, options?: { + toContainText(expected: string|RegExp|ReadonlyArray, options?: { /** * Whether to perform case-insensitive match. `ignoreCase` option takes precedence over the corresponding regular * expression flag if specified. @@ -5831,7 +5831,7 @@ interface LocatorAssertions { * @param expected Expected class or RegExp or a list of those. * @param options */ - toHaveClass(expected: string|RegExp|Array, options?: { + toHaveClass(expected: string|RegExp|ReadonlyArray, options?: { /** * Time to retry the assertion for in milliseconds. Defaults to `timeout` in `TestConfig.expect`. */ @@ -5936,7 +5936,7 @@ interface LocatorAssertions { * @param name Snapshot name. * @param options */ - toHaveScreenshot(name: string|Array, options?: { + toHaveScreenshot(name: string|ReadonlyArray, options?: { /** * When set to `"disabled"`, stops CSS animations, CSS transitions and Web Animations. Animations get different * treatment depending on their duration: @@ -6153,7 +6153,7 @@ interface LocatorAssertions { * @param expected Expected string or RegExp or a list of those. * @param options */ - toHaveText(expected: string|RegExp|Array, options?: { + toHaveText(expected: string|RegExp|ReadonlyArray, options?: { /** * Whether to perform case-insensitive match. `ignoreCase` option takes precedence over the corresponding regular * expression flag if specified. @@ -6217,7 +6217,7 @@ interface LocatorAssertions { * @param values Expected options currently selected. * @param options */ - toHaveValues(values: Array, options?: { + toHaveValues(values: ReadonlyArray, options?: { /** * Time to retry the assertion for in milliseconds. Defaults to `timeout` in `TestConfig.expect`. */ @@ -6266,7 +6266,7 @@ interface PageAssertions { * @param name Snapshot name. * @param options */ - toHaveScreenshot(name: string|Array, options?: { + toHaveScreenshot(name: string|ReadonlyArray, options?: { /** * When set to `"disabled"`, stops CSS animations, CSS transitions and Web Animations. Animations get different * treatment depending on their duration: @@ -6585,7 +6585,7 @@ interface SnapshotAssertions { * @param name Snapshot name. * @param options */ - toMatchSnapshot(name: string|Array, options?: { + toMatchSnapshot(name: string|ReadonlyArray, options?: { /** * An acceptable ratio of pixels that are different to the total amount of pixels, between `0` and `1`. Default is * configurable with `TestConfig.expect`. Unset by default. diff --git a/tests/playwright-test/expect.spec.ts b/tests/playwright-test/expect.spec.ts index f725711943..e56034c886 100644 --- a/tests/playwright-test/expect.spec.ts +++ b/tests/playwright-test/expect.spec.ts @@ -223,6 +223,20 @@ test('should compile generic matchers', async ({ runTSC }) => { expect(result.exitCode).toBe(0); }); +test('should work when passing a ReadonlyArray', async ({ runTSC }) => { + const result = await runTSC({ + 'a.spec.ts': ` + import { test, expect } from '@playwright/test'; + test('example', async ({ page }) => { + const readonlyArray: ReadonlyArray = ['1', '2', '3']; + expect(page.locator('.foo')).toHaveText(readonlyArray); + await page.locator('.foo').setInputFiles(readonlyArray); + }); + ` + }); + expect(result.exitCode).toBe(0); +}); + test('should work with expect message', async ({ runTSC }) => { const result = await runTSC({ 'a.spec.ts': ` diff --git a/utils/generate_types/index.js b/utils/generate_types/index.js index 70112e5a69..263ef1fb8d 100644 --- a/utils/generate_types/index.js +++ b/utils/generate_types/index.js @@ -246,7 +246,7 @@ class TypesGenerator { const descriptions = []; for (let [eventName, value] of classDesc.events) { eventName = eventName.toLowerCase(); - const type = this.stringifyComplexType(value && value.type, ' ', classDesc.name, eventName, 'payload'); + const type = this.stringifyComplexType(value && value.type, 'out', ' ', classDesc.name, eventName, 'payload'); const argName = this.argNameForType(type); const params = argName ? `${argName}: ${type}` : ''; descriptions.push({ @@ -300,7 +300,7 @@ class TypesGenerator { } const jsdoc = this.memberJSDOC(member, indent); const args = this.argsFromMember(member, indent, classDesc.name); - let type = this.stringifyComplexType(member.type, indent, classDesc.name, member.alias); + let type = this.stringifyComplexType(member.type, 'out', indent, classDesc.name, member.alias); if (member.async) type = `Promise<${type}>`; // do this late, because we still want object definitions for overridden types @@ -373,12 +373,12 @@ class TypesGenerator { } /** - * @param {docs.Type} type + * @param {docs.Type|null} type */ - stringifyComplexType(type, indent, ...namespace) { + stringifyComplexType(type, direction, indent, ...namespace) { if (!type) return 'void'; - return this.stringifySimpleType(type, indent, ...namespace); + return this.stringifySimpleType(type, direction, indent, ...namespace); } /** @@ -393,7 +393,7 @@ class TypesGenerator { parts.push(properties.map(member => { const comment = this.memberJSDOC(member, indent + ' '); const args = this.argsFromMember(member, indent + ' ', name); - const type = this.stringifyComplexType(member.type, indent + ' ', name, member.name); + const type = this.stringifyComplexType(member.type, 'out', indent + ' ', name, member.name); return `${comment}${this.nameForProperty(member)}${args}: ${type};`; }).join('\n\n')); parts.push(indent + '}'); @@ -401,21 +401,23 @@ class TypesGenerator { } /** - * @param {docs.Type=} type + * @param {docs.Type | null | undefined} type + * @param {'in' | 'out'} direction * @returns{string} */ - stringifySimpleType(type, indent = '', ...namespace) { + stringifySimpleType(type, direction, indent = '', ...namespace) { if (!type) return 'void'; if (type.name === 'Object' && type.templates) { - const keyType = this.stringifySimpleType(type.templates[0], indent, ...namespace); - const valueType = this.stringifySimpleType(type.templates[1], indent, ...namespace); + const keyType = this.stringifySimpleType(type.templates[0], direction, indent, ...namespace); + const valueType = this.stringifySimpleType(type.templates[1], direction, indent, ...namespace); return `{ [key: ${keyType}]: ${valueType}; }`; } let out = type.name; if (out === 'int' || out === 'float') out = 'number'; - + if (out === 'Array' && direction === 'in') + out = 'ReadonlyArray'; if (type.name === 'Object' && type.properties && type.properties.length) { const name = namespace.map(n => n[0].toUpperCase() + n.substring(1)).join(''); const shouldExport = exported[name]; @@ -431,10 +433,10 @@ class TypesGenerator { if (type.args) { const stringArgs = type.args.map(a => ({ - type: this.stringifySimpleType(a, indent, ...namespace), + type: this.stringifySimpleType(a, direction, indent, ...namespace), name: a.name.toLowerCase() })); - out = `((${stringArgs.map(({ name, type }) => `${name}: ${type}`).join(', ')}) => ${this.stringifySimpleType(type.returnType, indent, ...namespace)})`; + out = `((${stringArgs.map(({ name, type }) => `${name}: ${type}`).join(', ')}) => ${this.stringifySimpleType(type.returnType, 'out', indent, ...namespace)})`; } else if (type.name === 'function') { out = 'Function'; } @@ -443,9 +445,9 @@ class TypesGenerator { if (out === 'Any') return 'any'; if (type.templates) - out += '<' + type.templates.map(t => this.stringifySimpleType(t, indent, ...namespace)).join(', ') + '>'; + out += '<' + type.templates.map(t => this.stringifySimpleType(t, direction, indent, ...namespace)).join(', ') + '>'; if (type.union) - out = type.union.map(t => this.stringifySimpleType(t, indent, ...namespace)).join('|'); + out = type.union.map(t => this.stringifySimpleType(t, direction, indent, ...namespace)).join('|'); return out.trim(); } @@ -455,7 +457,7 @@ class TypesGenerator { argsFromMember(member, indent, ...namespace) { if (member.kind === 'property') return ''; - return '(' + member.argsArray.map(arg => `${this.nameForProperty(arg)}: ${this.stringifyComplexType(arg.type, indent, ...namespace, member.alias, arg.alias)}`).join(', ') + ')'; + return '(' + member.argsArray.map(arg => `${this.nameForProperty(arg)}: ${this.stringifyComplexType(arg.type, 'in', indent, ...namespace, member.alias, arg.alias)}`).join(', ') + ')'; } /** From afe90d648e94f37dc0c4b7945c029214a0bee741 Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Wed, 13 Dec 2023 09:06:02 -0800 Subject: [PATCH 165/491] fix: do not generate api call steps inside named expects (#28609) Fixes: https://github.com/microsoft/playwright/issues/28528 --- .../src/client/channelOwner.ts | 4 +- packages/playwright/src/index.ts | 2 +- tests/playwright-test/test-step.spec.ts | 92 +++++++++++++++++++ 3 files changed, 96 insertions(+), 2 deletions(-) diff --git a/packages/playwright-core/src/client/channelOwner.ts b/packages/playwright-core/src/client/channelOwner.ts index c41be351bd..b493e7d5d5 100644 --- a/packages/playwright-core/src/client/channelOwner.ts +++ b/packages/playwright-core/src/client/channelOwner.ts @@ -180,7 +180,9 @@ export abstract class ChannelOwner = ({ const csiListener: ClientInstrumentationListener = { onApiCallBegin: (apiName: string, params: Record, frames: StackFrame[], wallTime: number, userData: any) => { const testInfo = currentTestInfo(); - if (!testInfo || apiName.startsWith('expect.') || apiName.includes('setTestIdAttribute')) + if (!testInfo || apiName.includes('setTestIdAttribute')) return { userObject: null }; const step = testInfo._addStep({ location: frames[0] as any, diff --git a/tests/playwright-test/test-step.spec.ts b/tests/playwright-test/test-step.spec.ts index c22270277e..c8590b7e4f 100644 --- a/tests/playwright-test/test-step.spec.ts +++ b/tests/playwright-test/test-step.spec.ts @@ -1385,3 +1385,95 @@ test('should step w/ box', async ({ runInlineTest }) => { }, ]); }); + +test('should not generate dupes for named expects', async ({ runInlineTest }) => { + const result = await runInlineTest({ + 'reporter.ts': stepHierarchyReporter, + 'playwright.config.ts': ` + module.exports = { + reporter: './reporter', + }; + `, + 'a.test.ts': ` + import { test, expect } from '@playwright/test'; + test('timeout', async ({ page }) => { + await page.setContent('
    hi
    '); + await expect(page.locator('div'), 'Checking color') + .toHaveCSS('background-color', 'rgb(1, 2, 3)'); + }); + ` + }, { reporter: '', workers: 1, timeout: 2000 }); + + expect(result.exitCode).toBe(0); + const objects = result.outputLines.map(line => JSON.parse(line)); + expect(objects).toEqual([ + { + category: 'hook', + title: 'Before Hooks', + steps: [ + { + category: 'fixture', + title: 'fixture: browser', + steps: [ + { + category: 'pw:api', + title: 'browserType.launch', + }, + ] + }, + { + category: 'fixture', + title: 'fixture: context', + steps: [ + { + category: 'pw:api', + title: 'browser.newContext', + }, + ] + }, + { + category: 'fixture', + title: 'fixture: page', + steps: [ + { + category: 'pw:api', + title: 'browserContext.newPage', + }, + ] + }, + ], + }, + { + category: 'pw:api', + title: 'page.setContent', + location: { + column: expect.any(Number), + file: 'a.test.ts', + line: expect.any(Number), + }, + }, + { + category: 'expect', + title: 'Checking color', + location: { + column: expect.any(Number), + file: 'a.test.ts', + line: expect.any(Number), + }, + }, + { + category: 'hook', + title: 'After Hooks', + steps: [ + { + category: 'fixture', + title: 'fixture: page', + }, + { + category: 'fixture', + title: 'fixture: context', + }, + ], + }, + ]); +}); \ No newline at end of file From 9d91b7caf51003cb5c11ec0cdeb8431ca0411aa9 Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Wed, 13 Dec 2023 09:34:20 -0800 Subject: [PATCH 166/491] fix(route): silently catch errors when handling route on server (#28612) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is a follow-up to 119afdf788f4fec0eaae0def91a95aee84971eae Since continue/fulfill/abort now may throw on the server after page has been closed, we need to catch the errors manually. On the client it's fixed by the original change. This fixes errors in the existing tests: ``` 1) [chromium] › library/browsercontext-route.spec.ts:172:3 › should support Set-Cookie header ──── Error: at ../packages/playwright-core/src/server/chromium/crConnection.ts:147 145 | const id = this._connection._rawSend(this._sessionId, method, params); 146 | return new Promise((resolve, reject) => { > 147 | this._callbacks.set(id, { resolve, reject, error: new ProtocolError('error', method) }); | ^ 148 | }); 149 | } 150 | at /Users/yurys/playwright/packages/playwright-core/src/server/chromium/crConnection.ts:147:57 at new Promise () at CRSession.send (/Users/yurys/playwright/packages/playwright-core/src/server/chromium/crConnection.ts:146:12) at RouteImpl.continue (/Users/yurys/playwright/packages/playwright-core/src/server/chromium/crNetworkManager.ts:566:25) at FrameManager.requestStarted (/Users/yurys/playwright/packages/playwright-core/src/server/frames.ts:299:23) at CRNetworkManager._onRequest (/Users/yurys/playwright/packages/playwright-core/src/server/chromium/crNetworkManager.ts:314:57) at CRNetworkManager._onRequestPaused (/Users/yurys/playwright/packages/playwright-core/src/server/chromium/crNetworkManager.ts:202:12) at CRSession.emit (node:events:517:28) at /Users/yurys/playwright/packages/playwright-core/src/server/chromium/crConnection.ts:172:14 at runNextTicks (node:internal/process/task_queues:60:5) at processImmediate (node:internal/timers:447:9) ``` ![image](https://github.com/microsoft/playwright/assets/9798949/1c436dc2-f113-4ba6-952c-dca5a8c5fa62) Reference https://github.com/microsoft/playwright/issues/28490 --- .../playwright-core/src/server/chromium/crServiceWorker.ts | 2 +- packages/playwright-core/src/server/frames.ts | 4 ++-- packages/playwright-core/src/server/recorder/recorderApp.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/playwright-core/src/server/chromium/crServiceWorker.ts b/packages/playwright-core/src/server/chromium/crServiceWorker.ts index b75f9922e9..11d6385356 100644 --- a/packages/playwright-core/src/server/chromium/crServiceWorker.ts +++ b/packages/playwright-core/src/server/chromium/crServiceWorker.ts @@ -119,7 +119,7 @@ export class CRServiceWorker extends Worker { const r = new network.Route(request, route); if (this._browserContext._requestInterceptor?.(r, request)) return; - r.continue({ isFallback: true }); + r.continue({ isFallback: true }).catch(() => {}); } } diff --git a/packages/playwright-core/src/server/frames.ts b/packages/playwright-core/src/server/frames.ts index 68430aaeb3..6631da6d69 100644 --- a/packages/playwright-core/src/server/frames.ts +++ b/packages/playwright-core/src/server/frames.ts @@ -296,7 +296,7 @@ export class FrameManager { frame.setPendingDocument({ documentId: request._documentId, request }); if (request._isFavicon) { if (route) - route.continue(request, { isFallback: true }); + route.continue(request, { isFallback: true }).catch(() => {}); return; } this._page.emitOnContext(BrowserContext.Events.Request, request); @@ -308,7 +308,7 @@ export class FrameManager { return; if (this._page._browserContext._requestInterceptor?.(r, request)) return; - r.continue({ isFallback: true }); + r.continue({ isFallback: true }).catch(() => {}); } } diff --git a/packages/playwright-core/src/server/recorder/recorderApp.ts b/packages/playwright-core/src/server/recorder/recorderApp.ts index 1324393a06..40637302a0 100644 --- a/packages/playwright-core/src/server/recorder/recorderApp.ts +++ b/packages/playwright-core/src/server/recorder/recorderApp.ts @@ -97,7 +97,7 @@ export class RecorderApp extends EventEmitter implements IRecorderApp { ], body: buffer.toString('base64'), isBase64: true - }); + }).catch(() => {}); }); return true; }); From 9b2585cd4ef802bc01d4183dd708e127cf62773a Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Wed, 13 Dec 2023 11:42:29 -0800 Subject: [PATCH 167/491] fix: 'should collect trace with resources, but no js' test in service mode (#28628) --- packages/playwright-core/src/client/elementHandle.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/playwright-core/src/client/elementHandle.ts b/packages/playwright-core/src/client/elementHandle.ts index 0a31ff96ff..9315d50eae 100644 --- a/packages/playwright-core/src/client/elementHandle.ts +++ b/packages/playwright-core/src/client/elementHandle.ts @@ -266,7 +266,7 @@ export async function convertInputFiles(files: string | FilePayload | string[] | if (context._connection.isRemote()) { const streams: channels.WritableStreamChannel[] = await Promise.all((items as string[]).map(async item => { const lastModifiedMs = (await fs.promises.stat(item)).mtimeMs; - const { writableStream: stream } = await context._channel.createTempFile({ name: path.basename(item), lastModifiedMs }); + const { writableStream: stream } = await context._wrapApiCall(() => context._channel.createTempFile({ name: path.basename(item), lastModifiedMs }), true); const writable = WritableStream.from(stream); await pipelineAsync(fs.createReadStream(item), writable.stream()); return stream; From e31e5b1b7c7628921875c98cc98ebdfb2b53b766 Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Wed, 13 Dec 2023 18:47:13 -0800 Subject: [PATCH 168/491] fix: use blob fileName as is without adding shard suffix (#28634) Fixes https://github.com/microsoft/playwright/issues/27284 --- .github/workflows/tests_primary.yml | 4 ++-- packages/playwright/src/reporters/blob.ts | 8 +++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/workflows/tests_primary.yml b/.github/workflows/tests_primary.yml index b117bb2267..a76dc9fd3c 100644 --- a/.github/workflows/tests_primary.yml +++ b/.github/workflows/tests_primary.yml @@ -131,11 +131,11 @@ jobs: - run: npx playwright install --with-deps - run: npm run ttest -- --shard ${{ matrix.shard }} env: - PWTEST_BOT_NAME: "${{ matrix.os }}-node${{ matrix.node-version }}" + PWTEST_BOT_NAME: "${{ matrix.os }}-node${{ matrix.node-version }}-${{ matrix.shard }}" if: matrix.os != 'ubuntu-latest' - run: xvfb-run npm run ttest -- --shard ${{ matrix.shard }} env: - PWTEST_BOT_NAME: "${{ matrix.os }}-node${{ matrix.node-version }}" + PWTEST_BOT_NAME: "${{ matrix.os }}-node${{ matrix.node-version }}-${{ matrix.shard }}" if: matrix.os == 'ubuntu-latest' - run: ./utils/upload_flakiness_dashboard.sh ./test-results/report.json if: always() diff --git a/packages/playwright/src/reporters/blob.ts b/packages/playwright/src/reporters/blob.ts index f15fe6f87d..192b639a5a 100644 --- a/packages/playwright/src/reporters/blob.ts +++ b/packages/playwright/src/reporters/blob.ts @@ -109,12 +109,14 @@ export class BlobReporter extends TeleReporterEmitter { } private _computeReportName(config: FullConfig) { - let reportName = this._options.fileName ?? 'report.zip'; + if (this._options.fileName) + return this._options.fileName; + let reportName = 'report'; if (config.shard) { const paddedNumber = `${config.shard.current}`.padStart(`${config.shard.total}`.length, '0'); - reportName = `${reportName.slice(0, -4)}-${paddedNumber}.zip`; + reportName = `${reportName}-${paddedNumber}`; } - return reportName; + return `${reportName}.zip`; } override _serializeAttachments(attachments: TestResult['attachments']): JsonAttachment[] { From e8c8852c0038c7f2a407b70192a365ac04452c81 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Wed, 13 Dec 2023 20:06:01 -0800 Subject: [PATCH 169/491] chore: discourage methods on ElementHandle (#28637) Mirrors the deprecations from page.* over to ElementHandle. --- docs/src/api/class-elementhandle.md | 35 +++++ packages/playwright-core/types/types.d.ts | 152 ++++++++++++++++++++++ 2 files changed, 187 insertions(+) diff --git a/docs/src/api/class-elementhandle.md b/docs/src/api/class-elementhandle.md index df73e558d0..12edbe1817 100644 --- a/docs/src/api/class-elementhandle.md +++ b/docs/src/api/class-elementhandle.md @@ -156,6 +156,7 @@ await page.Mouse.ClickAsync(box.X + box.Width / 2, box.Y + box.Height / 2); ## async method: ElementHandle.check * since: v1.8 +* discouraged: Use locator-based [`method: Locator.check`] instead. Read more about [locators](../locators.md). This method checks the element by performing the following steps: 1. Ensure that element is a checkbox or a radio input. If not, this method throws. If the element is already @@ -191,6 +192,7 @@ When all steps combined have not finished during the specified [`option: timeout ## async method: ElementHandle.click * since: v1.8 +* discouraged: Use locator-based [`method: Locator.click`] instead. Read more about [locators](../locators.md). This method clicks the element by performing the following steps: 1. Wait for [actionability](../actionability.md) checks on the element, unless [`option: force`] option is set. @@ -241,6 +243,7 @@ Returns the content frame for element handles referencing iframe nodes, or `null ## async method: ElementHandle.dblclick * since: v1.8 +* discouraged: Use locator-based [`method: Locator.dblclick`] instead. Read more about [locators](../locators.md). * langs: - alias-csharp: DblClickAsync @@ -289,6 +292,7 @@ When all steps combined have not finished during the specified [`option: timeout ## async method: ElementHandle.dispatchEvent * since: v1.8 +* discouraged: Use locator-based [`method: Locator.dispatchEvent`] instead. Read more about [locators](../locators.md). The snippet below dispatches the `click` event on the element. Regardless of the visibility state of the element, `click` is dispatched. This is equivalent to calling @@ -383,6 +387,9 @@ Optional event-specific initialization properties. ## async method: ElementHandle.evalOnSelector * since: v1.9 +* discouraged: This method does not wait for the element to pass actionability + checks and therefore can lead to the flaky tests. Use [`method: Locator.evaluate`], + other [Locator] helper methods or web-first assertions instead. * langs: - alias-python: eval_on_selector - alias-js: $eval @@ -445,6 +452,8 @@ Optional argument to pass to [`param: expression`]. ## async method: ElementHandle.evalOnSelectorAll * since: v1.9 +* discouraged: In most cases, [`method: Locator.evaluateAll`], + other [Locator] helper methods and web-first assertions do a better job. * langs: - alias-python: eval_on_selector_all - alias-js: $$eval @@ -511,6 +520,7 @@ Optional argument to pass to [`param: expression`]. ## async method: ElementHandle.fill * since: v1.8 +* discouraged: Use locator-based [`method: Locator.fill`] instead. Read more about [locators](../locators.md). This method waits for [actionability](../actionability.md) checks, focuses the element, fills it and triggers an `input` event after filling. Note that you can pass an empty string to clear the input field. @@ -538,11 +548,13 @@ Value to set for the ``, `